# Copyright (c) 1995-2005 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.
#

# Get standard environment
. $PCP_DIR/etc/pcp.env
# ... and _is_archive()
. $PCP_SHARE_DIR/lib/utilproc.sh

prog=`basename $0`

PMVIEW=pmview
PMPROBE=pmprobe
CONFIRM="$PCP_XCONFIRM_PROG"
ECHONL="echo -n"
_multiple_sources=false

#
# Standard usage and command-line argument parsing for pmview front ends.
# This file should be included by pmview front end scripts to present a
# consistent interface. See pmview(1), dkvis(1), mpvis(1) and nfsvis(1)
# for more information on their respective interfaces.
#

#
# The front end scripts should call _pmview_usage after their own usage 
# information in a subroutine called _usage. The _usage subroutine may be 
# called by either _pmview_note or _pmview_args.
#
_pmview_usage()
{
  if $_multiple_sources
  then
      echo -n '
  -A align      align sample times on natural boundaries
  -a archive[,archive...]
                metrics source is one or more PCP archive logs
  -C            check configuration file and exit
  -h host[,host...] metrics source is PMCD on one or more hosts'

  else

      echo -n '
  -A align      align sample times on natural boundaries
  -a archive    metrics source is a PCP archive log
  -C            check configuration file and exit
  -h host       metrics source is PMCD on host'

  fi

  echo '
  -n pmnsfile   use an alternative PMNS
  -O offset     initial offset into the time window
  -p port       port name for connection to existing time control
  -S starttime  start of the time window
  -T endtime    end of the time window
  -t interval   sample interval [default 2.0 seconds]
  -x version    use pmlaunch(5) version [default version 2.0]
  -z            set reporting timezone to local time of metrics source
  -Z timezone   set reporting timezone

  -display      display-string
  -geometry     geometry-string
  -name         name-string
  -title        title-string'
}

# Most front-end scripts can handle input from only one source, and
# hence only one -h or -a option.
#
# For those than can handle multiple sources, they should call
# _pmview_multiple_sources_are_ok before calling _pmview_args to
# enable the following extensions to the -a and -h options:
#	-a a1 -a a2 ...		multiple -a options
#	-h h1 -h h2 ...		multiple -h options
#	-a a1,a2,...		multiple archives with the -a option
#	-h h1,h2,...		multiple hosts with the -h option
#
_pmview_multiple_sources_are_ok()
{
    _multiple_sources=true
}

