#!/usr/bin/perl
#
# loaddisk - print disk load averages (operations in wait queues). Solaris 8+.
#
# This Perl program uses the Sun::Solaris::Kstat library to fetch values.
#
# 22-Sep-2005, ver 0.82  (check for new versions, http://www.brendangregg.com)
#
#
# USAGE: loaddisk [-h] | [interval [count]]
#        loaddisk                 # print a 1 second sample
#        loaddisk -h              # print help
#        loaddisk 1               # print continually, every 1 second
#        loaddisk 1 5             # print 5 times, every 1 second
#
# The load averages are based on taking the total length of the wait
#  queues for all the disks, averaged across the interval. A load average
#  of 1.00 would indicate there was an average of one operation waiting to be
#  serviced (usually this is bad). 2.00 would indicate an average of two
#  operations in wait queues.
#
# A load of 0.00 indicates the disk transactions are not waiting to
#  be serviced, however this does not mean the disks are idle - they
#  may be busy but not saturated. (see iostat).
#
# The total number of disks needs to be taken into acocunt. A load of 1.00
#  on a single disk server is probably a problem, however a load of 1.00
#  on a server with ten disks only indicates that further investigation
#  is required (iostat, sar, ...); perhaps a single disk has a load of 1.00,
#  or perhaps each of the ten disks has a load of 0.10, adding to 1.00. 
#  Currently this command does not display load on a disk by disk basis
#  (use iostat for now).
#  
# NOTE: If you have an unusual disk, check it's instance name is listed
#  in this code (a few lines beneath this comment block).
#
#
# SEE ALSO: kstat -m dad [interval [count]]
#           iostat -xnmp [interval [count]]
#
# REFERENCE: "How do disks really work" Adrian Cockcroft, swol-06-1996
#
# 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();

#
#  Disk instance names
#
@Disk = qw(cmdk dad sd ssd);


#
#  --- 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;
$Disk{$_} = 1 foreach (@Disk);


#
#  --- Main ---
#

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

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

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


#
#  --- Subroutines ---
#

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

	$Kstat->update();

	foreach $driver (keys(%$Kstat)) {

	   ### Check that this is a disk structure,
	   next unless $Disk{$driver};

	   $Driver = $Kstat->{$driver};
	   foreach $instance (keys(%$Driver)) {
		$Instance = $Driver->{$instance};
		foreach $category (keys(%$Instance)) {
		
		   ### Check that this isn't a slice
		   next if $category =~ /,/;

		   $Category = $Instance->{$category};
		   if (defined $$Category{wlentime}) {
			$wait += $$Category{wlentime};
			# use the last wlastupdate value found,
			$time = $$Category{wlastupdate};
		   }
		}
	   }
	}
	return ($wait,$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;
}

