dnl $List(FREEBSD7): echo FREEBSD
dnl $List(FREEBSD6): echo FREEBSD
dnl $List(FREEBSD5): echo FREEBSD
dnl $List(FREEBSD4): echo FREEBSD
dnl $List(FREEBSD4): echo FREEBSD
dnl $List(FREEBSD3): echo FREEBSD3
dnl $List(HPUX9): echo HPUX
dnl $List(HPUX9): echo HPUX
dnl $List(SLACK): echo SLACK
dnl $List(LINUX): echo LINUX
dnl $List(AS2): echo LINUX
dnl $List(AS3): echo LINUX
dnl $List(AS4): echo LINUX
dnl $List(SUN5): echo SOL8
dnl $List(SOL8): echo SOL8
dnl $List(SOL9): echo SOL8
dnl $List(SOL10): echo SOL8
dnl $List(IBMR2): echo IBMR2
dnl $List(AIX): echo IBMR2
dnl $List(*): %;%.
changequote(,)dnl
#!/usr/bin/env perl
# $Id: unixstats.pl.host,v 2.11 2008/12/12 22:01:21 tswan Exp $
#

use lib  '/usr/local/lib/sac/perl'.join('.', unpack('c*', $^V)),
	'/usr/local/lib/sac';
use strict;
use Socket;
use Sys::Hostname;
use Getopt::Std;
use POSIX qw(floor);

my($progname) = $0;
$progname ||= 'unixstats';
$progname = $1 if $progname =~ m,.*/(.+)$,o;
my(%opts);
getopts('dN:hH:p:S:T:O:Vx', \%opts);
# ZZZ configure here is you want a default suffix added to your
# plain hostname
my($suffix) ||= $opts{'S'};	#  e.g. "prod", or "lab", or "test"
my($common) = $opts{'T'};
$common ||= "fedex.com";	# common tail to remove
my($admindept) = $opts{'O'};
$admindept ||= "sac";		# admin default department

# RRD update interval must be >= 10 sec, or we move the load too much --ksb
my($stall,$remainder);
# Under -p (persistant) we are a service run at boot, otherwise assume we
# are run from cron once a minute or so, or the command line -x to test.
if ($opts{'p'}) {
	if ($opts{'p'} < 10) {
		$opts{'p'} = 10;
	}
	$stall = $opts{'x'} ? 0 : floor(0.5+rand($opts{'p'}));
	$remainder = $opts{'p'}-$stall;
} else {
	$stall = $opts{'x'} ? 0 : floor(3.5+rand(55));
	$remainder = undef;
}
# get hostname: CONFIG for your domain, not  ".fedex.com" and ".sac"
my($host) = hostname();
ifdef(FORCE_LOCAL_DOMAIN,
	$host =~ m/([^.]*)\..*$/o and do {
	$host = $1;
};
$host .= "FORCE_LOCAL_DOMAIN";
,	$host =~ m/(.*)\.$common$/ and do {
	$host = $1;
	$host =~ m/(.*)\.$admindept$/ and do {
		$host = $1;
	};
};
)dnl

# If our hostname is not a FQDN under $common we might have to adjust the
# results from the above.  Or just force it on the command line with
# -N real-name.
$host .= ".$suffix" if ($suffix && $suffix ne $admindept);
$opts{'N'} ||= $opts{'n'};	# backwards compatibility with old Linix
$host = $opts{'N'} if ($opts{'N'});

my($peghost) = shift(@ARGV);
$peghost ||= $opts{'H'};	# backwards compatible with HPUX
my($pegport);
$peghost ||= 'peg.sac.fedex.com:31415';
if ($peghost =~ m/^([^:]+):([0-9]+)$/) {
	$pegport = $2;
	$peghost = $1;
}
if ($peghost =~ m/^:([0-9]+)$/) {
	$pegport = $1;
}
$pegport ||= 31415;
if ($opts{'V'}) {
	print "$progname: ", '$Id: unixstats.pl.host,v 2.11 2008/12/12 22:01:21 tswan Exp $', "\n",
		"update: $peghost:$pegport\n",
		"node: $host", ($opts{'N'} ? " [forced]": ''), "\n";
	if (defined($admindept) && defined($common)) {
		print "remove: admin \"$admindept\" after toplevel \"$common\"\n";
	} elsif (defined $common) {
		print "remove: toplevel \"$common\"\n";
	} elsif (defined $admindept) {
		print "squelch: admin \"$admindept\"\n";
	}
	if (defined($suffix) && (!defined $admindept || $suffix ne $admindept)) {
		print "add: suffix $suffix\n";
	}
	if (defined $remainder) {
		print "updates: every ", $stall+$remainder, ", at offset $stall\n";
	} else {
		print "update: once, stalling for $stall\n";
	}
	exit(0);
}

