#!/usr/bin/env perl
use Getopt::Long;
use Term::ANSIColor qw(:constants);

@statlist = ("v2ops", "v2err", "v3ops", "v3err");

my %data = ( interval => 5,
	     bottom => 0,
             color => 0,
	     vertical => 0,
);

our %deltas;
our %cache;

# set stdout to unbuffered to allow people using rsh to see data immediately
select STDOUT;
$| = 1;

sub print_matching {
    my %options = @_;
    my $cachekey = join(",", @{$options{match}}, @{$options{unmatch}});
    my @keys, @keylist;

#    print "Cache key is $cachekey\n";
#    foreach my $cacheitem (keys %cache){
#	print "Cache item $cacheitem = $cache{$cacheitem}\n";
#    }

    if (defined($cache{$cachekey})){
#	print "Found key in cache\n";
	@keylist = split(/,/, $cache{$cachekey});
    } else {
#	print "Did not find key in cache\n";
	@keylist = keys %deltas;
#    print "Keylist is ", join(',', @keylist), "\n";
#	print "Num keys = ", (scalar @keylist), "\n";
	foreach my $unmatch (@{$options{unmatch}}){
	    @keylist = grep !/$unmatch/, @keylist;
#	    print "Num keys ($unmatch) = ", (scalar @keylist), "\n";
	}
#	print "Num keys = ", (scalar @keylist), "\n";
	foreach my $match (@{$options{match}}){
	    @keylist = grep /$match/, @keylist;
#	    print "Num keys ($match) = ", (scalar @keylist), "\n";
	}
#	print "Num keys = ", (scalar @keylist), "\n";
	$cache{$cachekey} = join(',',@keylist) if (scalar @keylist);
    }

#    print "Keylist is ", join(',', @keylist), "\n";
#    undef @keys;
    print join(" ", @{$options{match}}), ": ";
    foreach my $key (sort @keylist){
	print " $deltas{$key}";
#	push @keys, $key;
    }
    print "\n";
    
#    print join(' ', @keys), "\n";
}

sub handle_stat {
    my ($stat, $value) = @_;
    my $dodelta = ($value =~ /^\s*-*\d+\s*$/);

    $stat =~ s/^\s+//;
    $value =~ s/^\s+//;
    if (defined($stats{$stat})){
	if ($stats{$stat} ne $value){
	    $cur = $stats{$stat} if $dodelta;
	    $stats{$stat} = "$value";
	    if ($data{vertical}){
		$deltas{$stat} = ($stats{$stat} - $cur);
#		print "Set $stat to $deltas{$stat}\n"; 
	    } else {
		print "$stat: "; #, $stats{$stat}, "->"; 
		if ($dodelta){
		    print $stats{$stat}, " d: ", ($stats{$stat} - $cur), "\n";
		} else {
		    print $stats{$stat}, "\n";
		}
		$printed = 1;
	    }
	}
    } else {
	$stats{$stat} = "$value";
	if (!$data{vertical}){
	    print "$stat: ", $stats{$stat}, "\n";
	    $printed = 1;
	}
    }
}

# set auto reset to true
$Term::ANSIColor::AUTORESET = 1;

warn "Error parsing command line" unless GetOptions(\%data, 'interval=i', 'bottom', 'help', 'color', 'timestamp', 'vertical');

if ($data{help}){
    print <<"EOF";
Usage:
    $0 [-interval=seconds] [-bottom] [-color] [-timestamp]

    -interval  indicates the interval between reads of /proc/spinstats.
    -bottom    indicates that the "bottom" statistics should be monitored.
    -color     print some things in color
    -timestamp print time stamps in header
    -help      prints this message and exits.

Note that all command-line options can be specified as the shortest
string that uniquely identifies the option.
EOF
    
    exit 0;
}

$tag = undef;
$build = "";

