summaryrefslogtreecommitdiff
path: root/src/pmlogger
diff options
context:
space:
mode:
Diffstat (limited to 'src/pmlogger')
-rw-r--r--src/pmlogger/GNUmakefile69
-rw-r--r--src/pmlogger/control52
-rw-r--r--src/pmlogger/crontab.in8
-rw-r--r--src/pmlogger/pmlogger.service.in13
-rwxr-xr-xsrc/pmlogger/pmlogger_check.sh867
-rwxr-xr-xsrc/pmlogger/pmlogger_daily.sh952
-rwxr-xr-xsrc/pmlogger/pmlogger_merge.sh282
-rwxr-xr-xsrc/pmlogger/pmlogmv.sh261
-rwxr-xr-xsrc/pmlogger/pmnewlog.sh702
-rw-r--r--src/pmlogger/rc_pmlogger301
-rw-r--r--src/pmlogger/src/GNUmakefile50
-rw-r--r--src/pmlogger/src/callback.c771
-rw-r--r--src/pmlogger/src/check.c164
-rw-r--r--src/pmlogger/src/dopdu.c1491
-rw-r--r--src/pmlogger/src/error.c35
-rw-r--r--src/pmlogger/src/events.c113
-rw-r--r--src/pmlogger/src/fetch.c186
-rw-r--r--src/pmlogger/src/gram.y570
-rw-r--r--src/pmlogger/src/lex.l144
-rw-r--r--src/pmlogger/src/logger.h181
-rw-r--r--src/pmlogger/src/pmlogger.c1186
-rw-r--r--src/pmlogger/src/ports.c734
-rw-r--r--src/pmlogger/src/preamble.c208
-rw-r--r--src/pmlogger/src/rewrite.c47
-rw-r--r--src/pmlogger/src/util.c81
25 files changed, 9468 insertions, 0 deletions
diff --git a/src/pmlogger/GNUmakefile b/src/pmlogger/GNUmakefile
new file mode 100644
index 0000000..07fecfe
--- /dev/null
+++ b/src/pmlogger/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+OTHERS = pmnewlog.sh control rc_pmlogger \
+ pmlogger_daily.sh pmlogger_check.sh pmlogger_merge.sh pmlogmv.sh
+LDIRT = crontab pmlogger.service
+
+ifeq ($(TARGET_OS),linux)
+CRONTAB_USER = $(PCP_USER)
+CRONTAB_PATH = $(PCP_ETC_DIR)/cron.d/pcp-pmlogger
+else
+CRONTAB_USER =
+CRONTAB_PATH = $(PCP_SYSCONF_DIR)/pmlogger/crontab
+endif
+
+default:: crontab pmlogger.service
+
+default:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: default
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_SYSCONF_DIR)/pmlogger
+ $(INSTALL) -m 664 -o $(PCP_USER) -g $(PCP_GROUP) control $(PCP_PMLOGGERCONTROL_PATH)
+ $(INSTALL) -m 755 pmnewlog.sh $(PCP_BINADM_DIR)/pmnewlog$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_daily.sh $(PCP_BINADM_DIR)/pmlogger_daily$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_check.sh $(PCP_BINADM_DIR)/pmlogger_check$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_merge.sh $(PCP_BINADM_DIR)/pmlogger_merge$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogmv.sh $(PCP_BIN_DIR)/pmlogmv$(SHELLSUFFIX)
+ $(INSTALL) -m 755 rc_pmlogger $(PCP_RC_DIR)/pmlogger
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmlogger.service $(PCP_SYSTEMDUNIT_DIR)/pmlogger.service
+endif
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)/pmlogger
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_TMP_DIR)/pmlogger
+ifeq ($(TARGET_OS),linux)
+ $(INSTALL) -m 755 -d `dirname $(CRONTAB_PATH)`
+endif
+ $(INSTALL) -m 644 crontab $(CRONTAB_PATH)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+pmlogger.service : pmlogger.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+crontab : crontab.in
+ $(SED) -e 's;@user@;'$(CRONTAB_USER)';' -e 's;@path@;'$(PCP_BINADM_DIR)';' $< > $@
diff --git a/src/pmlogger/control b/src/pmlogger/control
new file mode 100644
index 0000000..7e25ef8
--- /dev/null
+++ b/src/pmlogger/control
@@ -0,0 +1,52 @@
+#
+# PCP archive logging configuration/control
+#
+# This file is used by various of the PCP archive logging administrative
+# tools to perform maintenance on the pmlogger instances running on
+# the local host.
+#
+# This file contains one line per host to be logged, fields are
+# Host name of host to be logged
+# P(rimary) is this the primary logger? y or n
+# S(ocks) should this logger be launched with pmsocks? y or n
+# Directory full pathname to directory where archive logs are
+# to be maintained ... note all scripts "cd" to here as
+# a first step
+# Args optional additional arguments to pmlogger and/or pmnewlog
+#
+
+# === VARIABLE ASSIGNMENTS ===
+#
+# DO NOT REMOVE OR EDIT THE FOLLOWING LINE
+$version=1.1
+
+# if pmsocks is being used, edit the IP address for $SOCKS_SERVER
+#$SOCKS_SERVER=123.456.789.123
+
+# for remote loggers running over a WAN with potentially long delays
+$PMCD_CONNECT_TIMEOUT=150
+$PMCD_REQUEST_TIMEOUT=120
+
+# === LOGGER CONTROL SPECIFICATIONS ===
+#
+#Host P? S? directory args
+
+# local primary logger
+#
+# (LOCALHOSTNAME is expanded to local: in the first column,
+# and to `hostname` in the fourth (directory) column.)
+#
+LOCALHOSTNAME y n PCP_LOG_DIR/pmlogger/LOCALHOSTNAME -r -T24h10m -c config.default
+
+# Note: if multiple pmloggers for the same host (e.g. both primary and
+# non-primary loggers are active), then they MUST use different
+# directories
+
+# local non-primary logger
+#LOCALHOSTNAME n n PCP_LOG_DIR/pmlogger/mysummary -r -T24h10m -c config.Summary
+
+# remote host
+#remote n n PCP_LOG_DIR/pmlogger/remote -r -T24h10m -c config.remote
+
+# thru the firewall via socks
+#distant n y PCP_LOG_DIR/pmlogger/distant -r -T24h10m -c config.distant
diff --git a/src/pmlogger/crontab.in b/src/pmlogger/crontab.in
new file mode 100644
index 0000000..1427e07
--- /dev/null
+++ b/src/pmlogger/crontab.in
@@ -0,0 +1,8 @@
+#
+# Performance Co-Pilot crontab entries for a monitored site
+# with one or more pmlogger instances running
+#
+# daily processing of archive logs (with compression enabled)
+10 0 * * * @user@ @path@/pmlogger_daily -X xz -x 3
+# every 30 minutes, check pmlogger instances are running
+25,55 * * * * @user@ @path@/pmlogger_check -C
diff --git a/src/pmlogger/pmlogger.service.in b/src/pmlogger/pmlogger.service.in
new file mode 100644
index 0000000..a0e4fe4
--- /dev/null
+++ b/src/pmlogger/pmlogger.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Performance Metrics Archive Logger
+Documentation=man:pmlogger(1)
+After=local-fs.target network.target
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmlogger start
+ExecStop=@path@/pmlogger stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmlogger/pmlogger_check.sh b/src/pmlogger/pmlogger_check.sh
new file mode 100755
index 0000000..a05fd0f
--- /dev/null
+++ b/src/pmlogger/pmlogger_check.sh
@@ -0,0 +1,867 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Administrative script to check pmlogger processes are alive, and restart
+# them as required.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMLOGGER=pmlogger
+PMLOGCONF="$PCP_BINADM_DIR/pmlogconf"
+
+# error messages should go to stderr, not the GUI notifiers
+unset PCP_STDERR
+
+# constant setup
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+echo >$tmp/lock
+trap "rm -rf \`[ -f $tmp/lock ] && cat $tmp/lock\` $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+# control file for pmlogger administration ... edit the entries in this
+# file to reflect your local configuration
+#
+CONTROL=$PCP_PMLOGGERCONTROL_PATH
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the fourth column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+[ -z "$PWDCMND" ] && PWDCMND=/bin/pwd
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+# default location
+#
+logfile=pmlogger.log
+
+
+# option parsing
+#
+SHOWME=false
+MV=mv
+CP=cp
+KILL=pmsignal
+TERSE=false
+VERBOSE=false
+VERY_VERBOSE=false
+CHECK_RUNLEVEL=false
+START_PMLOGGER=true
+
+echo > $tmp/usage
+cat >> $tmp/usage << EOF
+Options:
+ -c=FILE,--control=FILE configuration of pmlogger instances to manage
+ -C query system service runlevel information
+ -N,--showme perform a dry run, showing what would be done
+ -s,--stop stop pmlogger processes instead of starting them
+ -T,--terse produce a terser form of output
+ -V,--verbose increase diagnostic verbosity
+ --help
+EOF
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -C) CHECK_RUNLEVEL=true
+ ;;
+ -N) SHOWME=true
+ MV="echo + mv"
+ CP="echo + cp"
+ KILL="echo + kill"
+ ;;
+ -s) START_PMLOGGER=false
+ ;;
+ -T) TERSE=true
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 0 ]
+then
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+fi
+
+QUIETLY=false
+if [ $CHECK_RUNLEVEL = true ]
+then
+ # determine whether to start/stop based on runlevel settings - we
+ # need to do this when running unilaterally from cron, else we'll
+ # always start pmlogger up (even when we shouldn't).
+ #
+ QUIETLY=true
+ if is_chkconfig_on pmlogger
+ then
+ START_PMLOGGER=true
+ else
+ START_PMLOGGER=false
+ fi
+fi
+
+if [ $START_PMLOGGER = false ]
+then
+ # if pmlogger has never been started, there's no work to do to stop it
+ [ ! -d "$PCP_TMP_DIR/pmlogger" ] && exit
+ $QUIETLY || $PCP_BINADM_DIR/pmpost "stop pmlogger from $prog"
+fi
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+_error()
+{
+ echo 2>&1 "$prog: [$CONTROL:$line]"
+ echo 2>&1 "Error: $1"
+ echo 2>&1 "... logging for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_warning()
+{
+ echo 2>&1 "$prog [$CONTROL:$line]"
+ echo 2>&1 "Warning: $1"
+}
+
+_message()
+{
+ case $1
+ in
+ restart)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Restarting$iam pmlogger for host \"$host\" ...""$PCP_ECHO_C"
+ ;;
+ esac
+}
+
+_unlock()
+{
+ rm -f lock
+ echo >$tmp/lock
+}
+
+_get_ino()
+{
+ # get inode number for $1
+ # throw away stderr (and return '') in case $1 has been removed by now
+ #
+ stat "$1" 2>/dev/null \
+ | sed -n '/Device:[ ].*[ ]Inode:/{
+s/Device:[ ].*[ ]Inode:[ ]*//
+s/[ ].*//
+p
+}'
+}
+
+_get_configfile()
+{
+ # extract the pmlogger configuration file (-c) from a list of arguments
+ #
+ echo $@ | sed -n \
+ -e 's/^/ /' \
+ -e 's/[ ][ ]*/ /g' \
+ -e 's/-c /-c/' \
+ -e 's/.* -c\([^ ]*\).*/\1/p'
+}
+
+_configure_pmlogger()
+{
+ # update a pmlogger configuration file if it should be created/modified
+ #
+ configfile="$1"
+ hostname="$2"
+
+ if [ -f "$configfile" ]
+ then
+ # look for "magic" string at start of file, and ensure we created it
+ sed 1q "$configfile" | grep '^#pmlogconf [0-9]' >/dev/null
+ magic=$?
+ grep '^# Auto-generated by pmlogconf' "$configfile" >/dev/null
+ owned=$?
+ if [ $magic -eq 0 -a $owned -eq 0 ]
+ then
+ # pmlogconf file that we own, see if re-generation is needed
+ cp "$configfile" $tmp/pmlogger
+ if $PMLOGCONF -c -q -h $hostname $tmp/pmlogger >$tmp/diag 2>&1
+ then
+ if grep 'No changes' $tmp/diag >/dev/null 2>&1
+ then
+ :
+ elif [ -w $configfile ]
+ then
+ $VERBOSE && echo "Reconfigured: \"$configfile\" (pmlogconf)"
+ eval $MV $tmp/pmlogger "$configfile"
+ else
+ _warning "no write access to pmlogconf file \"$configfile\", skip reconfiguration"
+ ls -l "$configfile"
+ fi
+ else
+ _warning "pmlogconf failed to reconfigure \"$configfile\""
+ cat "s;$tmp/pmlogger;$configfile;g" $tmp/diag
+ echo "=== start pmlogconf file ==="
+ cat $tmp/pmlogger
+ echo "=== end pmlogconf file ==="
+ fi
+ fi
+ elif [ ! -e "$configfile" ]
+ then
+ # file does not exist, generate it, if possible
+ if $SHOWME
+ then
+ echo "+ $PMLOGCONF -c -q -h $hostname $configfile"
+ elif ! $PMLOGCONF -c -q -h $hostname "$configfile" >$tmp/diag 2>&1
+ then
+ _warning "pmlogconf failed to generate \"$configfile\""
+ cat $tmp/diag
+ echo "=== start pmlogconf file ==="
+ cat "$configfile"
+ echo "=== end pmlogconf file ==="
+ else
+ chown $PCP_USER:$PCP_GROUP "$configfile" >/dev/null 2>&1
+ fi
+ fi
+}
+
+_get_logfile()
+{
+ # looking for -lLOGFILE or -l LOGFILE in args
+ #
+ want=false
+ for a in $args
+ do
+ if $want
+ then
+ logfile="$a"
+ want=false
+ break
+ fi
+ case "$a"
+ in
+ -l)
+ want=true
+ ;;
+ -l*)
+ logfile=`echo "$a" | sed -e 's/-l//'`
+ break
+ ;;
+ esac
+ done
+}
+
+_check_archive()
+{
+ if [ ! -e "$logfile" ]
+ then
+ echo "$prog: Error: cannot find pmlogger output file at \"$logfile\""
+ if $TERSE
+ then
+ :
+ else
+ logdir=`dirname "$logfile"`
+ echo "Directory (`cd "$logdir"; $PWDCMND`) contents:"
+ LC_TIME=POSIX ls -la "$logdir"
+ fi
+ elif [ -f "$logfile" ]
+ then
+ echo "Contents of pmlogger output file \"$logfile\" ..."
+ cat "$logfile"
+ fi
+}
+
+_check_logger()
+{
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " [process $1] ""$PCP_ECHO_C"
+
+ # wait until pmlogger process starts, or exits
+ #
+ delay=5
+ [ ! -z "$PMCD_CONNECT_TIMEOUT" ] && delay=$PMCD_CONNECT_TIMEOUT
+ x=5
+ [ ! -z "$PMCD_REQUEST_TIMEOUT" ] && x=$PMCD_REQUEST_TIMEOUT
+
+ # wait for maximum time of a connection and 20 requests
+ #
+ delay=`expr \( $delay + 20 \* $x \) \* 10` # tenths of a second
+ while [ $delay -gt 0 ]
+ do
+ if [ -f $logfile ]
+ then
+ # $logfile was previously removed, if it has appeared again
+ # then we know pmlogger has started ... if not just sleep and
+ # try again
+ #
+ if echo "connect $1" | pmlc 2>&1 | grep "Unable to connect" >/dev/null
+ then
+ :
+ else
+ $VERBOSE && echo " done"
+ return 0
+ fi
+
+ _plist=`_get_pids_by_name pmlogger`
+ _found=false
+ for _p in `echo $_plist`
+ do
+ [ $_p -eq $1 ] && _found=true
+ done
+
+ if $_found
+ then
+ # process still here, just not accepting pmlc connections
+ # yet, try again
+ :
+ else
+ $VERBOSE || _message restart
+ echo " process exited!"
+ if $TERSE
+ then
+ :
+ else
+ echo "$prog: Error: failed to restart pmlogger"
+ echo "Current pmlogger processes:"
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS | tee $tmp/tmp | sed -n -e 1p
+ for _p in `echo $_plist`
+ do
+ sed -n -e "/^[ ]*[^ ]* [ ]*$_p /p" < $tmp/tmp
+ done
+ echo
+ fi
+ _check_archive
+ return 1
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ $VERBOSE && [ `expr $delay % 10` -eq 0 ] && \
+ $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ done
+ $VERBOSE || _message restart
+ echo " timed out waiting!"
+ if $TERSE
+ then
+ :
+ else
+ sed -e 's/^/ /' $tmp/out
+ fi
+ _check_archive
+ return 1
+}
+
+# note on control file format version
+# 1.0 was shipped as part of PCPWEB beta, and did not include the
+# socks field [this is the default for backwards compatibility]
+# 1.1 is the first production release, and the version is set in
+# the control file with a $version=1.1 line (see below)
+#
+version=''
+
+echo >$tmp/dir
+rm -f $tmp/err $tmp/pmloggers
+
+line=0
+cat $CONTROL \
+ | sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+ | while read host primary socks dir args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ dir_hostname=`hostname || echo localhost`
+ dir=`echo $dir | sed -e "s;LOCALHOSTNAME;$dir_hostname;"`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ $VERY_VERBOSE && echo "[control:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $primary $socks $dir $args"
+ cmd=`echo "$host $primary $socks $dir $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$version" -o "$version" = "1.0" ]
+ then
+ if [ -z "$version" ]
+ then
+ echo "$prog: Warning: processing default version 1.0 control format"
+ version=1.0
+ fi
+ args="$dir $args"
+ dir="$socks"
+ socks=n
+ fi
+
+ if [ -z "$primary" -o -z "$socks" -o -z "$dir" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ if $VERY_VERBOSE
+ then
+ pflag=''
+ [ $primary = y ] && pflag=' -P'
+ echo "Check pmlogger$pflag -h $host ... in $dir ..."
+ fi
+
+ # check for directory duplicate entries
+ #
+ if [ "`grep $dir $tmp/dir`" = "$dir" ]
+ then
+ _error "Cannot start more than one pmlogger instance for archive directory \"$dir\""
+ continue
+ else
+ echo "$dir" >>$tmp/dir
+ fi
+
+ # make sure output directory exists
+ #
+ if [ ! -d "$dir" ]
+ then
+ mkdir -p -m 755 "$dir" >$tmp/err 2>&1
+ if [ ! -d "$dir" ]
+ then
+ cat $tmp/err
+ _error "cannot create directory ($dir) for PCP archive files"
+ continue
+ else
+ _warning "creating directory ($dir) for PCP archive files"
+ fi
+ chown $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ fi
+
+ cd "$dir"
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ # ensure pcp user will be able to write there
+ #
+ chown -R $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ if [ ! -w "$dir" ]
+ then
+ echo "$prog: Warning: no write access in $dir, skip lock file processing"
+ else
+ # demand mutual exclusion
+ #
+ rm -f $tmp/stamp $tmp/out
+ delay=200 # tenths of a second
+ while [ $delay -gt 0 ]
+ do
+ if pmlock -v lock >>$tmp/out 2>&1
+ then
+ echo $dir/lock >$tmp/lock
+ break
+ else
+ [ -f $tmp/stamp ] || touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ if [ -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: removing lock file older than 30 minutes"
+ LC_TIME=POSIX ls -l $dir/lock
+ rm -f lock
+ else
+ # there is a small timing window here where pmlock
+ # might fail, but the lock file has been removed by
+ # the time we get here, so just keep trying
+ #
+ :
+ fi
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ done
+
+ if [ $delay -eq 0 ]
+ then
+ # failed to gain mutex lock
+ #
+ # maybe pmlogger_daily is running ... check and silently
+ # move on if this is the case
+ #
+ # Note: $PCP_RUN_DIR may not exist (see pmlogger_daily note), but
+ # only if pmlogger_daily has not run, so no chance of a
+ # collision
+ #
+ if [ -f "$PCP_RUN_DIR"/pmlogger_daily.pid ]
+ then
+ # maybe, check pid matches a running /bin/sh
+ #
+ pid=`cat "$PCP_RUN_DIR"/pmlogger_daily.pid`
+ if _get_pids_by_name sh | grep "^$pid\$" >/dev/null
+ then
+ # seems to be still running ... nothing for us to see
+ # or do here
+ #
+ continue
+ fi
+ fi
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: is another PCP cron job running concurrently?"
+ LC_TIME=POSIX ls -l $dir/lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($dir/lock) ..."
+ continue
+ fi
+ fi
+
+ pid=''
+ if [ "X$primary" = Xy ]
+ then
+ # NB: FQDN cleanup: previously, we used to quietly accept several
+ # putative-aliases in the first (hostname) slot for a primary logger,
+ # which were all supposed to refer to the local host. So now we
+ # squash them all to the officially pcp-preferred way to access it.
+ # This does not get used by pmlogger in the end (gets -P and not -h
+ # in the primary logger case), but it *does* matter for pmlogconf.
+ host=local:
+
+ if test -f "$PCP_TMP_DIR/pmlogger/primary"
+ then
+ if $VERY_VERBOSE
+ then
+ _host=`sed -n 2p <"$PCP_TMP_DIR/pmlogger/primary"`
+ _arch=`sed -n 3p <"$PCP_TMP_DIR/pmlogger/primary"`
+ $PCP_ECHO_PROG $PCP_ECHO_N "... try $PCP_TMP_DIR/pmlogger/primary: host=$_host arch=$_arch""$PCP_ECHO_C"
+ fi
+ primary_inode=`_get_ino $PCP_TMP_DIR/pmlogger/primary`
+ $VERY_VERBOSE && echo primary_inode=$primary_inode
+ for file in $PCP_TMP_DIR/pmlogger/*
+ do
+ case "$file"
+ in
+ */primary|*\*)
+ ;;
+ */[0-9]*)
+ inode=`_get_ino "$file"`
+ $VERY_VERBOSE && echo $file inode=$inode
+ if [ "$primary_inode" = "$inode" ]
+ then
+ pid="`echo $file | sed -e 's/.*\/\([^/]*\)$/\1/'`"
+ break
+ fi
+ ;;
+ esac
+ done
+ if [ -z "$pid" ]
+ then
+ if $VERY_VERBOSE
+ then
+ echo "primary pmlogger process pid not found"
+ ls -l "$PCP_TMP_DIR/pmlogger"
+ fi
+ else
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "primary pmlogger process $pid identified, OK"
+ else
+ $VERY_VERBOSE && echo "primary pmlogger process $pid not running"
+ pid=''
+ fi
+ fi
+ fi
+ else
+ for log in $PCP_TMP_DIR/pmlogger/[0-9]*
+ do
+ [ "$log" = "$PCP_TMP_DIR/pmlogger/[0-9]*" ] && continue
+ if $VERY_VERBOSE
+ then
+ _host=`sed -n 2p <$log`
+ _arch=`sed -n 3p <$log`
+ $PCP_ECHO_PROG $PCP_ECHO_N "... try $log host=$_host arch=$_arch: ""$PCP_ECHO_C"
+ fi
+ # throw away stderr in case $log has been removed by now
+ match=`sed -e '3s/\/[0-9][0-9][0-9][0-9][0-9.]*$//' $log 2>/dev/null \
+ | $PCP_AWK_PROG '
+BEGIN { m = 0 }
+NR == 3 && $0 == "'$dir'" { m = 2; next }
+END { print m }'`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
+ if [ "$match" = 2 ]
+ then
+ pid=`echo $log | sed -e 's,.*/,,'`
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "pmlogger process $pid identified, OK"
+ break
+ fi
+ $VERY_VERBOSE && echo "pmlogger process $pid not running, skip"
+ pid=''
+ else
+ $VERY_VERBOSE && echo "different directory, skip"
+ fi
+ done
+ fi
+
+ if [ -z "$pid" -a $START_PMLOGGER = true ]
+ then
+ rm -f Latest
+
+ if [ "X$primary" = Xy ]
+ then
+ args="-P $args"
+ iam=" primary"
+ # clean up port-map, just in case
+ #
+ PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
+ rm -f "$PM_LOG_PORT_DIR/primary"
+ else
+ args="-h $host $args"
+ iam=""
+ fi
+
+ # each new log started is named yyyymmdd.hh.mm
+ #
+ LOGNAME=`date "+%Y%m%d.%H.%M"`
+
+ # handle duplicates/aliases (happens when pmlogger is restarted
+ # within a minute and LOGNAME is the same)
+ #
+ suff=''
+ for file in $LOGNAME.*
+ do
+ [ "$file" = "$LOGNAME"'.*' ] && continue
+ # we have a clash! ... find a new -number suffix for the
+ # existing files ... we are going to keep $LOGNAME for the
+ # new pmlogger below
+ #
+ if [ -z "$suff" ]
+ then
+ for xx in 0 1 2 3 4 5 6 7 8 9
+ do
+ for yy in 0 1 2 3 4 5 6 7 8 9
+ do
+ [ "`echo $LOGNAME-${xx}${yy}.*`" != "$LOGNAME-${xx}${yy}.*" ] && continue
+ suff=${xx}${yy}
+ break
+ done
+ [ ! -z "$suff" ] && break
+ done
+ if [ -z "$suff" ]
+ then
+ _error "unable to break duplicate clash for archive basename $LOGNAME"
+ fi
+ $VERBOSE && echo "Duplicate archive basename ... rename $LOGNAME.* files to $LOGNAME-$suff.*"
+ fi
+ eval $MV -f $file `echo $file | sed -e "s/$LOGNAME/&-$suff/"`
+ done
+
+ configfile=`_get_configfile $args`
+ if [ ! -z "$configfile" ]
+ then
+ # if this is a relative path and not relative to cwd,
+ # substitute in the default pmlogger search location.
+ #
+ if [ ! -f "$configfile" -a "`basename $configfile`" = "$configfile" ]
+ then
+ configfile="$PCP_SYSCONF_DIR/pmlogger/$configfile"
+ fi
+
+ # check configuration file exists and is up to date
+ _configure_pmlogger "$configfile" "$host"
+ fi
+
+ $VERBOSE && _message restart
+
+ sock_me=''
+ if [ "$socks" = y ]
+ then
+ # only check for pmsocks if it's specified in the control file
+ have_pmsocks=false
+ if which pmsocks >/dev/null 2>&1
+ then
+ # check if pmsocks has been set up correctly
+ if pmsocks ls >/dev/null 2>&1
+ then
+ have_pmsocks=true
+ fi
+ fi
+
+ if $have_pmsocks
+ then
+ sock_me="pmsocks "
+ else
+ echo "$prog: Warning: no pmsocks available, would run without"
+ sock_me=""
+ fi
+ fi
+
+ _get_logfile
+ if [ -f $logfile ]
+ then
+ $VERBOSE && $SHOWME && echo
+ eval $MV -f $logfile $logfile.prior
+ fi
+
+ args="$args -m pmlogger_check"
+ if $SHOWME
+ then
+ echo
+ echo "+ ${sock_me}$PMLOGGER $args $LOGNAME"
+ _unlock
+ continue
+ else
+ $PCP_BINADM_DIR/pmpost "start pmlogger from $prog for host $host"
+ ${sock_me}$PMLOGGER $args $LOGNAME >$tmp/out 2>&1 &
+ pid=$!
+ fi
+
+ # wait for pmlogger to get started, and check on its health
+ _check_logger $pid
+
+ # the archive folio Latest is for the most recent archive in
+ # this directory
+ #
+ if [ -f $LOGNAME.0 ]
+ then
+ $VERBOSE && echo "Latest folio created for $LOGNAME"
+ mkaf $LOGNAME.0 >Latest
+ chown $PCP_USER:$PCP_GROUP Latest >/dev/null 2>&1
+ else
+ logdir=`dirname $LOGNAME`
+ if $TERSE
+ then
+ echo "$prog: Error: archive file `cd $logdir; $PWDCMND`/$LOGNAME.0 missing"
+ else
+ echo "$prog: Error: archive file $LOGNAME.0 missing"
+ echo "Directory (`cd $logdir; $PWDCMND`) contents:"
+ LC_TIME=POSIX ls -la $logdir
+ fi
+ fi
+
+ elif [ ! -z "$pid" -a $START_PMLOGGER = false ]
+ then
+ # Send pmlogger a SIGTERM, which is noted as a pending shutdown.
+ # Add pid to list of loggers sent SIGTERM - may need SIGKILL later.
+ #
+ $VERY_VERBOSE && echo "+ $KILL -s TERM $pid"
+ eval $KILL -s TERM $pid
+ $PCP_ECHO_PROG $PCP_ECHO_N "$pid ""$PCP_ECHO_C" >> $tmp/pmloggers
+ fi
+
+ _unlock
+done
+
+# check all the SIGTERM'd loggers really died - if not, use a bigger hammer.
+#
+if $SHOWME
+then
+ :
+elif [ $START_PMLOGGER = false -a -s $tmp/pmloggers ]
+then
+ pmloggerlist=`cat $tmp/pmloggers`
+ if ps -p "$pmloggerlist" >/dev/null 2>&1
+ then
+ $VERY_VERBOSE && ( echo; $PCP_ECHO_PROG $PCP_ECHO_N "+ $KILL -KILL `cat $tmp/pmies` ...""$PCP_ECHO_C" )
+ eval $KILL -s KILL $pmloggerlist >/dev/null 2>&1
+ delay=30 # tenths of a second
+ while ps -f -p "$pmloggerlist" >$tmp/alive 2>&1
+ do
+ if [ $delay -gt 0 ]
+ then
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ continue
+ fi
+ echo "$prog: Error: pmlogger process(es) will not die"
+ cat $tmp/alive
+ status=1
+ break
+ done
+ fi
+fi
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh
new file mode 100755
index 0000000..12bc3d7
--- /dev/null
+++ b/src/pmlogger/pmlogger_daily.sh
@@ -0,0 +1,952 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Daily administrative script for PCP archive logs
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+# error messages should go to stderr, not the GUI notifiers
+#
+unset PCP_STDERR
+
+# constant setup
+#
+prog=`basename $0`
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+
+_cleanup()
+{
+ lockfile=`cat $tmp/lock 2>/dev/null`
+ rm -f "$PCP_RUN_DIR/pmlogger_daily.pid" "$lockfile"
+ rm -rf $tmp
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+echo >$tmp/lock
+
+if is_chkconfig_on pmlogger
+then
+ PMLOGGER_CTL=on
+else
+ PMLOGGER_CTL=off
+fi
+
+# control file for pmlogger administration ... edit the entries in this
+# file to reflect your local configuration (see also -c option below)
+#
+CONTROL=$PCP_PMLOGGERCONTROL_PATH
+
+# default number of days to keep archive logs
+#
+CULLAFTER=14
+
+# default compression program and days until starting compression
+#
+COMPRESS=xz
+COMPRESSAFTER=""
+COMPRESSREGEX="\.(meta|index|Z|gz|bz2|zip|xz|lzma|lzo|lz4)$"
+
+# threshold size to roll $PCP_LOG_DIR/NOTICES
+#
+NOTICES=$PCP_LOG_DIR/NOTICES
+ROLLNOTICES=20480
+
+# mail addresses to send daily NOTICES summary to
+#
+MAILME=""
+MAILFILE=$PCP_LOG_DIR/NOTICES.daily
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the fourth column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+# (see BugWorks ID #595416).
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+if [ -z "$PWDCMND" ]
+then
+ # Looks like we have no choice here...
+ # force it to a known IRIX location
+ PWDCMND=/bin/pwd
+fi
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+echo > $tmp/usage
+cat >> $tmp/usage <<EOF
+Options:
+ -c=FILE,--control=FILE pmlogger control file
+ -k=N,--discard=N remove archives after N days
+ -m=ADDRs,--mail=ADDRs send daily NOTICES entries to email addresses
+ -M do not rewrite, merge or rename archives
+ -N,--showme perform a dry run, showing what would be done
+ -o merge yesterdays logs only (old form, default is all)
+ -r,--norewrite do not process archives with pmlogrewrite(1)
+ -s=SIZE,--rotate=SIZE rotate NOTICES file after reaching SIZE bytes
+ -t=WANT implies -VV, keep verbose output trace for WANT days
+ -V,--verbose verbose output (multiple times for very verbose)
+ -x=N,--compress-after=N compress archive data files after N days
+ -X=PROGRAM,--compressor=PROGRAM use PROGRAM for archive data file compression
+ -Y=REGEX,--regex=REGEX egrep filter when compressing files ["$COMPRESSREGEX"]
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ status=1
+ exit
+}
+
+# option parsing
+#
+SHOWME=false
+VERBOSE=false
+VERY_VERBOSE=false
+MYARGS=""
+OFLAG=false
+TRACE=0
+RFLAG=false
+MFLAG=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -k) CULLAFTER="$2"
+ shift
+ check=`echo "$CULLAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" -a X"$check" != Xforever ]
+ then
+ echo "Error: -k option ($CULLAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -m) MAILME="$2"
+ shift
+ ;;
+ -N) SHOWME=true
+ MYARGS="$MYARGS -N"
+ ;;
+ -M) MFLAG=true
+ RFLAG=true
+ ;;
+ -o) OFLAG=true
+ ;;
+ -r) RFLAG=true
+ ;;
+ -s) ROLLNOTICES="$2"
+ shift
+ check=`echo "$ROLLNOTICES" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" ]
+ then
+ echo "Error: -s option ($ROLLNOTICES) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -t) TRACE="$2"
+ shift
+ # from here on, all stdout and stderr output goes to
+ # $PCP_LOG_DIR/pmlogger/daily.<date>.trace
+ #
+ exec 1>$PCP_LOG_DIR/pmlogger/daily.`date "+%Y%m%d.%H.%M"`.trace 2>&1
+ VERBOSE=true
+ VERY_VERBOSE=true
+ MYARGS="$MYARGS -V -V"
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ MYARGS="$MYARGS -V"
+ ;;
+ -x) COMPRESSAFTER="$2"
+ shift
+ check=`echo "$COMPRESSAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" ]
+ then
+ echo "Error: -x option ($COMPRESSAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -X) COMPRESS="$2"
+ shift
+ ;;
+ -Y) COMPRESSREGEX="$2"
+ shift
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ ;;
+ esac
+ shift
+done
+
+[ $# -ne 0 ] && _usage
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+# each new archive log started by pmnewlog or pmlogger_check is named
+# yyyymmdd.hh.mm
+#
+LOGNAME=`date "+%Y%m%d.%H.%M"`
+
+_error()
+{
+ _report Error "$1"
+}
+
+_warning()
+{
+ _report Warning "$1"
+}
+
+_report()
+{
+ echo "$prog: $1: $2"
+ echo "[$CONTROL:$line] ... logging for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_unlock()
+{
+ rm -f lock
+ echo >$tmp/lock
+}
+
+# filter file names to leave those that look like PCP archives
+# managed by pmlogger_check and pmlogger_daily, namely they begin
+# with a datestamp
+#
+# need to handle both the year 2000 and the old name formats, and
+# possible ./ prefix (from find .)
+#
+_filter_filename()
+{
+ sed -n \
+ -e 's/^\.\///' \
+ -e '/^[12][0-9][0-9][0-9][0-1][0-9][0-3][0-9][-.]/p' \
+ -e '/^[0-9][0-9][0-1][0-9][0-3][0-9][-.]/p'
+}
+
+_get_ino()
+{
+ # get inode number for $1
+ # throw away stderr (and return '') in case $1 has been removed by now
+ #
+ stat "$1" 2>/dev/null \
+ | sed -n '/Device:[ ].*[ ]Inode:/{
+s/Device:[ ].*[ ]Inode:[ ]*//
+s/[ ].*//
+p
+}'
+}
+
+# mails out any entries for the previous 24hrs from the PCP notices file
+#
+if [ ! -z "$MAILME" ]
+then
+ # get start time of NOTICES entries we want - all earlier are discarded
+ #
+ args=`pmdate -1d '-v yy=%Y -v my=%b -v dy=%d'`
+ args=`pmdate -1d '-v Hy=%H -v My=%M'`" $args"
+ args=`pmdate '-v yt=%Y -v mt=%b -v dt=%d'`" $args"
+
+ #
+ # Basic algorithm:
+ # from NOTICES head, look for a DATE: entry for yesterday or today;
+ # if its yesterday, find all HH:MM timestamps which are in the window,
+ # until the end of yesterday is reached;
+ # copy out the remainder of the file (todays entries).
+ #
+ # initially, entries have one of three forms:
+ # DATE: weekday mon day HH:MM:SS year
+ # Started by pmlogger_daily: weekday mon day HH:MM:SS TZ year
+ # HH:MM message
+ #
+
+ # preprocess to provide a common date separator - if new date stamps are
+ # ever introduced into the NOTICES file, massage them first...
+ #
+ rm -f $tmp/pcp
+ $PCP_AWK_PROG '
+/^Started/ { print "DATE:",$4,$5,$6,$7,$9; next }
+ { print }
+ ' $NOTICES | \
+ $PCP_AWK_PROG -F ':[ \t]*|[ \t]+' $args '
+$1 == "DATE" && $3 == mt && $4 == dt && $8 == yt { tday = 1; print; next }
+$1 == "DATE" && $3 == my && $4 == dy && $8 == yy { yday = 1; print; next }
+ { if ( tday || (yday && $1 > Hy) || (yday && $1 == Hy && $2 >= My) )
+ print
+ }' >$tmp/pcp
+
+ if [ -s $tmp/pcp ]
+ then
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "PCP NOTICES summary for `hostname`" $MAILME <$tmp/pcp
+ else
+ echo "$prog: Warning: cannot find a mail agent to send mail ..."
+ echo "PCP NOTICES summary for `hostname`"
+ cat $tmp/pcp
+ fi
+ [ -w `dirname "$NOTICES"` ] && mv $tmp/pcp "$MAILFILE"
+ fi
+fi
+
+
+# Roll $PCP_LOG_DIR/NOTICES -> $PCP_LOG_DIR/NOTICES.old if larger
+# that 10 Kbytes, and you can write in $PCP_LOG_DIR
+#
+if [ -s "$NOTICES" -a -w `dirname "$NOTICES"` ]
+then
+ if [ "`wc -c <"$NOTICES"`" -ge $ROLLNOTICES ]
+ then
+ if $VERBOSE
+ then
+ echo "Roll $NOTICES -> $NOTICES.old"
+ echo "Start new $NOTICES"
+ fi
+ if $SHOWME
+ then
+ echo "+ mv -f $NOTICES $NOTICES.old"
+ echo "+ touch $NOTICES"
+ else
+ echo >>"$NOTICES"
+ echo "*** rotated by $prog: `date`" >>"$NOTICES"
+ mv -f "$NOTICES" "$NOTICES.old"
+ echo "Started by $prog: `date`" >"$NOTICES"
+ (id "$PCP_USER" && chown $PCP_USER:$PCP_GROUP "$NOTICES") >/dev/null 2>&1
+ fi
+ fi
+fi
+
+# Keep our pid in $PCP_RUN_DIR/pmlogger_daily.pid ... this is checked
+# by pmlogger_check when it fails to obtain the lock should it be run
+# while pmlogger_daily is running
+#
+# For most packages, $PCP_RUN_DIR is included in the package,
+# but for Debian and cases where /var/run is a mounted filesystem
+# it may not exist, so create it here before it is used to create
+# any pid/lock files
+#
+# $PCP_RUN_DIR creation is also done in pmcd startup, but pmcd may
+# not be running on this system
+#
+if [ ! -d "$PCP_RUN_DIR" ]
+then
+ mkdir -p -m 775 "$PCP_RUN_DIR"
+ chown $PCP_USER:$PCP_GROUP "$PCP_RUN_DIR"
+fi
+echo $$ >"$PCP_RUN_DIR"/pmlogger_daily.pid
+
+# note on control file format version
+# 1.0 was shipped as part of PCPWEB beta, and did not include the
+# socks field [this is the default for backwards compatibility]
+# 1.1 is the first production release, and the version is set in
+# the control file with a $version=1.1 line (see below)
+#
+
+rm -f $tmp/err
+line=0
+version=''
+cat $CONTROL \
+| sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+| while read host primary socks dir args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ dir_hostname=`hostname || echo localhost`
+ dir=`echo $dir | sed -e "s;LOCALHOSTNAME;$dir_hostname;"`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ $VERY_VERBOSE && echo "[control:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $primary $socks $dir $args"
+ cmd=`echo "$host $primary $socks $dir $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$version" -o "$version" = "1.0" ]
+ then
+ if [ -z "$version" ]
+ then
+ echo "$prog: Warning: processing default version 1.0 control format"
+ version=1.0
+ fi
+ args="$dir $args"
+ dir="$socks"
+ socks=n
+ fi
+
+ if [ -z "$primary" -o -z "$socks" -o -z "$dir" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ if $VERY_VERBOSE
+ then
+ pflag=''
+ [ $primary = y ] && pflag=' -P'
+ echo "Check pmlogger$pflag -h $host ... in $dir ..."
+ fi
+
+ if [ ! -d $dir ]
+ then
+ _error "archive directory ($dir) does not exist"
+ continue
+ fi
+
+ cd $dir
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ if $VERBOSE
+ then
+ echo
+ echo "=== daily maintenance of PCP archives for host $host ==="
+ echo
+ fi
+
+ if [ ! -w $dir ]
+ then
+ echo "$prog: Warning: no write access in $dir, skip lock file processing"
+ else
+ # demand mutual exclusion
+ #
+ fail=true
+ rm -f $tmp/stamp
+ for try in 1 2 3 4
+ do
+ if pmlock -v lock >$tmp/out
+ then
+ echo $dir/lock >$tmp/lock
+ fail=false
+ break
+ else
+ if [ ! -f $tmp/stamp ]
+ then
+ touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ fi
+ if [ ! -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ :
+ else
+ echo "$prog: Warning: removing lock file older than 30 minutes"
+ LC_TIME=POSIX ls -l $dir/lock
+ rm -f lock
+ fi
+ fi
+ sleep 5
+ done
+
+ if $fail
+ then
+ # failed to gain mutex lock
+ #
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: is another PCP cron job running concurrently?"
+ LC_TIME=POSIX ls -l $dir/lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($dir/lock) ..."
+ continue
+ fi
+ fi
+
+ pid=''
+ if [ X"$primary" = Xy ]
+ then
+ # NB: FQDN cleanup: previously, we used to quietly accept several
+ # putative-aliases in the first (hostname) slot for a primary logger,
+ # which were all supposed to refer to the local host. So now we
+ # squash them all to the officially pcp-preferred way to access it.
+ host=local:
+
+ if test -f "$PCP_TMP_DIR/pmlogger/primary"
+ then
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "... try $PCP_TMP_DIR/pmlogger/primary: ""$PCP_ECHO_C"
+ primary_inode=`_get_ino $PCP_TMP_DIR/pmlogger/primary`
+ $VERY_VERBOSE && echo primary_inode=$primary_inode
+ for file in $PCP_TMP_DIR/pmlogger/*
+ do
+ case "$file"
+ in
+ */primary|*\*)
+ ;;
+ */[0-9]*)
+ inode=`_get_ino "$file"`
+ $VERY_VERBOSE && echo $file inode=$inode
+ if [ "$primary_inode" = "$inode" ]
+ then
+ pid="`echo $file | sed -e 's/.*\/\([^/]*\)$/\1/'`"
+ break
+ fi
+ ;;
+ esac
+ done
+ if [ -z "$pid" ]
+ then
+ if $VERY_VERBOSE
+ then
+ echo "primary pmlogger process pid not found"
+ ls -l "$PCP_TMP_DIR/pmlogger"
+ fi
+ else
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "primary pmlogger process $pid identified, OK"
+ else
+ $VERY_VERBOSE && echo "primary pmlogger process $pid not running"
+ pid=``
+ fi
+ fi
+ fi
+ else
+ for log in $PCP_TMP_DIR/pmlogger/[0-9]*
+ do
+ [ "$log" = "$PCP_TMP_DIR/pmlogger/[0-9]*" ] && continue
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "... try $log: ""$PCP_ECHO_C"
+ match=`sed -e '3s/\/[0-9][0-9][0-9][0-9][0-9.]*$//' $log \
+ | $PCP_AWK_PROG '
+BEGIN { m = 0 }
+NR == 3 && $0 == "'$dir'" { m = 2; next }
+END { print m }'`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
+ if [ "$match" = 2 ]
+ then
+ pid=`echo $log | sed -e 's,.*/,,'`
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "pmlogger process $pid identified, OK"
+ break
+ fi
+ $VERY_VERBOSE && echo "pmlogger process $pid not running, skip"
+ pid=''
+ else
+ $VERY_VERBOSE && echo "different directory, skip"
+ fi
+ done
+ fi
+
+ if [ -z "$pid" ]
+ then
+ if [ "$PMLOGGER_CTL" = "on" ]
+ then
+ _error "no pmlogger instance running for host \"$host\""
+ fi
+ else
+ # now execute pmnewlog to "roll the archive logs"
+ #
+ [ X"$primary" != Xy ] && args="-p $pid $args"
+ # else: -P is already the default
+ [ X"$socks" = Xy ] && args="-s $args"
+ args="$args -m pmlogger_daily"
+ $SHOWME && echo "+ pmnewlog$MYARGS $args $LOGNAME"
+ if pmnewlog$MYARGS $args $LOGNAME
+ then
+ :
+ else
+ _error "problems executing pmnewlog for host \"$host\""
+ touch $tmp/err
+ fi
+ fi
+ $VERBOSE && echo
+
+ # Merge archive logs.
+ #
+ # Will work for new style YYYYMMDD.HH.MM[-NN] archives and old style
+ # YYMMDD.HH.MM[-NN] archives.
+ # Note: we need to handle duplicate-breaking forms like
+ # YYYYMMDD.HH.MM-seq# (even though pmlogger_merge already picks most
+ # of these up) in case the base YYYYMMDD.HH.MM archive is for some
+ # reason missing here
+ #
+ # Assume if .meta file is present then other archive components are
+ # also present (if not the case it is a serious process botch, and
+ # pmlogger_merge will fail below)
+ #
+ # Find all candidate input archives, remove any that contain today's
+ # date and group the remainder by date.
+ #
+ TODAY=`date +%Y%m%d`
+
+ find *.meta \
+ \( -name "*.[0-2][0-9].[0-5][0-9].meta" \
+ -o -name "*.[0-2][0-9].[0-5][0-9]-[0-9][0-9].meta" \
+ \) \
+ -print 2>/dev/null \
+ | sed \
+ -e "/^$TODAY\./d" \
+ -e 's/\.meta//' \
+ | sort -n \
+ | $PCP_AWK_PROG '
+ { if (lastdate != "" && match($1, "^" lastdate "\\.") == 1) {
+ # same date as previous one
+ inlist = inlist " " $1
+ next
+ }
+ else {
+ # different date as previous one
+ if (inlist != "") print lastdate,inlist
+ inlist = $1
+ lastdate = $1
+ sub(/\..*/, "", lastdate)
+ }
+ }
+END { if (inlist != "") print lastdate,inlist }' >$tmp/list
+
+ if $OFLAG
+ then
+ # -o option, preserve the old semantics, and only process the
+ # previous day's archives ... aim for a time close to midday
+ # yesterday and report that date
+ #
+ now_hr=`pmdate %H`
+ hr=`expr 12 + $now_hr`
+ grep "^[0-9]*`pmdate -${hr}H %y%m%d` " $tmp/list >$tmp/tmp
+ mv $tmp/tmp $tmp/list
+ fi
+
+ # pmlogrewrite if no -r on command line and
+ # (a) pmlogrewrite exists in the same directory that the input
+ # archives are found, or
+ # (b) if $PCP_VAR_LIB/config/pmlogrewrite exists
+ # "exists" => file, directory or symbolic link
+ #
+ rewrite=''
+ if $RFLAG
+ then
+ :
+ else
+ for type in -f -d -L
+ do
+ if [ $type "$dir/pmlogrewrite" ]
+ then
+ rewrite="$dir/pmlogrewrite"
+ break
+ fi
+ done
+ if [ -z "$rewrite" ]
+ then
+ for type in -f -d -L
+ do
+ if [ $type "$PCP_VAR_DIR/config/pmlogrewrite" ]
+ then
+ rewrite="$PCP_VAR_DIR/config/pmlogrewrite"
+ break
+ fi
+ done
+ fi
+ fi
+
+ rm -f $tmp/skip
+ if $MFLAG
+ then
+ # -M don't rewrite, merge or rename
+ #
+ :
+ else
+ if [ ! -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "$prog: Warning: no archives found to merge"
+ $VERY_VERBOSE && ls -l
+ fi
+ else
+ cat $tmp/list \
+ | while read outfile inlist
+ do
+ if [ -f $outfile.0 -o -f $outfile.index -o -f $outfile.meta ]
+ then
+ echo "$prog: Warning: output archive ($outfile) already exists"
+ echo "[$CONTROL:$line] ... skip log merging, culling and compressing for host \"$host\""
+ touch $tmp/skip
+ break
+ else
+ if [ -n "$rewrite" ]
+ then
+ $VERY_VERBOSE && echo "Rewriting input archives using $rewrite"
+ for arch in $inlist
+ do
+ if pmlogrewrite -iq -c "$rewrite" $arch
+ then
+ :
+ else
+ echo "$prog: Warning: rewrite for $arch using -c $rewrite failed"
+ echo "[$CONTROL:$line] ... skip log merging, culling and compressing for host \"$host\""
+ touch $tmp/skip
+ break
+ fi
+ done
+
+ fi
+ if $VERY_VERBOSE
+ then
+ for arch in $inlist
+ do
+ echo "Input archive $arch ..."
+ pmdumplog -L $arch
+ done
+ fi
+ narch=`echo $inlist | wc -w | sed -e 's/ //g'`
+ if [ "$narch" = 1 ]
+ then
+ # optimization ... rename don't merge for one input
+ # archive case
+ #
+ if $SHOWME
+ then
+ echo "+ pmlogmv$MYARGS $inlist $outfile"
+ else
+ if pmlogmv$MYARGS $inlist $outfile
+ then
+ if $VERY_VERBOSE
+ then
+ echo "Renamed output archive $outfile ..."
+ pmdumplog -L $outfile
+ fi
+ else
+ _error "problems executing pmlogmv for host \"$host\""
+ fi
+ fi
+ else
+ # more than one input archive, merge away
+ #
+ if $SHOWME
+ then
+ echo "+ pmlogger_merge$MYARGS -f $inlist $outfile"
+ else
+ if pmlogger_merge$MYARGS -f $inlist $outfile
+ then
+ if $VERY_VERBOSE
+ then
+ echo "Merged output archive $outfile ..."
+ pmdumplog -L $outfile
+ fi
+ else
+ _error "problems executing pmlogger_merge for host \"$host\""
+ fi
+ fi
+ fi
+ fi
+ done
+ fi
+ fi
+
+ if [ -f $tmp/skip ]
+ then
+ # this is sufficiently serious that we don't want to remove
+ # the lock file, so problems are not compounded the next time
+ # the script is run
+ $VERY_VERBOSE && echo "Skip culling and compression ..."
+ continue
+ fi
+
+ # and cull old archives
+ #
+ if [ X"$CULLAFTER" != X"forever" ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # FreeBSD semantics for find(1) -mtime +N are "rounded up to
+ # the next full 24-hour period", compared to GNU/Linux semantics
+ # "any fractional part is ignored". So, these are almost always
+ # off by one day in terms of the files selected.
+ # For consistency, try to match the GNU/Linux semantics by using
+ # one MORE day.
+ #
+ mtime=`expr $CULLAFTER + 1`
+ else
+ mtime=$CULLAFTER
+ fi
+ find . -type f -mtime +$mtime \
+ | _filter_filename \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Archive files older than $CULLAFTER days being removed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + rm -f
+ else
+ cat $tmp/list | xargs rm -f
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no archive files found to cull"
+ fi
+ fi
+
+ # and compress old archive data files
+ # (after cull - don't compress unnecessarily)
+ #
+ if [ ! -z "$COMPRESSAFTER" ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # See note above re. find(1) on FreeBSD
+ #
+ mtime=`expr $COMPRESSAFTER - 1`
+ else
+ mtime=$COMPRESSAFTER
+ fi
+ find . -type f -mtime +$mtime \
+ | _filter_filename \
+ | egrep -v "$COMPRESSREGEX" \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Archive files older than $COMPRESSAFTER days being compressed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + $COMPRESS
+ else
+ cat $tmp/list | xargs $COMPRESS
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no archive files found to compress"
+ fi
+ fi
+
+ # and cull old trace files (from -t option)
+ #
+ if [ "$TRACE" -gt 0 ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # See note above re. find(1) on FreeBSD
+ #
+ mtime=`expr $TRACE - 1`
+ else
+ mtime=$TRACE
+ fi
+ find $PCP_LOG_DIR/pmlogger -type f -mtime +$mtime \
+ | sed -n -e '/pmlogger\/daily\..*\.trace/p' \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Trace files older than $TRACE days being removed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + rm -f
+ else
+ cat $tmp/list | xargs rm -f
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no trace files found to cull"
+ fi
+ fi
+
+ _unlock
+
+done
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmlogger/pmlogger_merge.sh b/src/pmlogger/pmlogger_merge.sh
new file mode 100755
index 0000000..ba3edd1
--- /dev/null
+++ b/src/pmlogger/pmlogger_merge.sh
@@ -0,0 +1,282 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# merge a group of logfiles, e.g. all those for today
+#
+# default case, w/out arguments uses the default pmlogger filename
+# conventions for today's logs, namely `date +%Y%m%d` for both the
+# input-basename and the output-name
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+
+prog=`basename $0`
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+force=false
+VERBOSE=false
+SHOWME=false
+RM=rm
+
+_abandon()
+{
+ echo "$prog: These error(s) are fatal, no output archive has been created."
+ status=1
+ exit
+}
+
+_warning()
+{
+ echo "$prog: Trying to continue, although output archive may be corrupted."
+ force=false
+}
+
+cat > $tmp/usage << EOF
+Usage: [options] [input-basename ... output-name]
+
+Options:
+ -f, --force remove input files after creating output files
+ -N, --showme perform a dry run, showing what would be done
+ -V, --verbose increase diagnostic verbosity
+ --help
+EOF
+
+# option parsing
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+
+ -f) force=true
+ ;;
+
+ -N) SHOWME=true
+ RM="echo + rm"
+ ;;
+
+ -V) VERBOSE=true
+ ;;
+
+ --) shift
+ break
+ ;;
+
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+ ;;
+ esac
+ shift
+done
+
+if [ $# -eq 0 ]
+then
+ trylist=`date +%Y%m%d`
+ output=$input
+elif [ $# -ge 2 ]
+then
+ trylist=""
+ while [ $# -ge 2 ]
+ do
+ trylist="$trylist $1"
+ shift
+ done
+ output="$1"
+else
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+fi
+
+fail=false
+mergelist=""
+rmlist=""
+
+# handle dupicate-breaking name form of the base name
+# i.e. YYYYMMDD.HH.MM-seq# and ensure no duplicates
+#
+rm -f $tmp/input
+echo >$tmp/input
+for try in $trylist
+do
+ grep "^$try\$" $tmp/input >/dev/null || echo "$try" >>$tmp/input
+ for xxx in $try-*.index
+ do
+ [ "$xxx" = "$try-*.index" ] && continue
+ tie=`basename $xxx .index`
+ grep "^$tie\$" $tmp/input >/dev/null || echo "$tie" >>$tmp/input
+ done
+done
+
+for input in `cat $tmp/input`
+do
+ for file in $input.index
+ do
+ file=`basename $file .index`
+ rmlist="$rmlist $file"
+ empty=0
+ if [ ! -f "$file.index" ]
+ then
+ echo "$prog: Error: \"index\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.index" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ ! -f "$file.meta" ]
+ then
+ echo "$prog: Error: \"meta\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.meta" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ ! -f "$file.0" ]
+ then
+ echo "$prog: Error: \"volume 0\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.0" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ $empty -eq 3 ]
+ then
+ echo "$prog: Warning: archive \"$file\" is empty and will be skipped"
+ else
+ mergelist="$mergelist $file"
+ fi
+ done
+done
+
+if [ -f $output.index ]
+then
+ echo "$prog: Error: \"index\" file already exists for output archive \"$output\""
+ fail=true
+fi
+if [ -f $output.meta ]
+then
+ echo "$prog: Error: \"meta\" file already exists for output archive \"$output\""
+ fail=true
+fi
+if [ -f $output.0 ]
+then
+ echo "$prog: Error: \"volume 0\" file already exists for output archive \"$output\""
+ fail=true
+fi
+
+$fail && _abandon
+
+i=0
+list=""
+part=0
+if [ -z "$mergelist" ]
+then
+ $VERBOSE && echo "No archives to be merged."
+else
+ $VERBOSE && echo "Input archives to be merged:"
+ for input in $mergelist
+ do
+ for file in $input.index
+ do
+ if [ $i -ge 35 ]
+ then
+ # this limit requires of the order of 3 x 35 input + 3 x 1
+ # output = 108 file descriptors which should be well below any
+ # shell-imposed or system-imposed limits
+ #
+ $VERBOSE && echo " -> partial merge to $tmp/$part"
+ cmd="pmlogextract $list $tmp/$part"
+ if $SHOWME
+ then
+ echo "+ $cmd"
+ else
+ if $cmd
+ then
+ :
+ else
+ $VERBOSE || echo " -> partial merge to $tmp/$part"
+ echo "$prog: Directory: `pwd`"
+ echo "$prog: Failed: pmlogextract $list $tmp/$part"
+ _warning
+ fi
+ fi
+ list=$tmp/$part
+ part=`expr $part + 1`
+ i=0
+ fi
+ file=`basename $file .index`
+ list="$list $file"
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " $file""$PCP_ECHO_C"
+ numvol=`echo $file.[0-9]* | wc -w | sed -e 's/ *//g'`
+ if [ $numvol -gt 1 ]
+ then
+ $VERBOSE && echo " ($numvol volumes)"
+ else
+ $VERBOSE && echo
+ fi
+ i=`expr $i + 1`
+ done
+ done
+
+ cmd="pmlogextract $list $output"
+ if $SHOWME
+ then
+ echo "+ $cmd"
+ else
+ if $cmd
+ then
+ :
+ else
+ echo "$prog: Directory: `pwd`"
+ echo "$prog: Failed: pmlogextract $list $output"
+ _warning
+ fi
+ $VERBOSE && echo "Output archive files:"
+ for file in $output.meta $output.index $output.0
+ do
+ if [ -f $file ]
+ then
+ $VERBOSE && LC_TIME=POSIX ls -l $file
+ else
+ echo "$prog: Error: file \"$file\" not created"
+ force=false
+ fi
+ done
+ fi
+fi
+
+if $force
+then
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "Removing input archive files ...""$PCP_ECHO_C"
+ for input in $rmlist
+ do
+ for file in $input.index
+ do
+ file=`basename $file .index`
+ [ "$file" = "$output" ] && continue
+ eval $RM -f $file.index $file.meta $file.[0-9]*
+ done
+ done
+ $VERBOSE && echo " done"
+fi
+
+exit
diff --git a/src/pmlogger/pmlogmv.sh b/src/pmlogger/pmlogmv.sh
new file mode 100755
index 0000000..7a07098
--- /dev/null
+++ b/src/pmlogger/pmlogmv.sh
@@ -0,0 +1,261 @@
+#!/bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2014 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Move (rename) a PCP archive.
+#
+# Operation is atomic across the multiple files of a PCP archive
+#
+
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$status" 0
+trap "_cleanup; rm -rf $tmp; exit \$status" 1 2 3 15
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] oldname newname
+
+Options:
+ -N, --showme perform a dry run, showing what would be done
+ -V, --verbose increase diagnostic verbosity
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit 1
+}
+
+verbose=false
+showme=false
+LN=ln
+RM=rm
+RMF="rm -f"
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -N) # show me
+ showme=true
+ LN='echo >&2 + ln'
+ RM='echo >&2 + rm'
+ RMF='echo >&2 + rm -f'
+ ;;
+ -V) # verbose
+ verbose=true
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -\?)
+ _usage
+ # NOTREACHED
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 2 ]
+then
+ _usage
+ # NOTREACHED
+fi
+
+if [ -f "$1" ]
+then
+ # oldname is an existing file, strip the expected PCP suffix
+ #
+ case "$1"
+ in
+ *[0-9])
+ old=`echo "$1" | sed -e 's/\.[0-9][0-9]*$//'`
+ ;;
+ *.index|*.meta)
+ old=`echo "$1" | sed -e 's/\.[a-z][a-z]*$//'`
+ ;;
+ *)
+ echo >&2 "$prog: Error: oldname argument ($1) is not a PCP archive"
+ exit
+ ;;
+ esac
+else
+ old="$1"
+fi
+new="$2"
+
+_cleanup()
+{
+ if [ -f $tmp/old ]
+ then
+ for f in `cat $tmp/old`
+ do
+ if [ ! -f "$f" ]
+ then
+ part=`echo "$f" | sed -e 's/.*\.\([^.][^.]*\)$/\1/'`
+ if [ ! -f "$new.$part" ]
+ then
+ echo >&2 "$prog: Fatal: $f and $new.$part lost"
+ ls -l "$old"* "$new"*
+ rm -f $tmp/old
+ return
+ fi
+ $verbose && echo >&2 "cleanup: recover $f from $new.$part"
+ if eval $LN "$new.$part" "$f"
+ then
+ :
+ else
+ echo >&2 "$prog: Fatal: ln $new.$part $f failed!"
+ ls -l "$old"* "$new"*
+ rm -f $tmp/old
+ return
+ fi
+ fi
+ done
+ fi
+ if [ -f $tmp/new ]
+ then
+ for f in `cat $tmp/new`
+ do
+ $verbose && echo >&2 "cleanup: remove $f"
+ eval $RMF "$f"
+ done
+ rm -f $tmp/new
+ fi
+ exit
+}
+
+# get oldnames inventory check required files are present
+#
+ls "$old".* 2>&1 | egrep '\.(index|meta|[0-9][0-9]*)$' >$tmp/old
+if [ -s $tmp/old ]
+then
+ # $old may be an ambiguous suffix, e.g. 20140417.00 (with more than
+ # one .HH archives) ... pick the suffixes and make sure there are
+ # no duplicates
+ #
+ touch $tmp/ok
+ sed <$tmp/old \
+ -e 's/.*\.index$/index/' \
+ -e 's/.*\.meta$/meta/' \
+ -e 's/.*\.\([0-9][0-9]*\)$/\1/' \
+ | sort \
+ | uniq -c \
+ | while read c x
+ do
+ case $c
+ in
+ 1)
+ ;;
+ *)
+ echo >&2 "$prog: Error: oldname argument ($old) is a prefix for multiple PCP archive files:"
+ grep "\\.$x\$" $tmp/old | sed -e 's/^/ /' >&2
+ rm -f $tmp/ok
+ ;;
+ esac
+ done
+ [ -f $tmp/ok ] || exit
+else
+ echo >&2 "$prog: Error: cannot find any files for the input archive ($old)"
+ exit
+fi
+if grep -q '.[0-9][0-9]*$' $tmp/old
+then
+ :
+else
+ echo >&2 "$prog: Error: cannot find any data files for the input archive ($old)"
+ ls -l "$old"*
+ exit
+fi
+if grep -q '.meta$' $tmp/old
+then
+ :
+else
+ echo >&2 "$prog: Error: cannot find .metadata file for the input archive ($old)"
+ ls -l "$old"*
+ exit
+fi
+
+# (hard) link oldnames and newnames
+#
+for f in `cat $tmp/old`
+do
+ if [ ! -f "$f" ]
+ then
+ echo >&2 "$prog: Error: ln-pass: input file vanished: $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ part=`echo "$f" | sed -e 's/.*\.\([^.][^.]*\)$/\1/'`
+ if [ -f "$new.$part" ]
+ then
+ echo >&2 "$prog: Error: ln-pass: output file already exists: $new.$part"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ $verbose && echo >&2 "link $f -> $new.$part"
+ echo "$new.$part" >>$tmp/new
+ if eval $LN "$f" "$new.$part"
+ then
+ :
+ else
+ echo >&2 "$prog: Error: ln $f $new.$part failed!"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+done
+
+# unlink oldnames provided link count is 2
+#
+for f in `cat $tmp/old`
+do
+ if [ ! -f "$f" ]
+ then
+ echo >&2 "$prog: Error: rm-pass: input file vanished: $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ links=`stat $f | sed -n -e '/Links:/s/.*Links:[ ]*\([0-9][0-9]*\).*/\1/p'`
+ xpect=2
+ $showme && xpect=1
+ if [ -z "$links" -o "$links" != $xpect ]
+ then
+ echo >&2 "$prog: Error: rm-pass: link count "$links" (not $xpect): $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ fi
+ $verbose && echo >&2 "remove $f"
+ if eval $RM "$f"
+ then
+ :
+ else
+ echo >&2 "$prog: Warning: rm $f failed!"
+ fi
+done
+
+status=0
diff --git a/src/pmlogger/pmnewlog.sh b/src/pmlogger/pmnewlog.sh
new file mode 100755
index 0000000..ff68936
--- /dev/null
+++ b/src/pmlogger/pmnewlog.sh
@@ -0,0 +1,702 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# stop and restart a pmlogger instance
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+
+# error messages should go to stderr, not the GUI notifiers
+#
+unset PCP_STDERR
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+VERBOSE=false
+SHOWME=false
+CP=cp
+MV=mv
+RM=rm
+KILL=pmsignal
+primary=true
+myname="primary pmlogger"
+connect=primary
+access=""
+config=""
+saveconfig=""
+logfile="pmlogger.log"
+namespace=""
+args=""
+sock_me=""
+proxyhost="$PMPROXY_HOST"
+proxyport="$PMPROXY_PORT"
+
+cat > $tmp/usage << EOF
+# Usage: [options] archive
+
+pmnewlog options:
+ -a=FILE,--access=FILE specify access controls for the new pmlogger
+ -C=FILE,--save=FILE save the configuration of new pmlogger in FILE
+ -c=FILE,--config=FILE file to load configuration from
+ -N,--showme perform a dry run, showing what would be done
+ --namespace
+ -P,--primary execute as primary logger instance
+ -p=PID,--pid=PID restart non-primary logger with pid
+ -s,--socks use pmsocks
+ -V,--verbose turn on verbose reporting of pmnewlog progress
+ --help
+
+pmlogger options:
+ --debug
+ -c=FILE,--config=FILE file to load configuration from
+ -l=FILE, --log=FILE redirect diagnostics and trace output
+ -L, --linger run even if not primary logger instance and nothing to log
+ -m=MSG, --note=MSG descriptive note to be added to the port map file
+ --namespace
+ -P, --primary execute as primary logger instance
+ -r, --report report record sizes and archive growth rate
+ -t=DELTA, --interval=DELTA default logging interval
+ -T=TIME, --finish=TIME end of the time window
+ -v=SIZE, --volsize=SIZE switch log volumes after size has been accumulated
+ -y set timezone for times to local time rather than from PMCD host
+EOF
+
+_abandon()
+{
+ echo
+ echo "Sorry, but this is fatal. No new pmlogger instance has been started."
+ status=1
+ exit
+}
+
+_check_pid()
+{
+ if $SHOWME
+ then
+ :
+ else
+ _get_pids_by_name pmlogger | grep "^$1\$"
+ fi
+}
+
+_check_logfile()
+{
+ if [ ! -f $logfile ]
+ then
+ echo "Cannot find pmlogger output file at \"$logfile\""
+ else
+ echo "Contents of pmlogger output file \"$logfile\" ..."
+ cat $logfile
+ fi
+}
+
+_check_logger()
+{
+ # wait until pmlogger process starts, or exits
+ #
+ delay=5
+ [ ! -z "$PMCD_CONNECT_TIMEOUT" ] && delay=$PMCD_CONNECT_TIMEOUT
+ x=5
+ [ ! -z "$PMCD_REQUEST_TIMEOUT" ] && x=$PMCD_REQUEST_TIMEOUT
+
+ # wait for maximum time of a connection and 20 requests
+ #
+ delay=`expr $delay + 20 \* $x`
+ i=0
+ while [ $i -lt $delay ]
+ do
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ if $SHOWME
+ then
+ echo "+ echo 'connect $1' | $pmlc_prefix pmlc ..."
+ $VERBOSE && echo " done"
+ return 0
+ elif echo "connect $1" | eval $pmlc_prefix pmlc 2>&1 | grep "Unable to connect" >/dev/null
+ then
+ :
+ else
+ sleep 5
+ $VERBOSE && echo " done"
+ return 0
+ fi
+
+ if _get_pids_by_name pmlogger | grep "^$1\$" >/dev/null
+ then
+ :
+ else
+ $VERBOSE || _message restart
+ echo " process exited!"
+ _check_logfile
+ return 1
+ fi
+ sleep 5
+ i=`expr $i + 5`
+ done
+ $VERBOSE || _message restart
+ echo " timed out waiting!"
+ sed -e 's/^/ /' $tmp/out
+ _check_logfile
+ return 1
+}
+
+_message()
+{
+ case $1
+ in
+ looking)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Looking for $myname ...""$PCP_ECHO_C"
+ ;;
+ get_host)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Getting logged host name from $myname ...""$PCP_ECHO_C"
+ ;;
+ get_state)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Contacting $myname to get logging state ...""$PCP_ECHO_C"
+ ;;
+ restart)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Waiting for new pmlogger to start ..""$PCP_ECHO_C"
+ ;;
+ esac
+}
+
+_do_cmd()
+{
+ if $SHOWME
+ then
+ echo "+ $1"
+ else
+ eval $1
+ fi
+}
+
+# option parsing
+#
+# pmlogger, without -V version which is redefined as -V (verbose)
+# and -s exit_size which is redefined as -s (pmsocks), and ignore
+# [ -h host ] and [ -x fd ] as they make no sense in the argument
+# part of the pmlogger control file for a long-running pmlogger.
+#
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+
+# pmnewlog options and flags
+#
+
+ -a) access="$2"
+ shift
+ if [ ! -f "$access" ]
+ then
+ echo "$prog: Error: cannot find accessfile ($access)"
+ _abandon
+ fi
+ ;;
+
+ -C) saveconfig="$2"
+ shift
+ ;;
+
+ -N) SHOWME=true
+ CP="echo + cp"
+ MV="echo + mv"
+ RM="echo + rm"
+ KILL="echo + kill"
+ ;;
+
+ -p) pid=$2
+ shift
+ primary=false
+ myname="pmlogger (process $pid)"
+ connect=$pid
+ ;;
+
+ -s) if which pmsocks >/dev/null 2>&1
+ then
+ sock_me="pmsocks "
+ else
+ echo "$prog: Warning: no pmsocks available, would run without"
+ sock_me=""
+ fi
+ ;;
+
+ -V) VERBOSE=true
+ ;;
+
+# pmlogger options and flags that need special handling
+#
+
+ -c) config="$2"
+ shift
+ if [ ! -f "$config" ]
+ then
+ if [ -f "$PCP_SYSCONF_DIR/pmlogger/$config" ]
+ then
+ config="$PCP_SYSCONF_DIR/pmlogger/$config"
+ else
+ echo "$prog: Error: cannot find configfile ($config)"
+ _abandon
+ fi
+ fi
+ ;;
+
+ -l) logfile="$2"
+ shift
+ ;;
+
+ -n) namespace="-n $2"
+ args="${args}$1 $2 "
+ shift
+ ;;
+
+ -P) primary=true
+ myname="primary pmlogger"
+ ;;
+
+# pmlogger flags passed through
+#
+
+ -L|-r|-y)
+ args="${args}$1 "
+ ;;
+
+ -D|-m|-t|-T|-v)
+ args="${args}$1 $2 "
+ shift
+ ;;
+
+ --) # end of options, start of arguments
+ shift
+ break
+ ;;
+
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 1 ]
+then
+ echo "$prog: Insufficient arguments" >&2
+ echo >&2
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+fi
+
+# initial sanity checking for new archive name
+#
+archive="$1"
+
+# check that designated pmlogger is really running
+#
+$VERBOSE && _message looking
+$PCP_PS_PROG $PCP_PS_ALL_FLAGS \
+| if $primary
+then
+ grep 'pmlogger .*-P' | grep -v grep
+else
+ $PCP_AWK_PROG '$2 == '"$pid"' && /pmlogger/ { print }'
+fi >$tmp/out
+
+if [ -s $tmp/out ]
+then
+ $VERBOSE && echo " found"
+ $VERBOSE && cat $tmp/out
+ # expecting something like
+ # pcp 30019 ... pmlogger ...args... -h hostname ...args...
+ # pick pid and hostname (safer to do it here if possible because it
+ # captures the possible -h hostname@proxy construct which is lost
+ # if one gets the hostname from pmlogger via pmlc)
+ #
+ pid=`$PCP_AWK_PROG '{ print $2 }' <$tmp/out`
+ hostname=`sed -n <$tmp/out -e '/ -h /{
+s/.* -h //
+s/ .*//
+p
+}'`
+ # may have @proxyhost or @proxyhost:proxyport appended to hostname
+ #
+ proxyhost=`echo "$hostname" | sed -n -e '/@/{
+s/.*@//
+s/:.*//
+p
+}'`
+ proxyport=`echo "$hostname" | sed -n -e '/@.*:/s/.*;//p'`
+else
+ if $VERBOSE
+ then
+ :
+ else
+ _message looking
+ echo
+ fi
+ echo "$prog: Error: process not found"
+ _abandon
+fi
+
+if [ -n "$proxyhost" ]
+then
+ if [ -n "$proxyport" ]
+ then
+ pmlc_prefix="PMPROXY_HOST=$proxyhost PMPROXY_PORT=$proxyport"
+ else
+ pmlc_prefix="PMPROXY_HOST=$proxyhost"
+ fi
+else
+ pmlc_prefix=''
+fi
+
+# pass primary/not primary down
+#
+$primary && args="$args-P "
+
+# pass logfile option down
+#
+args="$args-l $logfile "
+
+# if not a primary pmlogger, get name of pmcd host pmlogger is connected to
+#
+if $primary
+then
+ hostname=localhost
+elif [ -z "$hostname" ]
+then
+ # did not get pmcd hostname from ps output above, talk to pmlogger
+ # via pmlc
+ #
+ # start critical section ... no interrupts due to pmlogger SIGPIPE
+ # bug in PCP 1.1
+ #
+ trap "echo; echo $prog:' Interrupt! ... I am talking to pmlogger, please wait ...'" 1 2 3 15
+ $VERBOSE && _message get_host
+
+ _do_cmd "( echo 'connect $connect' ; echo status ) | eval $pmlc_prefix pmlc 2>$tmp/err >$tmp/out"
+
+ # end critical section
+ #
+ trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+ if $SHOWME || [ ! -s $tmp/err ]
+ then
+ $VERBOSE && echo " done"
+ else
+ if grep "Unable to connect" $tmp/err >/dev/null
+ then
+ $VERBOSE || _message get_host
+ echo " failed to connect"
+ echo
+ sed -e 's/^/ /' $tmp/err
+ _abandon
+ else
+ $VERBOSE || _message get_host
+ echo
+ echo "$prog: Warning: errors from talking to $myname via pmlc"
+ sed -e 's/^/ /' $tmp/err
+ echo
+ echo "continuing ..."
+ fi
+ fi
+
+ if $SHOWME
+ then
+ hostname=somehost
+ else
+ hostname=`sed -n -e '/^pmlogger/s/.* from host //p' <$tmp/out`
+ if [ -z "$hostname" ]
+ then
+ echo "$prog: Error: failed to get host name from $myname"
+ echo "This is what was collected from $myname."
+ echo
+ sed -e 's/^/ /' $tmp/out
+ _abandon
+ fi
+ args="$args-h $hostname "
+ fi
+else
+ # got hostname from ps output
+ #
+ args="$args-h $hostname "
+fi
+
+# extract/construct config file if required
+#
+if [ -z "$config" ]
+then
+ # start critical section ... no interrupts due to pmlogger SIGPIPE
+ # bug in PCP 1.1
+ #
+ trap "echo; echo $prog:' Interrupt! ... I am talking to pmlogger, please wait ...'" 1 2 3 15
+ $VERBOSE && _message get_state
+
+ # iterate over top-level names in pmns, and query pmlc for
+ # current configuration ... note exclusion of "proc" metrics
+ # ... others may be excluded in a similar fashion
+ #
+ if $SHOWME
+ then
+ echo "+ ( echo 'connect $connect'; echo 'query ...'; ... ) | eval $pmlc_prefix pmlc $namespace | $PCP_AWK_PROG ..."
+ else
+ echo "connect $connect" >$tmp/pmlc.cmd
+ for top in `pminfo -h $hostname $namespace \
+ | sed -e 's/\..*//' -e '/^proc$/d' \
+ | sort -u`
+ do
+ echo "query $top" >>$tmp/pmlc.cmd
+ done
+ eval $pmlc_prefix pmlc $namespace <$tmp/pmlc.cmd 2>$tmp/err \
+ | $PCP_AWK_PROG >$tmp/out '
+/^[^ ]/ { metric = $1; next }
+$1 == "mand" || ( $1 == "adv" && $2 == "on" ) { print $0 " " metric }'
+ fi
+
+ # end critical section
+ #
+ trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+ if $SHOWME || [ ! -s $tmp/err ]
+ then
+ $VERBOSE && echo " done"
+ else
+ if grep "Unable to connect" $tmp/err >/dev/null
+ then
+ $VERBOSE || _message get_state
+ echo " failed to connect"
+ echo
+ sed -e 's/^/ /' $tmp/err
+ _abandon
+ else
+ $VERBOSE || _message get_state
+ echo
+ echo "$prog: Warning: errors from talking to $myname via pmlc"
+ sed -e 's/^/ /' $tmp/err
+ echo
+ echo "continuing ..."
+ fi
+ fi
+
+ if [ ! -s $tmp/out ]
+ then
+ if $SHOWME
+ then
+ :
+ else
+ echo "$prog: Error: failed to collect configuration info from $myname"
+ echo "Most likely this pmlogger instance is inactive."
+ _abandon
+ fi
+ fi
+
+ # convert to a pmlogger config file
+ #
+ if $SHOWME
+ then
+ echo "+ create new pmlogger config file ..."
+ else
+ sed <$tmp/out >$tmp/config \
+ -e 's/ on nl/ on/' \
+ -e 's/ off nl/ off/'\
+ -e 's/ *mand *\(o[nf]*\) /log mandatory \1 /' \
+ -e 's/ *adv *on /log advisory on/' \
+ -e 's/\[[0-9][0-9]* or /[/' \
+ -e 's/\(\[[^]]*]\) \([^ ]*\)/\2 \1/' \
+ -e 's/ */ /g'
+
+ if [ ! -s $tmp/config ]
+ then
+ echo "$prog: Error: failed to generate a pmlogger configuration file for pmlogger"
+ echo "This is what was collected from $myname."
+ echo
+ sed -e 's/^/ /' $tmp/out
+ _abandon
+ fi
+ fi
+ config=$tmp/config
+fi
+
+# optionally append access control specifications
+#
+if [ -n "$access" ]
+then
+ if grep '\[access]' $config >/dev/null
+ then
+ echo "$prog: Error: pmlogger configuration file already contains an"
+ echo " access control section, specifications from \"$access\" cannot"
+ echo " be applied."
+ _abandon
+ fi
+ cat $access >>$config
+fi
+
+# add config file to the args, save config file if -C
+#
+args="$args-c $config "
+if [ -n "$saveconfig" ]
+then
+ if eval $CP $config $saveconfig
+ then
+ echo "New pmlogger configuration file saved as $saveconfig"
+ else
+ echo "$prog: Warning: unable to save configuration file as $saveconfig"
+ fi
+fi
+
+# kill off existing pmlogger
+#
+$VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "Terminating $myname ...""$PCP_ECHO_C"
+for sig in USR1 TERM KILL
+do
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " SIG$sig ...""$PCP_ECHO_C"
+ eval $KILL -s $sig $pid
+ sleep 5
+ [ "`_check_pid $pid`" = "" ] && break
+done
+
+if [ "`_check_pid $pid`" = "" ]
+then
+ $VERBOSE && echo " done"
+else
+ echo " failed!"
+ _abandon
+fi
+
+# the archive folio Latest is for the most recent archive in this directory
+#
+dir=`dirname $archive`
+eval $RM -f $dir/Latest
+
+# clean up port-map, just in case
+#
+PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
+eval $RM -f "$PM_LOG_PORT_DIR"/$pid
+$primary && eval $RM -f $PM_LOG_PORT_DIR/primary
+
+# finally do it, ...
+#
+cd $dir
+$SHOWME && echo "+ cd $dir"
+[ "$dir" = . ] && dir=`pwd`
+archive=`basename $archive`
+
+# handle duplicates/aliases (happens when pmlogger is restarted
+# within a minute and basename is the same)
+#
+suff=''
+for file in $archive.*
+do
+ [ "$file" = "$archive"'.*' ] && continue
+ # we have a clash! ... find a new -number suffix for the
+ # existing files ... we are going to keep $archive for the
+ # new pmlogger below
+ #
+ if [ -z "$suff" ]
+ then
+ for xx in 0 1 2 3 4 5 6 7 8 9
+ do
+ for yy in 0 1 2 3 4 5 6 7 8 9
+ do
+ [ "`echo $archive-${xx}${yy}.*`" != "$archive-${xx}${yy}.*" ] && continue
+ suff=${xx}$yy
+ break
+ done
+ [ ! -z "$suff" ] && break
+ done
+ if [ -z "$suff" ]
+ then
+ echo "$prog: Error: unable to break duplicate clash for archive basename \"$archive\""
+ _abandon
+ fi
+ $VERBOSE && echo "Duplicate archive basename ... rename $archive.* files to $archive-$suff.*"
+ fi
+ eval $MV -f $file `echo $file | sed -e "s/$archive/&-$suff/"`
+done
+
+$VERBOSE && echo "Launching new pmlogger in directory \"$dir\" as ..."
+[ -f $logfile ] && eval $MV -f $logfile $logfile.prior
+$VERBOSE && echo "${sock_me}pmlogger $args$archive"
+
+if $SHOWME
+then
+ echo "+ ${sock_me}pmlogger $args$archive &"
+ echo "+ ... assume pid is 12345"
+ new_pid=12345
+else
+ ${sock_me}pmlogger $args$archive &
+ new_pid=$!
+fi
+
+# stall a bit ...
+#
+STALL_TIME=10
+sleep $STALL_TIME
+
+$VERBOSE && _message restart
+if _check_logger $new_pid || $SHOWME
+then
+ $VERBOSE && echo "New pmlogger status ..."
+ $VERBOSE && _do_cmd "( echo 'connect $new_pid'; echo status ) | $pmlc_prefix pmlc"
+
+ # make the "Latest" archive folio
+ #
+ i=0
+ failed=true
+ WAIT_TIME=10
+ while [ $i -lt $WAIT_TIME ]
+ do
+ if $SHOWME || [ -f $archive.0 -a -f $archive.meta -a $archive.index ]
+ then
+ _do_cmd "mkaf $archive.0 >Latest" 2>$tmp/err
+ if [ -s $tmp/err ]
+ then
+ # errors from mkaf typically result from race conditions
+ # at the start of pmlogger, e.g.
+ # Warning: cannot extract hostname from archive "..." ...
+ #
+ # simply keep trying
+ :
+ else
+ failed=false
+ break
+ fi
+ fi
+ sleep 1
+ i=`expr $i + 1`
+ done
+
+ if $failed
+ then
+ ELAPSED=`expr $STALL_TIME + $WAIT_TIME`
+ echo "Warning: pmlogger [pid=$new_pid host=$hostname] failed to create archive files within $ELAPSED seconds"
+ if [ -f $tmp/err ]
+ then
+ echo "Warnings/errors from mkaf ..."
+ cat $tmp/err
+ fi
+ fi
+else
+ _abandon
+fi
+
+exit
diff --git a/src/pmlogger/rc_pmlogger b/src/pmlogger/rc_pmlogger
new file mode 100644
index 0000000..251b43b
--- /dev/null
+++ b/src/pmlogger/rc_pmlogger
@@ -0,0 +1,301 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000-2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Start or Stop the Performance Co-Pilot pmlogger processes.
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 94 06
+# description: pmlogger is a performance metrics logger for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmlogger
+# Required-Start: $local_fs
+# Should-Start: $network $remote_fs $syslog $time $pmcd
+# Required-Stop: $local_fs
+# Should-Stop: $network $remote_fs $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmlogger (the performance metrics logger for PCP)
+# Description: Configure and control pmlogger (the performance metrics logger for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMLOGCTRL=$PCP_PMLOGGERCONTROL_PATH
+PMLOGGER=$PCP_BINADM_DIR/pmlogger
+prog=$PCP_RC_DIR/`basename $0`
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if is_chkconfig_on pmlogger
+then
+ PMLOGGER_CTL=on
+else
+ PMLOGGER_CTL=off
+fi
+
+VERBOSE_CTL=on
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ IAM=0
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+# Note: _start_pmcheck() runs in the background, in parallel with
+# the rest of the script. It might complete well after the caller
+# so tmpfile handling is especially problematic. Goal is to speed
+# bootup by starting potentially slow (remote monitoring) pmlogger
+# processes in the background.
+#
+_start_pmcheck()
+{
+ bgstatus=0
+ bgtmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+ trap "rm -rf $bgtmp; exit \$bgstatus" 0 1 2 3 15
+
+ pmlogger_check $VFLAG >$bgtmp/pmcheck.out 2>$bgtmp/pmcheck
+ bgstatus=$?
+ if [ -s $bgtmp/pmcheck ]
+ then
+ $PCP_BINADM_DIR/pmpost "pmlogger_check failed in $prog, mailing output to root"
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "pmlogger_check failed in $prog" root <$bgtmp/pmcheck
+ else
+ echo "$prog: pmlogger_check failed ..."
+ cat $bgtmp/pmcheck
+ fi
+ fi
+ exit $bgstatus # co-process is now complete
+}
+
+_start_pmlogger()
+{
+ if which pmlogger_check >/dev/null 2>&1
+ then
+ # pmlogger_check uses $PMLOGCTRL to start everything that is needed
+ #
+ if [ ! -f $PMLOGCTRL ]
+ then
+ echo "$prog:"'
+Error: PCP archive logger control file '$PMLOGCTRL'
+ is missing! Cannot start any Performance Co-Pilot archive logger(s).'
+ # failure
+ false
+ else
+ # really start the pmlogger instances based on the control file.
+ # done in the background to avoid delaying the init script,
+ # failures are notified by mail
+ #
+ $ECHO $PCP_ECHO_N "Starting pmlogger ..." "$PCP_ECHO_C"
+ _start_pmcheck &
+ # success
+ true
+ fi
+ else
+ echo "$prog:"'
+Warning: Performance Co-Pilot installation is incomplete (at least the
+ script "pmlogger_check" is missing) and the PCP archive logger(s)
+ cannot be started.'
+ # failure
+ false
+ fi
+ $RC_STATUS -v
+}
+
+_shutdown()
+{
+ # Is any pmlogger running?
+ #
+ _get_pids_by_name pmlogger >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$prog: pmlogger not running"
+ return 0
+ fi
+
+ [ "$1" = quietly ] || \
+ $ECHO $PCP_ECHO_N "Stopping pmlogger ...""$PCP_ECHO_C"
+
+ # Terminate those pmloggers started by either pmlogger_check or
+ # pmlogger_daily ... relies on the -m option to pmlogger and the
+ # annotation in the (optional) 4th line of the port map files
+ #
+ for pid in `cat $tmp/tmp`
+ do
+ if [ -f "$PCP_TMP_DIR/pmlogger/$pid" ]
+ then
+ note=`sed -n -e 4p <"$PCP_TMP_DIR/pmlogger/$pid"`
+ if [ "$note" = pmlogger_check -o "$note" = pmlogger_daily ]
+ then
+ pmsignal -s TERM $pid
+ fi
+ fi
+ done
+ $RC_STATUS -v
+ $PCP_BINADM_DIR/pmpost "stop pmlogger from $prog"
+}
+
+_usage()
+{
+ echo "Usage: $prog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+ REBUILDOPT=''
+ VFLAG='-V'
+else # For a quiet startup and shutdown
+ ECHO=:
+ REBUILDOPT=-s
+ VFLAG=
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the Performance Co-Pilot loggers.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmlogger
+ then
+ status=0
+ exit
+ fi
+ _shutdown quietly
+
+ # pmlogger messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ if [ -x $PMLOGGER ]
+ then
+ if [ "$PMLOGGER_CTL" = on ]
+ then
+ _start_pmlogger
+ fi
+ fi
+
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown verbose
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmlogger:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMLOGGER
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMLOGGER
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmlogger/src/GNUmakefile b/src/pmlogger/src/GNUmakefile
new file mode 100644
index 0000000..69b6cbb
--- /dev/null
+++ b/src/pmlogger/src/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmlogger$(EXECSUFFIX)
+
+CFILES = pmlogger.c fetch.c util.c error.c callback.c ports.c \
+ dopdu.c check.c preamble.c rewrite.c events.c
+HFILES = logger.h
+LFILES = lex.l
+YFILES = gram.y
+
+LCFLAGS += $(PIECFLAGS)
+LLDFLAGS += $(PIELDFLAGS)
+
+LLDLIBS = $(PCPLIB) $(LIB_FOR_PTHREADS)
+LDIRT = *.log foo.* gram.h lex.c y.tab.? $(YFILES:%.y=%.tab.?) $(CMDTARGET)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -S $(PCP_BIN_DIR)/$(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+.NOTPARALLEL:
+YFLAGS += -v
+gram.tab.h gram.tab.c: gram.y
+ $(YACC) -d -b `basename $< .y` $<
+
+lex.o gram.tab.o: gram.tab.h
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlogger/src/callback.c b/src/pmlogger/src/callback.c
new file mode 100644
index 0000000..e099ebc
--- /dev/null
+++ b/src/pmlogger/src/callback.c
@@ -0,0 +1,771 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+int last_log_offset;
+
+/*
+ * pro tem, we have a single context with the pmcd providing the
+ * results, hence need to send the profile each time
+ */
+static int one_context = 1;
+
+struct timeval last_stamp;
+__pmHashCtl hist_hash;
+
+/*
+ * These structures allow us to keep track of the _last_ fetch
+ * for each fetch in each AF group ... needed to track changes in
+ * instance availability.
+ */
+typedef struct _lastfetch {
+ struct _lastfetch *lf_next;
+ fetchctl_t *lf_fp;
+ pmResult *lf_resp;
+ __pmPDU *lf_pb;
+} lastfetch_t;
+
+typedef struct _AFctl {
+ struct _AFctl *ac_next;
+ int ac_afid;
+ lastfetch_t *ac_fetch;
+} AFctl_t;
+
+static AFctl_t *achead = (AFctl_t *)0;
+
+/* clear the "metric/instance was available at last fetch" flag for each metric
+ * and instance in the specified fetchgroup.
+ */
+static void
+clearavail(fetchctl_t *fcp)
+{
+ indomctl_t *idp;
+ pmID pmid;
+ pmidctl_t *pmp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ __pmHashNode *hp;
+ int i, inst;
+ int j;
+
+ for (idp = fcp->f_idp; idp != (indomctl_t *)0; idp = idp->i_next) {
+ for (pmp = idp->i_pmp; pmp != (pmidctl_t *)0; pmp = pmp->p_next) {
+ /* find the metric if it's in the history hash table */
+ pmid = pmp->p_pmid;
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+ if (hp == (__pmHashNode *)0)
+ /* not in history, no flags to update */
+ continue;
+ php = (pmidhist_t *)hp->data;
+
+ /* now we have the metric's entry in the history */
+
+ if (idp->i_indom != PM_INDOM_NULL) {
+ /*
+ * for each instance in the profile for this metric, find
+ * the history entry for the instance if it exists and
+ * reset the "was available at last fetch" flag
+ */
+ if (idp->i_numinst)
+ for (i = 0; i < idp->i_numinst; i++) {
+ inst = idp->i_instlist[i];
+ ihp = &php->ph_instlist[0];
+ for (j = 0; j < php->ph_numinst; j++, ihp++)
+ if (ihp->ih_inst == inst) {
+ PMLC_SET_AVAIL(ihp->ih_flags, 0);
+ break;
+ }
+ }
+ else
+ /*
+ * if the profile specifies "all instances" clear EVERY
+ * instance's "available" flag
+ * NOTE: even instances that don't exist any more
+ */
+ for (i = 0; i < php->ph_numinst; i++)
+ PMLC_SET_AVAIL(php->ph_instlist[i].ih_flags, 0);
+ }
+ /* indom is PM_INDOM_NULL */
+ else {
+ /* if the single-valued metric is in the history it will have 1
+ * instance */
+ ihp = &php->ph_instlist[0];
+ PMLC_SET_AVAIL(ihp->ih_flags, 0);
+ }
+ }
+ }
+}
+
+static void
+setavail(pmResult *resp)
+{
+ int i;
+
+ for (i = 0; i < resp->numpmid; i++) {
+ pmID pmid;
+ pmValueSet *vsp;
+ __pmHashNode *hp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ int j;
+
+ vsp = resp->vset[i];
+ pmid = vsp->pmid;
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+
+ if (hp != (__pmHashNode *)0)
+ php = (pmidhist_t *)hp->data;
+ else {
+ /* add new pmid to history if it's pmValueSet is OK */
+ if (vsp->numval <= 0)
+ continue;
+ /*
+ * use the OTHER hash list to find the pmid's desc and thereby its
+ * indom
+ */
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+ if (hp == (__pmHashNode *)0 ||
+ ((optreq_t *)hp->data)->r_desc == (pmDesc *)0)
+ /* not set up properly yet, not much we can do ... */
+ continue;
+ php = (pmidhist_t *)calloc(1, sizeof(pmidhist_t));
+ if (php == (pmidhist_t *)0) {
+ __pmNoMem("setavail: new pmid hist entry calloc",
+ sizeof(pmidhist_t), PM_FATAL_ERR);
+ }
+ php->ph_pmid = pmid;
+ php->ph_indom = ((optreq_t *)hp->data)->r_desc->indom;
+ /*
+ * now create a new insthist list for all the instances in the
+ * pmResult and we're done
+ */
+ php->ph_numinst = vsp->numval;
+ ihp = (insthist_t *)calloc(vsp->numval, sizeof(insthist_t));
+ if (ihp == (insthist_t *)0) {
+ __pmNoMem("setavail: inst list calloc",
+ vsp->numval * sizeof(insthist_t), PM_FATAL_ERR);
+ }
+ php->ph_instlist = ihp;
+ for (j = 0; j < vsp->numval; j++, ihp++) {
+ ihp->ih_inst = vsp->vlist[j].inst;
+ PMLC_SET_AVAIL(ihp->ih_flags, 1);
+ }
+ if ((j = __pmHashAdd(pmid, (void *)php, &hist_hash)) < 0) {
+ die("setavail: __pmHashAdd(hist_hash)", j);
+ }
+
+ return;
+ }
+
+ /* update an existing pmid history entry, adding any previously unseen
+ * instances
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ int inst = vsp->vlist[j].inst;
+ int k;
+
+ for (k = 0; k < php->ph_numinst; k++)
+ if (inst == php->ph_instlist[k].ih_inst)
+ break;
+
+ if (k < php->ph_numinst)
+ ihp = &php->ph_instlist[k];
+ else {
+ /* allocate new instance if required */
+ int need = (k + 1) * sizeof(insthist_t);
+
+ php->ph_instlist = (insthist_t *)realloc(php->ph_instlist, need);
+ if (php->ph_instlist == (insthist_t *)0) {
+ __pmNoMem("setavail: inst list realloc", need, PM_FATAL_ERR);
+ }
+ ihp = &php->ph_instlist[k];
+ ihp->ih_inst = inst;
+ ihp->ih_flags = 0;
+ php->ph_numinst++;
+ }
+ PMLC_SET_AVAIL(ihp->ih_flags, 1);
+ }
+ }
+}
+
+/*
+ * This has been taken straight from logmeta.c in libpcp. It is required
+ * here to get the timestamp of the indom.
+ * Note that the tp argument is used to return the timestamp of the indom.
+ * It is a merger of __pmLogGetIndom and searchindom.
+ */
+int
+__localLogGetInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int **instlist, char ***namelist)
+{
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA)
+ fprintf(stderr, "__localLogGetInDom( ..., %s)\n",
+ pmInDomStr(indom));
+#endif
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL)
+ return 0;
+
+ idp = (__pmLogInDom *)hp->data;
+
+ if (idp == NULL)
+ return PM_ERR_INDOM_LOG;
+
+ *instlist = idp->instlist;
+ *namelist = idp->namelist;
+ *tp = idp->stamp;
+
+ return idp->numinst;
+}
+
+
+/*
+ * compare pmResults for a particular metric, and return 1 if
+ * the set of instances has changed.
+ */
+static int
+check_inst(pmValueSet *vsp, int hint, pmResult *lrp)
+{
+ int i;
+ int j;
+ pmValueSet *lvsp;
+
+ /* Make sure vsp->pmid exists in lrp's result */
+ /* and find which value set in lrp it is. */
+ if (hint < lrp->numpmid && lrp->vset[hint]->pmid == vsp->pmid)
+ i = hint;
+ else {
+ for (i = 0; i < lrp->numpmid; i++) {
+ if (lrp->vset[i]->pmid == vsp->pmid)
+ break;
+ }
+ if (i == lrp->numpmid) {
+ fprintf(stderr, "check_inst: cannot find PMID %s in last result ...\n",
+ pmIDStr(vsp->pmid));
+ __pmDumpResult(stderr, lrp);
+ return 0;
+ }
+ }
+
+ lvsp = lrp->vset[i];
+
+ if (lvsp->numval != vsp->numval)
+ return 1;
+
+ /* compare instances */
+ for (i = 0; i < lvsp->numval; i++) {
+ if (lvsp->vlist[i].inst != vsp->vlist[i].inst) {
+ /* the hard way */
+ for (j = 0; j < vsp->numval; j++) {
+ if (lvsp->vlist[j].inst == vsp->vlist[i].inst)
+ break;
+ }
+ if (j == vsp->numval)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Lookup the first cache index associated with a given PMID in a given task.
+ */
+static int
+lookupTaskCacheIndex(task_t *tp, pmID pmid)
+{
+ int i;
+
+ for (i = 0; i < tp->t_numpmid; i++)
+ if (tp->t_pmidlist[i] == pmid)
+ return i;
+ return -1;
+}
+
+/*
+ * Iterate over *all* tasks and return all names for a given PMID.
+ * Returns the number of names found, and nameset allocated in a
+ * single allocation call (which the caller must free).
+ */
+static int
+lookupTaskCacheNames(pmID pmid, char ***namesptr)
+{
+ int i, numnames = 0, len = 0;
+ char *data, **names = NULL;
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ for (i = 0; i < tp->t_numpmid; i++) {
+ if (tp->t_pmidlist[i] != pmid)
+ continue;
+ len += strlen(tp->t_namelist[i]) + 1;
+ numnames++;
+ }
+ }
+
+ names = (char **)malloc(numnames * sizeof(names[0]) + len);
+ data = (char *)names + (numnames * sizeof(names[0]));
+ for (tp = tasklist, len = 0; tp != NULL; tp = tp->t_next) {
+ for (i = 0; i < tp->t_numpmid; i++) {
+ if (tp->t_pmidlist[i] != pmid)
+ continue;
+ names[len++] = data;
+ strcpy(data, tp->t_namelist[i]);
+ data += (strlen(tp->t_namelist[i]) + 1);
+ }
+ }
+
+ *namesptr = names;
+ return numnames;
+}
+
+void
+log_callback(int afid, void *data)
+{
+ int i;
+ int j;
+ int k;
+ int sts;
+ task_t *tp = (task_t *)data;
+ fetchctl_t *fp;
+ indomctl_t *idp;
+ pmResult *resp;
+ __pmPDU *pb;
+ AFctl_t *acp;
+ lastfetch_t *lfp;
+ lastfetch_t *free_lfp;
+ int needindom;
+ int needti;
+ static int flushsize = 100000;
+ long old_meta_offset;
+ long new_offset;
+ long new_meta_offset;
+ int pdu_bytes = 0;
+ int pdu_metrics = 0;
+ int numinst;
+ int *instlist;
+ char **namelist;
+ __pmTimeval tmp;
+ __pmTimeval resp_tval;
+ unsigned long peek_offset;
+
+ if (!parse_done)
+ /* ignore callbacks until all of the config file has been parsed */
+ return;
+
+ /* find AFctl_t for this afid */
+ for (acp = achead; acp != (AFctl_t *)0; acp = acp->ac_next) {
+ if (acp->ac_afid == afid)
+ break;
+ }
+ if (acp == (AFctl_t *)0) {
+ acp = (AFctl_t *)calloc(1, sizeof(AFctl_t));
+ if (acp == (AFctl_t *)0) {
+ __pmNoMem("log_callback: new AFctl_t entry calloc",
+ sizeof(AFctl_t), PM_FATAL_ERR);
+ }
+ acp->ac_afid = afid;
+ acp->ac_next = achead;
+ achead = acp;
+ }
+ else {
+ /* cleanup any fetchgroups that have gone away */
+ for (lfp = acp->ac_fetch; lfp != (lastfetch_t *)0; lfp = lfp->lf_next) {
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+ if (fp == lfp->lf_fp)
+ break;
+ }
+ if (fp == (fetchctl_t *)0) {
+ lfp->lf_fp = (fetchctl_t *)0; /* mark lastfetch_t as free */
+ if (lfp->lf_resp != (pmResult *)0) {
+ pmFreeResult(lfp->lf_resp);
+ lfp->lf_resp =(pmResult *)0;
+ }
+ }
+ }
+ }
+
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+
+ /* find lastfetch_t for this fetch group, else make a new one */
+ free_lfp = (lastfetch_t *)0;
+ for (lfp = acp->ac_fetch; lfp != (lastfetch_t *)0; lfp = lfp->lf_next) {
+ if (lfp->lf_fp == fp)
+ break;
+ if (lfp->lf_fp == (fetchctl_t *)0 && free_lfp == (lastfetch_t *)0)
+ free_lfp = lfp;
+ }
+ if (lfp == (lastfetch_t *)0) {
+ /* need new one */
+ if (free_lfp != (lastfetch_t *)0)
+ lfp = free_lfp; /* lucky */
+ else {
+ lfp = (lastfetch_t *)calloc(1, sizeof(lastfetch_t));
+ if (lfp == (lastfetch_t *)0) {
+ __pmNoMem("log_callback: new lastfetch_t entry calloc",
+ sizeof(lastfetch_t), PM_FATAL_ERR);
+ }
+ lfp->lf_next = acp->ac_fetch;
+ acp->ac_fetch = lfp;
+ }
+ lfp->lf_fp = fp;
+ }
+
+ if (one_context || fp->f_state & OPT_STATE_PROFILE) {
+ /* profile for this fetch group has changed */
+ pmAddProfile(PM_INDOM_NULL, 0, (int *)0);
+ for (idp = fp->f_idp; idp != (indomctl_t *)0; idp = idp->i_next) {
+ if (idp->i_indom != PM_INDOM_NULL && idp->i_numinst != 0)
+ pmAddProfile(idp->i_indom, idp->i_numinst, idp->i_instlist);
+ }
+ fp->f_state &= ~OPT_STATE_PROFILE;
+ }
+
+ clearavail(fp);
+
+ if ((sts = myFetch(fp->f_numpmid, fp->f_pmidlist, &pb)) < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: disconnecting because myFetch failed: %s\n", pmErrStr(sts));
+#endif
+ disconnect(sts);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: fetch group %p (%d metrics)\n", fp, fp->f_numpmid);
+#endif
+
+ /*
+ * hook to rewrite PDU buffer ...
+ lfp */
+ pb = rewrite_pdu(pb, archive_version);
+
+ if (rflag) {
+ /*
+ * bytes = PDU len - sizeof (header) + 2 * sizeof (int)
+ * see logputresult() in libpcp/logutil.c for details of how
+ * a PDU buffer is reformatted to make len shorter by one int
+ * before the record is written to the external file
+ */
+ pdu_bytes += ((__pmPDUHdr *)pb)->len - sizeof (__pmPDUHdr) +
+ 2*sizeof(int);
+ pdu_metrics += fp->f_numpmid;
+ }
+
+ /*
+ * Even without a -v option, we may need to switch volumes
+ * if the data file exceeds 2^31-1 bytes
+ */
+ peek_offset = ftell(logctl.l_mfp);
+ peek_offset += ((__pmPDUHdr *)pb)->len - sizeof(__pmPDUHdr) + 2*sizeof(int);
+ if (peek_offset > 0x7fffffff) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on max size, currently %ld\n", ftell(logctl.l_mfp));
+#endif
+ (void)newvolume(VOL_SW_MAX);
+ }
+
+ /*
+ * would prefer to save this up until after any meta data and/or
+ * temporal index writes, but __pmDecodeResult changes the pointers
+ * in the pdu buffer for the non INSITU values ... sigh
+ */
+ last_log_offset = ftell(logctl.l_mfp);
+ assert(last_log_offset >= 0);
+ if ((sts = __pmLogPutResult2(&logctl, pb)) < 0) {
+ fprintf(stderr, "__pmLogPutResult2: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+
+ __pmOverrideLastFd(fileno(logctl.l_mfp));
+ if ((sts = __pmDecodeResult(pb, &resp)) < 0) {
+ fprintf(stderr, "__pmDecodeResult: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ setavail(resp);
+ resp_tval.tv_sec = resp->timestamp.tv_sec;
+ resp_tval.tv_usec = resp->timestamp.tv_usec;
+
+ needti = 0;
+ old_meta_offset = ftell(logctl.l_mdfp);
+ assert(old_meta_offset >= 0);
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+ pmDesc desc;
+ char **names = NULL;
+ int numnames = 0;
+
+ sts = __pmLogLookupDesc(&logctl, vsp->pmid, &desc);
+ if (sts < 0) {
+ /* lookup name and descriptor in task cache */
+ int taskindex = lookupTaskCacheIndex(tp, vsp->pmid);
+ if (taskindex == -1) {
+ fprintf(stderr, "lookupTaskCacheIndex cannot find PMID %s\n",
+ pmIDStr(vsp->pmid));
+ exit(1);
+ }
+ desc = tp->t_desclist[taskindex];
+ numnames = lookupTaskCacheNames(vsp->pmid, &names);
+ if ((sts = __pmLogPutDesc(&logctl, &desc, numnames, names)) < 0) {
+ fprintf(stderr, "__pmLogPutDesc: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ if (numnames) {
+ free(names);
+ }
+ }
+ if (desc.type == PM_TYPE_EVENT) {
+ /*
+ * Event records need some special handling ...
+ */
+ if ((sts = do_events(vsp)) < 0) {
+ fprintf(stderr, "Failed to process event records: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ }
+ if (desc.indom != PM_INDOM_NULL && vsp->numval > 0) {
+ /*
+ * __pmLogGetInDom has been replaced by __localLogGetInDom so that
+ * the timestamp of the retrieved indom is also returned. The timestamp
+ * is then used to decide if the indom needs to be refreshed.
+ */
+ __pmTimeval indom_tval;
+ numinst = __localLogGetInDom(&logctl, desc.indom, &indom_tval, &instlist, &namelist);
+ if (numinst < 0)
+ needindom = 1;
+ else {
+ needindom = 0;
+ /* Need to see if result's insts all exist
+ * somewhere in the hashed/cached insts.
+ * Thus a potential numval^2 search.
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ for (k = 0; k < numinst; k++) {
+ if (vsp->vlist[j].inst == instlist[k])
+ break;
+ }
+ if (k == numinst) {
+ needindom = 1;
+ break;
+ }
+ }
+ }
+ /*
+ * Check here that the instance domain has not been changed
+ * by a previous iteration of this loop.
+ * So, the timestamp of resp must be after the update timestamp
+ * of the target instance domain.
+ */
+ if (needindom == 0 && lfp->lf_resp != (pmResult *)0 &&
+ __pmTimevalSub(&resp_tval, &indom_tval) < 0 )
+ needindom = check_inst(vsp, i, lfp->lf_resp);
+
+ if (needindom) {
+ /*
+ * Note. We do NOT free() instlist and namelist allocated
+ * here ... look for magic below log{Put,Get}InDom ...
+ */
+ if ((numinst = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) {
+ fprintf(stderr, "pmGetInDom(%s): %s\n", pmInDomStr(desc.indom), pmErrStr(numinst));
+ exit(1);
+ }
+ tmp.tv_sec = (__int32_t)resp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)resp->timestamp.tv_usec;
+ if ((sts = __pmLogPutInDom(&logctl, desc.indom, &tmp, numinst, instlist, namelist)) < 0) {
+ fprintf(stderr, "__pmLogPutInDom: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: indom (%s) changed\n", pmInDomStr(desc.indom));
+#endif
+ }
+ }
+ }
+
+ if (ftell(logctl.l_mfp) > flushsize) {
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: file size (%d) reached flushsize (%d)\n", (int)ftell(logctl.l_mfp), flushsize);
+#endif
+ }
+
+ if (last_log_offset == 0 || last_log_offset == sizeof(__pmLogLabel)+2*sizeof(int)) {
+ /* first result in this volume */
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: first result for this volume\n");
+#endif
+ }
+
+ if (needti) {
+ /*
+ * need to unwind seek pointer to start of most recent
+ * result (but if this is the first one, skip the label
+ * record, what a crock), ... ditto for the meta data
+ */
+ new_offset = ftell(logctl.l_mfp);
+ assert(new_offset >= 0);
+ new_meta_offset = ftell(logctl.l_mdfp);
+ assert(new_meta_offset >= 0);
+ fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, old_meta_offset, SEEK_SET);
+ tmp.tv_sec = (__int32_t)resp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)resp->timestamp.tv_usec;
+ __pmLogPutIndex(&logctl, &tmp);
+ /*
+ * ... and put them back
+ */
+ fseek(logctl.l_mfp, new_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, new_meta_offset, SEEK_SET);
+ flushsize = ftell(logctl.l_mfp) + 100000;
+ }
+
+ last_stamp = resp->timestamp; /* struct assignment */
+
+ if (lfp->lf_resp != (pmResult *)0) {
+ /*
+ * release memory that is allocated and pinned in pmDecodeResult
+ */
+ pmFreeResult(lfp->lf_resp);
+ }
+ lfp->lf_resp = resp;
+ if (lfp->lf_pb != NULL)
+ __pmUnpinPDUBuf(lfp->lf_pb);
+ lfp->lf_pb = pb;
+ }
+
+ if (rflag && tp->t_size == 0 && pdu_metrics > 0) {
+ char *name = NULL;
+ int taskindex;
+ int i;
+
+ tp->t_size = pdu_bytes;
+
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\nGroup [%d metrics] {\n", pdu_metrics);
+ else
+ fprintf(stderr, "\nMetric ");
+
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+ for (i = 0; i < fp->f_numpmid; i++) {
+ name = NULL;
+ taskindex = lookupTaskCacheIndex(tp, fp->f_pmidlist[i]);
+ if (taskindex >= 0)
+ name = tp->t_namelist[taskindex];
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\t");
+ fprintf(stderr, "%s", name ? name : pmIDStr(fp->f_pmidlist[i]));
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\n");
+ }
+ if (pdu_metrics > 1)
+ fprintf(stderr, "}");
+ }
+ fprintf(stderr, " logged ");
+
+ if (tp->t_delta.tv_sec == 0 && tp->t_delta.tv_usec == 0)
+ fprintf(stderr, "once: %d bytes\n", pdu_bytes);
+ else {
+ if (tp->t_delta.tv_usec == 0) {
+ fprintf(stderr, "every %d sec: %d bytes ",
+ (int)tp->t_delta.tv_sec, pdu_bytes);
+ }
+ else
+ fprintf(stderr, "every %d.%03d sec: %d bytes ",
+ (int)tp->t_delta.tv_sec, (int)tp->t_delta.tv_usec / 1000, pdu_bytes);
+ fprintf(stderr, "or %.2f Mbytes/day\n",
+ ((double)pdu_bytes * 24 * 60 * 60) /
+ (1024 * 1024 * (tp->t_delta.tv_sec + (double)tp->t_delta.tv_usec / 1000000)));
+ }
+ }
+
+ if (exit_samples > 0)
+ exit_samples--;
+
+ if (exit_samples == 0)
+ /* run out of samples in sample counter, so stop logging */
+ run_done(0, "Sample limit reached");
+
+ if (exit_bytes != -1 &&
+ (vol_bytes + ftell(logctl.l_mfp) >= exit_bytes))
+ /* reached exit_bytes limit, so stop logging */
+ run_done(0, "Byte limit reached");
+
+ if (vol_switch_samples > 0 &&
+ ++vol_samples_counter == vol_switch_samples) {
+ (void)newvolume(VOL_SW_COUNTER);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on samples (%d)\n", vol_samples_counter);
+#endif
+ }
+
+ if (vol_switch_bytes > 0 &&
+ (ftell(logctl.l_mfp) >= vol_switch_bytes)) {
+ (void)newvolume(VOL_SW_BYTES);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on size (%d)\n", (int)ftell(logctl.l_mfp));
+#endif
+ }
+
+}
+
+int
+putmark(void)
+{
+ struct {
+ __pmPDU hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* zero PMIDs to follow */
+ __pmPDU tail;
+ } mark;
+
+ if (last_stamp.tv_sec == 0 && last_stamp.tv_usec == 0)
+ /* no earlier pmResult, no point adding a mark record */
+ return 0;
+
+ mark.hdr = htonl((int)sizeof(mark));
+ mark.tail = mark.hdr;
+ mark.timestamp.tv_sec = last_stamp.tv_sec;
+ mark.timestamp.tv_usec = last_stamp.tv_usec + 1000; /* + 1msec */
+ if (mark.timestamp.tv_usec > 1000000) {
+ mark.timestamp.tv_usec -= 1000000;
+ mark.timestamp.tv_sec++;
+ }
+ mark.timestamp.tv_sec = htonl(mark.timestamp.tv_sec);
+ mark.timestamp.tv_usec = htonl(mark.timestamp.tv_usec);
+ mark.numpmid = htonl(0);
+
+ if (fwrite(&mark, 1, sizeof(mark), logctl.l_mfp) != sizeof(mark))
+ return -oserror();
+ else
+ return 0;
+}
diff --git a/src/pmlogger/src/check.c b/src/pmlogger/src/check.c
new file mode 100644
index 0000000..a6c2def
--- /dev/null
+++ b/src/pmlogger/src/check.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+char *chk_emess[] = {
+ "No error",
+ "Request for (advisory) ON conflicts with current (mandatory) ON state",
+ "Request for (advisory) OFF conflicts with current (mandatory) ON state",
+ "Request for (advisory) ON conflicts with current (mandatory) OFF state",
+ "Request for (advisory) OFF conflicts with current (mandatory) OFF state",
+};
+
+static void
+undo(task_t *tp, optreq_t *rqp, int inst)
+{
+ int j;
+ int k;
+ int sts;
+
+ if (rqp->r_numinst >= 1) {
+ /* remove instance from list of instance */
+ for (k =0, j = 0; j < rqp->r_numinst; j++) {
+ if (rqp->r_instlist[j] != inst)
+ rqp->r_instlist[k++] = rqp->r_instlist[j];
+ }
+ rqp->r_numinst = k;
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("undo: __pmOptFetchDel", sts);
+
+ if (rqp->r_numinst == 0) {
+ /* no more instances, remove specification */
+ if (tp->t_fetch == NULL) {
+ /* no more specifications, remove task */
+ task_t *xtp;
+ task_t *ltp = NULL;
+ for (xtp = tasklist; xtp != NULL; xtp = xtp->t_next) {
+ if (xtp == tp) {
+ if (ltp == NULL)
+ tasklist = tp->t_next;
+ else
+ ltp->t_next = tp->t_next;
+ break;
+ }
+ ltp = xtp;
+ }
+ }
+ __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash);
+ free(rqp);
+ }
+ else
+ /* re-insert modified specification */
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ }
+ else {
+ /*
+ * TODO ... current specification is for all instances,
+ * need to remove this instance from the set ...
+ * this requires some enhancement to optFetch
+ *
+ * pro tem, this metric-instance pair may continue to get
+ * logged, even though the logging state is recorded as
+ * OFF (this is the worst thing that can happen here)
+ */
+ }
+}
+
+int
+chk_one(task_t *tp, pmID pmid, int inst)
+{
+ optreq_t *rqp;
+ task_t *ctp;
+
+ rqp = findoptreq(pmid, inst);
+ if (rqp == NULL)
+ return 0;
+
+ ctp = rqp->r_fetch->f_aux;
+ if (ctp == NULL || ctp == tp)
+ /*
+ * can only happen if same metric+inst appears more than once
+ * in the same group ... this can never be a conflict
+ */
+ return 1;
+
+ if (PMLC_GET_MAND(ctp->t_state)) {
+ if (PMLC_GET_ON(ctp->t_state)) {
+ if (PMLC_GET_MAND(tp->t_state) == 0 && PMLC_GET_MAYBE(tp->t_state) == 0) {
+ if (PMLC_GET_ON(tp->t_state))
+ return -1;
+ else
+ return -2;
+ }
+ }
+ else {
+ if (PMLC_GET_MAND(tp->t_state) == 0 && PMLC_GET_MAYBE(tp->t_state) == 0) {
+ if (PMLC_GET_ON(tp->t_state))
+ return -3;
+ else
+ return -4;
+ }
+ }
+ /*
+ * new mandatory, over-rides the old mandatory
+ */
+ undo(ctp, rqp, inst);
+ }
+ else {
+ /*
+ * new anything, over-rides the old advisory
+ */
+ undo(ctp, rqp, inst);
+ }
+
+ return 0;
+}
+
+int
+chk_all(task_t *tp, pmID pmid)
+{
+ optreq_t *rqp;
+ task_t *ctp;
+
+ rqp = findoptreq(pmid, 0); /*TODO, not right!*/
+ if (rqp == NULL)
+ return 0;
+
+ ctp = rqp->r_fetch->f_aux;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "chk_all: pmid=%s task=" PRINTF_P_PFX "%p state=%s%s%s%s delta=%d.%06d\n",
+ pmIDStr(pmid), tp,
+ PMLC_GET_INLOG(tp->t_state) ? " " : "N",
+ PMLC_GET_AVAIL(tp->t_state) ? " " : "N",
+ PMLC_GET_MAND(tp->t_state) ? "M" : "A",
+ PMLC_GET_ON(tp->t_state) ? "Y" : "N",
+ (int)tp->t_delta.tv_sec, (int)tp->t_delta.tv_usec);
+ if (ctp == NULL)
+ fprintf(stderr, "compared to: NULL\n");
+ else
+ fprintf(stderr, "compared to: optreq task=" PRINTF_P_PFX "%p state=%s%s%s%s delta=%d.%06d\n",
+ ctp,
+ PMLC_GET_INLOG(ctp->t_state) ? " " : "N",
+ PMLC_GET_AVAIL(ctp->t_state) ? " " : "N",
+ PMLC_GET_MAND(ctp->t_state) ? "M" : "A",
+ PMLC_GET_ON(ctp->t_state) ? "Y" : "N",
+ (int)ctp->t_delta.tv_sec, (int)ctp->t_delta.tv_usec);
+ }
+#endif
+ return 0;
+}
diff --git a/src/pmlogger/src/dopdu.c b/src/pmlogger/src/dopdu.c
new file mode 100644
index 0000000..d3c35c4
--- /dev/null
+++ b/src/pmlogger/src/dopdu.c
@@ -0,0 +1,1491 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+
+/* return one of these when a status request is made from a PCP 1.x pmlc */
+typedef struct {
+ __pmTimeval ls_start; /* start time for log */
+ __pmTimeval ls_last; /* last time log written */
+ __pmTimeval ls_timenow; /* current time */
+ int ls_state; /* state of log (from __pmLogCtl) */
+ int ls_vol; /* current volume number of log */
+ __int64_t ls_size; /* size of current volume */
+ char ls_hostname[PM_LOG_MAXHOSTLEN];
+ /* name of pmcd host */
+ char ls_tz[40]; /* $TZ at collection host */
+ char ls_tzlogger[40]; /* $TZ at pmlogger */
+} __pmLoggerStatus_v1;
+
+#ifdef PCP_DEBUG
+/* This crawls over the data structure looking for weirdness */
+void
+reality_check(void)
+{
+ __pmHashNode *hp;
+ task_t *tp;
+ task_t *tp2;
+ fetchctl_t *fp;
+ optreq_t *rqp;
+ pmID pmid;
+ int i = 0, j, k;
+
+ /* check that all fetch_t's f_aux point back to their parent task */
+ for (tp = tasklist; tp != NULL; tp = tp->t_next, i++) {
+ if (tp->t_fetch == NULL)
+ fprintf(stderr, "task[%d] @" PRINTF_P_PFX "%p has no fetch group\n", i, tp);
+ j = 0;
+ for (fp = tp->t_fetch; fp != NULL; fp = fp->f_next) {
+ if (fp->f_aux != (void *)tp)
+ fprintf(stderr, "task[%d] fetch group[%d] has invalid task pointer\n",
+ i, j);
+ j++;
+ }
+
+ /* check that all optreq_t's in hash list have valid r_fetch->f_aux
+ * pointing to a task in the task list.
+ */
+ for (j = 0; j < tp->t_numpmid; j++) {
+ pmid = tp->t_pmidlist[j];
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ for (tp2 = tasklist; tp2 != NULL; tp2 = tp2->t_next)
+ if (rqp->r_fetch->f_aux == (void *)tp2)
+ break;
+ if (tp2 == NULL) {
+ fprintf(stderr, "task[%d] pmid %s optreq " PRINTF_P_PFX "%p for [",
+ i, pmIDStr(pmid), rqp);
+ if (rqp->r_numinst == 0)
+ fputs("`all instances' ", stderr);
+ else
+ for (k = 0; k < rqp->r_numinst; k++)
+ fprintf(stderr, "%d ", rqp->r_instlist[k]);
+ fputs("] bad task pointer\n", stderr);
+ }
+ }
+ }
+ }
+}
+
+void
+dumpit(void)
+{
+ int i;
+ task_t *tp;
+
+ reality_check();
+ for (tp = tasklist, i = 0; tp != NULL; tp = tp->t_next, i++) {
+ fprintf(stderr,
+ "\ntask[%d] @" PRINTF_P_PFX "%p: %s %s \ndelta = %f\n", i, tp,
+ PMLC_GET_MAND(tp->t_state) ? "mandatory " : "advisory ",
+ PMLC_GET_ON(tp->t_state) ? "on " : "off ",
+ tp->t_delta.tv_sec + (float)tp->t_delta.tv_usec / 1.0e6);
+ __pmOptFetchDump(stderr, tp->t_fetch);
+ }
+}
+
+/*
+ * stolen from __pmDumpResult
+ */
+static void
+dumpcontrol(FILE *f, const pmResult *resp, int dovalue)
+{
+ int i;
+ int j;
+
+ fprintf(f,"LogControl dump from " PRINTF_P_PFX "%p", resp);
+ fprintf(f, " numpmid: %d\n", resp->numpmid);
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+ fprintf(f," %s :", pmIDStr(vsp->pmid));
+ if (vsp->numval == 0) {
+ fprintf(f, " No values!\n");
+ continue;
+ }
+ else if (vsp->numval < 0) {
+ fprintf(f, " %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+ fprintf(f, " numval: %d", vsp->numval);
+ fprintf(f, " valfmt: %d", vsp->valfmt);
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) {
+ fprintf(f," inst [%d]", vp->inst);
+ }
+ else
+ fprintf(f, " singular");
+ if (dovalue) {
+ fprintf(f, " value ");
+ pmPrintValue(f, vsp->valfmt, PM_TYPE_U32, vp, 1);
+ }
+ fputc('\n', f);
+ }
+ }
+}
+
+#endif
+
+/* Called when optFetch or _pmHash routines fail. This is terminal. */
+void
+die(char *name, int sts)
+{
+ __pmNotifyErr(LOG_ERR, "%s error unrecoverable: %s\n", name, pmErrStr(sts));
+ exit(1);
+}
+
+optreq_t *
+findoptreq(pmID pmid, int inst)
+{
+ __pmHashNode *hp;
+ optreq_t *rqp;
+ optreq_t *all_rqp = NULL;
+ int j;
+
+ /*
+ * Note:
+ * The logic here assumes that for each metric-inst pair, there is
+ * at most one optreq_t structure, corresponding to the logging
+ * state of ON (mandatory or advisory) else OFF (mandatory). Other
+ * requests change the data structures, but do not leave optreq_t
+ * structures lying about, i.e. MAYBE (mandatory) is the default,
+ * and does not have to be explicitly stored, while OFF (advisory)
+ * reverts to MAYBE (mandatory).
+ * There is one exception to the above assumption, namely for
+ * cases where the initial specification includes "all" instances,
+ * then some later concurrent specification may refer to specific
+ * instances ... in this case, the specific optreq_t structure is
+ * the one that applies.
+ */
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if (rqp->r_numinst == 0) {
+ all_rqp = rqp;
+ continue;
+ }
+ for (j = 0; j < rqp->r_numinst; j++)
+ if (inst == rqp->r_instlist[j])
+ return rqp;
+ }
+
+ if (all_rqp != NULL)
+ return all_rqp;
+ else
+ return NULL;
+}
+
+/* Determine whether a metric is currently known. Returns
+ * -1 if metric not known
+ * inclusive OR of the flags below if it is known
+ */
+#define MF_HAS_INDOM 0x1 /* has an instance domain */
+#define MF_HAS_ALL 0x2 /* has an "all instances" */
+#define MF_HAS_INST 0x4 /* has specific instance(s) */
+#define MF_HAS_MAND 0x8 /* has at least one inst with mandatory */
+ /* logging (or is mandatory if no indom) */
+static int
+find_metric(pmID pmid)
+{
+ __pmHashNode *hp;
+ optreq_t *rqp;
+ int result = 0;
+ int found = 0;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if (found++ == 0)
+ if (rqp->r_desc->indom != PM_INDOM_NULL) {
+ result |= MF_HAS_INDOM;
+ if (rqp->r_numinst == 0)
+ result |= MF_HAS_ALL;
+ else
+ result |= MF_HAS_INST;
+ }
+ if (PMLC_GET_MAND(((task_t *)(rqp->r_fetch->f_aux))->t_state))
+ result |= MF_HAS_MAND;
+ }
+ return found ? result : -1;
+}
+
+/* Find an optreq_t suitable for adding a new instance */
+
+/* Add a new metric (given a pmValueSet and a pmDesc) to the specified task.
+ * Allocate and return a new task_t if the specified task pointer is nil.
+ *
+ * Note that this should only be called for metrics not currently in the
+ * logging data structure. All instances in the pmValueSet are added!
+ */
+static int
+add_metric(pmValueSet *vsp, task_t **result)
+{
+ pmID pmid = vsp->pmid;
+ task_t *tp = *result;
+ optreq_t *rqp;
+ pmDesc *dp;
+ char *name;
+ int sts, i, need = 0;
+
+ dp = (pmDesc *)malloc(sizeof(pmDesc));
+ if (dp == NULL) {
+ __pmNoMem("add_metric: new pmDesc malloc", sizeof(pmDesc), PM_FATAL_ERR);
+ }
+ if ((sts = pmLookupDesc(pmid, dp)) < 0)
+ die("add_metric: lookup desc", sts);
+ if ((sts = pmNameID(pmid, &name)) < 0)
+ die("add_metric: lookup name", sts);
+
+ /* allocate a new task if null task pointer passed in */
+ if (tp == NULL) {
+ tp = calloc(1, sizeof(task_t));
+ if (tp == NULL) {
+ __pmNoMem("add_metric: new task calloc", sizeof(task_t), PM_FATAL_ERR);
+ }
+ *result = tp;
+ }
+
+ /* add metric (and any instances specified) to task */
+ i = tp->t_numpmid++;
+ need = tp->t_numpmid * sizeof(pmID);
+ if (!(tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, need)))
+ __pmNoMem("add_metric: new task pmidlist realloc", need, PM_FATAL_ERR);
+ need = tp->t_numpmid * sizeof(char *);
+ if (!(tp->t_namelist = (char **)realloc(tp->t_namelist, need)))
+ __pmNoMem("add_metric: new task namelist realloc", need, PM_FATAL_ERR);
+ need = tp->t_numpmid * sizeof(pmDesc);
+ if (!(tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, need)))
+ __pmNoMem("add_metric: new task desclist realloc", need, PM_FATAL_ERR);
+ tp->t_pmidlist[i] = pmid;
+ tp->t_namelist[i] = name;
+ tp->t_desclist[i] = *dp; /* struct assignment */
+
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("add_metric: new task optreq calloc", need, PM_FATAL_ERR);
+ }
+ rqp->r_desc = dp;
+
+ /* Now copy instances if required. Remember that metrics with singular
+ * values actually have one instance specified to distinguish them from the
+ * "all instances" case (which has no instances). Use the pmDesc to check
+ * for this.
+ */
+ if (dp->indom != PM_INDOM_NULL)
+ need = rqp->r_numinst = vsp->numval;
+ if (need) {
+ need *= sizeof(rqp->r_instlist[0]);
+ rqp->r_instlist = (int *)malloc(need);
+ if (rqp->r_instlist == NULL) {
+ __pmNoMem("add_metric: new task optreq instlist malloc", need,
+ PM_FATAL_ERR);
+ }
+ for (i = 0; i < vsp->numval; i++)
+ rqp->r_instlist[i] = vsp->vlist[i].inst;
+ }
+
+ /* Add new metric to task's fetchgroup(s) and global hash table */
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ linkback(tp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("add_metric: __pmHashAdd", sts);
+ return 0;
+}
+
+/* Return true if a request for a new logging state (newstate) will be honoured
+ * when current state is curstate.
+ */
+static int
+update_ok(int curstate, int newstate)
+{
+ /* If new state is advisory and current is mandatory, reject request.
+ * Any new mandatory state is accepted. If the new state is advisory
+ * and the current state is advisory, it is accepted.
+ * Note that a new state of maybe (mandatory maybe) counts as mandatory
+ */
+ if (PMLC_GET_MAND(newstate) == 0 && PMLC_GET_MAYBE(newstate) == 0 &&
+ PMLC_GET_MAND(curstate))
+ return 0;
+ else
+ return 1;
+}
+
+/* Given a task and a pmID, find an optreq_t associated with the task suitable
+ * for inserting a new instance into.
+ * The one with the smallest number of instances is chosen. We could also
+ * have just used the first, but smallest-first gives a more even distribution.
+ */
+static optreq_t *
+find_instoptreq(task_t *tp, pmID pmid)
+{
+ optreq_t *result = NULL;
+ optreq_t *rqp;
+ int ni = 0;
+ __pmHashNode *hp;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL;
+ hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if ((task_t *)rqp->r_fetch->f_aux != tp)
+ continue;
+ if (rqp->r_numinst == 0)
+ continue; /* don't want "all instances" cases */
+ if (ni == 0 || rqp->r_numinst < ni) {
+ result = rqp;
+ ni = rqp->r_numinst;
+ }
+ }
+ return result;
+}
+
+/* Delete an optreq_t from its task, free it and remove it from the hash list.
+ */
+static void
+del_optreq(optreq_t *rqp)
+{
+ int sts;
+ task_t *tp = (task_t *)rqp->r_fetch->f_aux;
+
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("del_optreq: __pmOptFetchDel", sts);
+ if ((sts = __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash)) < 0)
+ die("del_optreq: __pmHashDel", sts);
+ free(rqp->r_desc);
+ if (rqp->r_numinst)
+ free(rqp->r_instlist);
+ free(rqp);
+ /* TODO: remove pmid from task if that was the last optreq_t for it */
+ /* TODO: remove task if last pmid removed */
+}
+
+/* Delete every instance of a given metric from the data structure.
+ * The pmid is deleted from the pmidlist of every task containing an instance.
+ * Return a pointer to the first pmDesc found (the only thing salvaged from the
+ * smoking ruins), or nil if no instances were found.
+ */
+static pmDesc *
+del_insts(pmID pmid)
+{
+ optreq_t *rqp;
+ __pmHashNode *hp;
+ task_t *tp;
+ pmDesc *dp = NULL;
+ int i, sts, keep;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; ) {
+ /* Do that BEFORE we nuke the node */
+ __pmHashNode * nextnode = hp->next;
+
+ if (pmid == (pmID)hp->key) {
+ rqp = (optreq_t *)hp->data;
+ tp = (task_t *)rqp->r_fetch->f_aux;
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("del_insts: __pmOptFetchDel", sts);
+ if ((sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("del_insts: __pmHashDel", sts);
+
+ /* save the first pmDesc pointer for return and subsequent
+ * re-use, but free all the others
+ */
+ if (dp != NULL)
+ free(rqp->r_desc);
+ else
+ dp = rqp->r_desc;
+
+ if (rqp->r_numinst)
+ free(rqp->r_instlist);
+ free(rqp);
+
+ /* remove pmid from the task's pmid list */
+ for (i = 0; i < tp->t_numpmid; i++)
+ if (tp->t_pmidlist[i] == pmid)
+ break;
+ keep = (tp->t_numpmid - 1 - i) * sizeof(tp->t_pmidlist[0]);
+ if (keep) {
+ memmove(&tp->t_pmidlist[i], &tp->t_pmidlist[i+1], keep);
+ memmove(&tp->t_desclist[i], &tp->t_desclist[i+1], keep);
+ memmove(&tp->t_namelist[i], &tp->t_namelist[i+1], keep);
+ }
+
+ /* don't bother shrinking the pmidlist */
+ tp->t_numpmid--;
+ if (tp->t_numpmid == 0) {
+ /* TODO: nuke the task if that was the last pmID */
+ }
+ }
+ hp = nextnode;
+ }
+
+ return dp;
+}
+
+/* Update an existing metric (given a pmValueSet) adding it to the specified
+ * task. Allocate and return a new task_t if the specified task pointer is nil.
+ */
+static int
+update_metric(pmValueSet *vsp, int reqstate, int mflags, task_t **result)
+{
+ pmID pmid = vsp->pmid;
+ task_t *ntp = *result; /* pointer to new task */
+ task_t *ctp; /* pointer to current task */
+ optreq_t *rqp;
+ pmDesc *dp;
+ int i, j, inst;
+ int sts, need = 0;
+ int addpmid = 0;
+ int freedp;
+
+ /* allocate a new task if null task pointer passed in */
+ if (ntp == NULL) {
+ ntp = calloc(1, sizeof(task_t));
+ if (ntp == NULL) {
+ __pmNoMem("update_metric: new task calloc", sizeof(task_t),
+ PM_FATAL_ERR);
+ }
+ *result = ntp;
+ }
+
+ if ((mflags & MF_HAS_INDOM) == 0) {
+ rqp = findoptreq(pmid, 0);
+ ctp = (task_t *)(rqp->r_fetch->f_aux);
+ if (!update_ok(ctp->t_state, reqstate))
+ return 1;
+
+ /* if the new state is advisory off, just remove the metric */
+ if ((PMLC_GET_MAYBE(reqstate)) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0))
+ del_optreq(rqp);
+ else {
+ /* update the optreq. For single valued metrics there are no
+ * instances involved so the sole optreq can just be re-used.
+ */
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: 1 metric __pmOptFetchDel", sts);
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ addpmid = 1;
+ }
+ }
+ else {
+ /* metric has an instance domain */
+ if (vsp->numval > 0) {
+ /* tricky: since optFetch can't handle instance profiles of the
+ * form "all except these specific instances", and managing it
+ * manually is just too hard, reject requests for specific
+ * metric instances if "all instances" of the metric are already
+ * being logged.
+ * Note: advisory off "all instances" is excepted since ANY request
+ * overrides and advisory off. E.g. "advisory off all" followed by
+ * "advisory on someinsts" turns on advisory logging for
+ * "someinsts". mflags will be zero for "advisory off" metrics.
+ */
+ if (mflags & MF_HAS_ALL)
+ return 1; /* can't turn "all" into specific insts */
+
+ for (i = 0; i < vsp->numval; i++) {
+ dp = NULL;
+ freedp = 0;
+ inst = vsp->vlist[i].inst;
+ rqp = findoptreq(pmid, inst);
+ if (rqp != NULL) {
+ dp = rqp->r_desc;
+ ctp = (task_t *)(rqp->r_fetch->f_aux);
+ /* no work required if new task and current are the same */
+ if (ntp == ctp)
+ continue;
+ if (!update_ok(ctp->t_state, reqstate))
+ continue;
+
+ /* remove inst's group from current task */
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: instance add __pmOptFetchDel", sts);
+
+ /* put group back if there are any instances left */
+ if (rqp->r_numinst > 1) {
+ /* remove inst from group */
+ for (j = 0; j < rqp->r_numinst; j++)
+ if (inst == rqp->r_instlist[j])
+ break;
+ /* don't call memmove to move zero bytes */
+ if (j < rqp->r_numinst - 1)
+ memmove(&rqp->r_instlist[j], &rqp->r_instlist[j+1],
+ (rqp->r_numinst - 1 - j) *
+ sizeof(rqp->r_instlist[0]));
+ rqp->r_numinst--;
+ /* (don't bother realloc-ing the instlist to a smaller size) */
+
+ __pmOptFetchAdd(&ctp->t_fetch, rqp);
+ linkback(ctp);
+ /* no need to update hash list, rqp already there */
+ }
+ /* if that was the last instance, free the group */
+ else {
+ if (( sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: instance __pmHashDel", sts);
+ freedp = 1;
+ free(rqp->r_instlist);
+ free(rqp);
+ }
+ }
+
+ /* advisory off (mandatory maybe) metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ if (freedp)
+ free(dp);
+ continue;
+ }
+ addpmid = 1;
+
+ /* try to find an existing optreq_t for the instance */
+ rqp = find_instoptreq(ntp, pmid);
+ if (rqp != NULL) {
+ if ((sts = __pmOptFetchDel(&ntp->t_fetch, rqp)) < 0)
+ die("update_metric: instance add __pmOptFetchDel", sts);
+ }
+ /* no existing optreq_t found, allocate & populate a new one */
+ else {
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("update_metric: optreq calloc",
+ sizeof(optreq_t), PM_FATAL_ERR);
+ }
+ /* if the metric existed but the instance didn't, we don't
+ * have a valid pmDesc (dp), so find one.
+ */
+ if (dp == NULL) {
+ /* find metric and associated pmDesc */
+ __pmHashNode *hp;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash);
+ hp != NULL; hp = hp->next) {
+ if (pmid == (pmID)hp->key)
+ break;
+ }
+ assert(hp != NULL);
+ dp = ((optreq_t *)hp->data)->r_desc;
+ }
+ /* recycle pmDesc from the old group, if possible */
+ if (freedp) {
+ rqp->r_desc = dp;
+ freedp = 0;
+ }
+ /* otherwise allocate & copy a new pmDesc via dp */
+ else {
+ need = sizeof(pmDesc);
+ rqp->r_desc = (pmDesc *)malloc(need);
+ if (rqp->r_desc == NULL) {
+ __pmNoMem("update_metric: new inst pmDesc malloc",
+ need, PM_FATAL_ERR);
+ }
+ memcpy(rqp->r_desc, dp, need);
+ }
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: __pmHashAdd", sts);
+ }
+
+ need = (rqp->r_numinst + 1) * sizeof(rqp->r_instlist[0]);
+ rqp->r_instlist = (int *)realloc(rqp->r_instlist, need);
+ if (rqp->r_instlist == NULL) {
+ __pmNoMem("update_metric: inst list resize", need,
+ PM_FATAL_ERR);
+ }
+ rqp->r_instlist[rqp->r_numinst++] = inst;
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ if (freedp)
+ free(dp);
+ }
+ }
+ /* the vset has numval == 0, a request for "all instances" */
+ else {
+ /* if the metric is a singular instance that has mandatory logging
+ * or has at least one instance with mandatory logging on, a
+ * request for advisory logging cannot be honoured
+ */
+ if ((mflags & MF_HAS_MAND) &&
+ PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_MAYBE(reqstate) == 0)
+ return 1;
+
+ if (mflags & MF_HAS_ALL) {
+ /* if there is an "all instances" for the metric, it will be
+ * the only optreq_t for the metric
+ */
+ rqp = findoptreq(pmid, 0);
+ ctp = (task_t *)rqp->r_fetch->f_aux;
+
+ /* if the metric is "advisory on, all instances" and the
+ * request is for "mandatory maybe, all instances" the current
+ * advisory logging state of the metric is retained
+ */
+ if (PMLC_GET_MAND(ctp->t_state) == 0 && PMLC_GET_MAYBE(reqstate))
+ return 0;
+
+ /* advisory off & mandatory maybe metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ del_optreq(rqp);
+ return 0;
+ }
+
+ addpmid = 1;
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: all inst __pmOptFetchDel", sts);
+ /* don't delete from hash list, rqp re-used */
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ }
+ else {
+ /* there are one or more specific instances for the metric.
+ * The metric cannot have an "all instances" at the same time.
+ *
+ * if the request is for "mandatory maybe, all instances" and
+ * the only instances of the metric all have advisory logging
+ * on, retain the current advisory semantics.
+ */
+ if (PMLC_GET_MAYBE(reqstate) &&
+ (mflags & MF_HAS_INST) && !(mflags & MF_HAS_MAND))
+ return 0;
+
+ dp = del_insts(pmid);
+
+ /* advisory off (mandatory maybe) metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ free(dp);
+ return 0;
+ }
+
+ addpmid = 1;
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("update_metric: all inst calloc",
+ sizeof(optreq_t), PM_FATAL_ERR);
+ }
+ rqp->r_desc = dp;
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: all inst __pmHashAdd", sts);
+ }
+ }
+ }
+
+ if (!addpmid)
+ return 0;
+
+ /* add pmid to new task if not already there */
+ for (i = 0; i < ntp->t_numpmid; i++)
+ if (pmid == ntp->t_pmidlist[i])
+ break;
+ if (i >= ntp->t_numpmid) {
+ pmDesc desc;
+ char *name;
+ int need;
+
+ if ((sts = pmLookupDesc(pmid, &desc)) < 0)
+ die("update_metric: cannot lookup desc", sts);
+ if ((sts = pmNameID(pmid, &name)) < 0)
+ die("update_metric: cannot lookup name", sts);
+
+ need = (ntp->t_numpmid + 1) * sizeof(pmID);
+ if (!(ntp->t_pmidlist = (pmID *)realloc(ntp->t_pmidlist, need)))
+ __pmNoMem("update_metric: grow task pmidlist", need, PM_FATAL_ERR);
+ need = (ntp->t_numpmid + 1) * sizeof(char *);
+ if (!(ntp->t_namelist = (char **)realloc(ntp->t_namelist, need)))
+ __pmNoMem("update_metric: grow task namelist", need, PM_FATAL_ERR);
+ need = (ntp->t_numpmid + 1) * sizeof(pmDesc);
+ if (!(ntp->t_desclist = (pmDesc *)realloc(ntp->t_desclist, need)))
+ __pmNoMem("update_metric: grow task desclist", need, PM_FATAL_ERR);
+ i = ntp->t_numpmid;
+ ntp->t_pmidlist[i] = pmid;
+ ntp->t_namelist[i] = name;
+ ntp->t_desclist[i] = desc;
+ ntp->t_numpmid++;
+ }
+ return 0;
+}
+
+/* Given a state and a delta, return the first matching task.
+ * Return NULL if a matching task was not found.
+ */
+task_t *
+find_task(int state, struct timeval *delta)
+{
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (state == (tp->t_state & 0x3) && /* MAND|ON */
+ delta->tv_sec == tp->t_delta.tv_sec &&
+ delta->tv_usec == tp->t_delta.tv_usec)
+ break;
+ }
+ return tp;
+}
+
+/* Return a mask containing the history flags for a given metric/instance.
+ * the history flags indicate whether the metric/instance is in the log at all
+ * and whether the last fetch of the metric/instance was successful.
+ *
+ * The result is suitable for ORing into the result returned by a control log
+ * request.
+ */
+static int
+gethistflags(pmID pmid, int inst)
+{
+ __pmHashNode *hp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ int i, found;
+ int val;
+
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
+ if ((pmID)hp->key == pmid)
+ break;
+ if (hp == NULL)
+ return 0;
+ php = (pmidhist_t *)hp->data;
+ ihp = &php->ph_instlist[0];
+ val = 0;
+ if (php->ph_indom != PM_INDOM_NULL) {
+ for (i = 0; i < php->ph_numinst; i++, ihp++)
+ if (ihp->ih_inst == inst)
+ break;
+ found = i < php->ph_numinst;
+ }
+ else
+ found = php->ph_numinst > 0;
+ if (found) {
+ PMLC_SET_INLOG(val, 1);
+ val |= ihp->ih_flags; /* only "available flag" is ever set */
+ }
+ return val;
+}
+
+/* take a pmResult (from a control log request) and half-clone it: return a
+ * pointer to a new pmResult struct which shares the pmValueSets in the
+ * original that have numval > 0, and has null pointers for the pmValueSets
+ * in the original with numval <= 0
+ */
+static pmResult *
+siamise_request(pmResult *request)
+{
+ int i, need;
+ pmValueSet *vsp;
+ pmResult *result;
+
+ need = sizeof(pmResult) + (request->numpmid - 1) * sizeof(pmValueSet *);
+ result = (pmResult *)malloc(need);
+ if (result == NULL) {
+ __pmNoMem("siamise_request: malloc pmResult", need, PM_FATAL_ERR);
+ }
+ for (i = 0; i < request->numpmid; i++) {
+ vsp = request->vset[i];
+ if (vsp->numval > 0)
+ result->vset[i] = request->vset[i];
+ else
+ result->vset[i] = NULL;
+ }
+ result->timestamp = request->timestamp; /* structure assignment */
+ result->numpmid = request->numpmid;
+
+ return result;
+}
+
+/* Temporarily borrow a bit in the metric/instance history to indicate that
+ * the instance currently exists in the instance domain. The macros below
+ * set and get the bit, which is cleared after we are finished with it here.
+ */
+
+#define PMLC_SET_USEINDOM(val, flag) (val = (val & ~0x1000) | (flag << 12 ))
+#define PMLC_GET_USEINDOM(val) ((val & 0x1000) >> 12)
+
+/* create a pmValueSet large enough to contain the union of the current
+ * instance domain of the specified metric and any previous instances from
+ * the history list.
+ */
+static pmValueSet *
+build_vset(pmID pmid, int usehist)
+{
+ __pmHashNode *hp;
+ pmidhist_t *php = NULL;
+ insthist_t *ihp;
+ int need = 0;
+ int i, numindom = 0;
+ pmDesc desc;
+ int have_desc;
+ int *instlist = NULL;
+ char **namelist = NULL;
+ pmValueSet *vsp;
+
+ if (usehist) {
+ /* find the number of instances of the metric in the history (1 if
+ * single-valued metric)
+ */
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
+ if ((pmID)hp->key == pmid)
+ break;
+ if (hp != NULL) {
+ php = (pmidhist_t *)hp->data;
+ need = php->ph_numinst;
+ }
+ }
+ /*
+ * get the current instance domain, so that if the metric hasn't been
+ * logged yet a sensible result is returned.
+ */
+ if ((have_desc = pmLookupDesc(pmid, &desc)) < 0)
+ goto no_info;
+ if (desc.indom == PM_INDOM_NULL)
+ need = 1; /* will be same in history */
+ else {
+ int j;
+
+ if ((numindom = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) {
+ have_desc = numindom;
+ goto no_info;
+ }
+ /* php will be null if usehist is false or there is no history yet */
+ if (php == NULL)
+ need = numindom; /* no history => use indom */
+ else
+ for (i = 0; i < numindom; i++) {
+ int inst = instlist[i];
+
+ for (j = 0; j < php->ph_numinst; j++)
+ if (inst == php->ph_instlist[j].ih_inst)
+ break;
+ /*
+ * if instance is in history but not instance domain, leave
+ * extra space for it in vset, otherwise use the USEINDOM
+ * flag to avoid another NxM comparison when building the vset
+ * instances later.
+ */
+ if (j >= php->ph_numinst)
+ need++;
+ else
+ PMLC_SET_USEINDOM(php->ph_instlist[j].ih_flags, 1);
+ }
+ }
+
+no_info:
+
+ need = sizeof(pmValueSet) + (need - 1) * sizeof(pmValue);
+ vsp = (pmValueSet *)malloc(need);
+ if (vsp == NULL) {
+ __pmNoMem("build_vset for control/enquire", need, PM_FATAL_ERR);
+ }
+ vsp->pmid = pmid;
+ if (have_desc < 0) {
+ vsp->numval = have_desc;
+ }
+ else if (desc.indom == PM_INDOM_NULL) {
+ vsp->vlist[0].inst = PM_IN_NULL;
+ vsp->numval = 1;
+ }
+ else {
+ int j;
+
+ i = 0;
+ /* get instances out of instance domain first */
+ if (numindom > 0)
+ for (j = 0; j < numindom; j++)
+ vsp->vlist[i++].inst = instlist[j];
+
+ /* then any not in instance domain from history */
+ if (php != NULL) {
+ ihp = &php->ph_instlist[0];
+ for (j = 0; j < php->ph_numinst; j++, ihp++)
+ if (PMLC_GET_USEINDOM(ihp->ih_flags))
+ /* it's already in the indom */
+ PMLC_SET_USEINDOM(ihp->ih_flags, 0);
+ else
+ vsp->vlist[i++].inst = ihp->ih_inst;
+ }
+ vsp->numval = i;
+ }
+ if (instlist)
+ free(instlist);
+ if (namelist)
+ free(namelist);
+
+ return vsp;
+}
+
+static int
+do_control(__pmPDU *pb)
+{
+ int sts;
+ int control;
+ int state;
+ int delta;
+ pmResult *request;
+ pmResult *result;
+ int siamised = 0; /* the verb from siamese (as in twins) */
+ int i;
+ int j;
+ int val;
+ pmValueSet *vsp;
+ optreq_t *rqp;
+ task_t *tp;
+ time_t now;
+ int reqstate = 0;
+
+ /*
+ * TODO - encoding for logging interval in requests and results?
+ */
+ if ((sts = __pmDecodeLogControl(pb, &request, &control, &state, &delta)) < 0)
+ return sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "do_control: control=%d state=%d delta=%d request ...\n",
+ control, state, delta);
+ dumpcontrol(stderr, request, 0);
+ }
+#endif
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ time(&now);
+ fprintf(stderr, "\n%s", ctime(&now));
+ fprintf(stderr, "pmlc request from %s: %s",
+ pmlc_host, control == PM_LOG_MANDATORY ? "mandatory" : "advisory");
+ if (state == PM_LOG_ON) {
+ if (delta == 0)
+ fprintf(stderr, " on once\n");
+ else
+ fprintf(stderr, " on %.1f sec\n", (float)delta/1000);
+ }
+ else if (state == PM_LOG_OFF)
+ fprintf(stderr, " off\n");
+ else
+ fprintf(stderr, " maybe\n");
+ }
+
+ /*
+ * access control checks
+ */
+ sts = 0;
+ switch (control) {
+ case PM_LOG_MANDATORY:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ case PM_LOG_ADVISORY:
+ if (denyops & PM_OP_LOG_ADV)
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ case PM_LOG_ENQUIRE:
+ /*
+ * Don't need to check [access] as you have to have _some_
+ * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
+ * and PM_OP_LOG_ENQ) to make a connection ... and if you
+ * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
+ * sense to deny PM_OP_LOG_ENQ operations.
+ */
+ break;
+
+ default:
+ fprintf(stderr, "Bad control PDU type %d\n", control);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ if (sts < 0) {
+ fprintf(stderr, "Error: %s\n", pmErrStr(sts));
+ if ((sts = __pmSendError(clientfd, FROM_ANON, sts)) < 0)
+ __pmNotifyErr(LOG_ERR,
+ "do_control: error sending Error PDU to client: %s\n",
+ pmErrStr(sts));
+ pmFreeResult(request);
+ return sts;
+ }
+
+ /* handle everything except PM_LOG_ENQUIRE */
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ /* update the logging status of metrics */
+
+ task_t *newtp = NULL; /* task for metrics/insts in request */
+ struct timeval tdelta = { 0 };
+ int newtask;
+ int mflags;
+
+ /* convert state and control to the bitmask used in pmlogger and values
+ * returned in results. Remember that reqstate starts with nothing on.
+ */
+ if (state == PM_LOG_ON)
+ PMLC_SET_ON(reqstate, 1);
+ else
+ PMLC_SET_ON(reqstate, 0);
+ if (control == PM_LOG_MANDATORY) {
+ if (state == PM_LOG_MAYBE)
+ /* mandatory+maybe => maybe+advisory+off */
+ PMLC_SET_MAYBE(reqstate, 1);
+ else
+ PMLC_SET_MAND(reqstate, 1);
+ }
+
+ /* try to find an existing task for the request
+ * Never return a "once only" task, it may have gone off already and just
+ * be hanging around like a bad smell.
+ */
+ if (delta != 0) {
+ tdelta.tv_sec = delta / 1000;
+ tdelta.tv_usec = (delta % 1000) * 1000;
+ newtp = find_task(reqstate, &tdelta);
+ }
+ newtask = (newtp == NULL);
+
+ for (i = 0; i < request->numpmid; i++) {
+ vsp = request->vset[i];
+ if (vsp->numval < 0)
+ /*
+ * request is malformed, as we cannot control logging
+ * for an undefined instance ... there is no way to
+ * return an error from here, so simply ignore this
+ * metric
+ */
+ continue;
+ mflags = find_metric(vsp->pmid);
+ if (mflags < 0) {
+ /* only add new metrics if they are ON or MANDATORY OFF
+ * Careful: mandatory+maybe is mandatory+maybe+off
+ */
+ if (PMLC_GET_ON(reqstate) ||
+ (PMLC_GET_MAND(reqstate) && !PMLC_GET_MAYBE(reqstate)))
+ add_metric(vsp, &newtp);
+ }
+ else
+ /* already a specification for this metric */
+ update_metric(vsp, reqstate, mflags, &newtp);
+ }
+
+ /* schedule new logging task if new metric(s) specified */
+ if (newtask && newtp != NULL) {
+ if (newtp->t_fetch == NULL) {
+ /* the new task ended up with no fetch groups, throw it away */
+ if (newtp->t_pmidlist != NULL)
+ free(newtp->t_pmidlist);
+ free(newtp);
+ }
+ else {
+ /* link new task into tasklist */
+ newtp->t_next = tasklist;
+ tasklist = newtp;
+
+ /* use only the MAND/ADV and ON/OFF bits of reqstate */
+ newtp->t_state = PMLC_GET_STATE(reqstate);
+ if (PMLC_GET_ON(reqstate)) {
+ newtp->t_delta = tdelta;
+ newtp->t_afid = __pmAFregister(&tdelta, (void *)newtp,
+ log_callback);
+ }
+ else
+ newtp->t_delta.tv_sec = newtp->t_delta.tv_usec = 0;
+ linkback(newtp);
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ dumpit();
+#endif
+
+ /* just ignore advisory+maybe---the returned pmResult will have the metrics
+ * in their original state indicating that the request could not be
+ * satisfied.
+ */
+
+ result = request;
+ result->timestamp.tv_sec = result->timestamp.tv_usec = 0; /* for purify */
+ /* write the current state of affairs into the result _pmResult */
+ for (i = 0; i < request->numpmid; i++) {
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ char *p;
+
+ sts = pmNameID(request->vset[i]->pmid, &p);
+ if (sts < 0)
+ fprintf(stderr, " metric: %s", pmIDStr(request->vset[i]->pmid));
+ else {
+ fprintf(stderr, " metric: %s", p);
+ free(p);
+ }
+ }
+
+ if (request->vset[i]->numval <= 0 && !siamised) {
+ result = siamise_request(request);
+ siamised = 1;
+ }
+ /*
+ * pmids with numval <= 0 in the request have a null vset ptr in the
+ * in the corresponding place in the siamised result.
+ */
+ if (result->vset[i] != NULL)
+ vsp = result->vset[i];
+ else {
+ /* the result should also contain the history for an all instances
+ * enquire request. Control requests just get the current indom
+ * since the user of pmlc really wants to see what's being logged
+ * now rather than in the past.
+ */
+ vsp = build_vset(request->vset[i]->pmid, control == PM_LOG_ENQUIRE);
+ result->vset[i] = vsp;
+ }
+ vsp->valfmt = PM_VAL_INSITU;
+ for (j = 0; j < vsp->numval; j++) {
+ rqp = findoptreq(vsp->pmid, vsp->vlist[j].inst);
+ val = 0;
+ if (rqp == NULL) {
+ PMLC_SET_STATE(val, 0);
+ PMLC_SET_DELTA(val, 0);
+ }
+ else {
+ tp = rqp->r_fetch->f_aux;
+ PMLC_SET_STATE(val, tp->t_state);
+ PMLC_SET_DELTA(val, (tp->t_delta.tv_sec*1000 + tp->t_delta.tv_usec/1000));
+ }
+
+ val |= gethistflags(vsp->pmid, vsp->vlist[j].inst);
+ vsp->vlist[j].value.lval = val;
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ int expstate = 0;
+ int statemask = 0;
+ int expdelta;
+ if (rqp != NULL && rqp->r_desc->indom != PM_INDOM_NULL) {
+ char *p;
+ if (j == 0)
+ fputc('\n', stderr);
+ if (pmNameInDom(rqp->r_desc->indom, vsp->vlist[j].inst, &p) >= 0) {
+ fprintf(stderr, " instance: %s", p);
+ free(p);
+ }
+ else
+ fprintf(stderr, " instance: #%d", vsp->vlist[j].inst);
+ }
+ else {
+ /* no pmDesc ... punt */
+ if (vsp->numval > 1 || vsp->vlist[j].inst != PM_IN_NULL) {
+ if (j == 0)
+ fputc('\n', stderr);
+ fprintf(stderr, " instance: #%d", vsp->vlist[j].inst);
+ }
+ }
+ if (state != PM_LOG_MAYBE) {
+ if (control == PM_LOG_MANDATORY)
+ PMLC_SET_MAND(expstate, 1);
+ else
+ PMLC_SET_MAND(expstate, 0);
+ if (state == PM_LOG_ON)
+ PMLC_SET_ON(expstate, 1);
+ else
+ PMLC_SET_ON(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ PMLC_SET_ON(statemask, 1);
+ }
+ else {
+ PMLC_SET_MAND(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ }
+ expdelta = PMLC_GET_ON(expstate) ? delta : 0;
+ if ((PMLC_GET_STATE(val) & statemask) != expstate ||
+ PMLC_GET_DELTA(val) != expdelta)
+ fprintf(stderr, " [request failed]");
+ fputc('\n', stderr);
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ __pmDumpResult(stderr, result);
+ }
+#endif
+
+ if ((sts = __pmSendResult(clientfd, FROM_ANON, result)) < 0)
+ __pmNotifyErr(LOG_ERR,
+ "do_control: error sending Error PDU to client: %s\n",
+ pmErrStr(sts));
+
+ if (siamised) {
+ for (i = 0; i < request->numpmid; i++)
+ if (request->vset[i]->numval <= 0)
+ free(result->vset[i]);
+ free(result);
+ }
+ pmFreeResult(request);
+
+ return 0;
+}
+
+/*
+ * sendstatus
+ */
+static int
+sendstatus(void)
+{
+ int rv;
+ int end;
+ int version;
+ static int firsttime = 1;
+ static char *tzlogger;
+ struct timeval now;
+
+ if (firsttime) {
+ tzlogger = __pmTimezone();
+ firsttime = 0;
+ }
+
+ if ((version = __pmVersionIPC(clientfd)) < 0)
+ return version;
+
+ if (version >= LOG_PDU_VERSION2) {
+ __pmLoggerStatus ls;
+
+ if ((ls.ls_state = logctl.l_state) == PM_LOG_STATE_NEW)
+ ls.ls_start.tv_sec = ls.ls_start.tv_usec = 0;
+ else
+ memcpy(&ls.ls_start, &logctl.l_label.ill_start, sizeof(ls.ls_start));
+ memcpy(&ls.ls_last, &last_stamp, sizeof(ls.ls_last));
+ __pmtimevalNow(&now);
+ ls.ls_timenow.tv_sec = (__int32_t)now.tv_sec;
+ ls.ls_timenow.tv_usec = (__int32_t)now.tv_usec;
+ ls.ls_vol = logctl.l_curvol;
+ ls.ls_size = ftell(logctl.l_mfp);
+ assert(ls.ls_size >= 0);
+
+ /* be careful of buffer size mismatches when copying strings */
+ end = sizeof(ls.ls_hostname) - 1;
+ strncpy(ls.ls_hostname, logctl.l_label.ill_hostname, end);
+ ls.ls_hostname[end] = '\0';
+ /* BTW, that string should equal pmcd_host[]. */
+
+ /* NB: FQDN cleanup: there is no such thing as 'the fully
+ qualified domain name' of a server: it may have several or
+ none; the set may have changed since the time the log
+ archive was collected. Now that we store the then-current
+ pmcd.hostname in the ill_hostname (and thus get it reported
+ in ls_hostname), we could pass something else informative
+ in the ls_fqdn slot. Namely, pmcd_host_conn[], which is the
+ access path pmlogger's using to get to the pmcd. */
+ end = sizeof(ls.ls_fqdn) - 1;
+ strncpy(ls.ls_fqdn, pmcd_host_conn, end);
+ ls.ls_fqdn[end] = '\0';
+
+ end = sizeof(ls.ls_tz) - 1;
+ strncpy(ls.ls_tz, logctl.l_label.ill_tz, end);
+ ls.ls_tz[end] = '\0';
+ end = sizeof(ls.ls_tzlogger) - 1;
+ if (tzlogger != NULL)
+ strncpy(ls.ls_tzlogger, tzlogger, end);
+ else
+ end = 0;
+ ls.ls_tzlogger[end] = '\0';
+
+ rv = __pmSendLogStatus(clientfd, &ls);
+ }
+ else
+ rv = PM_ERR_IPC;
+ return rv;
+}
+
+static int
+do_request(__pmPDU *pb)
+{
+ int sts;
+ int type;
+
+ if ((sts = __pmDecodeLogRequest(pb, &type)) < 0) {
+ __pmNotifyErr(LOG_ERR, "do_request: error decoding PDU: %s\n", pmErrStr(sts));
+ return PM_ERR_IPC;
+ }
+
+ switch (type) {
+ case LOG_REQUEST_STATUS:
+ /*
+ * Don't need to check [access] as you have to have _some_
+ * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
+ * and PM_OP_LOG_ENQ) to make a connection ... and if you
+ * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
+ * sense to deny LOG_REQUEST_STATUS operations.
+ * Also, this is needed internally by pmlc to discover pmcd's
+ * hostname.
+ */
+ sts = sendstatus();
+ break;
+
+ case LOG_REQUEST_NEWVOLUME:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ sts = newvolume(VOL_SW_PMLC);
+ if (sts >= 0)
+ sts = logctl.l_label.ill_vol;
+ sts = __pmSendError(clientfd, FROM_ANON, sts);
+ }
+ break;
+
+ case LOG_REQUEST_SYNC:
+ /*
+ * Don't need to check access controls, as this is now
+ * a no-op with unbuffered I/O from pmlogger.
+ *
+ * Do nothing, simply send status 0 back to pmlc.
+ */
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ break;
+
+ /*
+ * QA support ... intended for error injection
+ * If the request is > QA_OFF then this is a code to enable
+ * a specific style of error behaviour. If the request
+ * is QA_OFF, this disables the error behaviour.
+ *
+ * Supported behaviours.
+ * QA_SLEEPY
+ * After this exchange with pmlc, sleep for 5 seconds
+ * after each incoming pmlc request ... allows testing
+ * of timeout logic in pmlc
+ */
+
+ case QA_OFF:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ qa_case = 0;
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ }
+ break;
+
+ case QA_SLEEPY:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ qa_case = type;
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "do_request: bad request type %d\n", type);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ return sts;
+}
+
+static int
+do_creds(__pmPDU *pb)
+{
+ int i;
+ int sts;
+ int version = UNKNOWN_VERSION;
+ int credcount;
+ int sender;
+ __pmCred *credlist = NULL;
+
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) {
+ __pmNotifyErr(LOG_ERR, "do_creds: error decoding PDU: %s\n", pmErrStr(sts));
+ return PM_ERR_IPC;
+ }
+
+ for (i = 0; i < credcount; i++) {
+ if (credlist[i].c_type == CVERSION) {
+ version = credlist[i].c_vala;
+ if ((sts = __pmSetVersionIPC(clientfd, version)) < 0) {
+ free(credlist);
+ return sts;
+ }
+ }
+ }
+
+ if (credlist)
+ free(credlist);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "do_creds: pmlc version=%d\n", version);
+#endif
+
+ return sts;
+}
+
+/*
+ * Service a request from the pmlogger client.
+ * Return non-zero if the client has closed the connection.
+ */
+int
+client_req(void)
+{
+ int sts;
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ int pinpdu;
+
+ if ((pinpdu = sts = __pmGetPDU(clientfd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) <= 0) {
+ if (sts != 0)
+ fprintf(stderr, "client_req: %s\n", pmErrStr(sts));
+ return 1;
+ }
+ if (qa_case == QA_SLEEPY) {
+ /* error injection - delay before processing and responding */
+ sleep(5);
+ }
+ php = (__pmPDUHdr *)pb;
+ sts = 0;
+
+ switch (php->type) {
+ case PDU_CREDS: /* version 2 PDU */
+ sts = do_creds(pb);
+ break;
+ case PDU_LOG_REQUEST: /* version 2 PDU */
+ sts = do_request(pb);
+ break;
+ case PDU_LOG_CONTROL: /* version 2 PDU */
+ sts = do_control(pb);
+ break;
+ default: /* unknown PDU */
+ fprintf(stderr, "client_req: bad PDU type 0x%x\n", php->type);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (sts >= 0)
+ return 0;
+ else {
+ /* the client isn't playing by the rules */
+ __pmSendError(clientfd, FROM_ANON, sts);
+ return 1;
+ }
+}
diff --git a/src/pmlogger/src/error.c b/src/pmlogger/src/error.c
new file mode 100644
index 0000000..2eb3b1f
--- /dev/null
+++ b/src/pmlogger/src/error.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "logger.h"
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n%s\n", configfile, lineno, s);
+}
+
+void
+yyerror(char *s)
+{
+
+ fprintf(stderr, "Specification error in configuration file (%s)\n",
+ configfile);
+ fprintf(stderr, "[line %d] %s\n", lineno, s);
+ exit(1);
+}
diff --git a/src/pmlogger/src/events.c b/src/pmlogger/src/events.c
new file mode 100644
index 0000000..a8f4ec6
--- /dev/null
+++ b/src/pmlogger/src/events.c
@@ -0,0 +1,113 @@
+/*
+ * Unpack an array of event records
+ * Free space from unpack
+ *
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+/*
+ * Handle event records.
+ *
+ * Walk the packed array of events using similar logic to
+ * pmUnpackEventRecords() but we don't need any allocations.
+ *
+ * For each embedded event parameter, make sure the metadata for
+ * the associated metric is added to the archive.
+ */
+int
+do_events(pmValueSet *vsp)
+{
+ pmEventArray *eap;
+ char *base;
+ pmEventRecord *erp;
+ pmEventParameter *epp;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int i; /* instances ... */
+ int sts;
+ pmDesc desc;
+
+ for (i = 0; i < vsp->numval; i++) {
+ if ((sts = __pmCheckEventRecords(vsp, i)) < 0) {
+ __pmDumpEventRecords(stderr, vsp, i);
+ return sts;
+ }
+ eap = (pmEventArray *)vsp->vlist[i].value.pval;
+ if (eap->ea_nrecords == 0)
+ return 0;
+ base = (char *)&eap->ea_record[0];
+ for (r = 0; r < eap->ea_nrecords; r++) {
+ erp = (pmEventRecord *)base;
+ base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams);
+ if (erp->er_flags & PM_EVENT_FLAG_MISSED) {
+ /*
+ * no event "parameters" here, just a missed records count
+ * in er_nparams
+ */
+ continue;
+ }
+ for (p = 0; p < erp->er_nparams; p++) {
+ epp = (pmEventParameter *)base;
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ sts = __pmLogLookupDesc(&logctl, epp->ep_pmid, &desc);
+ if (sts < 0) {
+ int numnames;
+ char **names;
+ numnames = pmNameAll(epp->ep_pmid, &names);
+ if (numnames < 0) {
+ /*
+ * Event parameter metric not defined in the PMNS.
+ * This should not happen, but is probably not fatal, so
+ * issue a warning and make up a name based on the pmid
+ * event_param.<domain>.<cluster>.<item>
+ */
+ char *name;
+ size_t name_size = strlen("event_param")+3+1+4+1+4+1;
+ names = (char **)malloc(sizeof(char*) + name_size);
+ if (names == NULL)
+ return -oserror();
+ name = (char *)&names[1];
+ names[0] = name;
+ snprintf(name, name_size, "event_param.%s", pmIDStr(epp->ep_pmid));
+ fprintf(stderr, "Warning: metric %s has no name, using %s\n", pmIDStr(epp->ep_pmid), name);
+ }
+ sts = pmLookupDesc(epp->ep_pmid, &desc);
+ if (sts < 0) {
+ /* Event parameter metric does not have a pmDesc.
+ * This should not happen, but is probably not entirely
+ * fatal (although more serious than not having a metric
+ * name), issue a warning and construct a minimalist
+ * pmDesc
+ */
+ desc.pmid = epp->ep_pmid;
+ desc.type = PM_TYPE_AGGREGATE;
+ desc.indom = PM_INDOM_NULL;
+ desc.sem = PM_SEM_DISCRETE;
+ memset(&desc.units, '\0', sizeof(desc.units));
+ fprintf(stderr, "Warning: metric %s (%s) has no descriptor, using a default one\n", names[0], pmIDStr(epp->ep_pmid));
+ }
+ if ((sts = __pmLogPutDesc(&logctl, &desc, numnames, names)) < 0) {
+ fprintf(stderr, "__pmLogPutDesc: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ free(names);
+ }
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/pmlogger/src/fetch.c b/src/pmlogger/src/fetch.c
new file mode 100644
index 0000000..05e0378
--- /dev/null
+++ b/src/pmlogger/src/fetch.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Thread-safe note
+ *
+ * myFetch() returns a PDU buffer that is pinned from _pmGetPDU() or
+ * __pmEncodeResult() and this needs to be unpinned by the myFetch()
+ * caller when safe to do so.
+ */
+
+#include "logger.h"
+
+int
+myFetch(int numpmid, pmID pmidlist[], __pmPDU **pdup)
+{
+ int n = 0;
+ int ctx;
+ __pmPDU *pb;
+ __pmContext *ctxp;
+
+ if (numpmid < 1)
+ return PM_ERR_TOOSMALL;
+
+ if ((ctx = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type != PM_CONTEXT_HOST) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_NOTHOST;
+ }
+ }
+ else
+ return PM_ERR_NOCONTEXT;
+
+#if CAN_RECONNECT
+ if (ctxp->c_pmcd->pc_fd == -1) {
+ /* lost connection, try to get it back */
+ n = reconnect();
+ if (n < 0) {
+ PM_UNLOCK(ctxp->c_lock);
+ return n;
+ }
+ }
+#endif
+
+ if (ctxp->c_sent == 0) {
+ /*
+ * current profile is _not_ already cached at other end of
+ * IPC, so send current profile
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PROFILE)
+ fprintf(stderr, "myFetch: calling __pmSendProfile, context: %d\n", ctx);
+#endif
+ if ((n = __pmSendProfile(ctxp->c_pmcd->pc_fd, FROM_ANON, ctx, ctxp->c_instprof)) >= 0)
+ ctxp->c_sent = 1;
+ }
+
+ if (n >= 0) {
+ int newcnt;
+ pmID *newlist = NULL;
+ int have_dm;
+
+ /* for derived metrics, may need to rewrite the pmidlist */
+ have_dm = newcnt = __pmPrepareFetch(ctxp, numpmid, pmidlist, &newlist);
+ if (newcnt > numpmid) {
+ /* replace args passed into myFetch */
+ numpmid = newcnt;
+ pmidlist = newlist;
+ }
+
+ n = __pmSendFetch(ctxp->c_pmcd->pc_fd, FROM_ANON, ctx, &ctxp->c_origin, numpmid, pmidlist);
+ if (n >= 0){
+ int changed = 0;
+ do {
+ n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ /*
+ * expect PDU_RESULT or
+ * PDU_ERROR(changed > 0)+PDU_RESULT or
+ * PDU_ERROR(real error < 0 from PMCD) or
+ * 0 (end of file)
+ * < 0 (local error or IPC problem)
+ * other (bogus PDU)
+ */
+ if (n == PDU_RESULT) {
+ /*
+ * Success with a pmResult in a pdubuf.
+ *
+ * Need to process derived metrics, if any.
+ * This is ugly, we need to decode the pdubuf, rebuild
+ * the pmResult and encode back into a pdubuf ... the
+ * fastpath of not doing all of this needs to be
+ * preserved in the common case where derived metrics
+ * are not being logged.
+ */
+ if (have_dm) {
+ pmResult *result;
+ __pmPDU *npb;
+ int sts;
+ if ((sts = __pmDecodeResult(pb, &result)) < 0) {
+ n = sts;
+ }
+ else {
+ __pmFinishResult(ctxp, sts, &result);
+ if ((sts = __pmEncodeResult(ctxp->c_pmcd->pc_fd, result, &npb)) < 0)
+ n = sts;
+ else {
+ /* using PDU with derived metrics */
+ __pmUnpinPDUBuf(pb);
+ *pdup = npb;
+ }
+ }
+ }
+ else
+ *pdup = pb;
+ }
+ else if (n == PDU_ERROR) {
+ __pmDecodeError(pb, &n);
+ if (n > 0) {
+ /* PMCD state change protocol */
+ changed = n;
+ n = 0;
+ }
+ else {
+ fprintf(stderr, "myFetch: ERROR PDU: %s\n", pmErrStr(n));
+ disconnect(PM_ERR_IPC);
+ }
+ __pmUnpinPDUBuf(pb);
+ }
+ else if (n == 0) {
+ fprintf(stderr, "myFetch: End of File: PMCD exited?\n");
+ disconnect(PM_ERR_IPC);
+ }
+ else if (n < 0) {
+ fprintf(stderr, "myFetch: __pmGetPDU: Error: %s\n", pmErrStr(n));
+ disconnect(PM_ERR_IPC);
+ }
+ else {
+ fprintf(stderr, "myFetch: Unexpected %s PDU from PMCD\n", __pmPDUTypeStr(n));
+ disconnect(PM_ERR_IPC);
+ __pmUnpinPDUBuf(pb);
+ }
+ } while (n == 0);
+
+ if (changed & PMCD_ADD_AGENT) {
+ /*
+ * PMCD_DROP_AGENT does not matter, no values are returned.
+ * Trying to restart (PMCD_RESTART_AGENT) is less interesting
+ * than when we actually start (PMCD_ADD_AGENT) ... the latter
+ * is also set when a successful restart occurs, but more
+ * to the point the sequence Install-Remove-Install does
+ * not involve a restart ... it is the second Install that
+ * generates the second PMCD_ADD_AGENT that we need to be
+ * particularly sensitive to, as this may reset counter
+ * metrics ...
+ */
+ int sts;
+ if ((sts = putmark()) < 0) {
+ fprintf(stderr, "putmark: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ }
+ }
+ if (newlist != NULL)
+ free(newlist);
+ }
+
+ if (n < 0 && ctxp->c_pmcd->pc_fd != -1) {
+ disconnect(n);
+ }
+
+ PM_UNLOCK(ctxp->c_lock);
+ return n;
+}
diff --git a/src/pmlogger/src/gram.y b/src/pmlogger/src/gram.y
new file mode 100644
index 0000000..9a28d8a
--- /dev/null
+++ b/src/pmlogger/src/gram.y
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * There is a shift/reduced conflict reported by yacc when it cannot
+ * decide whatever it should take 'optinst' route or 'access' one in
+ * the following sutiation:
+ *
+ * log on once foo [access] all
+ *
+ * This conflict considered to be benign, since yacc takes 'the right' option
+ * if optinst is supplied. To work around the issue of access been treated
+ * as an option, enclose the list of metrics in the curly braces, i.e.
+ *
+ * log on once {foo} [access] all
+ */
+
+%{
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int mystate = GLOBAL; /* config file parser state */
+
+__pmHashCtl pm_hash;
+task_t *tasklist;
+
+static task_t *tp;
+static int numinst;
+static int *intlist;
+static char **extlist;
+static int state; /* logging state, current block */
+static char *metricName; /* current metric, current block */
+
+typedef struct _hl {
+ struct _hl *hl_next;
+ char *hl_name;
+ int hl_line;
+} hostlist_t;
+
+static hostlist_t *hl_root;
+static hostlist_t *hl_last;
+static hostlist_t *hlp;
+static hostlist_t *prevhlp;
+static int opmask; /* operations mask */
+static int specmask; /* specifications mask */
+static int allow; /* host allow/disallow state */
+
+static int lookup_metric_name(const char *);
+static void activate_new_metric(const char *);
+static void activate_cached_metric(const char *, int);
+static task_t *findtask(int, struct timeval *);
+
+%}
+%union {
+ long lval;
+ char * str;
+}
+
+%expect 1
+
+%term LSQB
+ RSQB
+ COMMA
+ LBRACE
+ RBRACE
+ COLON
+ SEMICOLON
+
+ LOG
+ MANDATORY ADVISORY
+ ON OFF MAYBE
+ EVERY ONCE DEFAULT
+ MSEC SECOND MINUTE HOUR
+
+ ACCESS ENQUIRE ALLOW DISALLOW ALL EXCEPT
+
+%token<str> NAME STRING IPSPEC HOSTNAME URL
+%token<lval> NUMBER
+
+%type<lval> frequency timeunits action
+%type<str> hostspec
+%%
+
+config : specopt accessopt
+ ;
+
+specopt : spec
+ | /* nothing */
+ ;
+
+spec : stmt
+ | spec stmt
+ ;
+
+stmt : dowhat somemetrics
+ {
+ mystate = GLOBAL;
+ if (tp->t_numvalid)
+ linkback(tp);
+ state = 0;
+ }
+ ;
+
+dowhat : logopt action
+ {
+ struct timeval delta;
+
+ delta.tv_sec = $2 / 1000;
+ delta.tv_usec = 1000 * ($2 % 1000);
+
+ /*
+ * Search for an existing task for this state/interval;
+ * only allocate and setup a new task if none exists.
+ */
+ if ((tp = findtask(state, &delta)) == NULL) {
+ if ((tp = (task_t *)calloc(1, sizeof(task_t))) == NULL) {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+ } else {
+ tp->t_delta = delta;
+ tp->t_state = state;
+ tp->t_next = tasklist;
+ tasklist = tp;
+ }
+ }
+ state = 0;
+ }
+ ;
+
+logopt : LOG
+ | /* nothing */
+ ;
+
+action : cntrl ON frequency
+ {
+ char emess[256];
+ if ($3 < 0) {
+ snprintf(emess, sizeof(emess),
+ "Logging delta (%ld msec) must be positive",$3);
+ yyerror(emess);
+ }
+ else if ($3 > PMLC_MAX_DELTA) {
+ snprintf(emess, sizeof(emess),
+ "Logging delta (%ld msec) cannot be bigger "
+ "than %d msec", $3, PMLC_MAX_DELTA);
+ yyerror(emess);
+ }
+
+ PMLC_SET_ON(state, 1);
+ $$ = $3;
+ }
+ | cntrl OFF { PMLC_SET_ON(state, 0);$$ = 0;}
+ | MANDATORY MAYBE
+ {
+ PMLC_SET_MAND(state, 0);
+ PMLC_SET_ON(state, 0);
+ PMLC_SET_MAYBE(state, 1);
+ $$ = 0;
+ }
+ ;
+
+cntrl : MANDATORY { PMLC_SET_MAND(state, 1); }
+ | ADVISORY { PMLC_SET_MAND(state, 0); }
+ | /*nothing == advisory*/ { PMLC_SET_MAND(state, 0); }
+ ;
+
+frequency : everyopt NUMBER timeunits { $$ = $2*$3; }
+ | ONCE { $$ = 0; }
+ | DEFAULT
+ {
+ extern struct timeval delta; /* default logging interval */
+ $$ = delta.tv_sec*1000 + delta.tv_usec/1000;
+ }
+ ;
+
+everyopt : EVERY
+ | /* nothing */
+ ;
+
+timeunits : MSEC { $$ = 1; }
+ | SECOND { $$ = 1000; }
+ | MINUTE { $$ = 60000; }
+ | HOUR { $$ = 3600000; }
+ ;
+
+somemetrics : LBRACE { mystate = INSPEC; } metriclist RBRACE
+ | metricspec
+ ;
+
+metriclist : metricspec
+ | metriclist metricspec
+ | metriclist COMMA metricspec
+ ;
+
+metricspec : NAME
+ {
+ if ((metricName = strdup($1)) == NULL) {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+ }
+ }
+ optinst
+ {
+ int index, sts;
+
+ /*
+ * search names for previously seen metrics for this task
+ * (note that name may be non-terminal in the PMNS here);
+ * if already found in this task, skip namespace PDUs.
+ */
+ if ((index = lookup_metric_name(metricName)) < 0) {
+ if ((sts = pmTraversePMNS(metricName, activate_new_metric)) < 0 ) {
+ char emess[256];
+ snprintf(emess, sizeof(emess),
+ "Problem with lookup for metric \"%s\" "
+ "... logging not activated", metricName);
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ }
+ }
+ else { /* name is cached already, handle instances */
+ activate_cached_metric(metricName, index);
+ }
+ freeinst(&numinst, intlist, extlist);
+ free(metricName);
+ }
+ ;
+
+optinst : LSQB instancelist RSQB
+ | /* nothing */
+ ;
+
+instancelist : instance
+ | instance instancelist
+ | instance COMMA instancelist
+ ;
+
+instance : NAME { buildinst(&numinst, &intlist, &extlist, -1, $1); }
+ | NUMBER { buildinst(&numinst, &intlist, &extlist, $1, NULL); }
+ | STRING { buildinst(&numinst, &intlist, &extlist, -1, $1); }
+ ;
+
+accessopt : LSQB ACCESS RSQB ctllist
+ | /* nothing */
+ ;
+
+ctllist : ctl
+ | ctl ctllist
+ ;
+
+ctl : allow hostlist COLON operation SEMICOLON
+ {
+ prevhlp = NULL;
+ for (hlp = hl_root; hlp != NULL; hlp = hlp->hl_next) {
+ int sts;
+
+ if (prevhlp != NULL) {
+ free(prevhlp->hl_name);
+ free(prevhlp);
+ }
+ sts = __pmAccAddHost(hlp->hl_name, specmask,
+ opmask, 0);
+ if (sts < 0) {
+ fprintf(stderr, "error was on line %d\n",
+ hlp->hl_line);
+ YYABORT;
+ }
+ prevhlp = hlp;
+ }
+ if (prevhlp != NULL) {
+ free(prevhlp->hl_name);
+ free(prevhlp);
+ }
+ opmask = 0;
+ specmask = 0;
+ hl_root = hl_last = NULL;
+ }
+ ;
+
+allow : ALLOW { allow = 1; }
+ | DISALLOW { allow = 0; }
+ ;
+
+hostlist : host
+ | host COMMA hostlist
+ ;
+
+host : hostspec
+ {
+ size_t sz = sizeof(hostlist_t);
+
+ hlp = (hostlist_t *)malloc(sz);
+ if (hlp == NULL) {
+ __pmNoMem("adding new host", sz, PM_FATAL_ERR);
+ }
+ if (hl_last != NULL) {
+ hl_last->hl_next = hlp;
+ hl_last = hlp;
+ }
+ else
+ hl_root = hl_last = hlp;
+ hlp->hl_next = NULL;
+ hlp->hl_name = strdup($1);
+ hlp->hl_line = lineno;
+ }
+ ;
+
+hostspec : IPSPEC
+ | URL
+ | HOSTNAME
+ | NAME
+ ;
+
+operation : operlist
+ {
+ specmask = opmask;
+ if (allow)
+ opmask = ~opmask;
+ }
+ | ALL
+ {
+ specmask = PM_OP_ALL;
+ if (allow)
+ opmask = PM_OP_NONE;
+ else
+ opmask = PM_OP_ALL;
+ }
+ | ALL EXCEPT operlist
+ {
+ specmask = PM_OP_ALL;
+ if (!allow)
+ opmask = ~opmask;
+ }
+ ;
+
+operlist : op
+ | op COMMA operlist
+ ;
+
+op : ADVISORY { opmask |= PM_OP_LOG_ADV; }
+ | MANDATORY { opmask |= PM_OP_LOG_MAND; }
+ | ENQUIRE { opmask |= PM_OP_LOG_ENQ; }
+ ;
+
+%%
+
+/*
+ * Search the cache for previously seen metrics for active task.
+ * Returns -1 if not found, else an index into tp->t_namelist.
+ */
+static int
+lookup_metric_name(const char *name)
+{
+ int j;
+
+ for (j = 0; j < tp->t_numpmid; j++)
+ if (strcmp(tp->t_namelist[j], name) == 0)
+ return j;
+ return -1;
+}
+
+/*
+ * Assumed calling context ...
+ * tp the correct task for the requested metric
+ * numinst number of instances associated with this request
+ * extlist[] external instance names if numinst > 0
+ * intlist[] internal instance identifier if numinst > 0 and
+ * corresponding extlist[] entry is NULL
+ */
+static void
+activate_cached_metric(const char *name, int index)
+{
+ int sts = 0;
+ int inst;
+ int i;
+ int j;
+ int skip = 0;
+ pmID pmid;
+ pmDesc *dp;
+ optreq_t *rqp;
+ char emess[1024];
+
+ /*
+ * need new malloc'd pmDesc, even if metric found in cache, as
+ * the fetchctl keeps its own (non-realloc-movable!) pointer.
+ */
+ dp = (pmDesc *)malloc(sizeof(pmDesc));
+ if (dp == NULL)
+ goto nomem;
+
+ if (index < 0) {
+ if ((sts = pmLookupName(1, (char **)&name, &pmid)) < 0 || pmid == PM_ID_NULL) {
+ snprintf(emess, sizeof(emess),
+ "Metric \"%s\" is unknown ... not logged", name);
+ goto snarf;
+ }
+ if ((sts = pmLookupDesc(pmid, dp)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Description unavailable for metric \"%s\" ... not logged",
+ name);
+ goto snarf;
+ }
+ tp->t_numpmid++;
+ tp->t_namelist = (char **)realloc(tp->t_namelist, tp->t_numpmid * sizeof(char *));
+ if (tp->t_namelist == NULL)
+ goto nomem;
+ if ((tp->t_namelist[tp->t_numpmid-1] = strdup(name)) == NULL)
+ goto nomem;
+ tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, tp->t_numpmid * sizeof(pmID));
+ if (tp->t_pmidlist == NULL)
+ goto nomem;
+ tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, tp->t_numpmid * sizeof(pmDesc));
+ if (tp->t_desclist == NULL)
+ goto nomem;
+ tp->t_pmidlist[tp->t_numpmid-1] = pmid;
+ tp->t_desclist[tp->t_numpmid-1] = *dp; /* struct assignment */
+ }
+ else {
+ *dp = tp->t_desclist[index];
+ pmid = tp->t_pmidlist[index];
+ }
+
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL)
+ goto nomem;
+ rqp->r_desc = dp;
+ rqp->r_numinst = numinst;
+
+ if (numinst) {
+ /*
+ * malloc here, and keep ... gets buried in optFetch data structures
+ */
+ rqp->r_instlist = (int *)malloc(numinst * sizeof(rqp->r_instlist[0]));
+ if (rqp->r_instlist == NULL)
+ goto nomem;
+ j = 0;
+ for (i = 0; i < numinst; i++) {
+ if (extlist[i] != NULL) {
+ sts = pmLookupInDom(dp->indom, extlist[i]);
+ if (sts < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%s\" is not defined for the metric \"%s\"",
+ extlist[i], name);
+ yywarn(emess);
+ rqp->r_numinst--;
+ continue;
+ }
+ inst = sts;
+ }
+ else {
+ char *p;
+ sts = pmNameInDom(dp->indom, intlist[i], &p);
+ if (sts < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%d\" is not defined for the metric \"%s\"",
+ intlist[i], name);
+ yywarn(emess);
+ rqp->r_numinst--;
+ continue;
+ }
+ free(p);
+ inst = intlist[i];
+ }
+ if ((sts = chk_one(tp, pmid, inst)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Incompatible request for metric \"%s\" "
+ "and instance \"%s\"", name, extlist[i]);
+ yywarn(emess);
+ fprintf(stderr, "%s\n", chk_emess[-sts]);
+ rqp->r_numinst--;
+ }
+ else if (sts == 0)
+ rqp->r_instlist[j++] = inst;
+ else /* already have this instance */
+ skip = 1;
+ }
+ if (rqp->r_numinst == 0)
+ skip = 1;
+ }
+ else {
+ if ((sts = chk_all(tp, pmid)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Incompatible request for metric \"%s\"", name);
+ yywarn(emess);
+
+ skip = 1;
+ }
+ }
+
+ if (!skip) {
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0) {
+ snprintf(emess, sizeof(emess), "__pmHashAdd failed "
+ "for metric \"%s\" ... logging not activated", name);
+ goto snarf;
+ }
+ tp->t_numvalid++;
+ }
+ else {
+ free(dp);
+ free(rqp);
+ }
+ return;
+
+nomem:
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+
+snarf:
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ if (dp != NULL)
+ free(dp);
+ return;
+}
+
+static void
+activate_new_metric(const char *name)
+{
+ activate_cached_metric(name, lookup_metric_name(name));
+}
+
+/*
+ * Given a logging state and an interval, return a matching task
+ * or NULL if none exists for that value pair.
+ */
+task_t *
+findtask(int state, struct timeval *delta)
+{
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (state == tp->t_state &&
+ delta->tv_sec == tp->t_delta.tv_sec &&
+ delta->tv_usec == tp->t_delta.tv_usec)
+ break;
+ }
+ return tp;
+}
+
+/*
+ * Complete the delayed processing of task elements, which can only
+ * be done once all configuration file parsing is complete.
+ */
+void
+yyend(void)
+{
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (tp->t_numvalid == 0)
+ continue;
+ PMLC_SET_MAYBE(tp->t_state, 0); /* clear req */
+ if (PMLC_GET_ON(tp->t_state))
+ tp->t_afid = __pmAFregister(&tp->t_delta, (void *)tp, log_callback);
+ }
+}
diff --git a/src/pmlogger/src/lex.l b/src/pmlogger/src/lex.l
new file mode 100644
index 0000000..f57b3ca
--- /dev/null
+++ b/src/pmlogger/src/lex.l
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int lineno=1;
+
+#include "gram.tab.h"
+
+static int
+ctx(int type)
+{
+ extern int mystate;
+ if (mystate == GLOBAL)
+ return type;
+ else {
+ yylval.str = yytext;
+ return NAME;
+ }
+}
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#else
+#undef input
+#define input() ((yytchar=fgetc(yyin)) == EOF ? 0 : yytchar)
+#undef unput
+#define unput(c) {yytchar=(c); ungetc(yytchar, yyin);}
+#endif /* FLEX_SCANNER */
+%}
+
+%%
+"[" { return LSQB; }
+"]" { return RSQB; }
+"," { return COMMA; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+":" { return COLON; }
+";" { return SEMICOLON; }
+
+milliseconds? { return ctx(MSEC); }
+mandatory { return ctx(MANDATORY); }
+advisory { return ctx(ADVISORY); }
+disallow { return ctx(DISALLOW); }
+minutes? { return ctx(MINUTE); }
+seconds? { return ctx(SECOND); }
+default { return ctx(DEFAULT); }
+enquire { return ctx(ENQUIRE); }
+access { return ctx(ACCESS); }
+except { return ctx(EXCEPT); }
+allow { return ctx(ALLOW); }
+every { return ctx(EVERY); }
+maybe { return ctx(MAYBE); }
+hours? { return ctx(HOUR); }
+msecs? { return ctx(MSEC); }
+mins? { return ctx(MINUTE); }
+once { return ctx(ONCE); }
+secs? { return ctx(SECOND); }
+log { return ctx(LOG); }
+all { return ctx(ALL); }
+off { return ctx(OFF); }
+on { return ctx(ON); }
+
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = yytext; return NAME; }
+
+[A-Za-z][A-Za-z0-9_.-]* { yylval.str = yytext; return HOSTNAME; }
+
+\"[^\"\n][^\"\n]*\" { /* strip quotes before returing */
+ yytext[strlen(yytext)-1] = '\0';
+ yylval.str = yytext+1;
+ return STRING;
+ }
+
+[0-9]+ { yylval.lval = atol(yytext); return NUMBER; }
+
+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.[0-9]+\.[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+\.\* { yylval.str = yytext; return IPSPEC; }
+
+([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+:: { yylval.str = yytext; return IPSPEC; }
+::([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+:([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+: { yylval.str = yytext; return IPSPEC; }
+
+([A-Fa-f0-9]+:)+\* { yylval.str = yytext; return IPSPEC; }
+::([A-Fa-f0-9]+:)*\* { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+:([A-Fa-f0-9]+:)*\* { yylval.str = yytext; return IPSPEC; }
+:\* { yylval.str = yytext; return IPSPEC; }
+
+\* { yylval.str = yytext; return IPSPEC; }
+
+unix:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+unix:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+
+\#.* { }
+
+[ \t\r]+ { }
+
+\n { lineno++; }
+
+. {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "Unexpected character '%c'", yytext[0]);
+ yyerror(emess);
+ }
+%%
+
+int
+yywrap (void)
+{
+ return 1;
+}
diff --git a/src/pmlogger/src/logger.h b/src/pmlogger/src/logger.h
new file mode 100644
index 0000000..f57b18b
--- /dev/null
+++ b/src/pmlogger/src/logger.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LOGGER_H
+#define _LOGGER_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+
+/*
+ * a task is a bundle of fetches to be done together - it
+ * originally corresponded one-to-one with a configuration
+ * file curly-brace-enclosed block, but no longer does.
+ */
+typedef struct task_s {
+ struct task_s *t_next;
+ struct timeval t_delta;
+ int t_state; /* logging state */
+ int t_numpmid;
+ int t_numvalid;
+ pmID *t_pmidlist;
+ char **t_namelist;
+ pmDesc *t_desclist;
+ fetchctl_t *t_fetch;
+ int t_afid;
+ int t_size;
+} task_t;
+
+extern task_t *tasklist; /* master list of tasks */
+extern __pmLogCtl logctl; /* global log control */
+
+/* config file parser states */
+#define GLOBAL 0
+#define INSPEC 1
+
+/* generic error messages */
+extern char *chk_emess[];
+extern void die(char *, int);
+
+/*
+ * hash control for per-metric (really per-metric per-log specification)
+ * -- used to establish and maintain state for ControlLog operations
+ */
+extern __pmHashCtl pm_hash;
+
+/* another hash list used for maintaining information about all metrics and
+ * instances that have EVER appeared in the log as opposed to just those
+ * currently being logged. It's a history list.
+ */
+extern __pmHashCtl hist_hash;
+
+typedef struct {
+ int ih_inst;
+ int ih_flags;
+} insthist_t;
+
+typedef struct {
+ pmID ph_pmid;
+ pmInDom ph_indom;
+ int ph_numinst;
+ insthist_t *ph_instlist;
+} pmidhist_t;
+
+/* access control goo */
+#define PM_OP_LOG_ADV 0x1
+#define PM_OP_LOG_MAND 0x2
+#define PM_OP_LOG_ENQ 0x4
+
+#define PM_OP_NONE 0x0
+#define PM_OP_ALL 0x7
+
+#define PMLC_SET_MAYBE(val, flag) \
+ val = ((val) & ~0x10) | (((flag) & 0x1) << 4)
+#define PMLC_GET_MAYBE(val) \
+ (((val) & 0x10) >> 4)
+
+/* volume switch types */
+#define VOL_SW_SIGHUP 0
+#define VOL_SW_PMLC 1
+#define VOL_SW_COUNTER 2
+#define VOL_SW_BYTES 3
+#define VOL_SW_TIME 4
+#define VOL_SW_MAX 5
+
+/* initial time of day from remote PMCD */
+extern struct timeval epoch;
+
+/* offset to start of last written pmResult */
+extern int last_log_offset;
+
+/* yylex() gets input from here ... */
+extern FILE *fconfig;
+extern FILE *yyin;
+extern char *configfile;
+extern int lineno;
+
+extern int myFetch(int, pmID *, __pmPDU **);
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern int yylex(void);
+extern int yyparse(void);
+extern void yyend(void);
+extern void buildinst(int *, int **, char ***, int, char *);
+extern void freeinst(int *, int *, char **);
+extern void linkback(task_t *);
+extern optreq_t *findoptreq(pmID, int);
+extern void log_callback(int, void *);
+extern int chk_one(task_t *, pmID, int);
+extern int chk_all(task_t *, pmID);
+extern int newvolume(int);
+extern void disconnect(int);
+#if CAN_RECONNECT
+extern int reconnect(void);
+#endif
+extern int do_preamble(void);
+extern void run_done(int,char *);
+extern __pmPDU *rewrite_pdu(__pmPDU *, int);
+extern int putmark(void);
+extern void dumpit(void);
+
+#include <sys/param.h>
+extern char pmlc_host[];
+
+#define LOG_DELTA_ONCE -1
+#define LOG_DELTA_DEFAULT -2
+
+/* command line parameters */
+extern char *archBase; /* base name for log files */
+extern char *pmcd_host; /* collecting from PMCD on this host */
+extern char *pmcd_host_conn; /* ... and this is how we connected to it */
+extern int primary; /* Non-zero for primary logger */
+extern int rflag;
+extern struct timeval delta; /* default logging interval */
+extern int ctlport; /* pmlogger control port number */
+extern char *note; /* note for port map file */
+
+/* pmlc support */
+extern void init_ports(void);
+extern int control_req(int ctlfd);
+extern int client_req(void);
+extern __pmHashCtl hist_hash;
+extern unsigned int denyops; /* for access control (ops not allowed) */
+extern struct timeval last_stamp;
+extern int clientfd;
+#define CFD_INET 0
+#define CFD_IPV6 1
+#define CFD_UNIX 2
+#define CFD_NUM 3
+extern int ctlfds[CFD_NUM];
+extern int exit_samples;
+extern int vol_switch_samples;
+extern __int64_t vol_switch_bytes;
+extern int vol_switch_flag;
+extern int vol_samples_counter;
+extern int archive_version;
+extern int parse_done;
+extern __int64_t exit_bytes;
+extern __int64_t vol_bytes;
+extern int exit_code;
+
+/* event record handling */
+extern int do_events(pmValueSet *);
+
+/* QA testing and error injection support ... see do_request() */
+extern int qa_case;
+#define QA_OFF 100
+#define QA_SLEEPY 101
+
+#endif /* _LOGGER_H */
diff --git a/src/pmlogger/src/pmlogger.c b/src/pmlogger/src/pmlogger.c
new file mode 100644
index 0000000..2a332e3
--- /dev/null
+++ b/src/pmlogger/src/pmlogger.c
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "logger.h"
+
+char *configfile;
+__pmLogCtl logctl;
+int exit_samples = -1; /* number of samples 'til exit */
+__int64_t exit_bytes = -1; /* number of bytes 'til exit */
+__int64_t vol_bytes; /* total in earlier volumes */
+struct timeval exit_time; /* time interval 'til exit */
+int vol_switch_samples = -1; /* number of samples 'til vol switch */
+__int64_t vol_switch_bytes = -1; /* number of bytes 'til vol switch */
+struct timeval vol_switch_time; /* time interval 'til vol switch */
+int vol_samples_counter; /* Counts samples - reset for new vol*/
+int vol_switch_afid = -1; /* afid of event for vol switch */
+int vol_switch_flag; /* sighup received - switch vol now */
+int parse_done;
+int primary; /* Non-zero for primary pmlogger */
+char *archBase; /* base name for log files */
+char *pmcd_host;
+char *pmcd_host_conn;
+struct timeval epoch;
+int archive_version = PM_LOG_VERS02; /* Type of archive to create */
+int linger; /* linger with no tasks/events */
+int rflag; /* report sizes */
+struct timeval delta = { 60, 0 }; /* default logging interval */
+int exit_code; /* code to pass to exit (zero/signum) */
+int qa_case; /* QA error injection state */
+char *note; /* note for port map file */
+
+static int pmcdfd; /* comms to pmcd */
+static __pmFdSet fds; /* file descriptors mask for select */
+static int numfds; /* number of file descriptors in mask */
+
+static int rsc_fd = -1; /* recording session control see -x */
+static int rsc_replay;
+static time_t rsc_start;
+static char *rsc_prog = "<unknown>";
+static char *folio_name = "<unknown>";
+static char *dialog_title = "PCP Archive Recording Session";
+
+void
+run_done(int sts, char *msg)
+{
+#ifdef PCP_DEBUG
+ if (msg != NULL)
+ fprintf(stderr, "pmlogger: %s, exiting\n", msg);
+ else
+ fprintf(stderr, "pmlogger: End of run time, exiting\n");
+#endif
+
+ /*
+ * write the last last temportal index entry with the time stamp
+ * of the last pmResult and the seek pointer set to the offset
+ * _before_ the last log record
+ */
+ if (last_stamp.tv_sec != 0) {
+ __pmTimeval tmp;
+ tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
+ tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
+ fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
+ __pmLogPutIndex(&logctl, &tmp);
+ }
+
+ exit(sts);
+}
+
+static void
+run_done_callback(int i, void *j)
+{
+ run_done(0, NULL);
+}
+
+static void
+vol_switch_callback(int i, void *j)
+{
+ newvolume(VOL_SW_TIME);
+}
+
+static int
+maxfd(void)
+{
+ int i;
+ int max = 0;
+
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] > max)
+ max = ctlfds[i];
+ }
+ if (clientfd > max)
+ max = clientfd;
+ if (pmcdfd > max)
+ max = pmcdfd;
+ if (rsc_fd > max)
+ max = rsc_fd;
+ return max;
+}
+
+/*
+ * tolower_str - convert a string to all lowercase
+ */
+static void
+tolower_str(char *str)
+{
+ char *s = str;
+
+ while (*s) {
+ *s = tolower((int)*s);
+ s++;
+ }
+}
+
+/*
+ * ParseSize - parse a size argument given in a command option
+ *
+ * The size can be in one of the following forms:
+ * "40" = sample counter of 40
+ * "40b" = byte size of 40
+ * "40Kb" = byte size of 40*1024 bytes = 40 kilobytes
+ * "40Mb" = byte size of 40*1024*1024 bytes = 40 megabytes
+ * time-format = time delta in seconds
+ *
+ */
+static int
+ParseSize(char *size_arg, int *sample_counter, __int64_t *byte_size,
+ struct timeval *time_delta)
+{
+ long x = 0; /* the size number */
+ char *ptr = NULL;
+ char *interval_err;
+
+ *sample_counter = -1;
+ *byte_size = -1;
+ time_delta->tv_sec = -1;
+ time_delta->tv_usec = -1;
+
+ x = strtol(size_arg, &ptr, 10);
+
+ /* must be positive */
+ if (x <= 0)
+ return -1;
+
+ if (*ptr == '\0') {
+ /* we have consumed entire string as a long */
+ /* => we have a sample counter */
+ *sample_counter = x;
+ return 1;
+ }
+
+ /* we have a number followed by something else */
+ if (ptr != size_arg) {
+ int len;
+
+ tolower_str(ptr);
+
+ /* chomp off plurals */
+ len = strlen(ptr);
+ if (ptr[len-1] == 's')
+ ptr[len-1] = '\0';
+
+ /* if bytes */
+ if (strcmp(ptr, "b") == 0 ||
+ strcmp(ptr, "byte") == 0) {
+ *byte_size = x;
+ return 1;
+ }
+
+ /* if kilobytes */
+ if (strcmp(ptr, "k") == 0 || strcmp(ptr, "kb") == 0 ||
+ strcmp(ptr, "kbyte") == 0 || strcmp(ptr, "kilobyte") == 0) {
+ *byte_size = x*1024;
+ return 1;
+ }
+
+ /* if megabytes */
+ if (strcmp(ptr, "m") == 0 ||
+ strcmp(ptr, "mb") == 0 ||
+ strcmp(ptr, "mbyte") == 0 ||
+ strcmp(ptr, "megabyte") == 0) {
+ *byte_size = x*1024*1024;
+ return 1;
+ }
+
+ /* if gigabytes */
+ if (strcmp(ptr, "g") == 0 ||
+ strcmp(ptr, "gb") == 0 ||
+ strcmp(ptr, "gbyte") == 0 ||
+ strcmp(ptr, "gigabyte") == 0) {
+ *byte_size = ((__int64_t)x)*1024*1024*1024;
+ return 1;
+ }
+ }
+
+ /* Doesn't fit pattern above, try a time interval */
+ if (pmParseInterval(size_arg, time_delta, &interval_err) >= 0)
+ return 1;
+ /* error message not used here */
+ free(interval_err);
+
+ /* Doesn't match anything, return an error */
+ return -1;
+}
+
+/* time manipulation */
+static void
+tsub(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+ if (a->tv_sec < 0) {
+ /* clip negative values at zero */
+ a->tv_sec = 0;
+ a->tv_usec = 0;
+ }
+}
+
+static char *
+do_size(double d)
+{
+ static char nbuf[100];
+
+ if (d < 10 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%ld bytes", (long)d);
+ else if (d < 10.0 * 1024 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%.1f Kbytes", d/1024);
+ else if (d < 10.0 * 1024 * 1024 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%.1f Mbytes", d/(1024 * 1024));
+ else
+ snprintf(nbuf, sizeof(nbuf), "%ld Mbytes", (long)d/(1024 * 1024));
+
+ return nbuf;
+}
+
+/*
+ * add text identified by p to the malloc buffer at bp[0] ... bp[nchar -1]
+ * return the length of the result or -1 for an error
+ */
+static int
+add_msg(char **bp, int nchar, char *p)
+{
+ int add_len;
+
+ if (nchar < 0 || p == NULL)
+ return nchar;
+
+ add_len = (int)strlen(p);
+ if (nchar == 0)
+ add_len++;
+ if ((*bp = realloc(*bp, nchar+add_len)) == NULL)
+ return -1;
+ if (nchar == 0)
+ strcpy(*bp, p);
+ else
+ strcat(&(*bp)[nchar-1], p);
+
+ return nchar+add_len;
+}
+
+
+/*
+ * generate dialog/message when launching application wishes to break
+ * its association with pmlogger
+ *
+ * cmd is one of the following:
+ * D detach pmlogger and let it run forever
+ * Q terminate pmlogger
+ * ? display status
+ * X fatal error or application exit ... user must decide
+ * to detach or quit
+ */
+void
+do_dialog(char cmd)
+{
+ FILE *msgf = NULL;
+ time_t now;
+ static char lbuf[100+MAXPATHLEN];
+ double archsize;
+ char *q;
+ char *p = NULL;
+ int nchar;
+ char *msg;
+#if HAVE_MKSTEMP
+ char tmp[MAXPATHLEN];
+#endif
+
+ time(&now);
+ now -= rsc_start;
+ if (now == 0)
+ /* hack is close enough! */
+ now = 1;
+
+ archsize = vol_bytes + ftell(logctl.l_mfp);
+
+ nchar = add_msg(&p, 0, "");
+ p[0] = '\0';
+
+ snprintf(lbuf, sizeof(lbuf), "PCP recording for the archive folio \"%s\" and the host", folio_name);
+ nchar = add_msg(&p, nchar, lbuf);
+ snprintf(lbuf, sizeof(lbuf), " \"%s\" has been in progress for %ld %s",
+ pmcd_host,
+ now < 240 ? now : now/60, now < 240 ? "seconds" : "minutes");
+ nchar = add_msg(&p, nchar, lbuf);
+ nchar = add_msg(&p, nchar, " and in that time the pmlogger process has created an");
+ nchar = add_msg(&p, nchar, " archive of ");
+ q = do_size(archsize);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, ".");
+ if (rsc_replay) {
+ nchar = add_msg(&p, nchar, "\n\nThis archive may be replayed with the following command:\n");
+ snprintf(lbuf, sizeof(lbuf), " $ pmafm %s replay", folio_name);
+ nchar = add_msg(&p, nchar, lbuf);
+ }
+
+ if (cmd == 'D') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has asked pmlogger");
+ nchar = add_msg(&p, nchar, " to continue independently and the PCP archive will grow at");
+ nchar = add_msg(&p, nchar, " the rate of ");
+ q = do_size((archsize * 3600) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per hour or ");
+ q = do_size((archsize * 3600 * 24) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per day.");
+ }
+
+ if (cmd == 'X') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has exited and you");
+ nchar = add_msg(&p, nchar, " must decide if the PCP recording session should be terminated");
+ nchar = add_msg(&p, nchar, " or continued. If recording is continued the PCP archive will");
+ nchar = add_msg(&p, nchar, " grow at the rate of ");
+ q = do_size((archsize * 3600) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per hour or ");
+ q = do_size((archsize * 3600 * 24) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per day.");
+ }
+
+ if (cmd == 'Q') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has terminated");
+ nchar = add_msg(&p, nchar, " this PCP recording session.\n");
+ }
+
+ if (cmd != 'Q') {
+ nchar = add_msg(&p, nchar, "\n\nAt any time this pmlogger process may be terminated with the");
+ nchar = add_msg(&p, nchar, " following command:\n");
+ snprintf(lbuf, sizeof(lbuf), " $ pmsignal -s TERM %" FMT_PID "\n", getpid());
+ nchar = add_msg(&p, nchar, lbuf);
+ }
+
+ if (cmd == 'X')
+ nchar = add_msg(&p, nchar, "\n\nTerminate this PCP recording session now?");
+
+ if (nchar > 0) {
+ char * xconfirm = __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG"));
+ int fd = -1;
+
+#if HAVE_MKSTEMP
+ snprintf(tmp, sizeof(tmp), "%s%cmsgXXXXXX", pmGetConfig("PCP_TMPFILE_DIR"), __pmPathSeparator());
+ msg = tmp;
+ fd = mkstemp(tmp);
+#else
+ if ((msg = tmpnam(NULL)) != NULL)
+ fd = open(msg, O_WRONLY|O_CREAT|O_EXCL, 0600);
+#endif
+ if (fd >= 0)
+ msgf = fdopen(fd, "w");
+ if (msgf == NULL) {
+ fprintf(stderr, "\nError: failed create temporary message file for recording session dialog\n");
+ fprintf(stderr, "Reason? %s\n", osstrerror());
+ if (fd != -1)
+ close(fd);
+ goto failed;
+ }
+ fputs(p, msgf);
+ fclose(msgf);
+ msgf = NULL;
+
+ if (cmd == 'X')
+ snprintf(lbuf, sizeof(lbuf), "%s -c -header \"%s - %s\" -file %s -icon question "
+ "-B Yes -b No 2>/dev/null",
+ xconfirm, dialog_title, rsc_prog, msg);
+ else
+ snprintf(lbuf, sizeof(lbuf), "%s -c -header \"%s - %s\" -file %s -icon info "
+ "-b Close 2>/dev/null",
+ xconfirm, dialog_title, rsc_prog, msg);
+
+ if ((msgf = popen(lbuf, "r")) == NULL) {
+ fprintf(stderr, "\nError: failed to start command for recording session dialog\n");
+ fprintf(stderr, "Command: \"%s\"\n", lbuf);
+ goto failed;
+ }
+
+ if (fgets(lbuf, sizeof(lbuf), msgf) == NULL) {
+ fprintf(stderr, "\n%s: pmconfirm(1) failed for recording session dialog\n",
+ cmd == '?' ? "Warning" : "Error");
+failed:
+ fprintf(stderr, "Dialog:\n");
+ fputs(p, stderr);
+ strcpy(lbuf, "Yes");
+ }
+ else {
+ /* strip at first newline */
+ for (q = lbuf; *q && *q != '\n'; q++)
+ ;
+ *q = '\0';
+ }
+
+ if (msgf != NULL)
+ pclose(msgf);
+ unlink(msg);
+ }
+ else {
+ fprintf(stderr, "Error: failed to create recording session dialog message!\n");
+ fprintf(stderr, "Reason? %s\n", osstrerror());
+ strcpy(lbuf, "Yes");
+ }
+
+ free(p);
+
+ if (cmd == 'Q' || (cmd == 'X' && strcmp(lbuf, "Yes") == 0)) {
+ run_done(0, "Recording session terminated");
+ }
+
+ if (cmd != '?') {
+ /* detach, silently go off to the races ... */
+ close(rsc_fd);
+ __pmFD_CLR(rsc_fd, &fds);
+ rsc_fd = -1;
+ }
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ { "config", 1, 'c', "FILE", "file to load configuration from" },
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ { "log", 1, 'l', "FILE", "redirect diagnostics and trace output" },
+ { "linger", 0, 'L', 0, "run even if not primary logger instance and nothing to log" },
+ { "note", 1, 'm', "MSG", "descriptive note to be added to the port map file" },
+ PMOPT_NAMESPACE,
+ { "primary", 0, 'P', 0, "execute as primary logger instance" },
+ { "report", 0, 'r', 0, "report record sizes and archive growth rate" },
+ { "size", 1, 's', "SIZE", "terminate after endsize has been accumulated" },
+ { "interval", 1, 't', "DELTA", "default logging interval [default 60.0 seconds]" },
+ PMOPT_FINISH,
+ { "", 0, 'u', 0, "output is unbuffered [default now, so -u is a no-op]" },
+ { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" },
+ { "volsize", 1, 'v', "SIZE", "switch log volumes after size has been accumulated" },
+ { "version", 1, 'V', "NUM", "version for archive (default and only version is 2)" },
+ { "", 1, 'x', "FD", "control file descriptor for running from pmRecordControl(3)" },
+ { "", 0, 'y', 0, "set timezone for times to local time rather than from PMCD host" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "c:D:h:l:Lm:n:Prs:T:t:uU:v:V:x:y?",
+ .long_options = longopts,
+ .short_usage = "[options] archive",
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts;
+ int sep = __pmPathSeparator();
+ int use_localtime = 0;
+ int isdaemon = 0;
+ char *pmnsfile = PM_NS_DEFAULT;
+ char *username;
+ char *logfile = "pmlogger.log";
+ /* default log (not archive) file name */
+ char *endnum;
+ int i;
+ task_t *tp;
+ optcost_t ocp;
+ __pmFdSet readyfds;
+ char *p;
+ char *runtime = NULL;
+ int ctx; /* handle corresponding to ctxp below */
+ __pmContext *ctxp; /* pmlogger has just this one context */
+
+ __pmGetUsername(&username);
+
+ /*
+ * Warning:
+ * If any of the pmlogger options change, make sure the
+ * corresponding changes are made to pmnewlog when pmlogger
+ * options are passed through from the control file
+ */
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'c': /* config file */
+ if (access(opts.optarg, F_OK) == 0)
+ configfile = opts.optarg;
+ else {
+ /* does not exist as given, try the standard place */
+ char *sysconf = pmGetConfig("PCP_SYSCONF_DIR");
+ int sz = strlen(sysconf)+strlen("/pmlogger/")+strlen(opts.optarg)+1;
+ if ((configfile = (char *)malloc(sz)) == NULL)
+ __pmNoMem("config file name", sz, PM_FATAL_ERR);
+ snprintf(configfile, sz,
+ "%s%c" "pmlogger" "%c%s",
+ sysconf, sep, sep, opts.optarg);
+ if (access(configfile, F_OK) != 0) {
+ /* still no good, error handling happens below */
+ free(configfile);
+ configfile = opts.optarg;
+ }
+ }
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'h': /* hostname for PMCD to contact */
+ pmcd_host_conn = opts.optarg;
+ break;
+
+ case 'l': /* log file name */
+ logfile = opts.optarg;
+ break;
+
+ case 'L': /* linger if not primary logger */
+ linger = 1;
+ break;
+
+ case 'm': /* note for port map file */
+ note = opts.optarg;
+ isdaemon = ((strcmp(note, "pmlogger_check") == 0) ||
+ (strcmp(note, "pmlogger_daily") == 0));
+ break;
+
+ case 'n': /* alternative name space file */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'P': /* this is the primary pmlogger */
+ primary = 1;
+ isdaemon = 1;
+ break;
+
+ case 'r': /* report sizes of pmResult records */
+ rflag = 1;
+ break;
+
+ case 's': /* exit size */
+ sts = ParseSize(opts.optarg, &exit_samples, &exit_bytes, &exit_time);
+ if (sts < 0) {
+ pmprintf("%s: illegal size argument '%s' for exit size\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else if (exit_time.tv_sec > 0) {
+ __pmAFregister(&exit_time, NULL, run_done_callback);
+ }
+ break;
+
+ case 'T': /* end time */
+ runtime = opts.optarg;
+ break;
+
+ case 't': /* change default logging interval */
+ if (pmParseInterval(opts.optarg, &delta, &p) < 0) {
+ pmprintf("%s: illegal -t argument\n%s", pmProgname, p);
+ free(p);
+ opts.errors++;
+ }
+ break;
+
+ case 'U': /* run as named user */
+ username = opts.optarg;
+ isdaemon = 1;
+ break;
+
+ case 'u': /* flush output buffers after each fetch */
+ /*
+ * all archive write I/O is unbuffered now, so maintain -u
+ * for backwards compatibility only
+ */
+ break;
+
+ case 'v': /* volume switch after given size */
+ sts = ParseSize(opts.optarg, &vol_switch_samples, &vol_switch_bytes,
+ &vol_switch_time);
+ if (sts < 0) {
+ pmprintf("%s: illegal size argument '%s' for volume size\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else if (vol_switch_time.tv_sec > 0) {
+ vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
+ vol_switch_callback);
+ }
+ break;
+
+ case 'V':
+ archive_version = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || archive_version != PM_LOG_VERS02) {
+ pmprintf("%s: -V requires a version number of %d\n",
+ pmProgname, PM_LOG_VERS02);
+ opts.errors++;
+ }
+ break;
+
+ case 'x': /* recording session control fd */
+ rsc_fd = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || rsc_fd < 0) {
+ pmprintf("%s: -x requires a non-negative numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ else {
+ time(&rsc_start);
+ }
+ break;
+
+ case 'y':
+ use_localtime = 1;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (primary && pmcd_host != NULL) {
+ pmprintf(
+ "%s: -P and -h are mutually exclusive; use -P only when running\n"
+ "%s on the same (local) host as the PMCD to which it connects.\n",
+ pmProgname, pmProgname);
+ opts.errors++;
+ }
+
+ if (!opts.errors && opts.optind != argc - 1) {
+ pmprintf("%s: insufficient arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (rsc_fd != -1 && note == NULL) {
+ /* add default note to indicate running with -x */
+ static char xnote[10];
+ snprintf(xnote, sizeof(xnote), "-x %d", rsc_fd);
+ note = xnote;
+ }
+
+ /* if we are running as a daemon, change user early */
+ if (isdaemon)
+ __pmSetProcessIdentity(username);
+
+ __pmOpenLog("pmlogger", logfile, stderr, &sts);
+ if (sts != 1) {
+ fprintf(stderr, "%s: Warning: log file (%s) creation failed\n", pmProgname, logfile);
+ /* continue on ... writing to stderr */
+ }
+
+ /* base name for archive is here ... */
+ archBase = argv[opts.optind];
+
+ if (pmcd_host_conn == NULL)
+ pmcd_host_conn = "local:";
+
+ /* initialise access control */
+ if (__pmAccAddOp(PM_OP_LOG_ADV) < 0 ||
+ __pmAccAddOp(PM_OP_LOG_MAND) < 0 ||
+ __pmAccAddOp(PM_OP_LOG_ENQ) < 0) {
+ fprintf(stderr, "%s: access control initialisation failed\n", pmProgname);
+ exit(1);
+ }
+
+ if (pmnsfile != PM_NS_DEFAULT) {
+ if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n", pmProgname, pmnsfile, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ if ((ctx = pmNewContext(PM_CONTEXT_HOST, pmcd_host_conn)) < 0) {
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n", pmProgname, pmcd_host_conn, pmErrStr(ctx));
+ exit(1);
+ }
+ pmcd_host = (char *)pmGetContextHostName(ctx);
+ if (strlen(pmcd_host) == 0) {
+ fprintf(stderr, "%s: pmGetContextHostName(%d) failed\n",
+ pmProgname, ctx);
+ exit(1);
+ }
+
+ if (rsc_fd == -1) {
+ /* no -x, so register client id with pmcd */
+ __pmSetClientIdArgv(argc, argv);
+ }
+
+ /*
+ * discover fd for comms channel to PMCD ...
+ */
+ if ((ctxp = __pmHandleToPtr(ctx)) == NULL) {
+ fprintf(stderr, "%s: botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, ctx);
+ exit(1);
+ }
+ pmcdfd = ctxp->c_pmcd->pc_fd;
+ PM_UNLOCK(ctxp->c_lock);
+
+ if (configfile != NULL) {
+ if ((yyin = fopen(configfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ }
+ else {
+ /* **ANY** Lex would read from stdin automagically */
+ configfile = "<stdin>";
+ }
+
+ __pmOptFetchGetParams(&ocp);
+ ocp.c_scope = 1;
+ __pmOptFetchPutParams(&ocp);
+
+ /* prevent early timer events ... */
+ __pmAFblock();
+
+ if (yyparse() != 0)
+ exit(1);
+ if (configfile != NULL)
+ fclose(yyin);
+ yyend();
+
+#ifdef PCP_DEBUG
+ fprintf(stderr, "Config parsed\n");
+#endif
+
+ fprintf(stderr, "Starting %slogger for host \"%s\" via \"%s\"\n",
+ primary ? "primary " : "", pmcd_host, pmcd_host_conn);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "optFetch Cost Parameters: pmid=%d indom=%d fetch=%d scope=%d\n",
+ ocp.c_pmid, ocp.c_indom, ocp.c_fetch, ocp.c_scope);
+
+ fprintf(stderr, "\nAfter loading config ...\n");
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (tp->t_numvalid == 0)
+ continue;
+ fprintf(stderr, " state: %sin log, %savail, %s, %s",
+ PMLC_GET_INLOG(tp->t_state) ? "" : "not ",
+ PMLC_GET_AVAIL(tp->t_state) ? "" : "un",
+ PMLC_GET_MAND(tp->t_state) ? "mand" : "adv",
+ PMLC_GET_ON(tp->t_state) ? "on" : "off");
+ fprintf(stderr, " delta: %ld usec",
+ (long)1000 * tp->t_delta.tv_sec + tp->t_delta.tv_usec);
+ fprintf(stderr, " numpmid: %d\n", tp->t_numpmid);
+ for (i = 0; i < tp->t_numpmid; i++) {
+ fprintf(stderr, " %s (%s):\n", pmIDStr(tp->t_pmidlist[i]), tp->t_namelist[i]);
+ }
+ __pmOptFetchDump(stderr, tp->t_fetch);
+ }
+ }
+#endif
+
+ if (!primary && tasklist == NULL && !linger) {
+ fprintf(stderr, "Nothing to log, and not the primary logger instance ... good-bye\n");
+ exit(1);
+ }
+
+ if ((sts = __pmLogCreate(pmcd_host, archBase, archive_version, &logctl)) < 0) {
+ fprintf(stderr, "__pmLogCreate: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ else {
+ /*
+ * try and establish $TZ from the remote PMCD ...
+ * Note the label record has been set up, but not written yet
+ */
+ char *name = "pmcd.timezone";
+ pmID pmid;
+ pmResult *resp;
+
+ __pmtimevalNow(&epoch);
+ sts = pmUseContext(ctx);
+
+ if (sts >= 0)
+ sts = pmLookupName(1, &name, &pmid);
+ if (sts >= 0)
+ sts = pmFetch(1, &pmid, &resp);
+ if (sts >= 0) {
+ if (resp->vset[0]->numval > 0) { /* pmcd.timezone present */
+ strcpy(logctl.l_label.ill_tz, resp->vset[0]->vlist[0].value.pval->vbuf);
+ /* prefer to use remote time to avoid clock drift problems */
+ epoch = resp->timestamp; /* struct assignment */
+ if (! use_localtime)
+ pmNewZone(logctl.l_label.ill_tz);
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr,
+ "main: Could not get timezone from host %s\n",
+ pmcd_host);
+ }
+#endif
+ pmFreeResult(resp);
+ }
+ }
+
+ /* do ParseTimeWindow stuff for -T */
+ if (runtime) {
+ struct timeval res_end; /* time window end */
+ struct timeval start;
+ struct timeval end;
+ struct timeval last_delta;
+ char *err_msg; /* parsing error message */
+ time_t now;
+ struct timeval now_tv;
+
+ time(&now);
+ now_tv.tv_sec = now;
+ now_tv.tv_usec = 0;
+
+ start = now_tv;
+ end.tv_sec = INT_MAX;
+ end.tv_usec = INT_MAX;
+ sts = __pmParseTime(runtime, &start, &end, &res_end, &err_msg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: illegal -T argument\n%s", pmProgname, err_msg);
+ exit(1);
+ }
+
+ last_delta = res_end;
+ tsub(&last_delta, &now_tv);
+ __pmAFregister(&last_delta, NULL, run_done_callback);
+
+ last_stamp = res_end;
+ }
+
+ fprintf(stderr, "Archive basename: %s\n", archBase);
+
+#ifndef IS_MINGW
+ /* detach yourself from the launching process */
+ if (isdaemon)
+ setpgid(getpid(), 0);
+#endif
+
+ /* set up control port */
+ init_ports();
+ __pmFD_ZERO(&fds);
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] >= 0)
+ __pmFD_SET(ctlfds[i], &fds);
+ }
+#ifndef IS_MINGW
+ __pmFD_SET(pmcdfd, &fds);
+#endif
+ if (rsc_fd != -1)
+ __pmFD_SET(rsc_fd, &fds);
+ numfds = maxfd() + 1;
+
+ if ((sts = do_preamble()) < 0)
+ fprintf(stderr, "Warning: problem writing archive preamble: %s\n",
+ pmErrStr(sts));
+
+ sts = 0; /* default exit status */
+
+ parse_done = 1; /* enable callback processing */
+ __pmAFunblock();
+
+ for ( ; ; ) {
+ int nready;
+ __pmFD_COPY(&readyfds, &fds);
+ nready = __pmSelectRead(numfds, &readyfds, NULL);
+
+ /* block signals to simplify IO handling */
+ __pmAFblock();
+ if (nready > 0) {
+
+ /* handle request on control port */
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] >= 0 && __pmFD_ISSET(ctlfds[i], &readyfds)) {
+ if (control_req(ctlfds[i])) {
+ /* new client has connected */
+ __pmFD_SET(clientfd, &fds);
+ if (clientfd >= numfds)
+ numfds = clientfd + 1;
+ }
+ }
+ }
+ if (clientfd >= 0 && __pmFD_ISSET(clientfd, &readyfds)) {
+ /* process request from client, save clientfd in case client
+ * closes connection, resetting clientfd to -1
+ */
+ int fd = clientfd;
+
+ if (client_req()) {
+ /* client closed connection */
+ __pmFD_CLR(fd, &fds);
+ __pmCloseSocket(clientfd);
+ clientfd = -1;
+ numfds = maxfd() + 1;
+ qa_case = 0;
+ }
+ }
+#ifndef IS_MINGW
+ if (pmcdfd >= 0 && __pmFD_ISSET(pmcdfd, &readyfds)) {
+ /*
+ * do not expect this, given synchronous commumication with the
+ * pmcd ... either pmcd has terminated, or bogus PDU ... or its
+ * Win32 and we are operating under the different conditions of
+ * our AF.c implementation there, which has to deal with a lack
+ * of signal support on Windows - race condition exists between
+ * this check and the async event timer callback.
+ */
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ sts = __pmGetPDU(pmcdfd, ANY_SIZE, TIMEOUT_NEVER, &pb);
+ if (sts <= 0) {
+ if (sts < 0)
+ fprintf(stderr, "Error: __pmGetPDU: %s\n", pmErrStr(sts));
+ disconnect(sts);
+ }
+ else {
+ php = (__pmPDUHdr *)pb;
+ fprintf(stderr, "Error: Unsolicited %s PDU from PMCD\n",
+ __pmPDUTypeStr(php->type));
+ disconnect(PM_ERR_IPC);
+ }
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+#endif
+ if (rsc_fd >= 0 && __pmFD_ISSET(rsc_fd, &readyfds)) {
+ /*
+ * some action on the recording session control fd
+ * end-of-file means launcher has quit, otherwise we
+ * expect one of these commands
+ * V<number>\n - version
+ * F<folio>\n - folio name
+ * P<name>\n - launcher's name
+ * R\n - launcher can replay
+ * D\n - detach from launcher
+ * Q\n - quit pmlogger
+ */
+ char rsc_buf[MAXPATHLEN];
+ char *rp = rsc_buf;
+ char myc;
+ int fake_x = 0;
+
+ for (rp = rsc_buf; ; rp++) {
+ if (read(rsc_fd, &myc, 1) <= 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "recording session control: eof\n");
+#endif
+ if (rp != rsc_buf) {
+ *rp = '\0';
+ fprintf(stderr, "Error: incomplete recording session control message: \"%s\"\n", rsc_buf);
+ }
+ fake_x = 1;
+ break;
+ }
+ if (rp >= &rsc_buf[MAXPATHLEN]) {
+ fprintf(stderr, "Error: absurd recording session control message: \"%100.100s ...\"\n", rsc_buf);
+ fake_x = 1;
+ break;
+ }
+ if (myc == '\n') {
+ *rp = '\0';
+ break;
+ }
+ *rp = myc;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (fake_x == 0)
+ fprintf(stderr, "recording session control: \"%s\"\n", rsc_buf);
+ }
+#endif
+
+ if (fake_x)
+ do_dialog('X');
+ else if (strcmp(rsc_buf, "Q") == 0 ||
+ strcmp(rsc_buf, "D") == 0 ||
+ strcmp(rsc_buf, "?") == 0)
+ do_dialog(rsc_buf[0]);
+ else if (rsc_buf[0] == 'F')
+ folio_name = strdup(&rsc_buf[1]);
+ else if (rsc_buf[0] == 'P')
+ rsc_prog = strdup(&rsc_buf[1]);
+ else if (strcmp(rsc_buf, "R") == 0)
+ rsc_replay = 1;
+ else if (rsc_buf[0] == 'V' && rsc_buf[1] == '0') {
+ /*
+ * version 0 of the recording session control ...
+ * this is all we grok at the moment
+ */
+ ;
+ }
+ else {
+ fprintf(stderr, "Error: illegal recording session control message: \"%s\"\n", rsc_buf);
+ do_dialog('X');
+ }
+ }
+ }
+ else if (vol_switch_flag) {
+ newvolume(VOL_SW_SIGHUP);
+ vol_switch_flag = 0;
+ }
+ else if (nready < 0 && neterror() != EINTR)
+ fprintf(stderr, "Error: select: %s\n", netstrerror());
+
+ __pmAFunblock();
+
+ if (exit_code)
+ break;
+ }
+ exit(exit_code);
+}
+
+int
+newvolume(int vol_switch_type)
+{
+ FILE *newfp;
+ int nextvol = logctl.l_curvol + 1;
+ time_t now;
+ static char *vol_sw_strs[] = {
+ "SIGHUP", "pmlc request", "sample counter",
+ "sample byte size", "sample time", "max data volume size"
+ };
+
+ vol_samples_counter = 0;
+ vol_bytes += ftell(logctl.l_mfp);
+ if (exit_bytes != -1) {
+ if (vol_bytes >= exit_bytes)
+ run_done(0, "Byte limit reached");
+ }
+
+ /*
+ * If we are not part of a callback but instead a
+ * volume switch from "pmlc" or a "SIGHUP" then
+ * get rid of pending volume switch in event queue
+ * as it will now be wrong, and schedule a new
+ * volume switch event.
+ */
+ if (vol_switch_afid >= 0 && vol_switch_type != VOL_SW_TIME) {
+ __pmAFunregister(vol_switch_afid);
+ vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
+ vol_switch_callback);
+ }
+
+ if ((newfp = __pmLogNewFile(archBase, nextvol)) != NULL) {
+ if (logctl.l_state == PM_LOG_STATE_NEW) {
+ /*
+ * nothing has been logged as yet, force out the label records
+ */
+ __pmtimevalNow(&last_stamp);
+ logctl.l_label.ill_start.tv_sec = (__int32_t)last_stamp.tv_sec;
+ logctl.l_label.ill_start.tv_usec = (__int32_t)last_stamp.tv_usec;
+ logctl.l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
+ logctl.l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
+ logctl.l_label.ill_vol = 0;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ logctl.l_state = PM_LOG_STATE_INIT;
+ }
+#if 0
+ if (last_stamp.tv_sec != 0) {
+ __pmTimeval tmp;
+ tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
+ tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
+ __pmLogPutIndex(&logctl, &tmp);
+ }
+#endif
+ fclose(logctl.l_mfp);
+ logctl.l_mfp = newfp;
+ logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ time(&now);
+ fprintf(stderr, "New log volume %d, via %s at %s",
+ nextvol, vol_sw_strs[vol_switch_type], ctime(&now));
+ return nextvol;
+ }
+ else
+ return -oserror();
+}
+
+void
+disconnect(int sts)
+{
+ time_t now;
+#if CAN_RECONNECT
+ int ctx;
+ __pmContext *ctxp;
+#endif
+
+ time(&now);
+ if (sts != 0)
+ fprintf(stderr, "%s: Error: %s\n", pmProgname, pmErrStr(sts));
+ fprintf(stderr, "%s: Lost connection to PMCD on \"%s\" at %s",
+ pmProgname, pmcd_host, ctime(&now));
+#if CAN_RECONNECT
+ if (primary) {
+ fprintf(stderr, "This is fatal for the primary logger.");
+ exit(1);
+ }
+ close(pmcdfd);
+ __pmFD_CLR(pmcdfd, &fds);
+ pmcdfd = -1;
+ numfds = maxfd() + 1;
+ if ((ctx = pmWhichContext()) >= 0)
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctx < 0 || ctxp == NULL) {
+ fprintf(stderr, "%s: disconnect botch: cannot get context: %s\n", pmProgname, pmErrStr(ctx));
+ exit(1);
+ }
+ ctxp->c_pmcd->pc_fd = -1;
+ PM_UNLOCK(ctxp->c_lock);
+#else
+ exit(1);
+#endif
+}
+
+#if CAN_RECONNECT
+int
+reconnect(void)
+{
+ int sts;
+ int ctx;
+ __pmContext *ctxp;
+
+ if ((ctx = pmWhichContext()) >= 0)
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctx < 0 || ctxp == NULL) {
+ fprintf(stderr, "%s: reconnect botch: cannot get context: %s\n", pmProgname, pmErrStr(ctx));
+ exit(1);
+ }
+ sts = pmReconnectContext(ctx);
+ if (sts >= 0) {
+ time_t now;
+ time(&now);
+ fprintf(stderr, "%s: re-established connection to PMCD on \"%s\" at %s\n",
+ pmProgname, pmcd_host, ctime(&now));
+ pmcdfd = ctxp->c_pmcd->pc_fd;
+ __pmFD_SET(pmcdfd, &fds);
+ numfds = maxfd() + 1;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ return sts;
+}
+#endif
diff --git a/src/pmlogger/src/ports.c b/src/pmlogger/src/ports.c
new file mode 100644
index 0000000..8d5eed3
--- /dev/null
+++ b/src/pmlogger/src/ports.c
@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define _WIN32_WINNT 0x0500 /* for CreateHardLink */
+#include <math.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "logger.h"
+
+#if !defined(SIGRTMAX)
+#if defined(NSIG)
+#define SIGRTMAX (NSIG)
+#else
+! bozo neither NSIG nor SIGRTMAX are defined
+#endif
+#endif
+
+/* The logger will try to allocate port numbers beginning with the number
+ * defined below. If that is in use it will keep adding one and trying again
+ * until it allocates a port.
+ */
+#define PORT_BASE 4330 /* Base of range for port numbers */
+
+static char *ctlfile; /* Control directory/portmap name */
+static char *linkfile; /* Link name for primary logger */
+static const char *socketPath; /* Path to unix domain sockets. */
+static const char *linkSocketPath;/* Link to socket for primary logger */
+
+int ctlfds[CFD_NUM] = {-1, -1, -1};/* fds for control ports: */
+int ctlport; /* pmlogger control port number */
+
+static void
+cleanup(void)
+{
+ if (linkfile != NULL)
+ unlink(linkfile);
+ if (ctlfile != NULL)
+ unlink(ctlfile);
+ if (linkSocketPath != NULL)
+ unlink(linkSocketPath);
+ if (socketPath != NULL)
+ unlink(socketPath);
+}
+
+static void
+sigexit_handler(int sig)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "pmlogger: Signalled (signal=%d), exiting\n", sig);
+#endif
+ cleanup();
+ _exit(sig);
+}
+
+static void
+sigterm_handler(int sig)
+{
+ /* exit as soon as possible, handler is deferred for log cleanup */
+ exit_code = sig;
+}
+
+static void
+sighup_handler(int sig)
+{
+ __pmSetSignalHandler(SIGHUP, sighup_handler);
+ vol_switch_flag = 1;
+}
+
+#ifndef IS_MINGW
+static void
+sigcore_handler(int sig)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "pmlogger: Signalled (signal=%d), exiting (core dumped)\n", sig);
+#endif
+ __pmSetSignalHandler(SIGABRT, SIG_DFL); /* Don't come back here */
+ cleanup();
+ _exit(sig);
+}
+
+static void
+sigpipe_handler(int sig)
+{
+ /*
+ * just ignore the signal, the write() will fail, and the PDU
+ * xmit will return with an error
+ */
+ __pmSetSignalHandler(SIGPIPE, sigpipe_handler);
+}
+
+static void
+sigusr1_handler(int sig)
+{
+ /*
+ * no-op now that all archive write I/O is unbuffered
+ */
+ __pmSetSignalHandler(SIGUSR1, sigusr1_handler);
+}
+#endif
+
+typedef struct {
+ int sig;
+ void (*func)(int);
+} sig_map_t;
+
+/* This is used to set the dispositions for the various signals received.
+ * Try to do the right thing for the various STOP/CONT signals.
+ */
+static sig_map_t sig_handler[] = {
+ { SIGHUP, sighup_handler }, /* Exit Hangup [see termio(7)] */
+ { SIGINT, sigterm_handler }, /* Exit Interrupt [see termio(7)] */
+#ifndef IS_MINGW
+ { SIGQUIT, sigcore_handler }, /* Core Quit [see termio(7)] */
+ { SIGILL, sigcore_handler }, /* Core Illegal Instruction */
+ { SIGTRAP, sigcore_handler }, /* Core Trace/Breakpoint Trap */
+ { SIGABRT, sigcore_handler }, /* Core Abort */
+#ifdef SIGEMT
+ { SIGEMT, sigcore_handler }, /* Core Emulation Trap */
+#endif
+ { SIGFPE, sigcore_handler }, /* Core Arithmetic Exception */
+ { SIGKILL, sigexit_handler }, /* Exit Killed */
+ { SIGBUS, sigcore_handler }, /* Core Bus Error */
+ { SIGSEGV, sigcore_handler }, /* Core Segmentation Fault */
+ { SIGSYS, sigcore_handler }, /* Core Bad System Call */
+ { SIGPIPE, sigpipe_handler }, /* Exit Broken Pipe */
+ { SIGALRM, sigterm_handler }, /* Exit Alarm Clock */
+#endif
+ { SIGTERM, sigterm_handler }, /* Exit Terminated */
+#ifndef IS_MINGW
+ { SIGUSR1, sigusr1_handler }, /* NOP User Signal 1 - [was fflush(3)] */
+ { SIGUSR2, sigexit_handler }, /* Exit User Signal 2 */
+ { SIGCHLD, SIG_IGN }, /* NOP Child stopped or terminated */
+#ifdef SIGPWR
+ { SIGPWR, SIG_DFL }, /* Ignore Power Fail/Restart */
+#endif
+ { SIGWINCH, SIG_DFL }, /* Ignore Window Size Change */
+ { SIGURG, SIG_DFL }, /* Ignore Urgent Socket Condition */
+#ifdef SIGPOLL
+ { SIGPOLL, sigexit_handler }, /* Exit Pollable Event [see streamio(7)] */
+#endif
+ { SIGSTOP, SIG_DFL }, /* Stop Stopped (signal) */
+ { SIGTSTP, SIG_DFL }, /* Stop Stopped (user) */
+ { SIGCONT, SIG_DFL }, /* Ignore Continued */
+ { SIGTTIN, SIG_DFL }, /* Stop Stopped (tty input) */
+ { SIGTTOU, SIG_DFL }, /* Stop Stopped (tty output) */
+ { SIGVTALRM, sigterm_handler }, /* Exit Virtual Timer Expired */
+
+ { SIGPROF, sigterm_handler }, /* Exit Profiling Timer Expired */
+ { SIGXCPU, sigcore_handler }, /* Core CPU time limit exceeded [see getrlimit(2)] */
+ { SIGXFSZ, sigcore_handler} /* Core File size limit exceeded [see getrlimit(2)] */
+#endif
+};
+
+/* Create a network socket for incoming connections and bind to it an address for
+ * clients to use.
+ * If supported, also create a unix domain socket for local clients to use.
+ * Only returns if it succeeds (exits on failure).
+ */
+
+static void
+GetPorts(char *file)
+{
+ int fd;
+ int mapfd = -1;
+ FILE *mapstream = NULL;
+ int sts;
+ int socketsCreated = 0;
+ int ctlix;
+ __pmSockAddr *myAddr;
+ char globalPath[MAXPATHLEN];
+ char localPath[MAXPATHLEN];
+ static int port_base = -1;
+
+ /* Try to create sockets for control connections. */
+ for (ctlix = 0; ctlix < CFD_NUM; ++ctlix) {
+ if (ctlix == CFD_UNIX) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ const char *socketError;
+ const char *errorPath;
+ /* Try to create a unix domain socket, if supported. */
+ fd = __pmCreateUnixSocket();
+ if (fd < 0) {
+ fprintf(stderr, "GetPorts: unix domain socket failed: %s\n", netstrerror());
+ continue;
+ }
+ if ((myAddr = __pmSockAddrAlloc()) == NULL) {
+ fprintf(stderr, "GetPorts: __pmSockAddrAlloc out of memory\n");
+ exit(1);
+ }
+ socketPath = __pmLogLocalSocketDefault(getpid(), globalPath, sizeof(globalPath));
+ __pmSockAddrSetFamily(myAddr, AF_UNIX);
+ __pmSockAddrSetPath(myAddr, socketPath);
+ __pmServerSetLocalSocket(socketPath);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+
+ /*
+ * If we cannot bind to the system wide socket path, then try binding
+ * to the user specific one.
+ */
+ if (sts < 0) {
+ char *tmpPath;
+ socketError = netstrerror();
+ errorPath = socketPath;
+ unlink(errorPath);
+ socketPath = __pmLogLocalSocketUser(getpid(), localPath, sizeof(localPath));
+ if (socketPath == NULL) {
+ sts = -ESRCH;
+ }
+ else {
+ /*
+ * Make sure that the directory exists. dirname may modify the
+ * contents of its first argument, so use a copy.
+ */
+ if ((tmpPath = strdup(socketPath)) == NULL) {
+ fprintf(stderr, "GetPorts: strdup out of memory\n");
+ exit(1);
+ }
+ sts = __pmMakePath(dirname(tmpPath),
+ S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ free(tmpPath);
+ if (sts >= 0 || oserror() == EEXIST) {
+ __pmSockAddrSetPath(myAddr, socketPath);
+ __pmServerSetLocalSocket(socketPath);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+ }
+ }
+ }
+ __pmSockAddrFree(myAddr);
+
+ if (sts < 0) {
+ /* Could not bind to either socket path. */
+ fprintf(stderr, "__pmBind(%s): %s\n", errorPath, socketError);
+ if (sts == -ESRCH)
+ fprintf(stderr, "__pmLogLocalSocketUser(): %s\n", osstrerror());
+ else
+ fprintf(stderr, "__pmBind(%s): %s\n", socketPath, netstrerror());
+ }
+ else {
+ /*
+ * For unix domain sockets, grant rw access to the socket for all,
+ * otherwise, on linux platforms, connection will not be possible.
+ * This must be done AFTER binding the address. See Unix(7) for details.
+ */
+ sts = chmod(socketPath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (sts != 0) {
+ fprintf(stderr, "GetPorts: chmod(%s): %s\n", socketPath, strerror(errno));
+ }
+ }
+ /* On error, don't leave the socket file lying around. */
+ if (sts < 0) {
+ unlink(socketPath);
+ socketPath = NULL;
+ }
+ else if ((socketPath = strdup(socketPath)) == NULL) {
+ fprintf(stderr, "GetPorts: strdup out of memory\n");
+ exit(1);
+ }
+#else
+ /*
+ * Unix domain sockets are not supported.
+ * This is not an error, just don't try to create one.
+ */
+ continue;
+#endif
+ }
+ else {
+ /* Try to create a network socket. */
+ if (ctlix == CFD_INET) {
+ fd = __pmCreateSocket();
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "GetPorts: inet socket creation failed: %s\n",
+ netstrerror());
+#endif
+ continue;
+ }
+ }
+ else {
+ fd = __pmCreateIPv6Socket();
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "GetPorts: ipv6 socket creation failed: %s\n",
+ netstrerror());
+#endif
+ continue;
+ }
+ }
+ if (port_base == -1) {
+ /*
+ * get optional stuff from environment ...
+ * PMLOGGER_PORT
+ */
+ char *env_str;
+ if ((env_str = getenv("PMLOGGER_PORT")) != NULL) {
+ char *end_ptr;
+
+ port_base = strtol(env_str, &end_ptr, 0);
+ if (*end_ptr != '\0' || port_base < 0) {
+ fprintf(stderr,
+ "GetPorts: ignored bad PMLOGGER_PORT = '%s'\n", env_str);
+ port_base = PORT_BASE;
+ }
+ }
+ else
+ port_base = PORT_BASE;
+ }
+
+ /*
+ * try to allocate ports from port_base. If port already in use, add one
+ * and try again.
+ */
+ if ((myAddr = __pmSockAddrAlloc()) == NULL) {
+ fprintf(stderr, "GetPorts: __pmSockAddrAlloc out of memory\n");
+ exit(1);
+ }
+ for (ctlport = port_base; ; ctlport++) {
+ if (ctlix == CFD_INET)
+ __pmSockAddrInit(myAddr, AF_INET, INADDR_ANY, ctlport);
+ else
+ __pmSockAddrInit(myAddr, AF_INET6, INADDR_ANY, ctlport);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+ if (sts < 0) {
+ if (neterror() != EADDRINUSE) {
+ fprintf(stderr, "__pmBind(%d): %s\n", ctlport, netstrerror());
+ break;
+ }
+ }
+ else
+ break;
+ }
+ __pmSockAddrFree(myAddr);
+ }
+
+ /* Now listen on the new socket. */
+ if (sts >= 0) {
+ sts = __pmListen(fd, 5); /* Max. of 5 pending connection requests */
+ if (sts == -1) {
+ __pmCloseSocket(fd);
+ fprintf(stderr, "__pmListen: %s\n", netstrerror());
+ }
+ else {
+ ctlfds[ctlix] = fd;
+ ++socketsCreated;
+ }
+ }
+ }
+
+ if (socketsCreated != 0) {
+ /* create and initialize the port map file */
+ unlink(file);
+ mapfd = open(file, O_WRONLY | O_EXCL | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (mapfd == -1) {
+ /* not a fatal error; continue on without control file */
+#ifdef DESPERATE
+ fprintf(stderr, "%s: error creating port map file %s: %s. Exiting.\n",
+ pmProgname, file, osstrerror());
+#endif
+ return;
+ }
+ /* write the port number to the port map file */
+ if ((mapstream = fdopen(mapfd, "w")) == NULL) {
+ /* not a fatal error; continue on without control file */
+ close(mapfd);
+#ifdef DESPERATE
+ perror("GetPorts: fdopen");
+#endif
+ return;
+ }
+ /* first the port number */
+ fprintf(mapstream, "%d\n", ctlport);
+
+ /* then the PMCD host (but don't bother try DNS-canonicalize) */
+ fprintf(mapstream, "%s\n", pmcd_host);
+
+ /* then the full pathname to the archive base */
+ __pmNativePath(archBase);
+ if (__pmAbsolutePath(archBase))
+ fprintf(mapstream, "%s\n", archBase);
+ else {
+ char path[MAXPATHLEN];
+
+ if (getcwd(path, MAXPATHLEN) == NULL)
+ fprintf(mapstream, "\n");
+ else
+ fprintf(mapstream, "%s%c%s\n", path, __pmPathSeparator(), archBase);
+ }
+
+ /* and finally, the annotation from -m or -x */
+ if (note != NULL)
+ fprintf(mapstream, "%s\n", note);
+ }
+
+ if (mapstream != NULL)
+ fclose(mapstream);
+ if (mapfd >= 0)
+ close(mapfd);
+ if (socketsCreated == 0)
+ exit(1);
+}
+
+/* Create the control port for this pmlogger and the file containing the port
+ * number so that other programs know which port to connect to.
+ * If this is the primary pmlogger, create the special link to the
+ * control file.
+ */
+void
+init_ports(void)
+{
+ int i, j, n, sts;
+ int sep = __pmPathSeparator();
+ int extlen, baselen;
+ char path[MAXPATHLEN];
+ pid_t mypid = getpid();
+
+ /*
+ * make sure control port files are removed when pmlogger terminates
+ * by trapping all the signals we can
+ */
+ for (i = 0; i < sizeof(sig_handler)/sizeof(sig_handler[0]); i++) {
+ __pmSetSignalHandler(sig_handler[i].sig, sig_handler[i].func);
+ }
+ /*
+ * install explicit handler for other signals ... we assume all
+ * of the interesting signals we are likely to receive are smaller
+ * than 32 (this is a hack 'cause there is no portable way of
+ * determining the maximum signal number)
+ */
+ for (j = 1; j < 32; j++) {
+ for (i = 0; i < sizeof(sig_handler)/sizeof(sig_handler[0]); i++) {
+ if (j == sig_handler[i].sig) break;
+ }
+ if (i == sizeof(sig_handler)/sizeof(sig_handler[0]))
+ /* not special cased in seg_handler[] */
+ __pmSetSignalHandler(j, sigexit_handler);
+ }
+
+#if defined(HAVE_ATEXIT)
+ if (atexit(cleanup) != 0) {
+ perror("atexit");
+ fprintf(stderr, "%s: unable to register atexit cleanup function. Exiting\n",
+ pmProgname);
+ cleanup();
+ exit(1);
+ }
+#endif
+
+ /* create the control port file (make the directory if necessary). */
+
+ /* count digits in mypid */
+ for (n = mypid, extlen = 1; n ; extlen++)
+ n /= 10;
+ /* baselen is directory + trailing / */
+ snprintf(path, sizeof(path), "%s%cpmlogger", pmGetConfig("PCP_TMP_DIR"), sep);
+ baselen = strlen(path) + 1;
+ /* likewise for PCP_DIR if it is set */
+ n = baselen + extlen + 1;
+ ctlfile = (char *)malloc(n);
+ if (ctlfile == NULL)
+ __pmNoMem("port file name", n, PM_FATAL_ERR);
+ strcpy(ctlfile, path);
+
+ /* try to create the port file directory. OK if it already exists */
+ sts = mkdir2(ctlfile, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (sts < 0) {
+ if (oserror() != EEXIST) {
+ fprintf(stderr, "%s: error creating port file dir %s: %s\n",
+ pmProgname, ctlfile, osstrerror());
+ exit(1);
+ }
+ } else {
+ chmod(ctlfile, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
+ }
+
+ /* remove any existing port file with my name (it's old) */
+ snprintf(ctlfile + (baselen-1), n, "%c%" FMT_PID, sep, mypid);
+ unlink(ctlfile);
+
+ /* get control port and write port map file */
+ GetPorts(ctlfile);
+
+ /*
+ * If this is the primary logger, make the special link for
+ * clients to connect specifically to it.
+ */
+ if (primary) {
+ baselen = snprintf(path, sizeof(path), "%s%cpmlogger",
+ pmGetConfig("PCP_TMP_DIR"), sep);
+ n = baselen + 9; /* separator + "primary" + null */
+ linkfile = (char *)malloc(n);
+ if (linkfile == NULL)
+ __pmNoMem("primary logger link file name", n, PM_FATAL_ERR);
+ snprintf(linkfile, n, "%s%cprimary", path, sep);
+#ifndef IS_MINGW
+ sts = link(ctlfile, linkfile);
+#else
+ sts = (CreateHardLink(linkfile, ctlfile, NULL) == 0);
+#endif
+ if (sts != 0) {
+ if (oserror() == EEXIST)
+ fprintf(stderr, "%s: there is already a primary pmlogger running\n", pmProgname);
+ else
+ fprintf(stderr, "%s: error creating primary logger link %s: %s\n",
+ pmProgname, linkfile, osstrerror());
+ exit(1);
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* Create a hard link to the local socket for users wanting the primary logger. */
+ linkSocketPath = __pmLogLocalSocketDefault(PM_LOG_PRIMARY_PID, path, sizeof(path));
+#ifndef IS_MINGW
+ sts = link(socketPath, linkSocketPath);
+#else
+ sts = (CreateHardLink(linkSocketPath, socketPath, NULL) == 0);
+#endif
+ if (sts != 0) {
+ if (oserror() == EEXIST)
+ fprintf(stderr, "%s: there is already a primary pmlogger running\n", pmProgname);
+ else
+ fprintf(stderr, "%s: error creating primary logger socket link %s: %s\n",
+ pmProgname, linkSocketPath, osstrerror());
+ exit(1);
+ }
+ if ((linkSocketPath = strdup(linkSocketPath)) == NULL) {
+ fprintf(stderr, "init_ports: strdup out of memory\n");
+ exit(1);
+ }
+#endif
+ }
+}
+
+/* Service a request on the control port Return non-zero if a new client
+ * connection has been accepted.
+ */
+
+int clientfd = -1;
+unsigned int denyops = 0; /* for access control (ops not allowed) */
+char pmlc_host[MAXHOSTNAMELEN];
+int connect_state = 0;
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+static int
+check_local_creds(__pmHashCtl *attrs)
+{
+ __pmHashNode *node;
+ const char *connectingUser;
+ char *end;
+ __pmUserID connectingUid;
+
+ /* Get the user name of the connecting process. */
+ connectingUser = ((node = __pmHashSearch(PCP_ATTR_USERID, attrs)) ?
+ (const char *)node->data : NULL);
+ if (connectingUser == NULL) {
+ /* We don't know who is connecting. */
+ return PM_ERR_PERMISSION;
+ }
+
+ /* Get the uid of the connecting process. */
+ errno = 0;
+ connectingUid = strtol(connectingUser, &end, 0);
+ if (errno != 0 || *end != '\0') {
+ /* Can't convert the connecting user to a uid cleanly. */
+ return PM_ERR_PERMISSION;
+ }
+
+ /* Allow connections from root (uid == 0). */
+ if (connectingUid == 0)
+ return 0;
+
+ /* Allow connections from the same user as us. */
+ if (connectingUid == getuid() || connectingUid == geteuid())
+ return 0;
+
+ /* Connection is not allowed. */
+ return PM_ERR_PERMISSION;
+}
+#endif /* defined(HAVE_STRUCT_SOCKADDR_UN) */
+
+int
+control_req(int ctlfd)
+{
+ int fd, sts;
+ char *abuf;
+ char *hostName;
+ __pmSockAddr *addr;
+ __pmSockLen addrlen;
+
+ if ((addr = __pmSockAddrAlloc()) == NULL) {
+ fputs("error allocating space for client sockaddr\n", stderr);
+ return 0;
+ }
+ addrlen = __pmSockAddrSize();
+ fd = __pmAccept(ctlfd, addr, &addrlen);
+ if (fd == -1) {
+ fprintf(stderr, "error accepting client: %s\n", netstrerror());
+ __pmSockAddrFree(addr);
+ return 0;
+ }
+ __pmSetSocketIPC(fd);
+ if (clientfd != -1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "control_req: send EADDRINUSE on fd=%d (client already on fd=%d)\n", fd, clientfd);
+#endif
+ sts = __pmSendError(fd, FROM_ANON, -EADDRINUSE);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ sts = __pmSetVersionIPC(fd, UNKNOWN_VERSION);
+ if (sts < 0) {
+ __pmSendError(fd, FROM_ANON, sts);
+ fprintf(stderr, "error connecting to client: %s\n", pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ hostName = __pmGetNameInfo(addr);
+ if (hostName == NULL || strlen(hostName) > MAXHOSTNAMELEN-1) {
+ abuf = __pmSockAddrToString(addr);
+ snprintf(pmlc_host, sizeof(pmlc_host), "%s", abuf);
+ free(abuf);
+ }
+ else {
+ /* this is safe, due to strlen() test above */
+ strcpy(pmlc_host, hostName);
+ }
+ if (hostName != NULL)
+ free(hostName);
+
+ sts = __pmAccAddClient(addr, &denyops);
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ abuf = __pmSockAddrToString(addr);
+ fprintf(stderr, "client addr: %s\n\n", abuf);
+ free(abuf);
+ __pmAccDumpHosts(stderr);
+ fprintf(stderr, "\ncontrol_req: connection rejected on fd=%d from %s: %s\n", fd, pmlc_host, pmErrStr(sts));
+ }
+#endif
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection access NACK to client: %s\n",
+ pmErrStr(sts));
+ sleep(1); /* QA 083 seems like there is a race w/out this delay */
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /*
+ * For connections on an AF_UNIX socket, check the user credentials of the
+ * connecting process.
+ */
+ if (__pmSockAddrGetFamily(addr) == AF_UNIX) {
+ __pmHashCtl clientattrs; /* Connection attributes (auth info) */
+ __pmHashInit(&clientattrs);
+
+ /* Get the user credentials. */
+ if ((sts = __pmServerSetLocalCreds(fd, &clientattrs)) < 0) {
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection credentials NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ /* Check the user credentials. */
+ if ((sts = check_local_creds(&clientattrs)) < 0) {
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection credentials NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ /* This information is no longer needed. */
+ __pmFreeAttrsSpec(&clientattrs);
+ __pmHashClear(&clientattrs);
+ }
+#endif
+
+ /* Done with this address. */
+ __pmSockAddrFree(addr);
+
+ /*
+ * encode pdu version in the acknowledgement
+ * also need "from" to be pmlogger's pid as this is checked at
+ * the other end
+ */
+ sts = __pmSendError(fd, (int)getpid(), LOG_PDU_VERSION);
+ if (sts < 0) {
+ fprintf(stderr, "error sending connection ACK to client: %s\n",
+ pmErrStr(sts));
+ __pmCloseSocket(fd);
+ return 0;
+ }
+ clientfd = fd;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "control_req: connection accepted on fd=%d from %s\n", fd, pmlc_host);
+#endif
+
+ return 1;
+}
diff --git a/src/pmlogger/src/preamble.c b/src/pmlogger/src/preamble.c
new file mode 100644
index 0000000..ff37f09
--- /dev/null
+++ b/src/pmlogger/src/preamble.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+#include "pmda.h"
+
+/*
+ * this routine creates the "fake" pmResult to be added to the
+ * start of the archive log to identify information about the
+ * archive beyond what is in the archive label.
+ */
+
+/* encode the domain(x), cluster (y) and item (z) parts of the PMID */
+#define PMID(x,y,z) ((x<<22)|(y<<10)|z)
+
+/* encode the domain(x) and serial (y) parts of the pmInDom */
+#define INDOM(x,y) ((x<<22)|y)
+
+/*
+ * Note: these pmDesc entries MUST correspond to the corrsponding
+ * entries from the real PMDA ...
+ * We fake it out here to accommodate logging from PCP 1.1
+ * PMCD's and to avoid round-trip dependencies in setting up
+ * the preamble
+ */
+static pmDesc desc[] = {
+/* pmcd.pmlogger.host */
+ { PMID(2,3,3), PM_TYPE_STRING, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+/* pmcd.pmlogger.port */
+ { PMID(2,3,0), PM_TYPE_U32, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+/* pmcd.pmlogger.archive */
+ { PMID(2,3,2), PM_TYPE_STRING, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+};
+/* names added for version 2 archives */
+static char* names[] = {
+"pmcd.pmlogger.host",
+"pmcd.pmlogger.port",
+"pmcd.pmlogger.archive"
+};
+
+static int n_metric = sizeof(desc) / sizeof(desc[0]);
+
+int
+do_preamble(void)
+{
+ int sts;
+ int i;
+ int j;
+ pid_t mypid = getpid();
+ pmResult *res;
+ __pmPDU *pb;
+ pmAtomValue atom;
+ __pmTimeval tmp;
+ char path[MAXPATHLEN];
+ char host[MAXHOSTNAMELEN];
+ int free_cp;
+
+ /* start to build the pmResult */
+ res = (pmResult *)malloc(sizeof(pmResult) + (n_metric - 1) * sizeof(pmValueSet *));
+ if (res == NULL)
+ return -oserror();
+
+ res->numpmid = n_metric;
+ last_stamp = res->timestamp = epoch; /* struct assignment */
+ tmp.tv_sec = (__int32_t)epoch.tv_sec;
+ tmp.tv_usec = (__int32_t)epoch.tv_usec;
+
+ for (i = 0; i < n_metric; i++)
+ res->vset[i] = NULL;
+
+ for (i = 0; i < n_metric; i++) {
+ res->vset[i] = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (res->vset[i] == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ res->vset[i]->pmid = desc[i].pmid;
+ res->vset[i]->numval = 1;
+ /* special case for each value 0 .. n_metric-1 */
+ free_cp = 0;
+ if (desc[i].pmid == PMID(2,3,3)) {
+ __pmHostEnt *servInfo;
+ /* my fully qualified hostname, cloned from the pmcd PMDA */
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ host[MAXHOSTNAMELEN-1] = '\0';
+ if ((servInfo = __pmGetAddrInfo(host)) == NULL)
+ atom.cp = host;
+ else {
+ atom.cp = __pmHostEntGetName(servInfo);
+ __pmHostEntFree(servInfo);
+ if (atom.cp == NULL)
+ atom.cp = host;
+ else
+ free_cp = 1;
+ }
+ }
+ else if (desc[i].pmid == PMID(2,3,0)) {
+ /* my control port number, from ports.c */
+ atom.l = ctlport;
+ }
+ else if (desc[i].pmid == PMID(2,3,2)) {
+ /*
+ * the full pathname to the base of the archive, cloned
+ * from GetPort() in ports.c
+ */
+ if (__pmAbsolutePath(archBase))
+ atom.cp = archBase;
+ else {
+ if (getcwd(path, MAXPATHLEN) == NULL)
+ atom.cp = archBase;
+ else {
+ strcat(path, "/");
+ strcat(path, archBase);
+ atom.cp = path;
+ }
+ }
+ }
+
+ sts = __pmStuffValue(&atom, &res->vset[i]->vlist[0], desc[i].type);
+ if (free_cp)
+ free(atom.cp);
+ if (sts < 0)
+ goto done;
+ res->vset[i]->vlist[0].inst = (int)mypid;
+ res->vset[i]->valfmt = sts;
+ }
+
+ if ((sts = __pmEncodeResult(fileno(logctl.l_mfp), res, &pb)) < 0)
+ goto done;
+
+ __pmOverrideLastFd(fileno(logctl.l_mfp)); /* force use of log version */
+ /* and start some writing to the archive log files ... */
+ sts = __pmLogPutResult2(&logctl, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0)
+ goto done;
+
+ for (i = 0; i < n_metric; i++) {
+ if ((sts = __pmLogPutDesc(&logctl, &desc[i], 1, &names[i])) < 0)
+ goto done;
+ if (desc[i].indom == PM_INDOM_NULL)
+ continue;
+ for (j = 0; j < i; j++) {
+ if (desc[i].indom == desc[j].indom)
+ break;
+ }
+ if (j == i) {
+ /* need indom ... force one with my PID as the only instance */
+ int *instid;
+ char **instname;
+
+ if ((instid = (int *)malloc(sizeof(*instid))) == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ *instid = (int)mypid;
+ snprintf(path, sizeof(path), "%" FMT_PID, mypid);
+ if ((instname = (char **)malloc(sizeof(char *)+strlen(path)+1)) == NULL) {
+ free(instid);
+ sts = -oserror();
+ goto done;
+ }
+ /*
+ * this _is_ correct ... instname[] is a one element array
+ * with the string value immediately following
+ */
+ instname[0] = (char *)&instname[1];
+ strcpy(instname[0], path);
+ /*
+ * Note. DO NOT free instid and instname ... they get hidden
+ * away in addindom() below __pmLogPutInDom()
+ */
+ if ((sts = __pmLogPutInDom(&logctl, desc[i].indom, &tmp, 1, instid, instname)) < 0)
+ goto done;
+ }
+ }
+
+ /* fudge the temporal index */
+ fseek(logctl.l_mfp, sizeof(__pmLogLabel)+2*sizeof(int), SEEK_SET);
+ fseek(logctl.l_mdfp, sizeof(__pmLogLabel)+2*sizeof(int), SEEK_SET);
+ __pmLogPutIndex(&logctl, &tmp);
+ fseek(logctl.l_mfp, 0L, SEEK_END);
+ fseek(logctl.l_mdfp, 0L, SEEK_END);
+ sts = 0;
+
+ /*
+ * and now free stuff
+ */
+done:
+ for (i = 0; i < n_metric; i++) {
+ if (res->vset[i] != NULL)
+ free(res->vset[i]);
+ }
+ free(res);
+
+ return sts;
+}
diff --git a/src/pmlogger/src/rewrite.c b/src/pmlogger/src/rewrite.c
new file mode 100644
index 0000000..cd8104e
--- /dev/null
+++ b/src/pmlogger/src/rewrite.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "logger.h"
+
+/*
+ * PDU for pmResult (PDU_RESULT) -- from libpcp/src/p_fetch.c
+ */
+
+typedef struct {
+ pmID pmid;
+ int numval; /* no. of vlist els to follow, or error */
+ int valfmt; /* insitu or pointer */
+ __pmValue_PDU vlist[1]; /* zero or more */
+} vlist_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* no. of PMIDs to follow */
+ __pmPDU data[1]; /* zero or more */
+} result_t;
+
+__pmPDU *
+rewrite_pdu(__pmPDU *pb, int version)
+{
+ if (version == PM_LOG_VERS02)
+ return pb;
+
+ fprintf(stderr, "Errors: do not know how to re-write the PDU buffer for a version %d archive\n", version);
+ exit(1);
+}
diff --git a/src/pmlogger/src/util.c b/src/pmlogger/src/util.c
new file mode 100644
index 0000000..eb09779
--- /dev/null
+++ b/src/pmlogger/src/util.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+void
+buildinst(int *numinst, int **intlist, char ***extlist, int intid, char *extid)
+{
+ char **el;
+ int *il;
+ int num = *numinst;
+
+ if (num == 0) {
+ il = NULL;
+ el = NULL;
+ }
+ else {
+ il = *intlist;
+ el = *extlist;
+ }
+
+ el = (char **)realloc(el, (num+1)*sizeof(el[0]));
+ il = (int *)realloc(il, (num+1)*sizeof(il[0]));
+
+ il[num] = intid;
+
+ if (extid == NULL)
+ el[num] = NULL;
+ else {
+ if (*extid == '"') {
+ char *p;
+ p = ++extid;
+ while (*p && *p != '"') p++;
+ *p = '\0';
+ }
+ el[num] = strdup(extid);
+ }
+
+ *numinst = ++num;
+ *intlist = il;
+ *extlist = el;
+}
+
+void
+freeinst(int *numinst, int *intlist, char **extlist)
+{
+ int i;
+
+ if (*numinst) {
+ free(intlist);
+ for (i = 0; i < *numinst; i++)
+ free(extlist[i]);
+ free(extlist);
+
+ *numinst = 0;
+ }
+}
+
+/*
+ * Link the new fetch groups back to their task structure
+ */
+void
+linkback(task_t *tp)
+{
+ fetchctl_t *fcp;
+
+ for (fcp = tp->t_fetch; fcp != NULL; fcp = fcp->f_next)
+ fcp->f_aux = (void *)tp;
+}