# One of the first actions of a front end script should be to call 
# _pmview_args. It sets the following variables:
#
# host		the first host specified with -h (if any) in the format
#			hostname
#		else the host from the first archive in the format
#			hostname (Archive archivename)
# arch		the first archive specified with -a (if any).
#		All archives are passed on to pmview with one -a
#		argument per archive via $args
# numsource	Number of metrics sources (hosts or archives)
# args          The list of args that pmview will comprehend and use.
# otherArgs     The arguments pmview will not understand and should be
#               handled by the front end script.
# titleArg	The title the user prefers. If empty, the title should be
#               provided by the front end script.
# prog		The name of the program.
# namespace	The namespace (including the flag) if specified, else empty
#               eg "-n foo"
# msource       The default metrics source, whether live or an archive,
#		including the flag. e.g. "-h blah" or "-a first_archive".
#		Taken from the first encountered -a or -h option.
#
# sourcelist	space separated list of hosts or archives
#
_pmview_args()
{

_seen_host=false
_seen_arch=false
host=""
arch=""
numsource=0
args=""
otherArgs=""
titleArg=""
namespace=""
msource=""

if [ $# -eq 1 -a '$1' = '-\?' ]
then
   _usage
   exit 0
fi

while [ $# -gt 0 ]
do
    case $1
    in
	-g*|-di*|-name|-xrm)
	    # assume an X11 argument
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: X-11 option $1 requires one argument"
		#NOTREACHED
	    fi
	    args="$args $1 '$2'"
	    shift
	    ;;

	-title)
	    # assume an X11 argument
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: Option $1 requires one argument"
		#NOTREACHED
		exit 1
	    fi
	    titleArg="$2"
	    shift
	    ;;

	-A|-D|-O|-p|-S|-T|-t|-x|-Z)
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: Option $1 requires one argument"
		#NOTREACHED
	    fi
	    args="$args $1 '$2'"
	    shift
	    ;;

	-A*|-D*|-O*|-p*|-S*|-T*|-t*|-Z*|-C|-z)
	    args="$args $1"
	    ;;

	-a)
	    if $_seen_host
	    then
		_pmview_note Usage-Error error "$prog: Only one of the -h or -a options may be specified"
		#NOTREACHED
	    fi
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: Option $1 requires one argument"
		#NOTREACHED
	    fi
	    for _archbase in `echo "$2" | sed -e 's/,/ /g'`
	    do
		if [ $numsource -gt 0 -a $_multiple_sources = false ]
		then
		    _pmview_note Usage-Error error "$prog: Only one -a option may be specified"
		    # NOTREACHED
		fi
		if _is_archive $_archbase 2>&1
		then
		    _archbase=`echo $_archbase | sed -e 's/\.[^.]*$//'`
		fi
		# at least $_archbase.0 and $_archbase.meta have to be here
		#
		if _is_archive $_archbase.0 && _is_archive $_archbase.meta
		then
		    :
		else
		    _pmview_note Error error "$prog: \"$_archbase\" is not the basename of a valid PCP archive"
		    #NOTREACHED
		fi
		if [ -z "$arch" ]
		then
		    # first archive seen
		    #
		    arch=$_archbase
		    msource="-a $_archbase"
		    host=`pmdumplog -l $arch \
			| $PCP_AWK_PROG '/^Performance/ {print $5}' \
			| sed -e 's/,//g'`
		    [ "X$host" = X ] && host="unknown host"
		    host="$host (Archive $arch)"
		fi

		# pmview(1) can handle multiple -a options, so
		# pass _all_ archive names back to the caller both as
		# -a options via $args and in $sourcelist (counted by
		# $numsource).
		#
		# Note: multiple -h options are handled slightly
		#	differently, see also the comments for -h below.
		#

		args="$args -a $_archbase"
		if [ -z "$sourcelist" ]; then
		    sourcelist=$_archbase
		else
		    sourcelist="$sourcelist $_archbase"
		fi
		numsource=`expr $numsource + 1`
	    done
	    _seen_arch=true
	    shift
	    ;;	

	-h)
	    if $_seen_arch
	    then
		_pmview_note Usage-Error error "$prog: Only one of the -h or -a options may be specified"
		#NOTREACHED
	    fi
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: Option $1 requires one argument"
		#NOTREACHED
	    fi
	    for _host in `echo "$2" | sed -e 's/,/ /g'`
	    do
		if [ $numsource -gt 0 -a $_multiple_sources = false ]
		then
		    _pmview_note Usage-Error error "$prog: Only one -h option may be specified"
		    # NOTREACHED
		fi
		if [ -z "$host" ]
		then
		    host=$_host
		    msource="-h $_host"

		    # pmview(1) can handle only one -h options, so
		    # pass the _first_ host name back to the caller as a
		    # -h option via $args and _all_ hostnames via
		    # $sourcelist (counted by $numsource).
		    #
		    # Note: multiple -a options are handled slightly
		    #	differently, see also the comments for -a above.
		    #

		    args="$args -h $_host"
		fi
		if [ -z "$sourcelist" ]; then
		    sourcelist=$_host
		else
		    sourcelist="$sourcelist $_host"
		fi
		numsource=`expr $numsource + 1`
	    done
	    _seen_host=true
	    shift
	    ;;	

	-n)
	    if [ $# -lt 2 ]
	    then
		_pmview_note Usage-Error error "$prog: Option $1 requires one argument"
		#NOTREACHED
	    fi
	    namespace="-n $2"
	    args="$args -n $2"
	    shift
	    ;;

	*)
	    otherArgs="$otherArgs $1"
	    ;;

    esac
    shift

