11.3. Using Perl to Access kstatsThe previous example illustrates how simple it is to extract the information you need from the kernel; however, it also shows how tedious it can be to format the output in a shell script. Fortunately, the Perl extension module that /usr/bin/ kstat uses is documented so that you can write custom Perl programs. Because Perl is a "real programming language" and is ideally suited for text formatting, you can write solutions that are quite robust and comprehensive. 11.3.1. The Tied-Hash Interface to the kstat FacilityAccess to kstats is made through a Perl extension in the XSUB interface module called Sun::Solaris::Kstat. To access Solaris kernel statistics in a Perl program, you use Sun::Solaris::Kstat; to import the module The module contains two methods, new() and update(), correlating with the libkstat C functions kstat_open() and kstat_chain_update(). The module provides kstat data through a tree of hashes based on a three-part key, consisting of the module, instance, and name (ks_module, ks_instance, and ks_name are members of the C-language kstat struct). Following is a synopsis. Sun::Solaris::Kstat->new(); Sun::Solaris::Kstat->update(); Sun::Solaris::Kstat->{module}{instance}{name}{statistic} The lowest-level "statistic" member of the hierarchy is a tied hash implemented in the XSUB module and holds the following elements from struct kstat:
Because the module converts all kstat types, you need not worry about the different data structures for named and raw types. Most of the Solaris OS raw kstat entries are decoded by the module, giving you easy access to low-level data about things such as kernel memory allocation, swap, NFS performance, etc. 11.3.2. The update() MethodThe update() method updates all the statistics you have accessed so far and adds a bit of functionality on top of the libkstat kstat_chain_update() function. If called in scalar context, it acts the same as kstat_chain_update(). It returns 0 if the kstat chain has not changed and 1 if it has. However, if update() is called in list context, it returns references to two arrays. The first array holds the keys of any kstats that have been added since the call to new() or the last call to update(); the second holds a list of entries that have been deleted. The entries in the arrays are strings of the form module:instance:name. This is useful for implementing programs that cache state information about devices, such as disks, that you can dynamically add or remove from a running system. Once you access a kstat, it will always be read by subsequent calls to update(). To stop it from being reread, you can clear the appropriate hash. For example: $kstat->{$module}{$instance}{$name} = (); 11.3.3. 64-Bit ValuesAt the time the kstat tied-hash interface was first released on the Solaris 8 OS, Perl 5 could not yet internally support 64-bit integers, so the kstat module approximates these values.
11.3.4. Getting Started with PerlAs in our first example, the following example shows a Perl program that gives the same output as obtained by calling /usr/sbin/psrinfo without arguments. #!/usr/bin/perl -w # psrinfo.perl: emulate the Solaris psrinfo command use strict; use Sun::Solaris::Kstat; my $kstat = Sun::Solaris::Kstat->new(); my $mh = $kstat->{cpu_info}; foreach my $cpu (keys(%$mh)) { my ($state, $when) = @{$kstat->{cpu_info}{$cpu} {"cpu_info".$cpu}}{qw(state state_begin)}; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($when))[0..5]; printf("%d\t%-8s since %.2d/%.2d/%.2d %.2d:%.2d:%.2d\n", $cpu,$state,$mon + 1,$mday,$year - 100,$hour,$min,$sec); } This program produces the following output: $ psrinfo.perl 0 on-line since 07/09/01 08:29:00 1 on-line since 07/09/01 08:29:07 The psrinfo command has a -v (verbose) option that prints much more detail about the processors in the system. The output looks like the following example: $ psrinfo -v Status of processor 0 as of: 08/17/01 16:52:44 Processor has been on-line since 08/14/01 16:27:56. The sparcv9 processor operates at 400 MHz, and has a sparcv9 floating point processor. Status of processor 1 as of: 08/17/01 16:52:44 Processor has been on-line since 08/14/01 16:28:03. The sparcv9 processor operates at 400 MHz, and has a sparcv9 floating point processor. All the information in the psrinfo command is accessible through the kstat interface. As an exercise, try modifying the simple psrinfo.perl example script to print the verbose information, as in this example. 11.3.5. netstatMulti Implemented in PerlThe Perl script in the following example has the same function as our previous example (in Section 11.2.2 ) that used the kstat and nawk commands. Note that we have to implement our own search methods to find the kstat entries that we want to work with. Although this script is not shorter than our first example, it is certainly easier to extend with new functionality. Without much work, you could create a generic search method, similar to how /usr/bin/kstat works, and import it into any Perl scripts that need to access Solaris kernel statistics. #!/usr/bin/perl -w # netstatMulti.perl: print out netstat-like stats for multiple interfaces # using the kstat tied hash facility use strict; use Sun::Solaris::Kstat; my $USAGE = "usage: $0 ... interval"; ###### # Main ###### sub interface_exists($); sub get_kstats(); sub print_kstats(); # process args my $argc = scalar(@ARGV); my @interfaces = (); my $fmt = "%-10s %-10u %-10u %-10u %-10u %-10u\n"; if ($argc < 2) { print "$USAGE\n"; exit 1; } elsif ( !($ARGV[-1] =~ /^\d+$/) ) { print "$USAGE\n"; print " interval must be an integer.\n"; exit 1; } # get kstat chain a la kstat_open() my $kstat = Sun::Solaris::Kstat->new(); # Check for interfaces foreach my $interface (@ARGV[-($argc)..-2]) { my $iface; if(! ($iface = interface_exists($interface)) ){ print "$USAGE\n"; print " interface $interface not found.\n"; exit 1; } push @interfaces, $iface; } my $interval = $ARGV[-1]; # print header print " input output \n"; print " packets errs packets errs colls\n"; # loop forever printing stats while(1) { get_kstats(); print_kstats(); sleep($interval); $kstat->update(); } ############# # Subroutines ############# # search for the first kstat with given name sub interface_exists($) { my ($name) = @_; my ($mod, $inst) = $name =~ /^(.+?)(\d+)$/; return(exists($kstat->{$mod}{$inst}{$name}) ? { module => $mod, instance => $inst, name => $name } : undef); } # get kstats for given interface sub get_kstats() { my (@statnames) = ('ipackets','ierrors','opackets', 'oerrors','collisions'); my ($m, $i, $n); foreach my $interface (@interfaces) { $m = $interface->{module}; $i = $interface->{instance}; $n = $interface->{name}; foreach my $statname (@statnames) { my $stat = $kstat->{$m}{$i}{$n}{$statname}; die "kstat not found: $m:$i:$n:$statname" unless defined $stat; my $begin_stat = "b_" . $statname; # name of first sample if(exists $interface->{$begin_stat}) { $interface->{$statname} = $stat - $interface->{$begin_stat}; }else { # save first sample to calculate deltas $interface->{$statname} = $stat; $interface->{$begin_stat} = $stat; } } } } # print out formatted information a la netstat sub print_kstats() { foreach my $i (@interfaces) { printf($fmt,$i->{name},$i->{ipackets},$i->{ierrors}, $i->{opackets},$i->{oerrors},$i->{collisions}); } } In the subroutine interface_exists(), you cache the members of the key if an entry is found. This way, you need not do another search in get_kstats(). You could fairly easily modify the script to display all network interfaces on the system (rather than take command-line arguments) and use the update() method to discover if interfaces are added or removed from the system (with ifconfig, for example). This exercise is left up to you. |