diff options
Diffstat (limited to 'src/pmlogger')
-rw-r--r-- | src/pmlogger/GNUmakefile | 69 | ||||
-rw-r--r-- | src/pmlogger/control | 52 | ||||
-rw-r--r-- | src/pmlogger/crontab.in | 8 | ||||
-rw-r--r-- | src/pmlogger/pmlogger.service.in | 13 | ||||
-rwxr-xr-x | src/pmlogger/pmlogger_check.sh | 867 | ||||
-rwxr-xr-x | src/pmlogger/pmlogger_daily.sh | 952 | ||||
-rwxr-xr-x | src/pmlogger/pmlogger_merge.sh | 282 | ||||
-rwxr-xr-x | src/pmlogger/pmlogmv.sh | 261 | ||||
-rwxr-xr-x | src/pmlogger/pmnewlog.sh | 702 | ||||
-rw-r--r-- | src/pmlogger/rc_pmlogger | 301 | ||||
-rw-r--r-- | src/pmlogger/src/GNUmakefile | 50 | ||||
-rw-r--r-- | src/pmlogger/src/callback.c | 771 | ||||
-rw-r--r-- | src/pmlogger/src/check.c | 164 | ||||
-rw-r--r-- | src/pmlogger/src/dopdu.c | 1491 | ||||
-rw-r--r-- | src/pmlogger/src/error.c | 35 | ||||
-rw-r--r-- | src/pmlogger/src/events.c | 113 | ||||
-rw-r--r-- | src/pmlogger/src/fetch.c | 186 | ||||
-rw-r--r-- | src/pmlogger/src/gram.y | 570 | ||||
-rw-r--r-- | src/pmlogger/src/lex.l | 144 | ||||
-rw-r--r-- | src/pmlogger/src/logger.h | 181 | ||||
-rw-r--r-- | src/pmlogger/src/pmlogger.c | 1186 | ||||
-rw-r--r-- | src/pmlogger/src/ports.c | 734 | ||||
-rw-r--r-- | src/pmlogger/src/preamble.c | 208 | ||||
-rw-r--r-- | src/pmlogger/src/rewrite.c | 47 | ||||
-rw-r--r-- | src/pmlogger/src/util.c | 81 |
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; +} |