[alt.sources] kernel monitoring, or interactively talking to a pipe

tchrist@convex.com (Tom Christiansen) (11/22/90)

Here is a program that sits around a looks at kernel memory location to
record certain values.  It's like vmstat, but more generalized. It does
so by opening a pipe (ok, 2 of them) to adb and sending a it command,
then reading a line back, etc.

For example:

    % vmscan 
    usage: ./vmscan [-sleep] symbol ...
    Valid symbols are: "Context Sw (p)", "Context Sw (v)", "Disk Wait", "Free",
		       "Idle", "Idle 1", "Idle 2", "Idle 3", "Interrupts",
		       "Page Ins", "Page Outs", "Page Wait", "Pgin", "Pgout",
		       "Real", "Reclaims", "Runnable", "Sleeping", "Swapped",
		       "Sys.", "System", "System 1", "System 2", "System 3",
		       "System calls", "User", "User (n)", "User (n) 1", "User
		       (n) 2", "User (n) 3", "User 1", "User 2", "User 3",
		       "Virt.", "ccu0", "ccu1", "ccu2", "ccu3", "ccu4", "ccu5",
		       "ccu6", "ccu7"

    % vmscan -2 User System "User (n)"
    User            System          User (n)
    637             61316           46089
    637             61320           46108
    637             61336           46132
    637             61341           46166
    637             61346           46192
    ...


Things that you'll have to change to make it work on 
your system:

1) There is a table with commands like this:

    User                    cp_time+0/wt
    User (n)                cp_time+4/wt
    System                  cp_time+8/wt

    The precise syntax of the adb command (Convex's is bizarre), 
    and especially the mapping between symbols you recognize
    and the ones your kernel does will need changing.

2)  The number of initial garbage lines adb puts out when 
    talking on a pipe may vary.  It may be none.  You will
    find this in the code.

4)  I've hard-wired $TIOCGWINSZ instead of getting it from ioctl.p[lh]
    as a Careful Programmer should.

3)  You should really be running perl. :-)

4)  You may have to install this setgid kmem to read /dev/mem.


For true perl aficcionados:

1)  You will notice that I use a dynamic format based on window size.
    This is done by using an ioctl to get the window size.    That
    way the usage message is really cool and uses however many 
    columns your window has.

2)  There is a subroutine called &open2 which is like a regular
    open but you get both a read and a write handle.  This is ok
    in this case cause I know that adb reads a line at a time,
    then writes a line at a time.  You should be able to extract
    this and use as is in other programs.  In fact, this is the
    main reason I'm posting this.  If you don't have a recent
    perl patch, quote your filehandle arguments when passing them.

--tom


#!/usr/bin/perl
# 
# vmscan: read stuff out of the kernel like vmstat
# tom christiansen <tchrist@convex.com>


# look for any -sleep switch
#
if ($ARGV[0] =~ /^-(\d+)/) {
    $snooze = $1;
    shift;
} else {
    $snooze = 30; 
}

# set path so taintperl doesn't hate us if running suid
$ENV{'PATH'} = '/bin:/usr/bin:/usr/ucb:/usr/convex:/usr/local';

die "$0: can't read /dev/mem\n" unless -r '/dev/mem';
die "$0: can't read /vmunix\n"  unless -r '/vmunix';

# now be very careful to keep at least one
# tab between the LHS and the RHS, and that
# LHS have no trailing spaces.

%code = split(/[\t\n]+/, <<EO_LIST);
User			cp_time+0/wt
User (n)		cp_time+4/wt
System			cp_time+8/wt
Idle			cp_time+0xc/wt
User 1			cp_time+10/wt
User (n) 1		cp_time+14/wt
System 1		cp_time+18/wt
Idle 1			cp_time+0x1c/wt
User 2			cp_time+20/wt
User (n) 2		cp_time+24/wt
System 2		cp_time+28/wt
Idle 2			cp_time+0x2c/wt
User 3			cp_time+30/wt
User (n) 3		cp_time+34/wt
System 3		cp_time+38/wt
Idle 3			cp_time+0x3c/wt
Runnable		total+0/h
Disk Wait		total+2/h
Page Wait		total+4/h
Swapped			total+8/h
Sleeping		total+6/h
Virt.			total+0xe/wt
Real			total+0x10/wt
Free			total+0x14/wt
Reclaims		cnt+0x38/wt
Page Ins		cnt+0x30/wt
Page Outs		cnt+0x34/wt
Pgin			cnt+0x38/wt
Pgout			cnt+0x3c/wt
Interrupts		cnt+0x10/wt
System calls		cnt+0xc/wt
Context Sw (p)		cnt+0/wt
Context Sw (v)		cnt+0x4/wt
Sys.			cnt+0xc/wt
ccu0			500/wt
ccu0			5b0/wt
ccu1			504/wt
ccu1			5b4/wt
ccu2			508/wt
ccu2			5b8/wt
ccu3			50c/wt
ccu3			5bc/wt
ccu4			510/wt
ccu4			5c0/wt
ccu5			514/wt
ccu5			5c4/wt
ccu6			518/wt
ccu6			5c8/wt
ccu7			51c/wt
ccu7			5cc/wt
EO_LIST

