#!/usr/local/bin/perl
#
# $Header: /home/vikas/netmgt/nocol/src/perlnocol/RCS/hostmon-client,v 1.3 1994/11/29 20:57:06 vikas Exp $
#
#	hostmon-client.sun4
#
# Part of the NOCOL monitoring package. Run's system commands on the
# system and prints out system statistics. Output is parsed by the
# 'hostmon' server program which then generates output in the NOCOL format.
#
#	Copyright 1994 Vikas Aggarwal, vikas@navya.com
#
# Author:  Vikas Aggarwal,  vikas@navya.com
#
# Log:
#	Vikas: Support for SunOS 4.x, Solaris2, OSF1
#	Doug Needham (dneedham@csi.compuserve.com):  BSDI port
#
#
# See COPYRIGHT file for full details.
##
#
## Checks the following:
#
#	Uptime =	'uptime' (for reboots)
#	Load average =	'uptime'
#	NFS stats =	'nfsstat' -rc/-m/
#	Swap space =	'pstat or swapon'
#	CPU times =	'iostat'
#	Disk i/o =	'iostat'
#	Page Faults/Swaps =	'vmstat'
#	Disk space =	'df'
#	Network Interface =	'netstat' -m/-s/-i/
#	
##
## Output of this file
##
#	TIME 784419191 secs
#	Uptime 62266 mins
#	Load5 0 load-5min
#	FileTable 13 %used
#	InodeTable 13 %used
#	ProcTable 13 %used
#	SwapSpace 25140 MBytes
#	NFStimeouts 0 %timeouts
#	NFSrequests 7433383 requests
#	DFspace_avail 6 MB /
#	DFspace_%used 71 %full /
#	DFspace_avail 72 MB /usr
#	DFspace_%used 44 %full /usr
#	DFspace_avail 354 MB /export/home
#	DFspace_%used 22 %full /export/home
#	DFinodes_avail 13699 inodes /
#	DFinodes_%used 9 %inodes /
#	DFinodes_avail 70880 inodes /usr
#	DFinodes_%used 6 %inodes /usr
#	DFinodes_avail 251048 inodes /export/home
#	DFinodes_%used 1 %inodes /export/home
#	IOseek 0.0 msps dk0
#	IObw 3.2 %util dk0
#	MemAvm 0 avm
#	MemFree 14784 fre
#	PageIn 0 pi
#	PageOut 0 po
#	CtxtSw  42 rate
#	CPUus 2 %user
#	CPUsy 1 %system
#	CPUidle 97 %idle
#	NetIErr 0 PktRate
#	NetOErr 0 PktRate
#	NetColl 0 PktRate
##
############################
## Variables customization #
############################
#
$sleeptime = 5*60 ;
@monitorlist = (uptime, pstat, nfsstat, df, iostat, vmstat, netstat);

## Following is needed for the 'daemon' part of the client which listens on
#  the following service port for any connection requests. The connections are
#  checked against a permit list and then the entire data file is dumped over.
$HOSTMON_SERVICE = "hostmon" unless $HOSTMON_SERVICE ;
$HOSTMON_PORT = 5355 unless $HOSTMON_PORT ;
$NLOG_HOST = "nocol.foo.com" ;	# SET_THIS to the permitted hostmon server host

local ($DATAFILE) = '/etc/motd' ; # default if no $dfile

## Define the list of permitted hosts that can connect to the port above
# and get data (the name of the host that runs the hostmon server)
# @permithosts = ("abc.foo.com", "129.1.1.134");
@permithosts = ("$NLOG_HOST") unless @permithosts ;

$debug = 0;                             # set to 1 for debugging output
 
#  The output data filename is "/tmp/<hostname>.hostmon"
#  This must match the name used in the rcp from 'hostmon' 
$dfile = "/tmp/" . `hostname` ; chop $dfile ; $dfile .= ".hostmon" ;

#######################################################################

