1#!/usr/perl5/bin/perl 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22# 23# 24# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 25# Use is subject to license terms. 26# 27 28require 5.8.4; 29use strict; 30use warnings; 31use locale; 32use Getopt::Std; 33use POSIX qw(locale_h strftime); 34use I18N::Langinfo qw(langinfo D_T_FMT); 35use File::Basename; 36use Sun::Solaris::Utils qw(textdomain gettext gmatch); 37use Sun::Solaris::Kstat; 38 39# 40# Print an usage message and exit 41# 42 43sub usage(@) 44{ 45 my (@msg) = @_; 46 print STDERR basename($0), ": @msg\n" if (@msg); 47 print STDERR gettext( 48 "Usage:\n" . 49 "kstat [ -qlp ] [ -T d|u ] [ -c class ]\n" . 50 " [ -m module ] [ -i instance ] [ -n name ] [ -s statistic ]\n" . 51 " [ interval [ count ] ]\n" . 52 "kstat [ -qlp ] [ -T d|u ] [ -c class ]\n" . 53 " [ module:instance:name:statistic ... ]\n" . 54 " [ interval [ count ] ]\n" 55 ); 56 exit(2); 57} 58 59# 60# Print a fatal error message and exit 61# 62 63sub error(@) 64{ 65 my (@msg) = @_; 66 print STDERR basename($0), ": @msg\n" if (@msg); 67 exit(1); 68} 69 70# 71# Generate an anonymous sub that can be used to filter the kstats we will 72# display. The generated sub will take one parameter, the string to match 73# against. There are three types of input catered for: 74# 1) Empty string. The returned sub will match anything 75# 2) String surrounded by '/' characters. This will be interpreted as a 76# perl RE. If the RE is syntactically incorrect, an error will be 77# reported. 78# 3) Any other string. The returned sub will use gmatch(3GEN) to match 79# against the passed string 80# 81 82sub gen_sub($) 83{ 84 my ($pat) = @_; 85 86 # Anything undefined or empty will always match 87 if (! defined($pat) || $pat eq '') { 88 return (sub { 1; }); 89 90 # Anything surrounded by '/' is a perl RE 91 } elsif ($pat =~ m!^/[^/]*/$!) { 92 my $sub; 93 if (! ($sub = eval "sub { return(\$_[0] =~ $pat); }" )) { 94 $@ =~ s/\s+at\s+.*\n$//; 95 usage($@); 96 } 97 return ($sub); 98 99 # Otherwise default to gmatch 100 } else { 101 return (sub { return(gmatch($_[0], $pat)); }); 102 } 103} 104 105# 106# Main routine of the script 107# 108 109# Set message locale 110setlocale(LC_ALL, ""); 111textdomain(TEXT_DOMAIN); 112 113# Process command options 114my (%opt, @matcher); 115getopts('?qlpT:m:i:n:s:c:', \%opt) || usage(); 116usage() if exists($opt{'?'}); 117 118# Validate -q and -l flags 119my $quiet = exists($opt{q}) ? 1 : 0; 120my $list = exists($opt{l}) ? 1 : 0; 121my $parseable = exists($opt{'p'}) || $list ? 1 : 0; 122usage(gettext("-q and -l are mutually exclusive")) if ($quiet && $list); 123 124# Get interval & count if specified 125my ($interval, $count) = (0, 1); 126if (@ARGV >= 2 && $ARGV[-2] =~ /^\d+$/ && $ARGV[-1] =~ /^\d+$/) { 127 $count = pop(@ARGV); 128 $interval = pop(@ARGV); 129 usage(gettext("Interval must be an integer >= 1")) if ($interval < 1); 130 usage(gettext("Count must be an integer >= 1")) if ($count < 1); 131} elsif (@ARGV >= 1 && $ARGV[-1] =~ /^\d+$/) { 132 $interval = pop(@ARGV); 133 $count = -1; 134 usage(gettext("Interval must be an integer >= 1")) if ($interval < 1); 135} 136 137# Get timestamp flag 138my $timestamp; 139my $timefmt; 140if ($timestamp = $opt{T}) { 141 if ($timestamp eq "d") { 142 $timefmt = langinfo(D_T_FMT) . "\n"; 143 $timestamp = sub { print(strftime($timefmt, localtime())); }; 144 } elsif ($timestamp eq "u") { 145 $timestamp = sub { print(time(), "\n"); }; 146 } else { 147 usage(gettext("Invalid timestamp specifier"), $timestamp); 148 } 149} 150 151# Deal with -[mins] flags 152if (grep(/[mins]/, keys(%opt))) { 153 usage(gettext("module:instance:name:statistic and " . 154 "-m -i -n -s are mutually exclusive")) if (@ARGV); 155 push(@ARGV, join(":", map(exists($opt{$_}) ? $opt{$_} : "", 156 qw(m i n s)))); 157} 158 159# Deal with class, if specified 160my $class = gen_sub(exists($opt{c}) ? $opt{c} : ''); 161 162# If no selectors have been defined, add a dummy one to match everything 163push(@ARGV, ":::") if (! @ARGV); 164 165# Convert each remaining option into four anonymous subs 166foreach my $p (@ARGV) { 167 push(@matcher, [ map(gen_sub($_), (split(/:/, $p, 4))[0..3]) ]); 168} 169 170# Loop, printing the selected kstats as many times and as often as required 171my $ks = Sun::Solaris::Kstat->new(strip_strings => 1); 172my $matched = 0; 173 174# Format strings for displaying data 175my $fmt1 = "module: %-30.30s instance: %-6d\n"; 176my $fmt2 = "name: %-30.30s class: %-.30s\n"; 177my $fmt3 = "\t%-30s %s\n"; 178 179while ($count == -1 || $count-- > 0) { 180 &$timestamp() if ($timestamp); 181 182 foreach my $m (@matcher) { 183 my ($module, $instance, $name, $statistic) = @$m; 184 185 foreach my $m (sort(grep(&$module($_), keys(%$ks)))) { 186 my $mh = $ks->{$m}; 187 188 foreach my $i (sort({ $a <=> $b } 189 grep(&$instance($_), keys(%$mh)))) { 190 my $ih = $mh->{$i}; 191 192 foreach my $n (sort(grep(&$name($_), 193 keys(%$ih)))) { 194 my $nh = $ih->{$n}; 195 196 # Prune any not in the required class 197 next if (! &$class($nh->{class})); 198 199 if ($quiet) { 200 $matched = grep(&$statistic($_), 201 keys(%$nh)) ? 1 : 0; 202 203 } elsif ($parseable) { 204 foreach my $s 205 (sort(grep(&$statistic($_), 206 keys(%$nh)))) { 207 print("$m:$i:$n:$s"); 208 print("\t$nh->{$s}") 209 if (! $list); 210 print("\n"); 211 $matched = 1; 212 } 213 214 # human-readable 215 } else { 216 if (my @stats = 217 sort(grep(&$statistic($_), 218 keys(%$nh)))) { 219 printf($fmt1, $m, $i); 220 printf($fmt2, $n, 221 $nh->{class}); 222 foreach my $s 223 (grep($_ ne "class", 224 @stats)) { 225 printf($fmt3, 226 $s, $nh->{$s}); 227 } 228 print("\n"); 229 $matched = 1; 230 } 231 } 232 } 233 } 234 } 235 } 236 # Toggle line buffering off/on to flush output 237 $| = 1; $| = 0; 238 239 if ($interval && $count) { 240 sleep($interval); 241 $ks->update(); 242 print("\n"); 243 } 244} 245exit($matched ? 0 : 1); 246