done

if [ -z "$host" ]
then
    host=`pmhostname`
    msource="-h $host"
fi
}

# standard fatal error reporting
# Usage: _pmview_error message goes in here
#        _pmview_error -f file
#
_pmview_error()
{
    _pmview_note Error error $*
}

# standard warning
# Usage: _pmview_warning message goes in here
#        _pmview_warning -f file
#
_pmview_warning()
{
    _pmview_note Warning warning $*
}

# standard info
# Usage: _pmview_info message goes in here
#        _pmview_info -f file
#
_pmview_info()
{
    _pmview_note Info info $*
}

# generic notifier
# Usage: _pmview_note tag icon args ...
#
_pmview_note()
{
    tag=$1; shift
    icon=$1; shift
    button=""
    [ $tag = Error ] && button="-B Quit"
    [ $tag = Usage-Error ] && button="-B Quit -b Usage"

    [ X"$PCP_STDERR" = XDISPLAY -a -z "$DISPLAY" ] && unset PCP_STDERR

    if [ $# -eq 2 -a "X$1" = X-f ]
    then
	case "$PCP_STDERR"
	in
	    DISPLAY)
		ans=`$CONFIRM -icon $icon -file $2 -useslider -header "$tag $prog" $button 2>&1`
		;;
	    '')
		echo "$tag:" >&2
		cat $2 >&2
		ans=Quit
		;;
	    *)
		echo "$tag:" >>$PCP_STDERR
		cat $2 >>$PCP_STDERR
		ans=Quit
		;;
	esac
    else
	case "$PCP_STDERR"
	in
	    DISPLAY)
		ans=`$CONFIRM -icon $icon -t "$*" -noframe -header "$tag $prog" $button 2>&1`
		;;
	    '')
		echo "$tag: $*" >&2
		ans=Quit
		;;
	    *)
		echo "$tag: $*" >>$PCP_STDERR
		ans=Quit
		;;
	esac
    fi

    if [ $tag = Usage-Error ]
    then
	[ $ans = Usage ] && _usage
	tag=Error
    fi

    [ $tag = Error ] && exit 1
}

# used internally by _pmview_cache_fetch() and _pmview_fetch()
#
_pmview_probe()
{
    flag=$1
    shift
    ( echo $* \
      ; echo "-----" \
      ; ( $PMPROBE $namespace $msource $flag $* 2>$tmp.pmview_err \
	  | tee -a $tmp.pmview_fetch \
	) \
    ) \
    | tr ' ' '\012' \
    | sed -e 's/"//g' \
    | $PCP_AWK_PROG '
BEGIN		{ last = 0 }
$1 == "-----"	{ state = 1; next }
state == 0	{ metric[last] = pat[last] = $1
		  # deal with arguments that are non-terminals in the PMNS
		  # so pmprobe a.b => a.b.c a.b.c.x a.b.d etc
		  gsub("\\.", "\\.", pat[last])
		  pat[last] = "^" pat[last] "\\."
		  last++
		  next
		}
state > 0	{ for (i = 0; i < last; i++) {
		    if (metric[i] == $1 ||  match($1, pat[i]) > 0) {
			# new matching metric name
			name=$1
			state=2
			next
		    }
		  }
		}
state == 2	{ if ($1 > 0) state = 3
		  else state = 1
		  next
		}
state == 3	{ printf("%s%s|%s\n", "'"$flag|$msource|"'",name,$1) }'
}

# Fetch metrics and cache result
# input
#   $1                 - pmprobe flag
#   $*                 - 1 or more metrics
# output
#   $tmp.pmview_cache  - cached values, with this format
#	            pmprobe flag|metric source|metric name|pmprobe result
#
_pmview_cache_fetch()
{
    flag=$1
    shift
    _pmview_probe $flag $* >>$tmp.pmview_cache

    return 0
}