#
## Following couple of subroutines are the 'daemon' part of the client.
#
sub checkconnect {
    local($con, $permit, $junk);
    local ($port) = $HOSTMON_PORT ;
    local(@permitaddr, $permitaddr);		# list built by checkconnect()
    local($dfile) = $DATAFILE unless $dfile ;

    $debug && print STDERR "(debug) telnet read datafile= $dfile\n";

    ## Convert IP address of permitted hosts.
    foreach $permit (@permithosts)
    {
	if ($permit =~ /^\d+/)	# IP address, not name
	{
	    local(@a) = split (/\./, $permit);
	    $permitaddr = pack ('C4', @a);

	    ($junk,$junk,$junk,$junk, @ipaddr) = 
		gethostbyaddr($permitaddr, $AF_INET);
	}
	else {
	    ($junk,$junk,$junk,$junk, $permitaddr) = gethostbyname($permit);
	}

	if ($permitaddr) { 
	    push (@permitaddr, $permitaddr);
	    if ($debug)
	    {
		local (@straddr) = unpack ('C4', $permitaddr);
		print STDERR "(dbg) telnet_permit $permit (@straddr)\n";
	    }
	}
	else {
	    print STDERR "Cannot gethostbyname for $permit, ignoring\n";
	}

    }			# end: foreach


    ## Get a socket
    local($AF_INET, $SOCK_STREAM) = (2, 1);
    local ($sockaddr) = 'S n a4 x8' ;
    ($junk, $junk, $proto) = getprotobyname('tcp');
    if (! socket(S, $AF_INET, $SOCK_STREAM, $proto))
    {
	print STDERR "socket() call failed: $!\n";
	return (-1);
    }
	
    ## Get port number
    ($junk, $junk, $port) = getservbyname($HOSTMON_SERVICE, 'tcp')
	unless $port =~ /^\d+$/;
    $debug && print STDERR "(dbg) telnet_daemon Port= $port\n" ;

    $here = pack($sockaddr, $AF_INET, $port, "\0\0\0\0");
    
    ## Now bind to the socket
    select (NS) ; $| = 1; select (STDOUT); # make unbuffered socket
    bind (S, $here) || die "bind: $!";
    listen (S, 5) || die "listen: $!";
    select (S); $| = 1; select (STDOUT);

    ## Listen for connections forever.
    for ($con = 1; ; ++$con)
    {
	local ($remoteaddr, $remote);

	($debug >1) && print STDERR "(telnet_daemon) Listening for connection: $con\n";
	($remote = accept(NS, S)) || die $!; # waits here for connection

	($af, $port, $remoteaddr) = unpack($sockaddr, $remote);
	@remoteaddr = unpack('C4', $remoteaddr);
	$debug && print STDERR "(telnet_daemon)connection from @remoteaddr\n" ;

	## Check remote's address
	local ($paddr);
	local ($goodhost) = 0;

	foreach $paddr (@permitaddr)  {
	    if ($paddr eq $remoteaddr) { $goodhost = 1; last; }	# note 'eq'
	}

	if (! $goodhost)  {
	    $debug && print STDERR "(telnet_daemon) rejecting connection from @remoteaddr\n";
	    print NS "Connection not permitted\n" ;
	    close (NS); next ;
	}
	$debug && print STDERR "(telnet_daemon) allowing connection from @remoteaddr\n";

#	while (<NS>) { print "$con: $_"; } # to read from remote site

	if (! -e $dfile || -z $dfile) {sleep 2;} # in case mv-ing over
	if (! -e $dfile || -z $dfile) {close NS; next;} # don't wait
	open (IFILE, $dfile);
	while (<IFILE>) {
	    print NS $_ ;
	}
	close IFILE ;
	close (NS);
    }				# end: forever

}	# end: checkconnect()


## Fork and create a telnet daemon that listens on port HOSTMON_PORT
#  Fork twice, and then exit so that the parent is the init process
#  since we dont want any orphans.
#
sub create_telnet_daemon {
    local ($myname) = "$0 (telnet_daemon)";
    local ($tmppidf) = "/tmp/pidxfr" . $$;

    if ($childpid) {
	foreach (`ps $childpid`) {chop; /$childpid\s+/ && return; }
    }

    if (fork) {			# in parent
	wait ;			# wait for first child to exit...
	open (CHILDPID, "< $tmppidf");
	while (<CHILDPID>)  { $childpid = $_ ; }
	close (CHILDPID); unlink ($tmppidf);
	$debug && print STDERR "(debug) Forked telnet_daemon ($childpid)\n"; 
	return ($childpid) ;
    }				# parent returns

    ## first child process forks the real child and then exits. This
    #  mumbo jumbo prevents a 'orphan' child (zombie processes).
    if (($childpid = fork)) {
	open (CHILDPID, "> $tmppidf");
	print CHILDPID $childpid ; # no newline
	close (CHILDPID);
	exit 0;
    }				# first child exits quickly

    # Child's child process is the real telnet daemon...
    sleep 1 until getppid == 1;
    foreach ('TERM','HUP','KILL','QUIT') { $SIG{$_} = 'DEFAULT'; }
    $0 = "$0 (telnet_daemon)" ;	# change the name shown in 'ps'
    sleep(2);			# allow older daemon to die first
    &checkconnect ;		# never returns
    exit ;			# in case it ever does return

}	# end: create_telnet_daemon()


