#! /bin/sh
#Tag 0x00010D01
# 
# Copyright (c) 1997-2000 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.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
# 
# Contact information: Silicon Graphics, Inc., 1500 Crittenden Lane,
# Mountain View, CA 94043, USA, or: http://www.sgi.com
#
# Standard Performance Metrics Domain Agent - Demo for Usenet News
#
# $Id: news.agent,v 2.12 2005/03/15 06:18:54 kenmcd Exp $
#

# Get standard environment
. /etc/pcp.env

prog=`basename $0`
iam=news
_debug=false
[ $# -gt 0 -a "X$1" = X-d ] && _debug=true

# make _trace true for trace debugging of PDU exchanges to $iam.log
#
_trace=false
_trace=true
$_trace && rm -f $iam.log

# temp files
#
tmp=/tmp/$$.pmdanews
trap "rm -f $tmp*; exit 0" 0 1 2 3 15

# echo to std out and to the log file if debugging
_fatal()
{
    echo "$prog: Error: $*"
    $_trace && echo "$prog: Error: $*" >>$iam.log
}

# strip top part of external PMID -> local PMID
_mypmid()
{
    pmid=`expr "$1" % $pmd_id`
}

#
# error codes and other symbolic constants from <pcp1.x/pcp/pmapi.h>
#
PM_COUNT_ONE=0
PM_ERR_INDOM=-1014
PM_ERR_INDOM_LOG=-1034
PM_ERR_INST=-1015
PM_ERR_INST_LOG=-1035
PM_ERR_IPC=-1021
PM_ERR_PMID=-1013
PM_ERR_PMID_LOG=-1033
PM_INDOM_NULL=-1
PM_IN_NULL=-1
PM_SEM_INSTANT=3
PM_TYPE_U32=1

# get PMDA domain from domain.h
eval `grep 'define.*NEWSAGENT' $PCP_PMDAS_DIR/news/domain.h \
      | sed -e 's/.*\(NEWSAGENT\)[ 	][ 	]*\(.[^ 	]*\).*/\1=\2/'`
# shift to make external PMID base
pmd_id=`echo $NEWSAGENT | $PCP_AWK_PROG '{ print $1 * (2 ^ 22) }'`

$_trace && ( echo; echo "shell variables ..." ) >>$iam.log 2>&1
$_trace && set >>$iam.log 2>&1
$_trace && echo '
---- end of preamble trace ---

' >>$iam.log

#
# try a few places for the $iam file
# Note. $PCP_PMDAS_DIR/news/active is really for debugging!
#
for file in $PCP_PMDAS_DIR/news/active /news/active /usr/lib/news/active
do
    if [ -f $file ]
    then
	ACTIVE=$file
	break
    fi
done
if [ X$ACTIVE = X ]
then
    _fatal "No active news file"
    exit 1
fi

# filter to select newsgroups of real interest
cat >$tmp.sed <<'End-of-File'
/^comp.sys.sgi /p
/^sgi.engr.all /p
/^sgi.bugs.sherwood /p
/^sgi.bad-attitude /p
/^sgi.general /p
End-of-File

#
# optionally set up at one or more instance domain(s) you know about ...
#
# the name of the file is magic, in as much as the instance domain identifier
# is encoded in it.
#
# the file format is one line per instance, with two fields - the first is
# the internal instance identifier, and the second is the external instance
# name.
#
indom=`expr $pmd_id + 1`
if [ ! -f $ACTIVE ]
then
    _fatal "no file $ACTIVE => you lose!"
    exit 1
else
    sed -n -f $tmp.sed <$ACTIVE \
    | sort \
    | $PCP_AWK_PROG >$tmp.indom_$indom '
$4 == "y"		{ print NR " " $1 }'
fi

_split()
{
    # split $1 into two variables, $2 for the first word, $3 for the rest
    xxx=`echo "$1" \
	  | sed -e 's/^[ 	]*//' \
		-e 's/$/ /' \
		-e "s/[ 	][ 	]*/\\' $3=\\'/" \
		-e "s/^/$2=\\'/" \
		-e 's/[ 	]*$/'"\\'"'/'`
    eval "$xxx"
}

_recv()
{
    #
    # handle input, including the \<newline> convention for continuation,
    # end-of-file, and the stripping of all leading and trailing white space
    #
    # returns with $cmd as the first word in the line, and $line as the
    # remainder of the line
    #
    _line=""
    while true
    do
	if read _tmp
	then
	    if [ "X$_line" = X ]
	    then
		# first time
		_line="$_tmp"
	    else
		# this must be a continuation line
		_line="$_line $_tmp"
	    fi
	    _tmp=`echo "$_line" | sed -e 's/[ 	]*\\$//'`
	    if [ "X$_line" = "X$_tmp" ]
	    then
		# no continuation
		break
	    else
		_line="$_tmp"
	    fi
	else
	    # end-of-file
	    $_trace && echo "_recv: EOF" >>$iam.log
	    exit 0
	fi
    done

    # strip white space and set $cmd and $line
    _split "$_line" cmd line

    $_trace && echo "_recv: \"$cmd $line\"" >>$iam.log
}

_resync()
{
    # suck input until we see the start of the next PDU
    cmd="."
    while [ "X$cmd" = X. ]
    do
	_recv
    done
}

_error()
{
    # send PDU_ERROR
    echo "ERROR $1"
    $_trace && echo "_xmit: ERROR $1" >>$iam.log
}

_cntprocs()
{
    $PCP_PS_PROG $PCP_PS_ALL_FLAGS \
    | sed -e 's/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9] / Mmm DD /' \
    | $PCP_AWK_PROG '
BEGIN			{ n = 0 }
$9 == "'"$1"'"	{ n++; next }
$9 ~ /\/'"$1"'$/	{ n++; next }
END				{ print n }'
}

_snapactive()
{
    cp $tmp.indom_$indom $tmp.indom
    if $1
    then
	# $1 == true for profile filtering over instance domain
	for prof in $tmp.prof_$indom $tmp.prof_def
	do
	    if [ -f $prof ]
	    then
		$PCP_AWK_PROG -f $prof $tmp.indom >$tmp.tmp
		mv $tmp.tmp $tmp.indom
		$_debug && ( rm -f indom; cp $tmp.indom $iam.indom )
		break
	    fi
	done
    fi
    sed -n -f $tmp.sed <$ACTIVE \
    | sort \
    | join -1 1 -2 2 -t' ' - $tmp.indom >$tmp.snap
    if [ $_debug ]
    then
	echo "_snap" >>$iam.log
	cat $tmp.snap >>$iam.log
    fi
}

while true
do
    case "$cmd"
    in

    "")
	_recv
	;;

    PROFILE)
	_split "$line" ctxnum line	# we can ignore the ctxnum, handled by pmcd
	_split "$line" state numprof
	rm -f $tmp.prof_*
	$_debug && rm -f prof_*
	cat <<End-of-File >$tmp.prof_def
{ if ($state == 0) print }
End-of-File
	$_debug && ( rm -f prof_def; cp $tmp.prof_def $iam.prof_def )
	i=0
	while [ $i -lt $numprof ]
	do
	    _recv
	    _split "$line" xindom line
	    _split "$line" xstate line
	    _split "$line" numinst line
	    if [ $numinst -gt 0 ]
	    then
		echo " { state=$xstate }" >$tmp.prof_$xindom
		for inst in $line
		do
			echo "\$1 == $inst { state=1-state }" >>$tmp.prof_$xindom
		done
		echo "state == 0 { print }" >>$tmp.prof_$xindom
		$_debug && ( rm -f prof_$xindom; cp $tmp.prof_$xindom $iam.prof_$xindom )
	    fi
	    i=`expr $i + 1`
	done
	cmd=""
	;;

    FETCH)
	# Note - no encoding for anything but integer values, yet
	_split "$line" ctxnum line	# we can ignore the ctxnum, handled by pmcd
	_split "$line" numpmid line	# we can ignore the ctxnum, handled by pmcd
	# the when arg is still in $line, but we can ignore it
	echo "RESULT $numpmid" >$tmp
	code=0
	i=0
	while [ $i -lt $numpmid ]
	do
	    _recv
	    if [ "X$cmd" = X. ]
	    then
		i=`expr $i + 1`
		_mypmid $line
		#
		# Instatiate Metric Values
		#
		# Real Stuff Goes Here
		#	- add one block to the case for each PMID you are
		#	  willing to provide a value for
		#
		case "$pmid"
		in
		    101)
			# news.readers.nnrpd
			echo ". $line 1 0 $PM_IN_NULL `_cntprocs in.nnrpd`" >>$tmp
			;;
		    111)
			# news.readers.rn
			echo ". $line 1 0 $PM_IN_NULL `_cntprocs rn`" >>$tmp
			;;
		    112)
			# news.readers.trn
			echo ". $line 1 0 $PM_IN_NULL `_cntprocs trn`" >>$tmp
			;;
		    113)
			# news.readers.xrn
			echo ". $line 1 0 $PM_IN_NULL `_cntprocs xrn`" >>$tmp
			;;
		    114)
			# news.readers.vn
			echo ". $line 1 0 $PM_IN_NULL `_cntprocs vn`" >>$tmp
			;;
		    201)
			# news.articles.total
			[ ! -f $tmp.snap ] && _snapactive false
			cnt=`$PCP_AWK_PROG <$tmp.snap '
BEGIN		{ n = 0 }
		{ n += $2 - $3 }
END		{ print n }'`
			echo ". $line 1 0 $PM_IN_NULL $cnt" >>$tmp
			;;
		    301)
			# news.articles.count
			[ ! -f $tmp.snap ] && _snapactive true
			nval=`wc -l <$tmp.snap | sed -e 's/  *//g'`
			$PCP_AWK_PROG <$tmp.snap >>$tmp '
