Whamcloud - gitweb
LU-13705 utils: improve llstat/llobdstat usability 78/39178/8
authorAndreas Dilger <adilger@dilger.ca>
Wed, 20 May 2020 06:53:33 +0000 (00:53 -0600)
committerOleg Drokin <green@whamcloud.com>
Wed, 28 Apr 2021 02:10:30 +0000 (02:10 +0000)
Allow llstat to work on the client with minimal effort, by allowing
client OBD device types like "llstat -i 1 llite" or "llstat -i 1 osc",
in addition to the existing "mdt" or "ost_io" RPC-level shortcuts.

Allow specifying stats with the same syntax as "lctl get_param" and
"lctl list_param" like "llstat -i 5 myth-OST0002" or similar specific
stats.

Make the code and usage between llstat and llobdstat more uniform,
so that it is easier to switch between using one and the other.

Fix the display of llstat to fit into 80 columns by default, but
allow it to detect if the terminal is wider and print more columns
(e.g. stddev) if there are more columns in the terminal. llobdstat
always fit within 80 columns, so this is not necessary there.

Fix llite.*.read_ahead_stats to be usable by llstat.

Test-Parameters: trivial
Signed-off-by: Andreas Dilger <adilger@dilger.ca>
Change-Id: I09c714018925e8d0a17a63eee673ae18512540e5
Reviewed-on: https://review.whamcloud.com/39178
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Emoly Liu <emoly@whamcloud.com>
Reviewed-by: Jian Yu <yujian@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
lustre/llite/lproc_llite.c
lustre/tests/sanity.sh
lustre/utils/llobdstat
lustre/utils/llstat

index 74ea4ff..697c4c1 100644 (file)
@@ -1653,22 +1653,22 @@ void ll_stats_ops_tally(struct ll_sb_info *sbi, int op, long count)
 EXPORT_SYMBOL(ll_stats_ops_tally);
 
 static const char *const ra_stat_string[] = {
-       [RA_STAT_HIT] = "hits",
-       [RA_STAT_MISS] = "misses",
-       [RA_STAT_DISTANT_READPAGE] = "readpage not consecutive",
-       [RA_STAT_MISS_IN_WINDOW] = "miss inside window",
-       [RA_STAT_FAILED_GRAB_PAGE] = "failed grab_cache_page",
-       [RA_STAT_FAILED_MATCH] = "failed lock match",
-       [RA_STAT_DISCARDED] = "read but discarded",
-       [RA_STAT_ZERO_LEN] = "zero length file",
-       [RA_STAT_ZERO_WINDOW] = "zero size window",
-       [RA_STAT_EOF] = "read-ahead to EOF",
-       [RA_STAT_MAX_IN_FLIGHT] = "hit max r-a issue",
-       [RA_STAT_WRONG_GRAB_PAGE] = "wrong page from grab_cache_page",
-       [RA_STAT_FAILED_REACH_END] = "failed to reach end",
-       [RA_STAT_ASYNC] = "async readahead",
-       [RA_STAT_FAILED_FAST_READ] = "failed to fast read",
-       [RA_STAT_MMAP_RANGE_READ] = "mmap range read",
+       [RA_STAT_HIT]                   = "hits",
+       [RA_STAT_MISS]                  = "misses",
+       [RA_STAT_DISTANT_READPAGE]      = "readpage_not_consecutive",
+       [RA_STAT_MISS_IN_WINDOW]        = "miss_inside_window",
+       [RA_STAT_FAILED_GRAB_PAGE]      = "failed_grab_cache_page",
+       [RA_STAT_FAILED_MATCH]          = "failed_lock_match",
+       [RA_STAT_DISCARDED]             = "read_but_discarded",
+       [RA_STAT_ZERO_LEN]              = "zero_length_file",
+       [RA_STAT_ZERO_WINDOW]           = "zero_size_window",
+       [RA_STAT_EOF]                   = "readahead_to_eof",
+       [RA_STAT_MAX_IN_FLIGHT]         = "hit_max_readahead_issue",
+       [RA_STAT_WRONG_GRAB_PAGE]       = "wrong_page_from_grab_cache_page",
+       [RA_STAT_FAILED_REACH_END]      = "failed_to_reach_end",
+       [RA_STAT_ASYNC]                 = "async_readahead",
+       [RA_STAT_FAILED_FAST_READ]      = "failed_to_fast_read",
+       [RA_STAT_MMAP_RANGE_READ]       = "mmap_range_read",
 };
 
 int ll_debugfs_register_super(struct super_block *sb, const char *name)
