#! /bin/sh
#
# Copyright (c) 2014,2018 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

PMLOGGER="$PCP_BINADM_DIR/pmlogger"
PMLOGGERENVS="$PCP_SYSCONFIG_DIR/pmlogger"

# 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
  -n=FILE, --namespace=FILE use an alternative PMNS
  -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
  -H=LABELHOST, --labelhost override the hostname written into the label
  -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
  -n=FILE, --namespace=FILE use an alternative PMNS
  -K=SPEC, --spec-local=SPEC optional additional PMDA spec for local connection
  -o, --local-PMDA        metrics source is local connection to a PMDA
  -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_VAR_DIR/config/pmlogger/$config" ]
		    then
			config="$PCP_VAR_DIR/config/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|-o|-r|-y)
		args="${args}$1 "
		;;

	-D|-H|-K|-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
    # we expect to find only one matching process ...
    #
    if [ "`wc -l <$tmp/out | sed -e 's/ //g'`" -gt 1 ]
    then
	$VERBOSE && echo " botch"
	if $primary
	then
	    echo "$prog: Error: expecting at most one primary pmlogger, found:"
	else
	    echo "$prog: Error: expecting at most one pmlogger with pid=$pid, found:"
	fi
	cat $tmp/out
	_abandon
    fi
    $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' -e '/^hotproc$/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, and prepare primary pmlogger environment
#
PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
eval $RM -f "$PM_LOG_PORT_DIR"/$pid
if $primary
then
    eval $RM -f "$PM_LOG_PORT_DIR/primary"
    envs=`grep ^PMLOGGER "$PMLOGGERENVS" 2>/dev/null`
else
    envs=""
fi

# 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.*"
	[ -f SaveLogs/$archive.log ] && eval $MV SaveLogs/$archive.log SaveLogs/$archive-$suff.log
    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
    eval $envs '${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

# if SaveLogs exists in the same directory that the archive
# is being created, save pmlogger log file there as well
# (we've already cd'd into $dir)
#
if [ -d SaveLogs ]
then
    if [ ! -f SaveLogs/$LOGNAME.log ]
    then
	_do_cmd "ln $logfile SaveLogs/$archive.log"
    fi
fi

exit