#

###
###
# All the following functions must print out the following:
#   		VARIABLE VALUE UNITS COMMENT
# For variables that are part of a varying maximum, normalize it and give
# the percentage instead (e.g. free memory, etc.)
###
###

### csh% uptime
###  12:09pm  up 16 days, 19:58,  5 users,  load average: 0.32, 0.21, 0.01
###
sub uptime {
    local ($cmd) = "uptime";
    local ($value) = -1 ;
    local ($uptime) = 0;    

    open (CMD, "$cmd |");
    while (<CMD>) {
	$debug &&  print STDERR "(debug)uptime: $_" ;
	chop; tr/\r\n//d;

	if ( /^.*up\s+(\d+)\s+day.*,\s*(\d+):(\d+),.*load\s*average:\s+(\S+),\s+(\S+),\s+(\S+).*$/ ) {
	    $uptime = ($1 * 1440) + ($2 * 60) + $3;
	    printf "Uptime %d mins\n", $uptime;
	    printf "Load5 %d load-5min\n", int($5) ;
	    last ;
	}
	elsif ( /^.*up\s+(\d+):(\d+),.*load\s*average:\s+(\S+),\s+(\S+),\s+(\S+).*$/ ) {
	    $uptime = ($1 * 60) + $2;
	    printf "Uptime %d mins\n", $uptime;
	    printf "Load5 %d load-5min\n", int($4) ;
	    last ;
	}
	elsif ( /.*load\s*average:\s+(\S+),\s+(\S+),\s+(\S+).*$/ ) {
	    printf "Load5 %d load-5min\n", int($2) ;
	    last ;
	}
    }	
    close (CMD);

#    print "Load 9999 load UnParseableInput\n";
}

### sunos4% pstat -T
#	367/1017 files
#	298/693 inodes
#	97/266 processes
#	53924/154220 swap
# BSDI replaces the inodes by a dynamic vnode table and doesn't have a process
# table.
#
# osf1% swapon -s
#	Total swap allocation:
#	    Allocated space:        32768 pages (256MB)
#	    Reserved space:          6738 pages ( 20%)
#	    Available space:        26030 pages ( 79%)

sub pstat {

    # Skip for BSDI version 1.0
    if ($ostype =~ /BSDI.*1\.0/) { return; } # older BSDI versions
    if ($OS_SOLARIS2) { return; }

    if ($OS_BSDI || $OS_SUNOS4) {
	open (CMD, "pstat -T |");
	while (<CMD>) {
	    if ($debug) {print STDERR "(debug)pstat: $_" ; }

	    chop; tr/\r\n//d;

	    if ( /^\s*(\d+)\/(\d+)\s*(files).*$/ ) {
		printf "FileTable %d %%used\n", int($1 * 100 / $2);}
	    elsif ( /^\s*(\d+)\/(\d+)\s*(inodes).*$/ ) {
		printf "InodeTable %d %%used\n", int($1 * 100 / $2);}
	    elsif ( /^\s*(\d+)\/(\d+)\s*(proc).*$/ ) {
		printf "ProcTable %d %%used\n", int($1 * 100 / $2);}
	    elsif ( /^\s*(\d+)\/(\d+)\s*(swap).*$/ ) {
		print "SwapSpace $1 MBytes\n";}

	    next;
	}	
	close (CMD);
    }
    elsif ($OS_OSF1) {
	local ($tmb);
	open (CMD, "swapon -s |");
	while (<CMD>) {
	    if ( /\s*Allocated\s+space.*(\d+)MB/ ) { $tmb = $1 ; next; }
	    if ( /\s*Available\s+space.*(\d+)%/ )  {
		printf "SwapSpace %d MBytes\n", int($tbm * $1 / 100);
		break ;		# no more processing needed
	    }
	}
	close (CMD);
    }	# endif $OS_OSF1

}  # end &pstat()

