#!/usr/bin/perl
#
# loadcpu - print CPU load averages (threads on run queue). Solaris 8+.
#
# These are saturation values - not the same "load" as printed by uptime. This 
#  Perl program uses the Sun::Solaris::Kstat library to fetch accurate values.
#
# 21-Aug-2004, ver 0.80  (check for new versions, http://www.brendangregg.com)
#
#
# USAGE: loadcpu [-h] | [interval [count]]
#        loadcpu                 # print a 1 second sample
#        loadcpu -h              # print help
#        loadcpu 1               # print continually, every 1 second
#        loadcpu 1 5             # print 5 times, every 1 second
#
#
# The load averages are based on the length of the run queue divided by 
#  sample time. A load average of 1.00 indicates that on average there
#  is a thread waiting in the ready run queue for CPU time (usually bad). 
#  A load average of 4.00 would indicate there were four threads on average
#  waiting for the CPU.
#
# A load average of 0.00 does not mean the CPU is idle, rather that it
#  is not saturated. This differs to the uptime command where it is 1.00 
#  that indicates a CPU is not saturated (100% utilised), and where 0.00 
#  would mean an idle CPU. (use vmstat to determine if a CPU is idle).
#
# The number of CPUs should be kept in mind, but the results are not the same
#  as with the uptime command. A load average of 4.00 would mean that across
#  the CPUs there is still an average of four threads waiting for CPU time -
#  regardless of the CPU count this is usually undesirable. CPU count still
#  makes a difference, a load average of 4.00 would be dispatched sooner on
#  a server with many CPUs rather than say one CPU.
#
# KStat values used are unix:0:sysinfo:runque and unix:0:sysinfo:updates.
#
#
# SEE ALSO: kstat -n sysinfo [interval [count]]
#           uptime
#           vmstat
#
# REFERENCE: "What exactly is load average?" Richard Pettit, comp.unix.solaris
#
# COPYRIGHT: Copyright (c) 2004 Brendan Gregg.
#
#  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.
#
#  (http://www.gnu.org/copyleft/gpl.html)
#
# Author: Brendan Gregg  [Sydney, Australia]
#
# 21-Mar-2004	Brendan Gregg	Created this.

use Sun::Solaris::Kstat;
my $Kstat = Sun::Solaris::Kstat->new();


#
#  --- Process command line args ---
#
if ($ARGV[0] eq "-h" || $ARGV[0] eq "--help" || $ARGV[0] eq "0") { &usage(); }
$sleep = $ARGV[0];
$loop = $ARGV[1];
if ($sleep eq "") {
	$sleep = 1; $loop = 1; 
} elsif ($loop eq "") {
	$loop = 2**32;
}
$PAGESIZE = 20;				# max lines per header
$lines = $PAGESIZE;			# counter for lines printed
$| = 1;


#
#  --- Main ---
#

while (1) {
	if ($lines++ >= $PAGESIZE) {
		$lines = 0;
		printf("%8s %6s %6s %6s %6s %6s %6s\n",
		 "CPU Time","1sec","5sec","15sec","1min","5min","15min");
	}

	#
	#  Store old and get new values
	#
	unshift(@Runque,$runque);
	unshift(@Update,$update);
	($runque,$update) = fetch();

	#
	#  Calculate load averages
	#
	$sec1 = ratio($runque,$Runque[0],$update,$Update[0]);
	$sec5 = ratio($runque,$Runque[4],$update,$Update[4]);
	$sec15 = ratio($runque,$Runque[14],$update,$Update[14]);
	$min1 = ratio($runque,$Runque[59],$update,$Update[59]);
	$min5 = ratio($runque,$Runque[299],$update,$Update[299]);
	$min15 = ratio($runque,$Runque[899],$update,$Update[899]);

	#
	#  Print load averages
	#
	if ($sec1 ne "" && (($count % $sleep) == 0)) {
		@Time = localtime();
		printf("%02d:%02d:%02d %6s %6s %6s %6s %6s %6s\n",$Time[2],
		 $Time[1],$Time[0],$sec1,$sec5,$sec15,$min1,$min5,$min15);

		### Check for end
		last if ++$printed == $loop;
	}
	$count++;


	### Interval
	sleep (1);

	### Memory cleanup
	pop(@Runque) if $count > 901;
	pop(@Update) if $count > 901;
}


#
#  --- Subroutines ---
#

# fetch - fetch current values for runque and updates.
#
sub fetch {
	$Kstat->update();
	return ($Kstat->{unix}->{0}->{sysinfo}->{runque},
	 $Kstat->{unix}->{0}->{sysinfo}->{updates});	
}


# ratio - calculate the ratio of the count delta over time delta;
# 	given count and oldcount, time and oldtime. Returns a string
#	of the value, or a null string if not enough data was given.
#
sub ratio {
	my ($count,$oldcount,$time,$oldtime) = @_;

	return "" unless defined $oldtime;

	$countd = $count - $oldcount;
	$timed = $time - $oldtime;
	if ($timed > 0) { 
		$ratio = $countd / $timed;
	} else {
		$ratio = 0;
	}
	return sprintf("%.2f",$ratio);
}


# usage - print usage and exit.
#
sub usage {
        print STDERR <<END;
USAGE: $0 [-h] | [interval [count]]
   eg, $0               # print a 1 second sample
       $0 1             # print continually every 1 second
       $0 1 5           # print 5 times, every 1 second
END
        exit 1;
}

