#!/usr/bin/perl
#
# loadnet - print network load (errors due to saturation). Solaris 8+.
#
# This Perl program uses the Sun::Solaris::Kstat library to fetch values.
#
# 24-Apr-2005, ver 0.81  (check for new versions, http://www.brendangregg.com)
#
#
# USAGE: loadnet [-h] | [interval [count]]
#        loadnet                 # print a 1 second sample
#        loadnet -h              # print help
#        loadnet 1               # print continually, every 1 second
#        loadnet 1 5             # print 5 times, every 1 second
#
# The load averages are based on the number of send and receive buffer 
#  errors for all the network interfaces (generated when the interfaces
#  are running at saturation), averaged across the interval and divided 
#  by a factor. A consistant load average of 1.00 would indicate there 
#  was a moderate amount of saturation across the network interfaces 
#  (usually this is bad). A load average of 8.00 would indicate heavy 
#  saturation (due to heavy demand) on the network interfaces.
#
# A load of 0.00 indicates the interface is not saturated (still may be
#  utilised).
#
# The total number of network interfaces needs to be taken into acocunt. 
#  A consistant load of 4.00 on a single interface server is probably a 
#  problem, but on a server with eight interfaces it may be sporadic activity
#  spread across the interfaces (still possibly a problem). Further 
#  investigation with netstat may be required.
#  
# NOTE: If you have an unusual network interface, check it's instance name
#  is listed in this code (a few lines beneath this comment block).
#
#
# SEE ALSO: kstat -n hme0 [interval [count]]       # or qfe0, ...
#           netstat -iI hme0 [interval [count]]
#           nicstat [interval [count]]             # % Utilisation
#           se netstat.se [interval]               # SE Toolkit
#           se nx.se [interval]                    # SE Toolkit
#
# 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();

#
#  Network card instance names
#
@Network = qw(be bge ce ci dmfe e1000g el eri elxl fa ge hme ipdptp iprb
              lane le nf ppp qe qfe rtls sppp vge);


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


#
#  --- Main ---
#

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

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

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


#
#  --- Subroutines ---
#

# fetch - fetch KStat values for the network interfaces. The values used 
#	are defer, nocanput, norcvbuf and noxmtbuf. 
#
sub fetch {
	my ($error,$time,$driver,$instance,$category);
	my (%Driver,%Instance,%Category);
	$error = 0;

	$Kstat->update();

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

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

	   $Driver = $Kstat->{$driver};
	   foreach $instance (keys(%$Driver)) {
		$Instance = $Driver->{$instance};
		foreach $category (keys(%$Instance)) {
		
		   $Category = $Instance->{$category};
		   if (defined $$Category{nocanput} || 
		    defined $$Category{norcvbuf}) {
			$error += $$Category{defer};
			$error += $$Category{nocanput};
			$error += $$Category{norcvbuf};
			$error += $$Category{noxmtbuf};
			# use the last wlastupdate value found,
			$time = $$Category{snaptime};
		   }
		}
	   }
	}

	#
	#  Divide errors by 200. This gives more sensible load averages,
	#  such as 4.00 meaning heavily saturated rather than 800.00.
	#  Future versions of this program may use a more elegant technique
	#  rather than a factor of 200.
	#
	$error = $error / 200;

	return ($error,$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;
}