BEGIN	{ printf ". '$line' '$nval' 0" }
	{ printf " %d %d",$NF,$2 - $3 }
END	{ print "" }'
			;;
		    302)
			# news.articles.last
			[ ! -f $tmp.snap ] && _snapactive true
			nval=`wc -l <$tmp.snap | sed -e 's/  *//g'`
			$PCP_AWK_PROG <$tmp.snap >>$tmp '
BEGIN	{ printf ". '$line' '$nval' 0" }
	{ printf " %d %d",$NF,$2 }
END	{ print "" }'
			;;
		    *)
			code=$PM_ERR_PMID
			$_trace && echo "$code: $pmid" >>$iam.log
			break
			;;
		esac
	    else
		code=$PM_ERR_IPC
		break
	    fi
	done
	if [ $code = 0 ]
	then
	    cat $tmp
	    $_trace && sed -e 's/^/_xmit: /' $tmp >>$iam.log
	    cmd=""
	else
	    _error $code
	    _resync
	fi
	rm -f $tmp.snap
	;;

    INSTANCE_REQ)
	_split "$line" indom line
	_split "$line" inst line
	_split "$line" name line
	if [ -f $tmp.indom_$indom ]
	then
	    # appears to be an instance domain we know something about
	    if [ "X$inst" = "X?" -a "X$name" = "X?" ]
	    then
		sed -e 's/[ 	]*#.*//' <$tmp.indom_$indom \
		| $PCP_AWK_PROG >$tmp.hdr '