### nfsstat
# Have to calculate deltas in each pass since the nfsstat command does not
# give us 'rate' over a time period.  
# STORE THE OLD VALUES IN EACH PASS AND CALC DELTA EACH TIME.
#
# DWN: Additionally, nfsstat on BSDI does not yet give the ability to
# restrict the output to client/server or RPC/NFS, so we have to look
# at the header lines.
#
# Timeouts are not the same as retries!  Timeouts to reflect problems 
# communicating to the server (nfs server not responding), which in turn
# is indicated by successive retries on a NFS request.  The cause for the
# retry may be local socket blockage, network loss, or a busy server.
#
# sunos4-vikas> nfsstat -rc
#
# Client rpc:
# calls    badcalls retrans  badxid   timeout  wait     newcred  timers
# 7915078  52102    0        345      52073    0        0        7164     
#
sub nfsstat {
    local ($cmd) = "nfsstat";
    local ($dcalls, $dtimeo, $dretries, $tvalue, $rvalue);

    open (CMD, "$cmd -rc |");	# '-rc' for client related data only
    while (<CMD>) {
	chop; tr/\r\n//d;
				# rate =  timeout/calls
	if ($OS_BSDI) { # skip until reach the TimedOut header
	    next until (/TimedOut/) ;
	    if ( /^\s*(\d+)\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s*$/ )  {
		$debug && print STDERR "(debug)nfsstat: $_\n" ;
		
		$dcalls = $3 - $onfscalls ; $onfscalls = $3 ;
		$dretries = $2 - $onfsretries; $onfsretries = $2;
		$dtimeo = $1 - $otimeo ; $otimeo = $1 ;
		last ;		# no more lines
	    }
	}
	elsif ($OS_SUNOS4 || $OS_SOLARIS2 || $OS_OSF1) {
	    if (/^\s*(\d+)\s+\d+\s+\d+\s+\d+\s+(\d+).*$/ )  {
		$debug && print STDERR "(debug)nfsstat: $_\n" ;

		$dcalls = $1 - $onfscalls ; $onfscalls = $1 ;
		$dtimeo = $2 - $otimeo ; $otimeo = $2 ;
		last ;		# no more lines
	    }
	}
    }				# end (while)
    close (CMD);

    if ($dcalls == 0) { $tvalue = 0 ; $rvalue = 0;}
    else {
	$tvalue = int($dtimeo * 100 / $dcalls);   # calc timeout rate
	$rvalue = int($dretries * 100 / $dcalls); # calc retry rate
    }

    if ($debug) { print STDERR "(debug)nfsstat: $dtimeo/$dcalls\n" ;}
    print "NFStimeouts $tvalue %timeouts\n";
    $OS_BSDI && print "NFSretries $rvalue %retries\n";

}	# end &nfsstat()

