#!/usr/bin/perl -w =pod =head1 NAME thetop - A 'top' utility for the schwartz =head1 SYNOPSIS thetop [--func FORMAT] [--arg FORMAT] [--sort ARGS] [--delay SECS] [--score-dir DIR] =head1 DESCRIPTION =cut #--------------------------------------# # Dependencies use strict; use Getopt::Long; use Term::Cap; use POSIX; #--------------------------------------# # Global Variables use vars qw( $OSPEED ); BEGIN { my $termios = POSIX::Termios->new; $termios->getattr; $OSPEED = $termios->getospeed || 9600; }; our $TERM = Term::Cap->Tgetent({OSPEED=>$OSPEED}); #--------------------------------------# # Main Program my ($score_dir, $delay, $func_col, @arg_col, $sort); GetOptions('score-dir=s' => \$score_dir, 'delay|d=s' => \$delay, 'func=s' => \$func_col, 'arg=s' => \@arg_col, 'sort|s=s' => \$sort, ); # Make sure we know where to find the scoreboard files unless ($score_dir) { foreach my $d (qw(/var/run /dev/shm /tmp)) { if (-e "$d/theschwartz") { $score_dir = "$d/theschwartz"; last; } } die "Can't find scoreboard directory. Use '--score-dir'\n" unless $score_dir; } # If we got some formatting instructions for the arg column, parse it out my %arg_col_by_func; if (@arg_col) { foreach my $a (@arg_col) { if ($a =~ /=/) { my ($func, $fmt) = split('=', $a); $arg_col_by_func{$func} = $fmt; } else { $arg_col_by_func{'__ALL__'} = $a; } } } # Make sure to give a reasonable default for delay $delay ||= 3; # Start reporting clr_screen(); while (1) { report($score_dir, $func_col, \%arg_col_by_func, $sort); sleep($delay); clr_screen(); } ################################################################################ sub report { my ($dir, $func_col, $arg_col_by_func, $sort) = @_; # Find the files available opendir(SD, $dir) or die "Can't read directory '$dir': $!\n"; my @files = map { $dir."/$_" } readdir(SD); closedir(SD); # Grab the data out of them my @data; foreach my $f (@files) { next unless $f =~ /scoreboard\.[0-9]+$/; open(SF, '<', $f) or die "Can't open score file '$f': $!\n"; my %dat = map { chomp; split('=') } ; close(SF); $dat{arg_array} = [split(',', $dat{arg}||'')]; push @data, \%dat; } my $num = scalar(@data); my $width = 80-17-$num; printf("Workers: %d total %${width}s\n\n", $num, scalar localtime); printf("% 5s % 20s % 2s % 7s % 41s\n", 'PID', 'FUNC', 'S', 'TIME', 'ARGS'); foreach my $d (sort { order_by($sort, $a, $b) } @data) { my $func_str = fmt_func($d, $func_col); printf("% 5s % 20s % 2s % 7s % 41s\n", $d->{pid}, $func_str, ($d->{done} ? 'S' : 'R'), fmt_time($d), fmt_arg($d, $arg_col_by_func, $func_str), ); } } sub order_by { my ($sort, $a, $b) = @_; if ($sort) { } else { # Default to push running tasks to the top return ($a->{done}||0) <=> ($b->{done}||0) || ($a->{started}||0) <=> ($b->{started}||0); } } sub fmt_func { my ($d, $fmt) = @_; my $val = $d->{funcname}; if ($fmt) { if ($fmt eq 'trim') { $val =~ s/^.+:://g; } else { $val =~ /($fmt)/; $val = $1; } } return substr($val, 0, 20), } sub fmt_time { my ($d) = @_; my $secs = ($d->{done}||time) - $d->{started}; if ($secs < 60) { return sprintf("%02d:%02d", 0, $secs); } elsif ($secs < 3600) { my $min = int($secs/60); $secs = $secs%60; return sprintf("%02d:%02d", $min, $secs); } else { my $hr = int($secs/60/60); my $min = int($secs/60%60); $secs = $secs%60; return sprintf("%d:%02d:%02d", $hr, $min, $secs); } } ## Format the arguments by interpreting the args as either a hash or an array ## and printing out the appropriate element. sub fmt_arg { my ($d, $arg_col_by_func, $func_str) = @_; my $val = $d->{arg}; my $func_orig = $d->{funcname}; if ($arg_col_by_func) { my $fmt = ($arg_col_by_func{$func_str} || $arg_col_by_func{$func_orig} || $arg_col_by_func{'__ALL__'}); if ($fmt) { my $arg_array = $d->{arg_array}; # If its a number treat the args as an array if ($fmt =~ /^[0-9]+$/) { $val = $arg_array->[$fmt]; } # otherwise, treat the args as a hash else { # Compensate for odd numbers of args push @$arg_array, undef if scalar(@$arg_array) % 2; my %h = @$arg_array; $val = $h{$fmt}; } } } return substr($val||'', 0, 41), } sub clr_screen { $TERM->Tputs('cl', 1, \*STDOUT); }