NF==0	{ next }
	{ print ". " $1 " " $2 >"'$tmp'"; numinst++ }
END	{ print "INSTANCE '"$indom"' " numinst }'
		cat $tmp.hdr $tmp
		$_trace && ( cat $tmp.hdr $tmp | sed -e 's/^/_xmit: /' >>$iam.log )
		cmd=""
	    elif [ "X$inst" = "X?" ]
	    then
		sed -e 's/[ 	]*#.*//' <$tmp.indom_$indom \
		| $PCP_AWK_PROG >$tmp '
NF==0		{ next }
$2 == "'$name'"	{ print "INSTANCE '"$indom"' 1"
		  print ". " $1 " " $2
		  exit
	    }'
	    if [ -s $tmp ]
	    then
		cat $tmp
		$_trace && sed -e 's/^/_xmit: /' $tmp >>$iam.log
		cmd=""
	    else
		_error $PM_ERR_INST
		_resync
	    fi
	    elif [ "X$name" = "X?" ]
	    then
		sed -e 's/[ 	]*#.*//' <$tmp.indom_$indom \
		| $PCP_AWK_PROG >$tmp '
NF==0		{ next }
$1 == '$inst'	{ print "INSTANCE '"$indom"' 1"
		  print ". " $1 " " $2
		  exit
		}'
		if [ -s $tmp ]
		then
		    cat $tmp
		    $_trace && sed -e 's/^/_xmit: /' $tmp >>$iam.log
		    cmd=""
		else
		    _error $PM_ERR_INST
		    _resync
		fi
	    else
		_error $PM_ERR_IPC
		_resync
	    fi
	else
	    _error $PM_ERR_INDOM
	    _resync
	fi
	;;

    DESC_REQ)
	_mypmid $line
	#
	# Real Stuff Goes In Here
	#
	# For each metric, need to return: pmid, type, indom, sem,
	# dimension (space, time and count) and scale (space, time
	# and count) -- refer to pmDesc in <pmapi.h>
	#
	xmit=""
	case "$pmid"
	in
	    101|111|112|113|114)
		xmit="DESC `expr $pmd_id + $pmid` $PM_TYPE_U32 $PM_INDOM_NULL $PM_SEM_INSTANT 0 0 1 0 0 $PM_COUNT_ONE"
		;;
	    201)
		xmit="DESC `expr $pmd_id + $pmid` $PM_TYPE_U32 $PM_INDOM_NULL $PM_SEM_INSTANT 0 0 1 0 0 $PM_COUNT_ONE"
		;;
	    301)
		xmit="DESC `expr $pmd_id + $pmid` $PM_TYPE_U32 $indom $PM_SEM_INSTANT 0 0 1 0 0 $PM_COUNT_ONE"
		;;
	    302)
		xmit="DESC `expr $pmd_id + $pmid` $PM_TYPE_U32 $indom $PM_SEM_INSTANT 0 0 1 0 0 $PM_COUNT_ONE"
		;;
	    *)
		_error $PM_ERR_PMID
		;;
	esac
	if [ "X$xmit" != X ]
	then
	    echo $xmit
	    $_trace && ( echo $xmit | sed -e 's/^/_xmit: /' >>$iam.log )
	    cmd=""
	else
	    _resync
	fi
	;;

    TEXT_REQ)
	_split "$line" fullpmid type
	_mypmid $fullpmid
	#
	# Real Stuff Goes In Here
	#
	# For each metric, need to return: no. bytes and text lines
	#
	rm -f $tmp
	ok=true
	case "$pmid"
	in
	    201)
		if [ $type = 5 ]
		then
		    #
		    # we are not going to do all of it ... valid types are
		    # PMID one-line (5), full PMID help (6),
		    # indom one-line (9) and full indom help (10)
		    #
		    echo "Total number of articles received for each newsgroup" >$tmp
		elif [ $type = 6 ]
		then
		    cat <<'End-of-File' >$tmp