## df
# Extract space availability stats. Printing out available MBytes and
# percent full (together they make sense).
# Note that this command prints out a number of lines with same variable
# name (one for each disk). The hostmon server picks out the worst of
# these lines.
#
# sun41-vikas> df -t 4.2
#   Filesystem            kbytes    used   avail capacity  Mounted on
#   /dev/sd0a              24239   12492    9324    57%    /
#   /dev/sd0g             191919  149692   23036    87%    /usr
#   /dev/sd3c            1230152 1062490   44647    96%    /usr/local
#
# bsdi-doug> df -i
#   Filesystem  1K-blocks  Used  Avail Capacity iused ifree %iused Mounted on
#    /dev/sd0a     7979    5877    1304    82%     631  3207  16%   /
#    flsvr:/vol/cvs 953478  699853  158277 82%       0    0   100%  /vol/cvs
#
sub df {

    if ($OS_BSDI || $OS_OSF1)
    {
	if ($OS_BSDI) { open (CMD, "df -i |"); }
	elsif ($OS_OSF1) { open (CMD, "df -t ufs -k -i |"); }
	else {return ; }

	while (<CMD>) {
	    chop; tr/\r\n//d;
	    if (/^\s*Filesystem/)  { next; } # ignore header line

	    if (/^\s*(\/dev\/.*|mfs:\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%\s+(\d+)\s+(\d+)\s+(\d+)%\s+(\/\S*)\s*$/)
	    {
		$debug && print STDERR "(debug)df: $_\n" ;

		printf "DFspace_avail %d MB $9\n", int($4/1000);
		print  "DFspace_\%used $5 \%full $9\n";
		print  "DFinodes_avail $7 inodes $9\n";
		print  "DFinodes_\%used $8 \%inodes $9\n";
	    }
	}
	close (CMD);
	return ;
    }		# end: if BSDI or OSF1
    elsif ($OS_SUNOS4 || $OS_SOLARIS2)
    {
	if ($OS_SUNOS4) {open (CMD, "df -t 4.2 |"); }
	elsif ($OS_SOLARIS2) {open (CMD, "df -l |"); }
	else {return; }
	while (<CMD>) {
	    chop; tr/\r\n//d;
	    if (/^\s*Filesystem/)  { next; } # ignore header line
	    $debug && print STDERR "(debug)df: $_\n" ;

	    if (/^.*\s+(\d+)\s+(\d+)%\s+(\/\S*)\s*$/)
	    {
		printf "DFspace_avail %d MB $3\n", int($1/1000);
		print "DFspace_\%used $2 \%full $3\n";
	    }
	}
	close (CMD);
    }				# endif SunOS or Solaris

    if ($OS_SUNOS4)		# extract inodes
    {
	open (CMD, "df -i -t 4.2 |");
	while (<CMD>) {
	    chop; tr/\r\n//d;
	    if (/^\s*Filesystem/)  { next; } # ignore header line

	    $debug && print STDERR "(debug)df: $_\n" ;

	    if (/^.*\s+(\d+)\s+(\d+)%\s+(\/\S*)\s*$/)
	    {
		print "DFinodes_avail $1 inodes $3\n";
		print "DFinodes_\%used $2 \%inodes $3\n";
	    }
	}
	close (CMD);
    }	# end:  if (SUNOS4 inodes)

}	# end &df()

##
# sunos41> iostat -d 5 3
#          sd1           sd2           sd3 
#  bps tps msps  bps tps msps  bps tps msps 
#   22   3  0.0   64  10  0.0    7   1  0.0 
#    0   0  0.0    0   0  0.0    0   0  0.0 
#    0   0  0.0    0   0  0.0    0   0  0.0 
#
# sunos41>  iostat -D 5 3
#          dk0           dk1           dk2 
# rps wps util  rps wps util  rps wps util 
#   1   1  2.7    2   0  2.6    0   0  1.7 
#   0   0  0.0    0   0  0.0    0   0  0.0 
#
# solaris> iostat -d 5 3
#          fd0           sd1           sd3 
# Kps tps serv  Kps tps serv  Kps tps serv 
#   0   0    0    2   0   29    0   0  152 
#   0   0    0    0   0    0    0   0    0 
#
# bsdi$ iostat -w 5 -c 3
#          tty          sd0          sd1         cpu
#   tin   tout sps tps msps sps tps msps us ni sy id
#     0      4   3   0  9.5   0   0  0.0  5  0  1 94
#    10     10   0   0  0.0   0   0  0.0  0  0  1 99
#
# osf1> iostat 5 3
#       tty     rz1      rz2      rz3      rz4     cpu
# tin tout bps tps  bps tps  bps tps  bps tps  us ni sy id
#   0    3   2   0    0   0    4   0    0   0   2  0  7 91
#   0   12   0   0    0   0    0   0    0   0   0  0  0100
##
sub iostat {
    local ($cmd) = "iostat";
    local ($str, $diskno, $numdisks);
    local (@IOname);

    if ($OS_BSDI) { open (CMD, "$cmd -w 5 -c 3 |"); }
    elsif ($OS_SUNOS4 || $OS_SOLARIS2) { open (CMD, "$cmd -d 5 3 |"); }
#    elsif ($OS_OSF1) { open (CMD, "$cmd 5 3 |"); }
    else  { return; }

    while (<CMD>) {
	chop; tr/\r\n//d;
	$str = $_ ;
	if (/\s+msps|tps\s+/) { next; } # ignore header line
	if (/^\s+[A-z]+/) {	# header line containing disk names...
	    if ($debug)  { print STDERR "(debug)iostat: $_\n"; }
	    $diskno = 0;
	    while ( $str =~ /\s*(\w+)\s+/ ) {
		if ( $1 eq 'tty' ) { next; }   # BSDI lists 'tty'
		if ( $1 eq 'cpu' ) { last; }   # 'cpu' is not a disk 
		$str = $' ; 		   # remaining string
		$IOname[++$diskno] = $1; # extract the name of disk
	    }			# end while()
	}

    }		# end: while(<CMD>)
    close (CMD);

    # Here we have the last line from the command above
    if ($debug)  { print STDERR "(debug)iostat: $str\n"; }
    $diskno = 0;
    if ($OS_SUNOS4 || $OS_SOLARIS2) {
	while ($str =~ /\s*\d+\s+\d+\s+(\d+\.\d+)/)  # extract decimal msps
	{
	    ++$diskno; $str = $' ; # remaining string
	    print "IOseek $1 msps $IOname[$diskno]\n"; # seek time, msecs/seek
	}
    }
    elsif ($OS_BSDI) {
	while ($str =~ /\s*(\d+)\s+(\d+)\s+(\d+\.\d+)/)  # extract bps,tps,msps
	{
	    ++$diskno; $str = $' ; # remaining string
	    print "IObps $1 bps $IOname[$diskno]\n"; # blocks xferred per sec
	    print "IOtps $2 tps $IOname[$diskno]\n"; # IO operations per sec
	    print "IOmsps $3 msps $IOname[$diskno]\n"; # seek time, msecs/seek
	}
    }


    # Now run with the -D option to extract utilization for SunOS-4.x
    if ($OS_SUNOS4 || $OS_SOLARIS2) {
	open (CMD, "$cmd -D 5 4 |");
	while (<CMD>) {
	    chop; tr/\r\n//d;
	    $str = $_;
	    if (/\s+util\s*/) { next; } # ignore header line
	    if (/^\s+[A-z]+/) {	# header line containing disk names...
		if ($debug)  { print STDERR "(debug)iostat: $_\n"; }
		$diskno = 0;
		while ( $str =~ /\s*(\w+)\s+/ ) {
		    $str = $' ; 		   # remaining string
		    $IOname[++$diskno] = $1; # extract the name of disk
		}
	    }
	}		# end: while()
	close (CMD);

	# Here we have the last line from the command above
	if ($debug)  { print STDERR "(debug)iostat: $str\n"; }
	$diskno =0;
	while ($str =~ /\s*\d+\s+\d+\s+(\d+\.\d+)/ )  # decimal utilization
	{
	    ++$diskno; $str = $' ; # remaining string
	    print "IObw $1 %util $IOname[$diskno]\n";
	}
    }				# end if $ostype = SunOS4.x
}	# end &iostat()


##        vmstat
# Extract paging, real-memory, cache hits, CPU load, Context switches.
# Note: Seems like the fields are fixed length, and sometimes there are
#       no spaces between the output fields.
# sunos> vmstat -S 5 2
# procs     memory              page               disk       faults     cpu
# r b w   avm   fre  si so  pi  po  fr  de  sr d0 d1 d2 d3  in  sy  cs us sy id
# 0 0 0     0 21372   0 14   1   0   0   0   0  1  0  0  0 149 382 127 29  3 68
# 0 0 0     0 21328   5  0   0   0   0   0   0  0  0  0  0 482 247  74  1  1 99
#
# solaris> vmstat -S 5 2
# procs     memory            page            disk          faults      cpu
# r b w   swap  free  si  so pi po fr de sr f0 s1 s3 --   in   sy   cs us sy id
# 0 0 0   4772  3640   0   0  0  0  0  0  0  0  0  0  0    3   21   16  1  1 97
# 0 0 0 281636  3700   0   0  0  0  0  0  0  0  0  0  0    8   20   18  0  2 98
#
# bsdi$ vmstat -w 5 -c 2
#    procs   memory     page                    disks     faults      cpu
#    r b w   avm   fre  flt  re  pi  po  fr  sr sd0 sd1   in   sy  cs us sy id
#    2 0 0     0   456    3   0   0   0   0   0   0   0  119   23   9  5  1 94
#    0 0 0     0   368  150  22   0   0   0   0   2   0  132  141  21  2  7 91
#
##    -1-      2     3    4   5   6   7   8   9   -10-    11   12  13 14 15 16
#
# osf1% vmstat 5 2
# Virtual Memory Statistics: (pagesize = 8192)
#   procs    memory         pages                          intr        cpu     
#   r w u    act  free wire fault cow zero react pin pout  in  sy  cs  us sy id
#   2 60 26  4774 4810 1781  20M   5M   8M  22K   5M  14K  72 -152 145  2  7 91
#   2 60 26  4777 4807 1781  120   14   77    0    9    0  69  69  70   0  1 99
## 
sub vmstat {
    local ($i) = 0;
    local($dline);
    local ($cmd) = "vmstat";

    if ($OS_BSDI) {open(CMD, "$cmd -w 5 -c 2 |"); }
    elsif ($OS_SUNOS4 || $OS_SOLARIS2) {open (CMD, "$cmd -S 5 2 |"); }
    elsif ($OS_OSF1) {open (CMD, "$cmd 5 2 |"); }
    else { return; }

    while (<CMD>) { $dline = $_; next; }

    # Here we have the last data line from the command above.
    if ($debug)  { print STDERR "(debug)vmstat: $dline"; }
    $_ = $dline ;
    chop; tr/\r\n//d;
	
    # if any CPU time is 100, vmstat doesnt have any spaces in it.
    s/100(\s+\d+){0,2}\s*$/ 100\1/;
    
    # I have also found vmstat to mess up the 'cs' field (no space if
    # it or the sorrounding fields are too long). Hence taking only
    # 3 digits of the context switches.
    ###  procs  #  avm  #  fre  #  si   #  so   #   pi  #  po   # disks  #      #    cs     #  us   #  sy   #  id   ######
    if (/^(\s+\d+){3}\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)(\s+\d+)+\s+\d*((\s|\d)\d\d)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/)
    {
	if ($OS_SOLARIS2) { printf "SwapSpace %d MBytes\n", int($2/1000); }
#	else {print "MemActive $2 avm\n";}
	printf "MemFree %d MB\n", int($3 / 1000); # convert KB to MB
	if ($OS_BSDI) {
	    print "PageFlt $4 pgflts\n";
	    print "PageRe $5 pages/sec\n";
	}
	elsif ($OS_SunOS4) {
	    print "SwapIn $4 si\n";
	    print "SwapOut $5 so\n";
	}
	elsif ($OS_OSF1) {
	    print "PageFlt $5 pgflts\n";
	}
	if (! $OS_OSF1) {
	    print "PageIn $6 pi\n";
	    print "PageOut $7 po\n";
	}
	print "CtxtSw $9 rate\n";
	print "CPUus $11 %user\n";
	print "CPUsy $12 %system\n";
	print "CPUidle $13 %idle\n";
    }
    close (CMD);

}	# end &vmstat()


