#!/usr/bin/perl
#
# loadmem - print memory load averages (scan rate). Solaris 8+.
#
# This Perl program uses the Sun::Solaris::Kstat library to fetch values.
#
# 21-Aug-2004, ver 0.80  (check for new versions, http://www.brendangregg.com)
#
#
# USAGE: loadmem [-h] | [interval [count]]
#        loadmem                 # print a 1 second sample
#        loadmem -h              # print help
#        loadmem 1               # print continually, every 1 second
#        loadmem 1 5             # print 5 times, every 1 second
#
# The load averages are based on dividing the scan rate for all the CPUs
#  by the speed of slow scan, averaged across the interval. A load average
#  of 1.00 would indicate that on average the kernel was scanning through 
#  memory at a slow pace looking for pages of memory to pageout. A memory
#  shortage. A load average of 6.00 would indicate rapid scanning through 
#  memory indicating a greater demand for memory.
#
# A load of 0.00 indicates no current memory shortage reported (yet you
#  may only have 1% memory left - there just isn't any current demand).
#
# That this program requires Solaris 8 or newer needs to be kept in mind, 
#  as this means the filesystem cache is managed by the cyclic page cache;
#  in previous versions of Solaris a consistant level of scanning was
#  normal as this was maintenance for the filesystem cache. Now with
#  the cyclic page cache any consistant scanning at all indicates a 
#  memory shortage.
#  
# The scan rate values are divided by slowscan (usually 100) to keep the 
#  values at levels similar to the other load programs.
#  
#
# SEE ALSO: kstat -m cpu_stat [interval [count]]
#           vmstat
#
# 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]
#
# 22-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",
		 "Mem Time","1sec","5sec","15sec","1min","5min","15min");
	}

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

	#
	#  Calculate load averages
	#
	$sec1 = ratio($scan,$Scan[0],$update,$Update[0]);
	$sec5 = ratio($scan,$Scan[4],$update,$Update[4]);
	$sec15 = ratio($scan,$Scan[14],$update,$Update[14]);
	$min1 = ratio($scan,$Scan[59],$update,$Update[59]);
	$min5 = ratio($scan,$Scan[299],$update,$Update[299]);
	$min15 = ratio($scan,$Scan[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(@Scan) if $count > 901;
	pop(@Update) if $count > 901;
}


#
#  --- Subroutines ---
#

# fetch - fetch KStat values for the scanrate. The values used are
# 	scan and snaptime.
#
sub fetch {
	my ($scan,$time,$driver,$instance,$category);
	my (%Driver,%Instance,%Category);
	$scan = 0;

	$Kstat->update();

	$Driver = $Kstat->{cpu_stat};
	foreach $instance (keys(%$Driver)) {

	   $Instance = $Driver->{$instance};
	   foreach $category (keys(%$Instance)) {
		
		$Category = $Instance->{$category};
		if (defined $$Category{scan}) {
			$scan += $$Category{scan};
			# use the last wlastupdate value found,
			$time = $$Category{snaptime};
		}
	   }
	}

	#
	#  Divide scanrate by slowscan. This gives more sensible load averages,
	#  eg a consistant load of 1.00 indicates consistantly at slowscan.
	#  slowscan is usually 100.
	#
	$scan = $scan / $Kstat->{unix}->{0}->{system_pages}->{slowscan};

	return ($scan,$time);
}


# 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;
}