index 8a58ac8..737e16d 100755 (executable)
@@ -3954,63 +3954,57 @@ test_33c() {
        local write_bytes
        local all_zeros
 
-       all_zeros=:
-       rm -fr $DIR/$tdir
+       all_zeros=true
        test_mkdir $DIR/$tdir
        # Read: 0, Write: 4, create/destroy: 2/0, stat: 1, punch: 0
 
-        sync
-        for ostnum in $(seq $OSTCOUNT); do
-                # test-framework's OST numbering is one-based, while Lustre's
-                # is zero-based
-                ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
-                # Parsing llobdstat's output sucks; we could grep the /proc
-                # path, but that's likely to not be as portable as using the
-                # llobdstat utility.  So we parse lctl output instead.
-                write_bytes=$(do_facet ost$ostnum lctl get_param -n \
-                        obdfilter/$ostname/stats |
-                        awk '/^write_bytes/ {print $7}' )
-                echo "baseline_write_bytes@$OSTnum/$ostname=$write_bytes"
-                if (( ${write_bytes:-0} > 0 ))
-                then
-                        all_zeros=false
-                        break;
-                fi
-        done
+       sync
+       for ostnum in $(seq $OSTCOUNT); do
+               # test-framework's OST numbering is one-based, while Lustre's
+               # is zero-based
+               ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
+               # check if at least some write_bytes stats are counted
+               write_bytes=$(do_facet ost$ostnum lctl get_param -n \
+                             obdfilter.$ostname.stats |
+                             awk '/^write_bytes/ {print $7}' )
+               echo "baseline_write_bytes@ost$ostnum/$ostname=$write_bytes"
+               if (( ${write_bytes:-0} > 0 )); then
+                       all_zeros=false
+                       break
+               fi
+       done
 
-        $all_zeros || return 0
+       $all_zeros || return 0
 
        # Write four bytes
        echo foo > $DIR/$tdir/bar
        # Really write them
        sync
 
-        # Total up write_bytes after writing.  We'd better find non-zeros.
-        for ostnum in $(seq $OSTCOUNT); do
-                ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
-                write_bytes=$(do_facet ost$ostnum lctl get_param -n \
-                        obdfilter/$ostname/stats |
-                        awk '/^write_bytes/ {print $7}' )
-                echo "write_bytes@$OSTnum/$ostname=$write_bytes"
-                if (( ${write_bytes:-0} > 0 ))
-                then
-                        all_zeros=false
-                        break;
-                fi
-        done
+       # Total up write_bytes after writing.  We'd better find non-zeros.
+       for ostnum in $(seq $OSTCOUNT); do
+               ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
+               write_bytes=$(do_facet ost$ostnum lctl get_param -n \
+                             obdfilter/$ostname/stats |
+                             awk '/^write_bytes/ {print $7}' )
+               echo "write_bytes@ost$ostnum/$ostname=$write_bytes"
+               if (( ${write_bytes:-0} > 0 )); then
+                       all_zeros=false
+                       break
+               fi
+       done
 
-        if $all_zeros
-        then
-                for ostnum in $(seq $OSTCOUNT); do
-                        ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
-                        echo "Check that write_bytes is present in obdfilter/*/stats:"
-                        do_facet ost$ostnum lctl get_param -n \
-                                obdfilter/$ostname/stats
-                done
-                error "OST not keeping write_bytes stats (b22312)"
-        fi
+       if $all_zeros; then
+               for ostnum in $(seq $OSTCOUNT); do
+                       ostname=$(printf "$FSNAME-OST%.4x" $((ostnum - 1)))
+                       echo "Check write_bytes is in obdfilter.*.stats:"
+                       do_facet ost$ostnum lctl get_param -n \
+                               obdfilter.$ostname.stats
+               done
+               error "OST not keeping write_bytes stats (b=22312)"
+       fi
 }