##       netstat
# Extract collision and error rates. Luckily, on SunOS there is an option
# for printing out the delta values. Since there is no easy way to 
# terminate the command after displaying 5 lines, forcibly close the
# command stream.
# Ignore the first few lines, look at the 5th and then forcibly close.
# Since the command only displays one interface's data, we look at the
# 'totals' field.
#
## NOTE: BSDI 1.0 does not seem to have a working '-w' argument, but works with
## the numeric argument implying the '-w'.
#
#  sunos> netstat 5
#       input   (le0)     output           input  (Total)    output       
#  packets errs  packets errs  colls  packets errs  packets errs  colls  
#   <first line with total values, ignore>
#  1467    0     64      0     0      1467    0     64      0     0     
#  1584    0     96      0     1      1584    0     96      0     1     
#
#
# 
sub netstat {
    local ($i) = 0;
    local ($cmd) = "netstat";

    if ($OS_SOLARIS2) {$cmd .= " -i "; }

    local($pid) = open (CMD, "$cmd 10 |");	# 10 second sampling
    while (<CMD>) {
	if (/^\s+[A-z]+/) { next; }	# ignore header lines if any
	if (++$i < 3)  { next ;} # ignore first 3 lines
	chop; tr/\r\n//d;
	$debug && print STDERR "(debug)netstat: $_\n" ;

	if (/^\s*\d+(\s+\d+)*\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/)
	{
	    local ($ipkts, $opkts) = ($2, $4);

	    if ($ipkts == 0) { $ipkts = 1; } # avoid division by zero
	    if ($opkts == 0) { $opkts = 1; }
	    printf "NetIErr %d PktRate\n", int ($3 * 100 / $ipkts);
	    printf "NetOErr %d PktRate\n", int ($5 * 100 / $opkts);
	    printf "NetColl %d PktRate\n", int ($6 * 100 / $opkts);
	}
	kill ('TERM', $pid) ; last ;		# no more lines
    }	
    close (CMD);
}	# end &netstat()


