#!/usr/bin/perl
# llstat.pl is a utility that takes stats files as input with optional 
# clear-flag. The clear-flag is used to clear the stats file before 
# printing stats information. The lustre stats files generally located
# inside proc/fs/lustre/. This program first reads the required statistics
# information from specified stat file, process the information and prints
# the output after every interval specified by user.

# Subroutine for printing usages information
sub usage()
{
	print STDERR "Usage: $pname [-c] [-g] [-i <interval>] [-h <help>] <stats_file>\n";
	print STDERR "       stats_file : /proc/fs/lustre/.../stat\n";
	print STDERR "       -i interval: polling period\n";
	print STDERR "       -c         : clear stats file first\n";
	print STDERR "       -g         : graphable output format\n";
	print STDERR "       -h         : help, display this information\n";
	print STDERR "example: $pname -i 1 ost (monitors /proc/fs/lustre/ost/OSS/ost/stats)\n";
	print STDERR "Use CTRL + C to stop statistics printing\n";
	exit 1;
}

sub print_headings()
{	my $cc = $_[0];
	if ($graphable) {
		if ( $print_once && $interval ) {
			printf "Timestamp [Name Count Rate Total Unit]...";
			printf "\n--------------------------------------------------------------------";
			$print_once = 0;
		}
		if ($cc && !$starttime) {
			$starttime = $cc
		}
	       	printf "\n%-5.0f", ($cc - $starttime);
	} else {
		printf "$statspath @ $cc\n";
		printf "%-25s %-10s %-10s %-10s", "Name", "Cur.Count", "Cur.Rate", "#Events";
		if ( $anysum ) {
			printf "%-8s %10s %10s %12s %10s", "Unit", "last", "min", "avg", "max";
		}
		if ( $anysumsquare ) {
			printf "%10s", "stddev";
		}
		printf "\n";
	}
}

# known units are: reqs, bytes, usec, bufs, regs, pages
# readstat subroutine reads and processes statistics from stats file.
# This subroutine gets called after every interval specified by user.
sub readstat()
{
	seek STATS, 0, 0;
	while (<STATS>) {
	chop;
	($name, $cumulcount, $samples, $unit, $min, $max, $sum, $sumsquare) 
		= split(/\s+/, $_);
	$prevcount = %cumulhash->{$name};
	if (defined($prevcount)) {
		$diff = $cumulcount - $prevcount;
		if ($name eq "snapshot_time") {
			$tdiff = int($diff);
			&print_headings($cumulcount);
			$| = 1;
			if ($tdiff == 0) {
			    $tdiff = 1; # avoid division by zero
			}
		}
		elsif ($cumulcount!=0) {
			if ($graphable) {
			    my $myunit = $unit;
			    my $myname = $name;
			    if (defined($sum)) {
				$myunit = "[reqs]";
				$myname = $myname . "_rq";
			    }
			    printf "   %s %lu %lu %lu %s", 
			    $myname, $diff, ($diff/$tdiff), $cumulcount, $myunit;
			} else {
			    printf "%-25s %-10lu %-10lu %-10lu",
			    $name, $diff, ($diff/$tdiff), $cumulcount;
			}
			if (defined($sum)) {
				my $sum_orig = $sum;
				my $sum_diff = $sum - %sumhash->{$name};
				if ($graphable) {
				    printf "   %s %lu %.2f %lu %s",
				    $name, $sum_diff, ($sum_diff/$tdiff), $sum, $unit;
				} else {
				    printf "%-8s %10lu %10lu %12.2f %10lu", $unit, 
				    $sum_diff, $min, ($sum/$cumulcount), $max;
				}
				if (defined($sumsquare)) {
					my $s = $sumsquare - (($sum_orig*$sum_orig)/$cumulcount);
					if ($s >= 0) {
						my $cnt = ($cumulcount >= 2) ? $cumulcount : 2 ;
						my $stddev = sqrt($s/($cnt - 1));
						if (!$graphable) {
							printf " %9.2f ", $stddev;
						}
					}
				}
			}
			if (!$graphable) {
				printf "\n";
			}
			$| = 1;
		}
	} else {
		if ($cumulcount!=0) {
			# print info when interval is not specified.
			printf "%-25s $cumulcount\n", $name
		}
		if (defined($sum)) {
			$anysum = 1;
		}
		if (defined($sumsquare)) {
			$anysumsquare = 1;
		}
	}
	%cumulhash->{$name} = $cumulcount;
	%sumhash->{$name} = $sum;
	} #end of while
}

#Globals
$pname = $0;
$defaultpath = "/proc/fs/lustre";
$obdstats = "stats";
$clear = 0;
$graphable = 0;
$interval = 0;
$statspath = "None";
$anysumsquare = 0;
$mhz = 0;
$print_once = 1;
%cumulhash;
%sumhash;
$anysum = 0;
$obddev = "";
$starttime = 0;
$ONE_MB = 1048576;

# Command line parameter parsing
use Getopt::Std;
getopts('hcgi:') or usage();
usage() if $opt_h;
$clear = 1 if $opt_c;
$graphable = 1 if $opt_g;
$interval = $opt_i if $opt_i;

my $i = 0;
foreach (@ARGV) {
        $obddev = $_;
        $i++;
        if ($i > 1) {
                print "ERROR: extra argument $_\n";
                usage();
        }
}
if ( !$obddev ) {
	print "ERROR: Need to specify stats_file\n";
	usage();
}
# Process arguments
if ( -f $obddev ) {
	$statspath = $obddev;
} elsif ( -f "$obddev/$obdstats" ) {
	$statspath = "$obddev/$obdstats";
} else {
	my $st = glob("$defaultpath/*/$obddev/$obdstats");
	if ( -f "$st" ) {
		$statspath = $st;
	} else {
		$st = glob("$defaultpath/*/*/$obddev/$obdstats");
		if ( -f "$st" ) {
			$statspath = $st;
		}
	}
}
if ( $statspath =~ /^None$/ ) {
	die "Cannot locate stat file for: $obddev\n";
}
# Clears stats file before printing information in intervals
if ( $clear ) {
	open ( STATS, "> $statspath") || die "Cannot clear $statspath: $!\n";
	print STATS " ";
	close STATS;
	sleep($interval);	    
}
use POSIX qw(strftime);
$time_v = time();
$hostname = `lctl list_nids | head -1`;
chop($hostname);
print "$pname: STATS on ", strftime("%D", localtime($time_v));
print " $statspath on $hostname\n";
open(STATS, $statspath) || die "Cannot open $statspath: $!\n";
do {
	readstat();
	if ($interval) { 
		sleep($interval);
	}
} while ($interval);
close STATS;