Total number of articles received for each newsgroup.
Note this is the historical running total, see news.articles.count for
the curent total of un-expired articles by newsgroup.
End-of-File
		fi
		;;
	    301)
		# (see above for type enums)
		if [ $type = 5 ]
		then
		    echo "Total number of un-expired articles in each newsgroup" >$tmp
		elif [ $type = 6 ]
		then
		    cat <<'End-of-File' >$tmp
Total number of un-expired articles in each news group.
End-of-File
		fi
		;;
	    101|111|112|113|114|302)
		# no help available
		;;
	    *)
		_error $PM_ERR_PMID
		ok=false
		;;
	esac
	if $ok
	then
	    if [ -s $tmp ]
	    then
		nbyte=`wc -c <$tmp | sed -e 's/ //g'`
	    else
		nbyte=0
	    fi
	    echo "TEXT $fullpmid $type $nbyte"
	    $_trace && echo "_xmit: TEXT $fullpmid $type $nbyte" >>$iam.log
	    if [ $nbyte != 0 ]
	    then
		sed -e 's/^/. /' $tmp
		$_trace && sed -e 's/^/. /' -e 's/^/_xmit: /' $tmp >>$iam.log
	    fi
	    cmd=""
	else
	    _resync
	fi
	;;

    CONTROL_REQ)
	_split "$line" control line
	_split "$line" state line
	_split "$line" rate numpmid
	#
	# this is reall a FYI NOP ... consume and ignore
	#
	while [ $numpmid -gt 0 ]
	do
	    _recv
	    numpmid=`expr $numpmid - 1`
	done
	cmd=""
	;;

    RESULT)
	_split "$line" numpmid line
	#
	# really a pmStore, we don't allow this within this domain, so
	# suck up the PDU and return -EACCES (-13)
	#
	while [ $numpmid -gt 0 ]
	do
	    _recv
	    numpmid=`expr $numpmid - 1`
	done
	_error -13
	_resync
	;;

    *)
	# bogus PDU, report error and discard input to start of next PDU
	_error $PM_ERR_IPC
	_resync
	;;
    esac
done
