#!/usr/bin/perl # llobdstat is a utility that parses OST statistics files # found at e.g. obdfilter..stats or osc.*.stats. # It is mainly useful to watch the bandwidth usage over time. my $pname = $0; my $obddev = ""; my $obdstats = "stats"; my $statspath = "None"; my $statsname = $obdstats; my $interval = 0; my $counter = 999999999; my $debug = 0; my $have_readkey = 1; my $width = 120; my $height = 25; sub usage() { print STDERR "Monitor read/write bandwidth of an OST device\n"; print STDERR "Usage: $pname [-h] [-i ] [-v] [ [}]\n"; print STDERR " stats_file : Lustre 'stats' file to watch\n"; print STDERR " -d : debug mode\n"; print STDERR " -h : help, display this information\n"; print STDERR " -i interval: polling period in seconds\n"; print STDERR " -n count : number of samples printed\n"; print STDERR "example: $pname -i 5 lustre-OST0000\n"; print STDERR "Use CTRL + C to stop statistics printing\n"; exit 1; } # Command line parameter parsing use Getopt::Std; getopts('dhi:n:') or usage(); usage() if $opt_h; $debug = $opt_d if $opt_d; $interval = $opt_i if $opt_i; $counter = $opt_n if $opt_n; my $i = 0; foreach (@ARGV) { if ($i == 0) { $obddev = $_; $obddev =~ s/\./\//g; } elsif ($i == 1) { $interval = $_; } elsif ($i == 2) { $counter = $_; } else { print "ERROR: extra argument $_\n"; usage(); } $i++; } if ( !$obddev ) { print "ERROR: Need to specify stats_file\n"; usage(); } # Process arguments my $procpath = "/proc/fs/lustre"; foreach my $param ( "$obddev", "$obddev*/$obdstats", "$obddev*/*/$obdstats", "*/$obddev*/$obdstats", "*/*/$obddev*/$obdstats" ) { if ($debug) { printf "trying $procpath/$param\n"; } my $st = glob("$procpath/$param"); if ( -f "$st" ) { $statspath = $st; $statsname = `lctl list_param $param | head -n 1`; if ($debug) { print "using $statspath\n" } last; } } if ($statspath =~ /^None$/) { # server stats are currently in /proc/fs/lustre, but may move eventually $procpath = "/sys/kernel/debug/lustre"; foreach my $param ( "$obddev", "$obddev*/$obdstats", "$obddev*/*/$obdstats", "*/$obddev*/$obdstats", "*/*/$obddev*/$obdstats" ) { if ($debug) { print "trying $procpath/$param\n"; } $st = glob("$procpath/$param"); if ($debug) { print "glob $procpath/$param = $st\n"; } if (-f "$st") { $statspath = $st; $statsname = `lctl list_param $param | head -n 1`; if ($debug) { print "using $statspath\n" } last; } } if ($statspath =~ /^None$/) { die "Cannot locate stat file for: $obddev\n"; } } # check if Term::ReadKey is installed for efficient tty size, but OK if missing eval "require Term::ReadKey" or $have_readkey = 0; if ($debug) { print "have_readkey=$have_readkey\n"; } if ($have_readkey) { eval "use Term::ReadKey"; } print "$pname on $statsname\n"; my %cur; my %last; # Removed some statstics like open, close that the OST doesn't contain. # To add statistics parameters one needs to specify parameter names in the # below declarations in the same sequence. my ($read_bytes, $write_bytes, $create, $destroy, $statfs, $punch, $timestamp) = ("read_bytes", "write_bytes", "create", "destroy", "statfs", "punch", "snapshot_time"); my @extinfo = ($create, $destroy, $statfs, $punch); my %shortname = ($create => "cx", $destroy => "dx", $statfs => "st", $punch => "pu"); # read statistics from the stats file. # This subroutine gets called after every interval specified by user. sub readstat() { my $prevcount; my @iodata; seek STATS, 0, 0; while () { chop; @iodata = split(/\s+/, $_); my $name = $iodata[0]; $prevcount = $cur{$name}; if (defined($prevcount)) { $last{$name} = $prevcount; } if ($name =~ /^$timestamp/) { $cur{$name} = $iodata[1]; } elsif ($name =~ /^$read_bytes$/) { if (defined($cur{"read_ops"})) { $last{"read_ops"} = $cur{"read_ops"}; } $cur{"read_ops"} = $iodata[1]; $cur{$name} = $iodata[6]; } elsif ($name =~ /^$write_bytes$/) { if (defined($cur{"write_ops"})) { $last{"write_ops"} = $cur{"write_ops"}; } $cur{"write_ops"} = $iodata[1]; $cur{$name} = $iodata[6]; } else { $cur{$name} = $iodata[1]; } } } # process stats information read from the stats file. # This subroutine gets called after every interval specified by user. sub process_stats() { my $delta; my $data; my $last_time = $last{$timestamp}; if (!defined($last_time)) { printf "Read: %.1f GiB, Write: %.1f GiB, cr: %lu dx: %lu, st: %lu, pu: %lu\n", $cur{$read_bytes} / (1 << 30), $cur{$write_bytes} / (1 << 30), $cur{$create}, $cur{$destroy}, $cur{$statfs}, $cur{$punch}; print "[NOTE: cx: create, dx: destroy, st: statfs, pu: punch]\n\n"; } else { my $timespan = $cur{$timestamp} - $last{$timestamp}; my $rtot = ($cur{$read_bytes} - $last{$read_bytes}) / (1 << 20); my $riops = ($cur{"read_ops"} - $last{"read_ops"}) / $timespan; my $rrate = $rtot / $timespan; my $wtot = ($cur{$write_bytes} - $last{$write_bytes}) / (1 << 20); my $wiops = ($cur{"write_ops"} - $last{"write_ops"}) / $timespan; my $wrate = $wtot / $timespan; # this is printed once per screen, like vmstat/iostat if ($count++ % ($height - 2) == 0) { print "Timestamp Read-MiB RdMiB/s WriteMiB WrMiB/s RdIOPS WrIOPS\n"; print "---------- -------- ------- -------- ------- ------ ------\n"; if ($have_readkey) { ($width, $height, $wpixels, $hpixels) = GetTerminalSize(); } else { ($height, $width) = split / /, `stty size 2> /dev/null`; #$width = 120 if ! $width } } # This print repeats after every interval. printf "%10lu %8.1f %7.1f %8.1f %7.1f %6lu %6lu", $cur{$timestamp}, $rtot, $rrate, $wtot, $wrate, $riops, $wiops; $delta = $cur{$getattr} - $last{$getattr}; if ( $delta != 0 ) { $rdelta = int ($delta/$timespan); print " ga:$delta,$rdelta/s"; } for $data ( @extinfo ) { $delta = $cur{$data} - $last{$data}; if ($delta != 0) { print " $shortname{$data}:$delta"; } } print "\n"; $| = 1; } } #Open the stat file with STATS open(STATS, $statspath) || die "Cannot open $statspath: $!\n"; do { # read the statistics from stat file. readstat(); process_stats(); if ($interval) { sleep($interval); %last = %cur; } # Repeat the statistics printing after every "interval" specified in # command line, up to counter times, if specified } while ($interval && $counter-- > 0); close STATS;