# Fetch metrics
#
# input
#   $1                 - pmprobe flag
#   $2                 - metric name
# output
#   $number            - number of values
#   $tmp.pmview_result - values
#
_pmview_fetch()
{
    flag=$1
    metric=$2
    rm -f $tmp.pmview_fetch $tmp.pmview_result
    if [ -s $tmp.pmview_cache ]
    then
	$PCP_AWK_PROG -F\| \
	    <$tmp.pmview_cache >$tmp.pmview_result \
'
$1 == "'"$flag"'" && $2 == "'"$msource"'" && $3 == "'"$metric"'" { print $4 }'
    fi

    if [ ! -s $tmp.pmview_result ]
    then
	# cache miss, forced to probe
	#
	_pmview_probe $flag $metric \
	| $PCP_AWK_PROG -F\| >$tmp.pmview_result '{print $4}'
    fi

    if [ -s $tmp.pmview_result ]
    then
	number=`wc -l <$tmp.pmview_result | sed -e 's/ //g'`
    else
	if [ -s $tmp.pmview_fetch ]
	then
	    check=`cut -d ' ' -f 2 $tmp.pmview_fetch`
	    if [ "$check" = "$metric" ]
	    then
		# *.pmview_fetch looks valid, extract numval from 2nd field
		#
		number=`cut -d ' ' -f 2 $tmp.pmview_fetch`
	    else
		# *.pmview_fetch exists, but does not contain
		# pmprobe output, more than likely this is some
		# sort of fatal error message ... but _real_ message
		# is likely to be in *.pmview_err
		#
		[ -s $tmp.pmview_err ] && mv $tmp.pmview_err $tmp.pmview_fetch
		number=-1
	    fi
	else
	    number=-1
	    mv $tmp.pmview_err $tmp.pmview_fetch
	fi
    fi
    if [ $number -le 0 ]
    then
	rm -f $tmp.pmview_result
	return 1
    fi

    return 0
}

# Fetch the metric values
#
# input
#   $1                 - metric name
# output
#   $number            - number of values
#   $tmp.pmview_result - values
#
_pmview_fetch_values()
{
    _pmview_fetch -v $1
    return $?
}

# Fetch the metric instance list
#
# input
#   $1                 - metric name
# output
#   $number            - number of instances
#   $tmp.pmview_result - instances
#
_pmview_fetch_indom()
{
    _pmview_fetch -I $1
    return $?
}

# Convert pmprobe/pminfo error message into something useful and
# consistent
#
_pmview_fetch_mesg()
{
    $PCP_AWK_PROG '
$1 == "pmprobe:"	{ $1 = "'$prog':"; print; exit }
$1 == "pminfo:"		{ $1 = "'$prog':"; print; exit }
$1 == "Error:"		{ $1 = ":"; 
			  printf("%s: %s%s\n", "'$prog'", metric, $0); exit }
$1 == "inst"		{ exit }
NF == 1			{ metric = $1; next }
NF == 0			{ next }
NF == 2 && $2 == "0"	{ printf("%s: %s: No values available\n", "'$prog'", $1); exit}
			{ $2 = ":"; print "'$prog': " $0; exit}' \
    | sed "s/ : /: /" \
    | fmt
}

# Generate error metric for failed fetch
#
_pmview_fetch_fail()
{
    cat $tmp.pmview_fetch | _pmview_fetch_mesg >> $tmp.msg
    echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp.msg
    _pmview_error -f $tmp.msg
    # NOTREACHED
}

# Generate warning message for failed fetch
#
_pmview_fetch_warn()
{
    cat $tmp.pmview_fetch | _pmview_fetch_mesg >> $tmp.msg
    echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp.msg
    _pmview_warning -f $tmp.msg
    rm -f $tmp.msg
}

# Check that $OPTARG for option $1 is a positive integer
# ...note the creative use of unary - to prevent leading signs
#
_pmview_unsigned()
{
    if [ "X-$OPTARG" != "X`expr 0 + -$OPTARG 2>/dev/null`" ]
    then
	_pmview_error "$prog: -$1 option must have a positive integral argument"
	# NOTREACHED
    fi
}