while (1){
    if ($data{vertical}){
	undef %deltas if $data{vertical};
	foreach my $skey (keys %stats){
	    $deltas{$skey} = 0;
	}
    }
	
    open(IF, "/proc/spinstats");
    @data = <IF>;
    close(IF);

    $printed = 0;
    foreach $line (@data){
	chomp $line;
	next if ($line =~ /^\s*$/);
	if ($line =~ /^Build: \w+\.(\w+)/){
	    # Handle buld line
	    $build = $1;
	} elsif ($line =~ /^(\w+ *\w*):$/){
	    # Handle tag line
	    $tag = $1;
	    $tag =~ s/ /_/;
	} elsif ($line =~ /:/){
	    # Bottom stat
	    next unless $data{bottom};
	    
	    $line =~ s/larges/reserved larges/ if ($line =~ /^reserved/); # Hack for strange file formatting

	    if ($line =~ /^rf rtt/){
		# need different processing for the rtt line
		($keya, $data, $keyb) = split(/[:\(\)]/, $line);
		$data =~ s/^\s+//;
		$data =~ s/\s+$//;
		$keya =~ s/^\s+//;
		$keya =~ s/\s+$//;
		$keyb =~ s/^\s+//;
		$keyb =~ s/\s+$//;
		@rftimes = split(/\s+/, $data);
		@rfkeys = split(/,/, $keyb);
		foreach my $time (@rftimes){
		    my $rfkey = $keya . " " . shift @rfkeys;
		    $rfkey =~ s/\s+/ /g;
		    handle_stat($rfkey, $time);
		}
	    } else {
		foreach $ind_stat (split(/,/,$line)){
		    ($stat, $value) = split(/:/, $ind_stat);
		    handle_stat($stat, $value);
		    if ($ind_stat =~ /ronometer/){
			if ($value =~ /\s*0\s+/){
			    if ($stats{larges} > 100){
				$bad_larges = 1;
			    } else {
				$bad_larges = 0;
			    }				
			    if ($stats{smalls} > 100){
				$bad_smalls = 1;
			    } else {
				$bad_smalls = 0;
			    }
			    if ($possible_error){
				if (!$warning_printed && !$data{color}){
				    print "Warning:";
				    print " Larges" if $bad_larges;
				    print " /" if ($bad_larges && $bad_smalls);
				    print " Smalls" if $bad_smalls;
				    print " greater than 100 under no load!\n";
				    $warning_printed = 1;
				}elsif(!$warning_printed){ # print in color
                    print BOLD YELLOW "Warning:";
				    print BOLD YELLOW " Larges" if $bad_larges;
				    print BOLD YELLOW " /" if ($bad_larges && $bad_smalls);
				    print BOLD YELLOW " Smalls" if $bad_smalls;
				    print BOLD YELLOW " greater than 100 under no load!\n";
                                    $warning_printed = 1;
                                }
			    } else {
				$possible_error = 1;
			    }
#			    print "++++> value = $value\n";
			} else {
			    $possible_error = 0;
			    $warning_printed = 0;
			}
		    }
		}
	    }
	} else {
	    # Top stat
	    @linestats = split(/\s+/, $line);
#	    print "Num = " . (scalar @linestats) . "\n";
	    if (scalar @linestats == 4){
		($dummy, $opname, $ops, $err) = @linestats;
		foreach $stat ("ops", "err"){
		    $op = "$opname-$stat";
		    $op = "$tag-$op" if $tag;
		    handle_stat($op, ${$stat});
	        }
	    } elsif (scalar @linestats == 6){
		($dummy, $opname, $v2ops, $v2err, $v3ops, $v3err) = @linestats;
		foreach $stat (@statlist){
		    $op = "$opname-$stat";
		    $op = "$tag-$op" if $tag;
		    handle_stat($op, ${$stat});
	        }
	    }
	}
    }

if ($data{vertical}){
    # Whip through all of the stats and print them
print_matching(match => ["Cifs"], unmatch => ["err"]);
print_matching(match => ["Nfs", "v2"], unmatch => ["err"]);
print_matching(match => ["Nfs", "v3"], unmatch => ["err"]);
print_matching(match => ["LockMgr"], unmatch => ["err"]);
print_matching(match => ["Mount"], unmatch => ["err"]);
$printed = 1;


}
    if ($printed && $data{color}){
        if($data{timestamp}){
            my $date=`date +\%x`;
            chomp $date;
            my $time=`date +\%X`;
            chomp $time;
            print BOLD BLUE "====";
            print BOLD GREEN " $date ";
            print BOLD BLUE "====";
            print BOLD RED " $build ";
            print BOLD BLUE "====";
            print BOLD GREEN " $time ";
            print BOLD BLUE "====\n";
        }else{
            print BOLD BLUE "====================";
            print BOLD RED " $build ";
            print BOLD BLUE "====================\n";
        }
    }elsif ($printed){
        if($data{timestamp}){
            my $date=`date +\%x`;
            chomp $date;
            my $time=`date +\%X`;
            chomp $time;
            print "==== $date ==== $build ==== $time ====\n";
        }else{
	        print "==================== $build ====================\n";
        }
    }
    sleep $data{interval};
}