if ($opts{'h'}) {
	print "$progname: usage [-dx] [-p delay] [-N node] [-O admin] [-S suffix] [-T toplevel] [peg][:port]\n",
		"$progname: -h|-V\n",
		"d          display RRD path only, and exit\n",
		"h          output a brief help message\n",
		"N node     use this node name, rather than our hostname\n",
		"O admin    set the administrators department suffix\n",
		"p delay    update persistantly, about every delay seconds\n",
		"S suffix   remove this suffix after toplevel, if present\n",
		"T toplevel remove this from the end of our hostname\n",
		"V          output the standard version information\n",
		"x          trace updates on stdout\n",
		"peg        sample collection host, running rrdd\n",
		"port       rrdd update port (otherwise $pegport)\n";
	exit(0);
}

sub mkUpdate()
{
ifelse(yes,ifelse(HOSTTYPE,SUN5,yes,HOSTTYPE,SOL8,yes,HOSTTYPE,SOL9,yes,HOSTTYPE,SOL10,yes,no),
# the Solaris specific part, gather the stats
	my(%s) = ();
	my($key, $line);

	# get 1-minute load average
	my($value) = `/usr/bin/uptime`;
	($s{'sysload'}) = ($value =~ m/average:\s+([0-9.]+),/);

	# get what we can from vmstat
	open(VMSTAT, "/usr/bin/vmstat -s|") ||
		die "popen: vmstat: $!";
	while ($line = <VMSTAT>) {
		chomp $line;

		($value,$key) = $line =~ m/^\s*([0-9]+)\s+(.*)$/o;
		for ($key) {
			m/^pages paged in/ and do
				{ $s{'pagein'} = $value; };
			m/^pages paged out/ and do
				{ $s{'pageout'} = $value; };
			m/^user\s+cpu/ and do
				{ $s{'cpu_user'} = $value; };
			m/^system\s+cpu/ and do
				{ $s{'cpu_sys'} = $value; };
			m/^idle\s+cpu/ and do
				{ $s{'cpu_idle'} = $value; };
			m/^wait\s+cpu/ and do
				{ $s{'cpu_wait'} = $value; };
			m/^system\s+cpu/ and do
				{ $s{'cpu_sys'} = $value; };
			m/^device\s+interrupts/ and do
				{ $s{'intr'} = $value; };
			m/^system\s+calls/ and do
				{ $s{'scall'} = $value; };
			m/^cpu\s+context\s+switches/ and do
				{ $s{'cswitch'} = $value; };
			m/^forks/ and do
				{ $s{'fork'} = $value; };
		}
	}
	close(VMSTAT);

	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,yes,ifelse(HOSTTYPE,FREEBSD,yes,HOSTTYPE,FREEBSD3,yes,HOSTTYPE,FREEBSD7,yes,HOSTTYPE,FREEBSD6,yes,HOSTTYPE,FREEBSD5,yes,HOSTTYPE,FREEBSD4,yes,no),
# FreeBSD any stats
	my(%s) = ();
	my($line,$key,$value);

	# N.B. order is important here, new ones ON THE END because sysctl
	# stops at the first unknown one.  Or spend a little more CPU
	# and put in "apply " in front of the command -- ksb
	open(SCTL, "/sbin/sysctl hw.ncpu vm.loadavg vm.stats.vm.v_swapin vm.stats.vm.v_vnodein vm.stats.vm.v_swapout vm.stats.vm.v_vnodeout vm.stats.sys.v_intr vm.stats.sys.v_syscall vm.stats.sys.v_swtch vm.stats.vm.v_forks kern.cp_time 2>/dev/null|") ||
		die "popen: sysctl: $!";

	my(%v) = ();
	while ($line = <SCTL>) {
		chomp $line;
		($key,$value) = split(': ', $line);
		$v{$key} ||= $value;
	}
	close(SCTL);
	if (exists($v{'vm.loadavg'})) {
		$s{'sysload'} = (split(' ',$v{'vm.loadavg'}))[1];
	} else {
		my($sysload) = `/usr/bin/uptime`;
		chomp($sysload);
		$s{'sysload'} =~ s/.*average[s:]\s*\([0-9.]*\),.*/$1/o;
	}

	# To get this w/o sysctl we need to run an iostat -C -w ... to
	# read the cpu percentages (rounded to ints) from iostat, that's
	# too much work for integer approximations. So we fake it from
	# the number of CPUs and the load average.  Better'n nada.  --ksb
	if (exists($v{'kern.cp_time'})) {
		($s{'cpu_user'}, $s{'cpu_nice'}, $s{'cpu_sys'}, $s{'cpu_int'}, $s{'cpu_idle'}) = split(' ', $v{'kern.cp_time'});
	}
	if (exists($v{'vm.stats.vm.v_swapin'}) || exists($v{'vm.stats.vm.v_vnodein'})) {
		$s{'pagein'} = 0+$v{'vm.stats.vm.v_swapin'}+$v{'vm.stats.vm.v_vnodein'};
	}
	if (exists($v{'vm.stats.vm.v_swapout'}) || exists($v{'vm.stats.vm.v_vodeout'})) {
		$s{'pageout'} = 0+$v{'vm.stats.vm.v_swapout'}+$v{'vm.stats.vm.v_vnodeout'};
	}
	if (exists($v{'vm.stats.sys.v_intr'})) {
		$s{'intr'} = $v{'vm.stats.sys.v_intr'};
	}
	if (exists($v{'vm.stats.sys.v_syscall'})) {
		$s{'scall'} = $v{'vm.stats.sys.v_syscall'};
	}
	if (exists($v{'vm.stats.sys.v_swtch'})) {
		$s{'cswitch'} = $v{'vm.stats.sys.v_swtch'};
	}
	if (exists($v{'vm.stats.vm.v_forks'})) {
		$s{'fork'} = $v{'vm.stats.vm.v_forks'};
	}
	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,HOSTTYPE,LINUX,
	# Linux common
	my(%s) = ();
	my($line,$key,$value);
	my($softint,$sysload);

	# get 1-minute load average
	if (open(UPTIME, "</proc/loadavg")) {
		$sysload = <UPTIME>;
		chomp($sysload);
		$sysload =~ s/\s+.*//;
		close(UPTIME);
	} else {
		$sysload = `/usr/bin/uptime`;
		chomp($sysload);
		$sysload =~ s/.*average:\s*\([0-9.]*\),.*/$1/o;
	}
	$s{'sysload'} = $sysload if ('' ne $sysload);

 ifelse(eval(HOSTOS < 40000),1,
	# RHEL3-
	# find the rest in /proc/stat
	open(STAT, "</proc/stat") ||
		die "open: /proc/stat: $!";
	while ($line = <STAT>) {
		chomp $line;
		($key,$value) = $line =~ m/^(\S+)\s+(.*)$/o;
		$key =~ s/:$//;
		for ($key) {
			m/^cpu$/ and do {
				($s{'cpu_user'}, $s{'cpu_nice'}, $s{'cpu_sys'}, $s{'cpu_idle'}, $s{'cpu_wait'}, $s{'cpu_int'}, $softint) = split(/\s+/,$value);
				next;
			};
			m/^page$/ and do {
				($s{'pagein'}, $s{'pageout'}) = split(/\s+/,$value);
				next;
			};
			m/^intr$/ and do {
				($s{'intr'}) = split(/\s+/,$value);
				next;
			};
			m/^processes$/ and do {
				$s{'fork'} = $value;
				next;
			};
			m/^ctxt$/ and do {
				$s{'cswitch'} = $value;
				next;
			};
		}
	}
	close(STAT);

	# Older versions of Linux don't have some fields.
	# Don't report them if they are undefinded.  --jad
	delete $s{'cpu_wait'} unless (defined $s{'cpu_wait'});
	if (defined $s{'cpu_int'}) {
		$softint ||= 0;
		$s{'cpu_int'} += $softint;
	} else {
		delete $s{'cpu_int'};
	}
,
	# RHEL 4+
	# vmstat -s is provided to grab page in/out events.  These were removed
	# from /proc/stat in later 2.4/2.6 kernels.

	open(VMSTAT, "/usr/bin/vmstat -s|") ||
		die "open: vmstat: $!";
	while ($line = <VMSTAT>) {
		($value,$key) = $line =~ m/^\s*([0-9]+)\s+(.*)$/o;
		for ($key) {
			m/^pages\s+paged\s+in/ and do
				{ $s{'pagein'} = $value; };
			m/^pages\s+paged\s+out/ and do
				{ $s{'pageout'} = $value; };
			m/^non-nice\s+user\s+cpu/ and do
				{ $s{'cpu_user'} = $value; };
			m/^nice\s+user\s+cpu/ and do
				{ $s{'cpu_nice'} = $value; };
			m/^system\s+cpu/ and do
				{ $s{'cpu_sys'} = $value; };
			m/^idle\s+cpu/ and do
				{ $s{'cpu_idle'} = $value; };
			m/^wait\s+cpu/i and do
				{ $s{'cpu_wait'} = $value; };
			m/^interrupts$/ and do
				{ $s{'intr'} = $value; };
			m/^cpu\s+context\s+switches/i and do
				{ $s{'cswitch'} = $value; };
			m/^forks/ and do
				{ $s{'fork'} = $value; };
		}
	}

)
	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,HOSTTYPE,SLACK,
# slackware version, largely untested -- ksb
	my(%s);
	my($line);

	# get 1-minute load average
	my($sysload);
	if (open(UPTIME, "</proc/loadavg")) {
		$sysload = <UPTIME>;
		chomp($sysload);
		$sysload =~ s/\s+.*//;
		close(UPTIME);
	} else {
		$sysload = `/usr/bin/uptime`;
		chomp($sysload);
		$sysload =~ s/.*average:\s*\([0-9.]*\),.*/$1/o;
	}
	$s{'sysload'} = $sysload if ('' ne $sysload);

	# rumage for the others we might find in /proc/stat
	open(PSTAT, "</proc/stat") ||
		die "open: /proc/stat: $!";
	while ($line = <PSTAT>) {
		chomp($line);
		if ($line =~ m/^cpu\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$/o) {
			$s{'cpu_user'} = $1;
			$s{'cpu_nice'} = $2;
			$s{'cpu_sys'} = $3;
			$s{'cpu_idle'} = $4;
			# XXX missing intr here
			next;
		}
		if ($line =~ m/^page\s*(\d*)\s*(\d+)\s*$/o) {
			$s{'pagein'} = $1;
			$s{'pageout'} = $2;
			next;
		}
		if ($line =~ m/^intr\s+(\d*)/o) {
			$s{'intr'} = $1;
			next;
		}
		if ($line =~ m/^ctxt\s+(\d*)/o) {
			$s{'cswitch'} = $1;
			next;
		}
		if ($line =~ m/^processes\s+(\d*)/o) {
			$s{'fork'} = $1;
			next;
		}
	}
	close(PSTAT);
	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,HOSTTYPE,IBMR2,
	my(%s) = ();
	my($line,$key,$value);

	# get 1-minute load average
	$value = `/usr/bin/uptime`;
	($s{'sysload'}) = ($value =~ /average:\s+([0-9.]+),/);

	open(VMSTAT, "/usr/bin/vmstat -s ; /usr/bin/vmstat -f|") ||
		die "popen: vmstat: $!";

	while ($line = <VMSTAT>) {
		chomp $line;
		($value,$key) = $line =~ m/^\s*([0-9]+)\s+(.*)$/o;

		for ($key) {
			m/^page ins/ and do
				{ $s{'pagein'} = $value; };
			m/^page outs/ and do
				{ $s{'pageout'} = $value; };
			m/^syscalls*/ and do
				{ $s{'scall'} = $value; };
			m/^cpu\s+context\s+switches/ and do
				{ $s{'cswitch'} = $value; };
			m/^device\s+interrupts/ and do
				{ $s{'intr'} = $value; };
			m/^forks/ and do
				{ $s{'fork'} = $value; };
		}
	}
	close(VMSTAT);
	open(IOSTAT, "/usr/bin/iostat -t|tail -1|") ||
		die "popen: iostat: $!";
#tty:      tin         tout   avg-cpu:  % user    % sys     % idle    % iowait
#          0.0          2.1               0.1      0.5       98.3       1.0
	# XXX: multiply by the number of on-line CPUs on the host, really
	my(@cpu) = split(/\s+/, <IOSTAT>);
	$s{'cpu_user'} = $cpu[2];
	$s{'cpu_sys'} = $cpu[3];
	$s{'cpu_idle'} = $cpu[4];
	$s{'cpu_wait'} = $cpu[5];
	close(IOSTAT);
	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,yes,ifelse(HOSTTYPE,HPUX,yes,HOSTTYPE,HPUX9,yes,HOSTTYPE,HPUX10,yes,no),
# HPUX9 (and maybe 10?) unix collection -- ksb
	my(%s) = ();
	my($line,$key,$value);

	# get 1-minute load average
	$value = `/usr/bin/uptime`;
	($s{'sysload'}) = ($value =~ /average:\s+([0-9.]+),/);

	open(VMSTAT, "/usr/bin/vmstat -f ; /usr/bin/vmstat -s|") ||
		die "popen: vmstat: $!";
	while ($line = <VMSTAT>) {
		chomp $line;
		($value,$key) = $line =~ m/^\s*([0-9]+)\s+(.*)$/o;

		for ($key) {
			m/^pages paged in/ and do {
				$s{'pagein'} = $value;
				next;
			};
			m/^pages paged out/ and do {
				$s{'pageout'} = $value;
				next;
			};
			m/^user\s*cpu/o and do {
				$s{'cpu_user'} = $1;
				next;
			};
			m/^system\s*cpu/o and do {
				$s{'cpu_sys'} = $1;
				next;
			};
			m/^idle\s*cpu/o and do {
				$s{'cpu_idle'} = $1;
				next;
			};
			m/^wait\s*cpu/o and do {
				$s{'cpu_wait'} = $1;
				next;
			};
			m/^system\s*cpu/o and do {
				$s{'cpu_sys'} = $1;
				next;
			};
			m/^nice\s*cpu/o and do {
				$s{'cpu_nice'} = $1;
				next;
			};
			m/^device\s*interrupts/o and do {
				$s{'intr'} = $1;
				next;
			};
			m/^system\s*calls/o and do {
				$s{'scall'} = $1;
				next;
			};
			m/^cpu\s*context\s*switches/o and do {
				$s{'cswitch'} = $1;
				next;
			};
			m/^forks,.*/o and do {
				$s{'fork'} = $1;
				next;
			};
			m/^forks/o and do {
				$s{'fork'} = $1;
				next;
			};
		}
	}
	close(VMSTAT);
	return "host/$host/unix.rrd ".join(':',keys(%s)).' N:'.join(':',values(%s));
,
# we don't have a host part
	die "unixstats make update: unkown type HOSTTYPE";
)
}

# Send to peg's rrdd, peg could move over time, so we look up up
my($update,$ipout,$proto,$sockaddr);
$proto = getprotobyname('udp');
socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) ||
	die "socket: inet: $!";
while ($ipout = inet_aton($peghost)) {
	sleep($stall);
	$update = mkUpdate();
	if ($opts{'d'}) {
		$update =~ s/\s.*//;
		print "$update\n";
		last;
	}
	print $update, "\n" if ($opts{'x'});
	$sockaddr = sockaddr_in($pegport, $ipout);
	send(SOCKET, "00 ".$update, 0, $sockaddr);
	last unless defined($remainder);
	sleep($remainder);
	next unless (0 == $stall);	# -x set stall to 0, recompute it
	$stall = floor(0.5+rand($opts{'p'}));
	$remainder = $opts{'p'}-$stall;
}
close(SOCKET);
exit(0);
dnl
changequote(`,')dnl