####################################################################
## Following routines are not doing any 'tests'. They are utility ##
## routines.                                                      ##
####################################################################

##
# Define a boolean variable for testing OS types easily in the above
# routines.
sub osinit {

    $ostype= `uname -s -r -m`; chop $ostype; # OS, revision, arch
    $debug && print STDERR "OSTYPE = $ostype\n";

    # set boolean values for OS's
    if ($ostype =~ /BSD.+386/ || $ostype =~ /BSDI/)  {$OS_BSDI = 1;}
    elsif ($ostype =~ /SunOS\s+4/) {$OS_SUNOS4 = 1;}
    elsif ($ostype =~ /SunOS\s+5/) {$OS_SOLARIS2 = 1;}
    elsif ($ostype =~ /OSF1/) {$OS_OSF1 = 1;}
    else  {print STDERR "Warning, OS $ostype is unknown\n"; }
    
    ## force units to 1K blocks for df and vmstat in BSDI
    if ($OS_BSDI) { $ENV{'BLOCKSIZE'}="1k"; }

}

sub standalone {
    local (@me) =split(/\//,$0); 
    $me = pop(@me);		# not local, since needed by exit routine
    local ($p);

    if (!$debug) { if ($p=fork) {print "$p\n"; exit;} }
    local($pidfile)= "/tmp/$me.pid";

    if(open(PID,"<$pidfile"))
    {
	local ($pid) = <PID>; chop $pid ; close(PID);
        if($pid =~ /^\d+$/)
        {
	    foreach (`ps $pid`) {
		chop;
		if(/$pid.*$me/) {
		    kill('TERM',$pid); print "($$) killing process $pid\n";
		    foreach (1..3) {
			if (-e $pidfile) {sleep 1;}
			else {break; }
		    }
		    kill(9, $pid); print " killing process $pid\n";
		}
	    }		# end: foreach()
	}
    }                   # end: open(PID...)

    if (open(PID,">$pidfile")){print PID "$$\n"; close(PID);}
    else {die ("standalone: cannot create $pidfile, $!\n") ; }

    foreach ('TERM','HUP','INT','KILL','QUIT') {
	$SIG{$_} = clean_out_onexit;
    }

}	# end standalone()


## Die on getting a terminate signal. Clean up data files.
##
sub clean_out_onexit {

    if ($childpid)		# kill the telnet_daemon
    {
	kill ('TERM', $childpid) ; sleep 1; kill (9, $childpid);
	print STDERR "($$) killing telnet_daemon ($childpid)\n";
    }
	
    unlink ($dfile) ;
    unlink ($dfile . '.tmp') ;	# delete temporary file
    unlink ("/tmp/$me.pid");	# pid filename
    die "($$) Terminating on signal\n" ;
}	# clean_out_onexit()

###
### main
###    

if ($debug) {
    print STDERR "(debug) MonitorList= @monitorlist\n" ;
    print STDERR "(debug) Outputfile= $dfile\n";
}

&osinit;			# OS specific initialization
&standalone ;			# create telnet_daemon before calling this ??
$OS_SOLARIS2 && undef(&create_telnet_daemon);	# cant handle sockets

defined (&create_telnet_daemon) &&  &create_telnet_daemon ;

local ($passno) = 1;		# keep track of pass number in loop
local ($tmpfile) = "$dfile" . ".tmp" ; # create temporary output file
local ($stime, $deltatime);
local ($chek_dpass) = int(2*3600 /$sleeptime); #check telnet_daemon every 2 hrs
local ($restart_dpass) = int(48*3600 /$sleeptime); #restart every 48 hours

select(DFILE);			# select default output file

while (1)
{
    $stime = time;

    open (DFILE, "> $tmpfile") ; 
    print "TIME $stime secs\n";	        # needed in the output
    foreach $s (@monitorlist) { &$s ;}	# call the subroutines.
    close (DFILE) ;
    print "Moving $tmpfile to $dfile\n";
    `mv $tmpfile $dfile` ;

    # Check the 'telnet_daemon' every chek_dpass passes.
    if ((++$passno % $chek_dpass) == 0) {
	defined (&create_telnet_daemon) && &create_telnet_daemon;
    }

    $deltatime = time - $stime;              # time to do tests
    $debug  && print STDERR "(dbg) sleep for= $sleeptime - $deltatime\n";
    if ($sleeptime > $deltatime) { sleep($sleeptime - $deltatime) ;}

    ## restart to avoid the memory leaks.
    if (($passno % $restart_dpass) == 0) {
	$passno = 0;
	if ( -x $0 ) { $childpid && kill ('TERM', $childpid); sleep 5;
		       $childpid && kill ('TERM', $childpid); sleep 5;
		       exec $0 ; # restart
	}
    }
}	# end while(1)

###