&usage if @ARGV <= 0;

for (@ARGV) { 
    next if defined $code{$_};
    warn "$0: \"$_\" is an undefined entry point\n";
    &usage;
}


# also helps for when we fork to keep the 
# stdout buffer empty in the kid
#
select(STDOUT);  $| = 1; # unbuffer

# print out symbol headers
for (@ARGV) {
    print $_, "\t";
    print "\t" if 8 > length;
}
print "\n";


# in case the worst happens
sub REAPER {
    wait;
    print STDERR "$0: kid died unexpectedly: status $?\n";
    exit 2;
} 
$SIG{'PIPE'} = $SIG{'CHLD'} = 'REAPER';

&open2(DAD_RDR, DAD_WTR, $cmd = 'adb -k /vmunix /dev/mem')
    || die "open2 of $cmd failed: $!";

# eat first three lines of adb noise
# fourth (and prompts) aren't printed in pipes
for $lines (1..3) {
    die "error reading adb pipe" unless defined($_ = <DAD_RDR>);
} 

# get all the code we need to feed the hungry adb
#
@commands = @code{@ARGV};  
#           ^^^^^^^^^^^^^^
# this means ($code{$ARGV[0],$code{$ARGV[1], ...})

while (1) {
    print DAD_WTR join("\n", @commands), "\n";
    for ($count = @commands; $count; $count--) {
	&REAPER() unless defined($_ = <DAD_RDR>);
	split;
	print $_[1], "\t\t";
    } 
    print "\n";
    sleep $snooze;
}

sub usage {
    $winsize = "\0" x 8;
    $TIOCGWINSZ = 0x40087468;  # should be require 'sys/ioctl.pl';

    if (ioctl(STDERR, $TIOCGWINSZ, $winsize)) { 
	($row, $col, $xpixel, $ypixel) = unpack('S4', $winsize);
    } else {
	$col = 80;
    } 

    $arrows = ('<' x ($col - 25));

    eval "format STDERR = \nValid symbols are: ^" .
	    $arrows . 
	    "\n\$symbols\n~~                 ^" . 
	    $arrows . 
	    "\n\$symbols\n.\n";

    select(STDERR);


    @keys = sort keys %code;
    for (@keys) {
	s/^/"/;
	s/$/"/;
    } 
    $symbols = join(", ", @keys);

    print "usage: $0 [-sleep] symbol ...\n";
    write;
    exit 1;
} 

# &open2: tom christiasen, <tchrist@convex.com>
#
#
# usage: $pid = open2('rdr', 'wtr', 'some cmd and args');
#
# spawn the given $cmd and connect $rdr for
# reading and $wtr for writing.  return pid
# of child, or 0 on failure.  
# 
# WARNING: this is dangerous, as you may block forever
# unless you are very careful.  
# 
# $wtr is left unbuffered.
# 
# abort program if
#	rdr or wtr are null
# 	pipe or fork or exec fails

sub open2 {
    local($dad_rdr, $dad_wtr, $cmd) = @_;

    local($kid_rdr) = 'open2_fh00';
    local($kid_wtr) = 'open2_fh01';
    local($kidpid);

    $dad_rdr ne '' 		|| die "open2: rdr should not be null";
    $dad_wtr ne '' 		|| die "open2: wtr should not be null";
    pipe($dad_rdr, $kid_wtr) 	|| die "open2: pipe 1 failed: $!";
    pipe($kid_rdr, $dad_wtr) 	|| die "open2: pipe 2 failed: $!";

    if (($kidpid = fork) < 0) {
	die "open2: fork failed: $!";
    } elsif ($kidpid == 0) {
	close $dad_rdr; 
	close $dad_wtr;
	open(STDIN,  ">&$kid_rdr");
	open(STDOUT, ">&$kid_wtr");
	exec $cmd;
	die "open2: exec of $cmd failed";   
    } 

    close $kid_rdr; 
    close $kid_wtr;

    select((select($dad_wtr), $| = 1)[0]);

    $kidpid;
}