-run_test 33c "test llobdstat and write_bytes"
+run_test 33c "test write_bytes stats"
 
 test_33d() {
        [[ $MDSCOUNT -lt 2 ]] && skip_env "needs >= 2 MDTs"
@@ -9635,18 +9629,9 @@ run_test 100 "check local port using privileged port ==========="
 
 function get_named_value()
 {
-    local tag
-
-    tag=$1
-    while read ;do
-        line=$REPLY
-        case $line in
-        $tag*)
-            echo $line | sed "s/^$tag[ ]*//"
-            break
-            ;;
-        esac
-    done
+    local tag=$1
+
+    grep -w "$tag" | sed "s/^$tag  *\([0-9]*\)  *.*/\1/"
 }
 
 export CACHE_MAX=$($LCTL get_param -n llite.*.max_cached_mb |
@@ -9665,10 +9650,10 @@ test_101a() {
        local nreads=10000
        local cache_limit=32
 
-       $LCTL set_param -n osc.*-osc*.rpc_stats 0
+       $LCTL set_param -n osc.*-osc*.rpc_stats=0
        trap cleanup_101a EXIT
-       $LCTL set_param -n llite.*.read_ahead_stats 0
-       $LCTL set_param -n llite.*.max_cached_mb $cache_limit
+       $LCTL set_param -n llite.*.read_ahead_stats=0
+       $LCTL set_param -n llite.*.max_cached_mb=$cache_limit
 
        #
        # randomly read 10000 of 64K chunks from file 3x 32MB in size
@@ -9678,7 +9663,7 @@ test_101a() {
 
        discard=0
        for s in $($LCTL get_param -n llite.*.read_ahead_stats |
-               get_named_value 'read but discarded' | cut -d" " -f1); do
+                  get_named_value 'read.but.discarded'); do
                        discard=$(($discard + $s))
        done
        cleanup_101a
@@ -9739,8 +9724,7 @@ ra_check_101() {
        local discard_limit=$((((STRIDE_LENGTH - 1)*3/(STRIDE_LENGTH*OSTCOUNT))* \
                             (STRIDE_LENGTH*OSTCOUNT - STRIDE_LENGTH)))
        DISCARD=$($LCTL get_param -n llite.*.read_ahead_stats |
-                       get_named_value 'read but discarded' |
-                       cut -d" " -f1 | calc_total)
+                 get_named_value 'read.but.discarded' | calc_total)
        if [[ $DISCARD -gt $discard_limit ]]; then
                $LCTL get_param llite.*.read_ahead_stats
                error "Too many ($DISCARD) discarded pages with size (${READ_SIZE})"
@@ -9773,7 +9757,7 @@ test_101b() {
                local READ_COUNT=$((STRIPE_SIZE/BSIZE))
                local STRIDE_LENGTH=$((STRIDE_SIZE/BSIZE))
                local OFFSET=$((STRIPE_SIZE/BSIZE*(OSTCOUNT - 1)))
-               $LCTL set_param -n llite.*.read_ahead_stats 0
+               $LCTL set_param -n llite.*.read_ahead_stats=0
                $READS -f $DIR/$tfile  -l $STRIDE_LENGTH -o $OFFSET \
                              -s $FILE_LENGTH -b $STRIPE_SIZE -a $READ_COUNT -n $ITERATION
                cancel_lru_locks osc
@@ -9796,7 +9780,7 @@ test_101c() {
        setup_test101bc $STRIPE_SIZE $FILE_LENGTH
 
        cancel_lru_locks osc
-       $LCTL set_param osc.*.rpc_stats 0
+       $LCTL set_param osc.*.rpc_stats=0
        $READS -f $DIR/$tfile -s$FILE_LENGTH -b$rsize -n$nreads -t 180
        $LCTL get_param osc.*.rpc_stats
        for osc_rpc_stats in $($LCTL get_param -N osc.*.rpc_stats); do
@@ -9819,7 +9803,7 @@ test_101c() {
        cleanup_test101bc
        true
 }
-run_test 101c "check stripe_size aligned read-ahead ================="
+run_test 101c "check stripe_size aligned read-ahead"
 
 test_101d() {
        [ $PARALLEL == "yes" ] && skip "skip parallel run"
@@ -9842,7 +9826,7 @@ test_101d() {
        echo Disable read-ahead
        local old_RA=$($LCTL get_param -n llite.*.max_read_ahead_mb | head -n 1)
        $LCTL set_param -n llite.*.max_read_ahead_mb=0
-       stack_trap "$LCTL set_param -n llite.*.max_read_ahead_mb $old_RA" EXIT
+       stack_trap "$LCTL set_param -n llite.*.max_read_ahead_mb=$old_RA" EXIT
        $LCTL get_param -n llite.*.max_read_ahead_mb
 
        echo "Reading the test file $file with read-ahead disabled"
@@ -9895,14 +9879,14 @@ test_101e() {
        cancel_lru_locks $OSC
 
        echo "Reset readahead stats"
-       $LCTL set_param -n llite.*.read_ahead_stats 0
+       $LCTL set_param -n llite.*.read_ahead_stats=0
 
        for ((i = 0; i < $count; i++)); do
                dd if=$file.$i of=/dev/null bs=$bsize count=$size_KB 2>/dev/null
        done
 
        local miss=$($LCTL get_param -n llite.*.read_ahead_stats |
-                    get_named_value 'misses' | cut -d" " -f1 | calc_total)
+                    get_named_value 'misses' | calc_total)
 
        for ((i = 0; i < $count; i++)); do
                rm -rf $file.$i 2>/dev/null
@@ -9927,7 +9911,7 @@ test_101f() {
        cancel_lru_locks osc
 
        echo Reset readahead stats
-       $LCTL set_param -n llite.*.read_ahead_stats 0
+       $LCTL set_param -n llite.*.read_ahead_stats=0
 
        echo mmap read the file with small block size
        iozone -i 1 -u 1 -l 1 -+n -r 32k -s 128m -B -f $DIR/$tfile \
@@ -9936,7 +9920,7 @@ test_101f() {
        echo checking missing pages
        $LCTL get_param llite.*.read_ahead_stats
        local miss=$($LCTL get_param -n llite.*.read_ahead_stats |
-                       get_named_value 'misses' | cut -d" " -f1 | calc_total)
+                       get_named_value 'misses' | calc_total)
 
        $LCTL set_param debug="$old_debug"
        [ $miss -lt 3 ] || error "misses too much pages ('$miss')!"
@@ -10041,7 +10025,7 @@ test_101h() {
        echo "Read 10M of data but cross 64M bundary"
        dd if=$DIR/$tfile of=/dev/null bs=10M skip=6 count=1
        local miss=$($LCTL get_param -n llite.*.read_ahead_stats |
-                       get_named_value 'misses' | cut -d" " -f1 | calc_total)
+                    get_named_value 'misses' | calc_total)
        [ $miss -eq 1 ] || error "expected miss 1 but got $miss"
        rm -f $p $DIR/$tfile
 }
@@ -10089,8 +10073,7 @@ test_101j() {
                local count=$(($file_size / $blk))
                dd if=$DIR/$tfile bs=$blk count=$count of=/dev/null
                local miss=$($LCTL get_param -n llite.*.read_ahead_stats |
-                            get_named_value 'failed to fast read' |
-                            cut -d" " -f1 | calc_total)
+                            get_named_value 'failed.to.fast.read' | calc_total)
                $LCTL get_param -n llite.*.read_ahead_stats
                [ $miss -eq $count ] || error "expected $count got $miss"
        done
index 3ab51c6..7a3a4ba 100755 (executable)
 #!/usr/bin/perl
-# llobdstat is a utility that parses obdfilter statistics files
-# found at proc/fs/lustre/<ostname>/stats.
-# It is mainly useful to watch the statistics change over time.
+# llobdstat is a utility that parses OST statistics files
+# found at e.g. obdfilter.<ostname>.stats or osc.<ostname>*.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 "Usage: $pname <ost_name> [<interval> [<count>}]\n";
-    print STDERR "where  ost_name  : ost name under obdfilter\n";
-    print STDERR "       interval  : sample interval in seconds\n";
-    print STDERR "example: $pname lustre-OST0000 2\n";
-    print STDERR "Use CTRL + C to stop statistics printing\n";
-    exit 1;
+       print STDERR "Monitor read/write bandwidth of an OST device\n";
+       print STDERR "Usage: $pname [-h] [-i <interval>] [-v] <ost_name> [<interval> [<count>}]\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;
 }
 
-my $statspath = "None";
-my $interval = 0;
-my $counter = undef;
+# 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();
+}
 
-if (($#ARGV < 0) || ($#ARGV > 2)) {
-    usage();
-} else {
-    if ( $ARGV[0] =~ /help$/ ) {
-        usage();
-    }
-    if ( -f $ARGV[0] ) {
-        $statspath = $ARGV[0];
-    } elsif ( -f "$ARGV[0]/$obdstats" ) {
-        $statspath = "$ARGV[0]/$obdstats";
-    } else {
-       my $st = glob ("/{proc,sys}/fs/lustre/obdfilter/$ARGV[0]");
+# 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;
-       } else {
-           my $st = glob("/{proc,sys}/fs/lustre/obdfilter/$ARGV[0]/$obdstats");
-           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";
        }
-    }
-    if ( $statspath =~ /^None$/ ) {
-        die "Cannot locate stat file for: $ARGV[0]\n";
-    }
-    if ($#ARGV > 0) {
-        $interval = $ARGV[1];
-    }
-    if ($#ARGV > 1) {
-        $counter = $ARGV[2];
-    }
 }
 
-print "$pname on $ARGV[0]\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;
-my $mhz = 0;
 
-# Removed some statstics like open, close that obdfilter doesn't contain.
+# 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,
-    $snapshot_time) = ("read_bytes", "write_bytes", "create", "destroy",
-                       "statfs", "punch", "snapshot_time");
+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");
 
-sub get_cpumhz()
-{
-    my $cpu_freq;
-    my $itc_freq; # On Itanium systems use this
-    if (open(CPUINFO, "/proc/cpuinfo")==0) {
-        return;
-    }
-    while (<CPUINFO>) {
-        if (/^cpu MHz\s+:\s*([\d\.]+)/) { $cpu_freq=$1; }
-        elsif (/^itc MHz\s+:\s*([\d\.]+)/) { $itc_freq=$1; }
-    }
-    if (defined($itc_freq)) {
-        $mhz = $itc_freq;
-    } elsif (defined($cpu_freq)) {
-        $mhz = $cpu_freq;
-    } else {
-        $mhz = 1;
-    }
-    close CPUINFO;
-}
-
-get_cpumhz();
-print "Processor counters run at $mhz MHz\n";
-
-# read statistics from obdfilter stats file.
+# read statistics from the stats file.
 # This subroutine gets called after every interval specified by user.
 sub readstat()
 {
@@ -103,7 +135,6 @@ sub readstat()
     seek STATS, 0, 0;
     while (<STATS>) {
         chop;
-#        ($name, $cumulcount, $samples, $unit, $min, $max, $sum, $sumsquare)
         @iodata = split(/\s+/, $_);
         my $name = $iodata[0];
 
@@ -111,46 +142,61 @@ sub readstat()
         if (defined($prevcount)) {
             $last{$name} = $prevcount;
         }
-        if ($name =~ /^read_bytes$/ || $name =~ /^write_bytes$/) {
-            $cur{$name} = $iodata[6];
-        }
-        elsif ($name =~ /^snapshot_time$/) {
-#            $cumulcount =~ /(\d+)/;
+        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 obdfilter stats file.
+# 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{$snapshot_time};
+    my $last_time = $last{$timestamp};
     if (!defined($last_time)) {
-        printf "Read: %-g, Write: %-g, create/destroy: %-g/%-g, stat: %-g, punch: %-g\n",
-        $cur{$read_bytes}, $cur{$write_bytes},
-        $cur{$create}, $cur{$destroy},
-        $cur{$statfs}, $cur{$punch};
-        if ($interval) {
-            print "[NOTE: cx: create, dx: destroy, st: statfs, pu: punch ]\n\n";
-            print "Timestamp   Read-delta  ReadRate  Write-delta  WriteRate\n";
-            print "--------------------------------------------------------\n";
-        }
+        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{$snapshot_time} - $last{$snapshot_time};
-        my $rdelta = $cur{$read_bytes} - $last{$read_bytes};
-        my $rrate = ($rdelta) / ($timespan * ( 1 << 20 ));
-        my $wdelta = $cur{$write_bytes} - $last{$write_bytes};
-        my $wrate = ($wdelta) / ($timespan * ( 1 << 20 ));
-        $rdelta = ($rdelta) / (1024 * 1024);
-        $wdelta = ($wdelta) / (1024 * 1024);
+        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  %6.2fMB  %6.2fMB/s   %6.2fMB  %6.2fMB/s",
-               $cur{$snapshot_time}, $rdelta, $rrate, $wdelta, $wrate;
+        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 ) {
@@ -169,7 +215,7 @@ sub process_stats()
     }
 }
 
-#Open the obdfilter stat file with STATS
+#Open the stat file with STATS
 open(STATS, $statspath) || die "Cannot open $statspath: $!\n";
 do {
     # read the statistics from stat file.
@@ -181,6 +227,5 @@ do {
     }
     # Repeat the statistics printing after every "interval" specified in
     # command line, up to counter times, if specified
-} while ($interval && (defined($counter) ? $counter-- > 0 : 1));
+} while ($interval && $counter-- > 0);
 close STATS;
-# llobdfilter.pl ends here.
index 940114f..f9afce7 100755 (executable)
 #!/usr/bin/perl
-# llstat 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
+# llstat 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 stats files may be in /proc/fs/lustre
+# or /sys/kernel/debug/lustre. This 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 : lustre/.../stat\n";
-       print STDERR "       -i interval: polling period\n";
+       print STDERR "Monitor operation count/rate of a subsystem\n";
+       print STDERR "Usage: $pname [-c] [-d] [-g] [-h] [-i <interval>] [-n count] <stats_file>\n";
+       print STDERR "       stats_file : Lustre 'stats' file to watch\n";
+       print STDERR "       -i interval: polling period in seconds\n";
        print STDERR "       -c         : clear stats file first\n";
+       print STDERR "       -d         : debug mode\n";
        print STDERR "       -g         : graphable output format\n";
        print STDERR "       -h         : help, display this information\n";
-       print STDERR "example: $pname -i 1 ost (monitors lustre/ost/OSS/ost/stats)\n";
+       print STDERR "       -n count   : number of samples printed\n";
+       print STDERR "example: $pname -i 1 ost_io (ost.OSS.ost_io.stats)\n";
        print STDERR "Use CTRL + C to stop statistics printing\n";
        exit 1;
 }
 
+#Globals
+my $pname = $0;
+my $obddev = "";
+my $obdstats = "stats";
+my $clear = 0;
+my $graphable = 0;
+my $interval = 0;
+my $statspath = "None";
+my $statsname = "stats";
+my $anysumsquare = 0;
+my $printed_once = 0;
+my %cumulhash;
+my %sumhash;
+my $anysum = 0;
+my $starttime = 0;
+my $width = 120;
+my $have_readkey = 0;
+my $debug = 0;
+my $counter = 999999999;
+my $ONE_MB = 1048576;
+
+# Command line parameter parsing
+use Getopt::Std;
+getopts('cdghi:w:') or usage();
+usage() if $opt_h;
+$clear = 1 if $opt_c;
+$debug = $opt_d if $opt_d;
+$graphable = 1 if $opt_g;
+$interval = $opt_i if $opt_i;
+$counter = $opt_n if $opt_n;
+$width = $opt_w if $opt_w;
+
+my $i = 0;
+foreach (@ARGV) {
+       $obddev = $_;
+       $obddev =~ s/\./\//g;
+       $i++;
+       if ($i > 1) {
+               print "ERROR: extra argument $_\n";
+               usage();
+       }
+}
+if (!$obddev) {
+       print "ERROR: Need to specify stats_file\n";
+       usage();
+}
+
+# Process arguments
+my $procpath = "/sys/kernel/debug/lustre";
+foreach my $param ( "$obddev", "$obddev*/$obdstats", "$obddev*/*/$obdstats",
+                   "*/$obddev*/$obdstats", "*/*/$obddev*/$obdstats" ) {
+       if ($debug) {
+               print "trying $procpath/$param\n";
+       }
+       my $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 '$statsname' from $statspath\n"
+               }
+               last;
+       }
+}
+if ($statspath =~ /^None$/) {
+       # some older stats are kept in /proc, but don't look there first
+       $procpath = "/proc/fs/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";
+       }
+}
+
 sub print_headings()
 {      my $cc = $_[0];
        if ($graphable) {
-               if ( $print_once && $interval ) {
+               if (!$printed_once && $interval) {
                        printf "Timestamp [Name Count Rate Total Unit]...";
                        printf "\n--------------------------------------------------------------------";
-                       $print_once = 0;
+                       $printed_once = 1;
                }
                if ($cc && !$starttime) {
                        $starttime = $cc
                }
-               printf "\n%-5.0f", ($cc - $starttime);
+               printf "\n%-5lu", ($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";
+               printf "$statsname @ $cc\n";
+               if ($width <= 90) {
+                       printf "%-20s %-6s %-9s", "Name", "Rate", "#Events";
+               } else {
+                       printf "%-23s %-6s %-6s %-10s", "Name", "Count", "Rate", "#Events";
                }
-               if ( $anysumsquare ) {
-                       printf "%10s", "stddev";
+               if ($anysum) {
+                       printf "%-7s %8s %6s %8s %10s", "Unit", "last", "min", "avg", "max";
+               }
+               if ($anysumsquare && $width >= 100) {
+                       printf " %8s", "stddev";
                }
                printf "\n";
        }
@@ -53,8 +154,8 @@ sub readstat()
        seek STATS, 0, 0;
        while (<STATS>) {
        chop;
-       ($name, $cumulcount, $samples, $unit, $min, $max, $sum, $sumsquare) 
-               split(/\s+/, $_);
+       ($name, $cumulcount, $samples, $unit, $min, $max, $sum, $sumsquare) =
+               split(/\s+/, $_);
        $prevcount = $cumulhash->{$name};
        if (defined($prevcount)) {
                $diff = $cumulcount - $prevcount;
@@ -63,40 +164,49 @@ sub readstat()
                        &print_headings($cumulcount);
                        $| = 1;
                        if ($tdiff == 0) {
-                           $tdiff = 1; # avoid division by zero
+                               $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;
+                               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 ($width <= 90) {
+                                       printf "%-20.20s %-6lu %-9lu",
+                                               $name, ($diff/$tdiff), $cumulcount;
+                               } else {
+                                       printf "%-23.23s %-6lu %-6lu %-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;
+                                       printf "   %s %lu %lu %lu %s", $name,
+                                               $diff ? ($sum_diff / $diff) : 0,
+                                               ($sum_diff/$tdiff), $sum, $unit;
                                } else {
-                                   printf "%-8s %10lu %10lu %12.2f %10lu", $unit, 
-                                   $sum_diff, $min, ($sum/$cumulcount), $max;
+                                       printf "%-7s %8lu %6lu %8lu %10lu", $unit,
+                                               $diff ? ($sum_diff / $diff) : 0, $min,
+                                               ($sum/$cumulcount), $max;
                                }
-                               if (defined($sumsquare)) {
-                                       my $s = $sumsquare - (($sum_orig*$sum_orig)/$cumulcount);
+                               if (defined($sumsquare) && $width >= 100) {
+                                       my $s = $sumsquare - (($sum_orig * $sum_orig) /
+                                                             $cumulcount);
                                        if ($s >= 0) {
-                                               my $cnt = ($cumulcount >= 2) ? $cumulcount : 2 ;
+                                               my $cnt = ($cumulcount >= 2) ?
+                                                         $cumulcount : 2 ;
                                                my $stddev = sqrt($s/($cnt - 1));
                                                if (!$graphable) {
-                                                       printf " %9.2f ", $stddev;
+                                                       printf " %8lu", $stddev;
                                                }
                                        }
                                }
@@ -107,7 +217,7 @@ sub readstat()
                        $| = 1;
                }
        } else {
-               if ($cumulcount!=0) {
+               if ($cumulcount != 0) {
                        # print info when interval is not specified.
                        printf "%-25s $cumulcount\n", $name
                }
@@ -121,83 +231,47 @@ sub readstat()
        $cumulhash->{$name} = $cumulcount;
        $sumhash->{$name} = $sum;
        } #end of while
-}
 
-#Globals
-$pname = $0;
-$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("/{proc,sys}/fs/lustre/*/$obddev/$obdstats");
-       if ( -f "$st" ) {
-               $statspath = $st;
-       } else {
-               $st = glob("/{proc,sys}/fs/lustre/*/*/$obddev/$obdstats");
-               if ( -f "$st" ) {
-                       $statspath = $st;
-               }
+       if (!$graphable) {
+               printf "\n";
        }
 }
-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";
 }
+
 # Clears stats file before printing information in intervals
-if ( $clear ) {
-       open ( STATS, "> $statspath") || die "Cannot clear $statspath: $!\n";
+if ($clear) {
+       open(STATS, "> $statspath") || die "Cannot clear $statspath: $!\n";
        print STATS " ";
        close STATS;
-       sleep($interval);           
+       sleep($interval);
 }
-use POSIX qw(strftime);
-$time_v = time();
-$hostname = `lctl list_nids | head -1`;
+
+$hostname = `lctl list_nids | head -1 2> /dev/null`;
 chop($hostname);
-print "$pname: STATS on ", strftime("%D", localtime($time_v));
-print " $statspath on $hostname\n";
+printf "%s: %s at %s on %s\n", $pname, $statsname, `date`, $hostname;
 open(STATS, $statspath) || die "Cannot open $statspath: $!\n";
 do {
+       # find the terminal width
+       if (!$opt_w) {
+               if ($have_readkey) {
+                       ($width, $height, $wpixels, $hpixels) = GetTerminalSize();
+               } else {
+                       ($height, $width) = split / /, `stty size 2> /dev/null`;
+                       #$width = 120 if ! $width
+               }
+       }
+
        readstat();
-       if ($interval) { 
+       if ($interval) {
                sleep($interval);
        }
-} while ($interval);
+} while ($interval && $counter-- > 0);
 close STATS;