Blame SOURCES/nfs-utils-1.3.0-mountstats-update.patch

e19a30
diff -up nfs-utils-1.3.0/tools/mountstats/mountstats.man.good nfs-utils-1.3.0/tools/mountstats/mountstats.man
e19a30
--- nfs-utils-1.3.0/tools/mountstats/mountstats.man.good	2015-07-30 15:19:01.718000115 -0400
e19a30
+++ nfs-utils-1.3.0/tools/mountstats/mountstats.man	2015-07-30 15:19:35.430613995 -0400
e19a30
@@ -1,32 +1,146 @@
e19a30
 .\"
e19a30
 .\" mountstats(8)
e19a30
 .\"
e19a30
-.TH mountstats 8 "15 Apr 2010"
e19a30
+.TH mountstats 8 "12 Dec 2014"
e19a30
 .SH NAME
e19a30
-mountstats \- Displays NFS client per-mount statistics
e19a30
+mountstats \- Displays various NFS client per-mount statistics
e19a30
 .SH SYNOPSIS
e19a30
-.BI "mountstats ["<options> "] " <mount_point> " [ " <mount_point> "]" 
e19a30
-.SH DESCRIPTION
e19a30
-The
e19a30
 .B mountstats
e19a30
-command displays NFS client statisitics on each given
e19a30
-.I <mount_point>
e19a30
+.RB [ \-h | \-\-help ]
e19a30
+.RB [ \-v | \-\-version ]
e19a30
+.RB [ \-f | \-\-file
e19a30
+.IR infile ]
e19a30
+.RB [ \-S | \-\-since
e19a30
+.IR sincefile ]
e19a30
+.\" .RB [ \-n | \-\-nfs | \-r | \-\-rpc | \-R | \-\-raw ]
e19a30
+.R [
e19a30
+.RB [ \-n | \-\-nfs ]
e19a30
+.R |
e19a30
+.RB [ \-r | \-\-rpc ]
e19a30
+.R |
e19a30
+.RB  [ \-R | \-\-raw ]
e19a30
+.R ]
e19a30
+.RI [ mountpoint ] ...
e19a30
+.P
e19a30
+.B mountstats iostat
e19a30
+.RB [ \-h | \-\-help ]
e19a30
+.RB [ \-v | \-\-version ]
e19a30
+.RB [ \-f | \-\-file
e19a30
+.IR infile ]
e19a30
+.RB [ \-S | \-\-since
e19a30
+.IR sincefile ]
e19a30
+.RI [ interval ]
e19a30
+.RI [ count ]
e19a30
+.RI [ mountpoint ] ...
e19a30
+.P
e19a30
+.B mounstats nfsstat
e19a30
+.RB [ \-h | \-\-help ]
e19a30
+.RB [ \-v | \-\-version ]
e19a30
+.RB [ \-f | \-\-file
e19a30
+.IR infile ]
e19a30
+.RB [ \-S | \-\-since
e19a30
+.IR sincefile ]
e19a30
+.RB [ \-3 ]
e19a30
+.RB [ \-4 ]
e19a30
+.RI [ mountpoint ] ...
e19a30
+.P
e19a30
+.SH DESCRIPTION
e19a30
+.RB "The " mountstats " command displays various NFS client statisitics for each given"
e19a30
+.IR mountpoint .
e19a30
+.P
e19a30
+.RI "If no " mountpoint " is given, statistics will be displayed for all NFS mountpoints on the client."
e19a30
+.SS Sub-commands
e19a30
+Valid
e19a30
+.BR mountstats (8)
e19a30
+subcommands are:
e19a30
+.IP "\fBmountstats\fP"
e19a30
+Display a combination of per-op RPC statistics, NFS event counts, and NFS byte counts.  This is the default sub-command that will be executed if no sub-command is given.
e19a30
+.IP "\fBiostat\fP"
e19a30
+Display iostat-like statistics.
e19a30
+.IP "\fBnfsstat\fP"
e19a30
+Display nfsstat-like statistics.
e19a30
 .SH OPTIONS
e19a30
+.SS Options valid for all sub-commands
e19a30
+.TP
e19a30
+.B \-h, \-\-help
e19a30
+show the help message and exit
e19a30
+.TP
e19a30
+.B \-v, \-\-version
e19a30
+show program's version number and exit
e19a30
+.TP
e19a30
+\fB\-f \fIinfile\fR, \fB\-\-file \fIinfile
e19a30
+Read stats from
e19a30
+.I infile
e19a30
+instead of 
e19a30
+.IR /proc/self/mountstats ".  " infile
e19a30
+must be in the same format as 
e19a30
+.IR /proc/self/mountstats .
e19a30
+This may be used with the
e19a30
+.BR \-S | \-\-since
e19a30
+options to display the delta between two different points in time.
e19a30
+This may not be used with the
e19a30
+.IR interval " or " count
e19a30
+options of the
e19a30
+.B iostat
e19a30
+sub-command.
e19a30
 .TP
e19a30
-.B " \-\-nfs
e19a30
-display only the NFS statistics
e19a30
+\fB\-S \fIsincefile\fR, \fB\-\-since \fIsincefile
e19a30
+Show difference between current stats and those in
e19a30
+.IR sincefile ".  " sincefile
e19a30
+must be in the same format as 
e19a30
+.IR /proc/self/mountstats .
e19a30
+This may be used with the
e19a30
+.BR \-f | \-\-file
e19a30
+options to display the delta between two different points in time.
e19a30
+This may not be used with the
e19a30
+.IR interval " or " count
e19a30
+options of the
e19a30
+.B iostat
e19a30
+sub-command.
e19a30
+.SS Options specific to the mountstats sub-command
e19a30
+.B \-n, \-\-nfs
e19a30
+Display only the NFS statistics
e19a30
 .TP
e19a30
-.B " \-\-rpc 
e19a30
-display only the RPC statistics
e19a30
+.B \-r, \-\-rpc
e19a30
+Display only the RPC statistics
e19a30
 .TP
e19a30
-.B " \-\-version 
e19a30
-display the version of this command
e19a30
+.B \-R, \-\-raw
e19a30
+Display only the raw statistics.  This is intended for use with the
e19a30
+.BR \-f | \-\-file
e19a30
+and
e19a30
+.BR \-S | \-\-since
e19a30
+options.
e19a30
+.SS Options specific to the iostat sub-command
e19a30
+.IP "\fIinterval\fP"
e19a30
+Specifies the amount of time in seconds between each report.  The first report contains statistics for the time since each file system was mounted.  Each subsequent report contains statistics collected during the interval since the previous report.  This may not be used with the
e19a30
+.BR \-f | \-\-file
e19a30
+or
e19a30
+.BR \-s | \-\-since
e19a30
+options.
e19a30
+.P
e19a30
+.IP "\fIcount\fP"
e19a30
+Determines the number of reports generated at
e19a30
+.I interval
e19a30
+seconds apart.  If the
e19a30
+.I interval
e19a30
+parameter is specified without the
e19a30
+.I count
e19a30
+parameter, the command generates reports continuously.  This may not be used with the
e19a30
+.BR \-f | \-\-file
e19a30
+or
e19a30
+.BR \-S | \-\-since 
e19a30
+options.
e19a30
+.SS Options specific to the nfsstat sub-command
e19a30
+.IP "\fB\-3\fP"
e19a30
+Show only NFS version 3 statistics.  The default is to show both version 3 and version 4 statistics.
e19a30
+.IP "\fB\-4\fP"
e19a30
+Show only NFS version 4 statistics.  The default is to show both version 3 and version 4 statistics.
e19a30
 .SH FILES
e19a30
 .TP
e19a30
 .B /proc/self/mountstats
e19a30
 .SH SEE ALSO
e19a30
 .BR iostat (8),
e19a30
 .BR nfsiostat (8),
e19a30
-.BR nfsstat(8)
e19a30
+.BR nfsstat (8)
e19a30
 .SH AUTHOR
e19a30
 Chuck Lever <chuck.lever@oracle.com>
e19a30
diff -up nfs-utils-1.3.0/tools/mountstats/mountstats.py.good nfs-utils-1.3.0/tools/mountstats/mountstats.py
e19a30
--- nfs-utils-1.3.0/tools/mountstats/mountstats.py.good	2015-07-30 15:16:42.390463126 -0400
e19a30
+++ nfs-utils-1.3.0/tools/mountstats/mountstats.py	2015-07-30 15:16:20.391062604 -0400
e19a30
@@ -24,14 +24,189 @@ MA 02110-1301 USA
e19a30
 """
e19a30
 
e19a30
 import sys, os, time
e19a30
+from operator import itemgetter, add
e19a30
+try:
e19a30
+    import argparse
e19a30
+except ImportError:
e19a30
+    print('%s:  Failed to import argparse - make sure argparse is installed!'
e19a30
+        % sys.argv[0])
e19a30
+    sys.exit(1)
e19a30
 
e19a30
-Mountstats_version = '0.2'
e19a30
+Mountstats_version = '0.3'
e19a30
 
e19a30
 def difference(x, y):
e19a30
     """Used for a map() function
e19a30
     """
e19a30
     return x - y
e19a30
 
e19a30
+NfsEventCounters = [
e19a30
+    'inoderevalidates',
e19a30
+    'dentryrevalidates',
e19a30
+    'datainvalidates',
e19a30
+    'attrinvalidates',
e19a30
+    'vfsopen',
e19a30
+    'vfslookup',
e19a30
+    'vfspermission',
e19a30
+    'vfsupdatepage',
e19a30
+    'vfsreadpage',
e19a30
+    'vfsreadpages',
e19a30
+    'vfswritepage',
e19a30
+    'vfswritepages',
e19a30
+    'vfsreaddir',
e19a30
+    'vfssetattr',
e19a30
+    'vfsflush',
e19a30
+    'vfsfsync',
e19a30
+    'vfslock',
e19a30
+    'vfsrelease',
e19a30
+    'congestionwait',
e19a30
+    'setattrtrunc',
e19a30
+    'extendwrite',
e19a30
+    'sillyrenames',
e19a30
+    'shortreads',
e19a30
+    'shortwrites',
e19a30
+    'delay',
e19a30
+    'pnfsreads',
e19a30
+    'pnfswrites'
e19a30
+]
e19a30
+
e19a30
+NfsByteCounters = [
e19a30
+    'normalreadbytes',
e19a30
+    'normalwritebytes',
e19a30
+    'directreadbytes',
e19a30
+    'directwritebytes',
e19a30
+    'serverreadbytes',
e19a30
+    'serverwritebytes',
e19a30
+    'readpages',
e19a30
+    'writepages'
e19a30
+]
e19a30
+
e19a30
+XprtUdpCounters = [
e19a30
+    'port',
e19a30
+    'bind_count',
e19a30
+    'rpcsends',
e19a30
+    'rpcreceives',
e19a30
+    'badxids',
e19a30
+    'inflightsends',
e19a30
+    'backlogutil'
e19a30
+]
e19a30
+
e19a30
+XprtTcpCounters = [
e19a30
+    'port',
e19a30
+    'bind_count',
e19a30
+    'connect_count',
e19a30
+    'connect_time',
e19a30
+    'idle_time',
e19a30
+    'rpcsends',
e19a30
+    'rpcreceives',
e19a30
+    'badxids',
e19a30
+    'inflightsends',
e19a30
+    'backlogutil'
e19a30
+]
e19a30
+
e19a30
+XprtRdmaCounters = [
e19a30
+    'port',
e19a30
+    'bind_count',
e19a30
+    'connect_count',
e19a30
+    'connect_time',
e19a30
+    'idle_time',
e19a30
+    'rpcsends',
e19a30
+    'rpcreceives',
e19a30
+    'badxids',
e19a30
+    'backlogutil',
e19a30
+    'read_chunks',
e19a30
+    'write_chunks',
e19a30
+    'reply_chunks',
e19a30
+    'total_rdma_req',
e19a30
+    'total_rdma_rep',
e19a30
+    'pullup',
e19a30
+    'fixup',
e19a30
+    'hardway',
e19a30
+    'failed_marshal',
e19a30
+    'bad_reply'
e19a30
+]
e19a30
+
e19a30
+Nfsv3ops = [
e19a30
+    'NULL',
e19a30
+    'GETATTR',
e19a30
+    'SETATTR',
e19a30
+    'LOOKUP',
e19a30
+    'ACCESS',
e19a30
+    'READLINK',
e19a30
+    'READ',
e19a30
+    'WRITE',
e19a30
+    'CREATE',
e19a30
+    'MKDIR',
e19a30
+    'SYMLINK',
e19a30
+    'MKNOD',
e19a30
+    'REMOVE',
e19a30
+    'RMDIR',
e19a30
+    'RENAME',
e19a30
+    'LINK',
e19a30
+    'READDIR',
e19a30
+    'READDIRPLUS',
e19a30
+    'FSSTAT',
e19a30
+    'FSINFO',
e19a30
+    'PATHCONF',
e19a30
+    'COMMIT'
e19a30
+]
e19a30
+
e19a30
+Nfsv4ops = [
e19a30
+    'NULL',
e19a30
+    'READ',
e19a30
+    'WRITE',
e19a30
+    'COMMIT',
e19a30
+    'OPEN',
e19a30
+    'OPEN_CONFIRM',
e19a30
+    'OPEN_NOATTR',
e19a30
+    'OPEN_DOWNGRADE',
e19a30
+    'CLOSE',
e19a30
+    'SETATTR',
e19a30
+    'FSINFO',
e19a30
+    'RENEW',
e19a30
+    'SETCLIENTID',
e19a30
+    'SETCLIENTID_CONFIRM',
e19a30
+    'LOCK',
e19a30
+    'LOCKT',
e19a30
+    'LOCKU',
e19a30
+    'ACCESS',
e19a30
+    'GETATTR',
e19a30
+    'LOOKUP',
e19a30
+    'LOOKUP_ROOT',
e19a30
+    'REMOVE',
e19a30
+    'RENAME',
e19a30
+    'LINK',
e19a30
+    'SYMLINK',
e19a30
+    'CREATE',
e19a30
+    'PATHCONF',
e19a30
+    'STATFS',
e19a30
+    'READLINK',
e19a30
+    'READDIR',
e19a30
+    'SERVER_CAPS',
e19a30
+    'DELEGRETURN',
e19a30
+    'GETACL',
e19a30
+    'SETACL',
e19a30
+    'FS_LOCATIONS',
e19a30
+    'RELEASE_LOCKOWNER',
e19a30
+    'SECINFO',
e19a30
+    'FSID_PRESENT',
e19a30
+    'EXCHANGE_ID',
e19a30
+    'CREATE_SESSION',
e19a30
+    'DESTROY_SESSION',
e19a30
+    'SEQUENCE',
e19a30
+    'GET_LEASE_TIME',
e19a30
+    'RECLAIM_COMPLETE',
e19a30
+    'LAYOUTGET',
e19a30
+    'GETDEVICEINFO',
e19a30
+    'LAYOUTCOMMIT',
e19a30
+    'LAYOUTRETURN',
e19a30
+    'SECINFO_NO_NAME',
e19a30
+    'TEST_STATEID',
e19a30
+    'FREE_STATEID',
e19a30
+    'GETDEVICELIST',
e19a30
+    'BIND_CONN_TO_SESSION',
e19a30
+    'DESTROY_CLIENTID'
e19a30
+]
e19a30
+
e19a30
 class DeviceData:
e19a30
     """DeviceData objects provide methods for parsing and displaying
e19a30
     data for a single mount grabbed from /proc/self/mountstats
e19a30
@@ -46,13 +221,13 @@ class DeviceData:
e19a30
             self.__nfs_data['export'] = words[1]
e19a30
             self.__nfs_data['mountpoint'] = words[4]
e19a30
             self.__nfs_data['fstype'] = words[7]
e19a30
-            if words[7].find('nfs') != -1:
e19a30
+            if words[7].find('nfs') != -1 and words[7] != 'nfsd':
e19a30
                 self.__nfs_data['statvers'] = words[8]
e19a30
         elif 'nfs' in words or 'nfs4' in words:
e19a30
             self.__nfs_data['export'] = words[0]
e19a30
             self.__nfs_data['mountpoint'] = words[3]
e19a30
             self.__nfs_data['fstype'] = words[6]
e19a30
-            if words[6].find('nfs') != -1:
e19a30
+            if words[6].find('nfs') != -1 and words[6] != 'nfsd':
e19a30
                 self.__nfs_data['statvers'] = words[7]
e19a30
         elif words[0] == 'age:':
e19a30
             self.__nfs_data['age'] = int(words[1])
e19a30
@@ -69,36 +244,18 @@ class DeviceData:
e19a30
             if self.__nfs_data['flavor'] == 6:
e19a30
                 self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1])
e19a30
         elif words[0] == 'events:':
e19a30
-            self.__nfs_data['inoderevalidates'] = int(words[1])
e19a30
-            self.__nfs_data['dentryrevalidates'] = int(words[2])
e19a30
-            self.__nfs_data['datainvalidates'] = int(words[3])
e19a30
-            self.__nfs_data['attrinvalidates'] = int(words[4])
e19a30
-            self.__nfs_data['syncinodes'] = int(words[5])
e19a30
-            self.__nfs_data['vfsopen'] = int(words[6])
e19a30
-            self.__nfs_data['vfslookup'] = int(words[7])
e19a30
-            self.__nfs_data['vfspermission'] = int(words[8])
e19a30
-            self.__nfs_data['vfsreadpage'] = int(words[9])
e19a30
-            self.__nfs_data['vfsreadpages'] = int(words[10])
e19a30
-            self.__nfs_data['vfswritepage'] = int(words[11])
e19a30
-            self.__nfs_data['vfswritepages'] = int(words[12])
e19a30
-            self.__nfs_data['vfsreaddir'] = int(words[13])
e19a30
-            self.__nfs_data['vfsflush'] = int(words[14])
e19a30
-            self.__nfs_data['vfsfsync'] = int(words[15])
e19a30
-            self.__nfs_data['vfslock'] = int(words[16])
e19a30
-            self.__nfs_data['vfsrelease'] = int(words[17])
e19a30
-            self.__nfs_data['setattrtrunc'] = int(words[18])
e19a30
-            self.__nfs_data['extendwrite'] = int(words[19])
e19a30
-            self.__nfs_data['sillyrenames'] = int(words[20])
e19a30
-            self.__nfs_data['shortreads'] = int(words[21])
e19a30
-            self.__nfs_data['shortwrites'] = int(words[22])
e19a30
-            self.__nfs_data['delay'] = int(words[23])
e19a30
+            i = 1
e19a30
+            for key in NfsEventCounters:
e19a30
+                try:
e19a30
+                    self.__nfs_data[key] = int(words[i])
e19a30
+                except IndexError as err:
e19a30
+                    self.__nfs_data[key] = 0
e19a30
+                i += 1
e19a30
         elif words[0] == 'bytes:':
e19a30
-            self.__nfs_data['normalreadbytes'] = int(words[1])
e19a30
-            self.__nfs_data['normalwritebytes'] = int(words[2])
e19a30
-            self.__nfs_data['directreadbytes'] = int(words[3])
e19a30
-            self.__nfs_data['directwritebytes'] = int(words[4])
e19a30
-            self.__nfs_data['serverreadbytes'] = int(words[5])
e19a30
-            self.__nfs_data['serverwritebytes'] = int(words[6])
e19a30
+            i = 1
e19a30
+            for key in NfsByteCounters:
e19a30
+                self.__nfs_data[key] = int(words[i])
e19a30
+                i += 1
e19a30
 
e19a30
     def __parse_rpc_line(self, words):
e19a30
         if words[0] == 'RPC':
e19a30
@@ -107,44 +264,20 @@ class DeviceData:
e19a30
         elif words[0] == 'xprt:':
e19a30
             self.__rpc_data['protocol'] = words[1]
e19a30
             if words[1] == 'udp':
e19a30
-                self.__rpc_data['port'] = int(words[2])
e19a30
-                self.__rpc_data['bind_count'] = int(words[3])
e19a30
-                self.__rpc_data['rpcsends'] = int(words[4])
e19a30
-                self.__rpc_data['rpcreceives'] = int(words[5])
e19a30
-                self.__rpc_data['badxids'] = int(words[6])
e19a30
-                self.__rpc_data['inflightsends'] = int(words[7])
e19a30
-                self.__rpc_data['backlogutil'] = int(words[8])
e19a30
+                i = 2
e19a30
+                for key in XprtUdpCounters:
e19a30
+                    self.__rpc_data[key] = int(words[i])
e19a30
+                    i += 1
e19a30
             elif words[1] == 'tcp':
e19a30
-                self.__rpc_data['port'] = words[2]
e19a30
-                self.__rpc_data['bind_count'] = int(words[3])
e19a30
-                self.__rpc_data['connect_count'] = int(words[4])
e19a30
-                self.__rpc_data['connect_time'] = int(words[5])
e19a30
-                self.__rpc_data['idle_time'] = int(words[6])
e19a30
-                self.__rpc_data['rpcsends'] = int(words[7])
e19a30
-                self.__rpc_data['rpcreceives'] = int(words[8])
e19a30
-                self.__rpc_data['badxids'] = int(words[9])
e19a30
-                self.__rpc_data['inflightsends'] = int(words[10])
e19a30
-                self.__rpc_data['backlogutil'] = int(words[11])
e19a30
+                i = 2
e19a30
+                for key in XprtTcpCounters:
e19a30
+                    self.__rpc_data[key] = int(words[i])
e19a30
+                    i += 1
e19a30
             elif words[1] == 'rdma':
e19a30
-                self.__rpc_data['port'] = words[2]
e19a30
-                self.__rpc_data['bind_count'] = int(words[3])
e19a30
-                self.__rpc_data['connect_count'] = int(words[4])
e19a30
-                self.__rpc_data['connect_time'] = int(words[5])
e19a30
-                self.__rpc_data['idle_time'] = int(words[6])
e19a30
-                self.__rpc_data['rpcsends'] = int(words[7])
e19a30
-                self.__rpc_data['rpcreceives'] = int(words[8])
e19a30
-                self.__rpc_data['badxids'] = int(words[9])
e19a30
-                self.__rpc_data['backlogutil'] = int(words[10])
e19a30
-                self.__rpc_data['read_chunks'] = int(words[11])
e19a30
-                self.__rpc_data['write_chunks'] = int(words[12])
e19a30
-                self.__rpc_data['reply_chunks'] = int(words[13])
e19a30
-                self.__rpc_data['total_rdma_req'] = int(words[14])
e19a30
-                self.__rpc_data['total_rdma_rep'] = int(words[15])
e19a30
-                self.__rpc_data['pullup'] = int(words[16])
e19a30
-                self.__rpc_data['fixup'] = int(words[17])
e19a30
-                self.__rpc_data['hardway'] = int(words[18])
e19a30
-                self.__rpc_data['failed_marshal'] = int(words[19])
e19a30
-                self.__rpc_data['bad_reply'] = int(words[20])
e19a30
+                i = 2
e19a30
+                for key in XprtRdmaCounters:
e19a30
+                    self.__rpc_data[key] = int(words[i])
e19a30
+                    i += 1
e19a30
         elif words[0] == 'per-op':
e19a30
             self.__rpc_data['per-op'] = words
e19a30
         else:
e19a30
@@ -178,12 +311,55 @@ class DeviceData:
e19a30
             return True
e19a30
         return False
e19a30
 
e19a30
-    def display_nfs_options(self):
e19a30
-        """Pretty-print the NFS options
e19a30
+    def nfs_version(self):
e19a30
+        if self.is_nfs_mountpoint():
e19a30
+            prog, vers = self.__rpc_data['programversion'].split('/')
e19a30
+            return int(vers)
e19a30
+
e19a30
+    def display_raw_stats(self):
e19a30
+        """Prints out stats in the same format as /proc/self/mountstats
e19a30
         """
e19a30
+        print('device %s mounted on %s with fstype %s %s' % \
e19a30
+            (self.__nfs_data['export'], self.__nfs_data['mountpoint'], \
e19a30
+            self.__nfs_data['fstype'], self.__nfs_data['statvers']))
e19a30
+        print('\topts:\t%s' % ','.join(self.__nfs_data['mountoptions']))
e19a30
+        print('\tage:\t%d' % self.__nfs_data['age'])
e19a30
+        print('\tcaps:\t%s' % ','.join(self.__nfs_data['servercapabilities']))
e19a30
+        print('\tsec:\tflavor=%d,pseudoflavor=%d' % (self.__nfs_data['flavor'], \
e19a30
+            self.__nfs_data['pseudoflavor']))
e19a30
+        print('\tevents:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsEventCounters]))
e19a30
+        print('\tbytes:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsByteCounters]))
e19a30
+        print('\tRPC iostats version: %1.1f p/v: %s (nfs)' % (self.__rpc_data['statsvers'], \
e19a30
+            self.__rpc_data['programversion']))
e19a30
+        if self.__rpc_data['protocol'] == 'udp':
e19a30
+            print('\txprt:\tudp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtUdpCounters]))
e19a30
+        elif self.__rpc_data['protocol'] == 'tcp':
e19a30
+            print('\txprt:\ttcp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtTcpCounters]))
e19a30
+        elif self.__rpc_data['protocol'] == 'rdma':
e19a30
+            print('\txprt:\trdma %s' % " ".join([str(self.__rpc_data[key]) for key in XprtRdmaCounters]))
e19a30
+        else:
e19a30
+            raise Exception('Unknown RPC transport protocol %s' % self.__rpc_data['protocol'])
e19a30
+        print('\tper-op statistics')
e19a30
+        prog, vers = self.__rpc_data['programversion'].split('/')
e19a30
+        if vers == '3':
e19a30
+            for op in Nfsv3ops:
e19a30
+                print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
e19a30
+        elif vers == '4':
e19a30
+            for op in Nfsv4ops:
e19a30
+                print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
e19a30
+        else:
e19a30
+            print('\tnot implemented for version %d' % vers)
e19a30
+        print()
e19a30
+
e19a30
+    def display_stats_header(self):
e19a30
         print('Stats for %s mounted on %s:' % \
e19a30
             (self.__nfs_data['export'], self.__nfs_data['mountpoint']))
e19a30
 
e19a30
+    def display_nfs_options(self):
e19a30
+        """Pretty-print the NFS options
e19a30
+        """
e19a30
+        self.display_stats_header()
e19a30
+
e19a30
         print('  NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions']))
e19a30
         print('  NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities']))
e19a30
         if 'nfsv4flags' in self.__nfs_data:
e19a30
@@ -201,7 +377,6 @@ class DeviceData:
e19a30
         print('Cache events:')
e19a30
         print('  data cache invalidated %d times' % self.__nfs_data['datainvalidates'])
e19a30
         print('  attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates'])
e19a30
-        print('  inodes synced %d times' % self.__nfs_data['syncinodes'])
e19a30
         print()
e19a30
         print('VFS calls:')
e19a30
         print('  VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates'])
e19a30
@@ -262,29 +437,82 @@ class DeviceData:
e19a30
         """
e19a30
         sends = self.__rpc_data['rpcsends']
e19a30
 
e19a30
-        # XXX: these should be sorted by 'count'
e19a30
-        print()
e19a30
+        allstats = []
e19a30
         for op in self.__rpc_data['ops']:
e19a30
-            stats = self.__rpc_data[op]
e19a30
-            count = stats[0]
e19a30
-            retrans = stats[1] - count
e19a30
+            allstats.append([op] + self.__rpc_data[op])
e19a30
+
e19a30
+        print()
e19a30
+        for stats in sorted(allstats, key=itemgetter(1), reverse=True):
e19a30
+            count = stats[1]
e19a30
             if count != 0:
e19a30
-                print('%s:' % op)
e19a30
+                print('%s:' % stats[0])
e19a30
                 print('\t%d ops (%d%%)' % \
e19a30
                     (count, ((count * 100) / sends)), end=' ')
e19a30
-                print('\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)), end=' ')
e19a30
-                print('\t%d major timeouts' % stats[2])
e19a30
+                retrans = stats[2] - count
e19a30
+                if retrans != 0:
e19a30
+                    print('\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)), end=' ')
e19a30
+                    print('\t%d major timeouts' % stats[3])
e19a30
+                else:
e19a30
+                    print('')
e19a30
                 print('\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \
e19a30
-                    (stats[3] / count, stats[4] / count))
e19a30
-                print('\tbacklog wait: %f' % (float(stats[5]) / count), end=' ')
e19a30
-                print('\tRTT: %f' % (float(stats[6]) / count), end=' ')
e19a30
+                    (stats[4] / count, stats[5] / count))
e19a30
+                print('\tbacklog wait: %f' % (float(stats[6]) / count), end=' ')
e19a30
+                print('\tRTT: %f' % (float(stats[7]) / count), end=' ')
e19a30
                 print('\ttotal execute time: %f (milliseconds)' % \
e19a30
-                    (float(stats[7]) / count))
e19a30
+                    (float(stats[8]) / count))
e19a30
+
e19a30
+    def client_rpc_stats(self):
e19a30
+        """Tally high-level rpc stats for the nfsstat command
e19a30
+        """
e19a30
+        sends = 0
e19a30
+        trans = 0
e19a30
+        authrefrsh = 0
e19a30
+        for op in self.__rpc_data['ops']:
e19a30
+            sends += self.__rpc_data[op][0]
e19a30
+            trans += self.__rpc_data[op][1]
e19a30
+        retrans = trans - sends
e19a30
+        # authrefresh stats don't actually get captured in
e19a30
+        # /proc/self/mountstats, so we fudge it here
e19a30
+        authrefrsh = sends
e19a30
+        return (sends, trans, authrefrsh)
e19a30
+
e19a30
+    def display_nfsstat_stats(self):
e19a30
+        """Pretty-print nfsstat-style stats
e19a30
+        """
e19a30
+        sends = 0
e19a30
+        for op in self.__rpc_data['ops']:
e19a30
+            sends += self.__rpc_data[op][0]
e19a30
+        if sends == 0:
e19a30
+            return
e19a30
+        print()
e19a30
+        vers = self.nfs_version()
e19a30
+        print('Client nfs v%d' % vers)
e19a30
+        info = []
e19a30
+        for op in self.__rpc_data['ops']:
e19a30
+            print('%-13s' % str.lower(op)[:12], end='')
e19a30
+            count = self.__rpc_data[op][0]
e19a30
+            pct = (count * 100) / sends
e19a30
+            info.append((count, pct))
e19a30
+            if (self.__rpc_data['ops'].index(op) + 1) % 6 == 0:
e19a30
+                print()
e19a30
+                for (count, pct) in info:
e19a30
+                    print('%-8u%3u%% ' % (count, pct), end='')
e19a30
+                print()
e19a30
+                info = []
e19a30
+        print()
e19a30
+        if len(info) > 0:
e19a30
+            for (count, pct) in info:
e19a30
+                print('%-8u%3u%% ' % (count, pct), end='')
e19a30
+            print()
e19a30
 
e19a30
     def compare_iostats(self, old_stats):
e19a30
         """Return the difference between two sets of stats
e19a30
         """
e19a30
+        if old_stats.__nfs_data['age'] > self.__nfs_data['age']:
e19a30
+            return self
e19a30
+
e19a30
         result = DeviceData()
e19a30
+        protocol = self.__rpc_data['protocol']
e19a30
 
e19a30
         # copy self into result
e19a30
         for key, value in self.__nfs_data.items():
e19a30
@@ -299,70 +527,118 @@ class DeviceData:
e19a30
         for op in result.__rpc_data['ops']:
e19a30
             result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
e19a30
 
e19a30
-        # update the remaining keys we care about
e19a30
-        result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
e19a30
-        result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil']
e19a30
-        result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes']
e19a30
-        result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes']
e19a30
-
e19a30
+        # update the remaining keys
e19a30
+        if protocol == 'udp':
e19a30
+            for key in XprtUdpCounters:
e19a30
+                result.__rpc_data[key] -= old_stats.__rpc_data[key]
e19a30
+        elif protocol == 'tcp':
e19a30
+            for key in XprtTcpCounters:
e19a30
+                result.__rpc_data[key] -= old_stats.__rpc_data[key]
e19a30
+        elif protocol == 'rdma':
e19a30
+            for key in XprtRdmaCounters:
e19a30
+                result.__rpc_data[key] -= old_stats.__rpc_data[key]
e19a30
+        result.__nfs_data['age'] -= old_stats.__nfs_data['age']
e19a30
+        for key in NfsEventCounters:
e19a30
+            result.__nfs_data[key] -= old_stats.__nfs_data[key]
e19a30
+        for key in NfsByteCounters:
e19a30
+            result.__nfs_data[key] -= old_stats.__nfs_data[key]
e19a30
         return result
e19a30
 
e19a30
+    def setup_accumulator(self, ops):
e19a30
+        """Initialize a DeviceData instance to tally stats for all mountpoints
e19a30
+        with the same major version. This is for the nfsstat command.
e19a30
+        """
e19a30
+        if ops == Nfsv3ops:
e19a30
+            self.__rpc_data['programversion'] = '100003/3'
e19a30
+            self.__nfs_data['fstype'] = 'nfs'
e19a30
+        elif ops == Nfsv4ops:
e19a30
+            self.__rpc_data['programversion'] = '100003/4'
e19a30
+            self.__nfs_data['fstype'] = 'nfs4'
e19a30
+        self.__rpc_data['ops'] = ops
e19a30
+        for op in ops:
e19a30
+            self.__rpc_data[op] = [0 for i in range(8)]
e19a30
+
e19a30
+    def accumulate_iostats(self, new_stats):
e19a30
+        """Accumulate counters from all RPC op buckets in new_stats.  This is
e19a30
+        for the nfsstat command.
e19a30
+        """
e19a30
+        for op in new_stats.__rpc_data['ops']:
e19a30
+            self.__rpc_data[op] = list(map(add, self.__rpc_data[op], new_stats.__rpc_data[op]))
e19a30
+
e19a30
+    def __print_rpc_op_stats(self, op, sample_time):
e19a30
+        """Print generic stats for one RPC op
e19a30
+        """
e19a30
+        if op not in self.__rpc_data:
e19a30
+            return
e19a30
+
e19a30
+        rpc_stats = self.__rpc_data[op]
e19a30
+        ops = float(rpc_stats[0])
e19a30
+        retrans = float(rpc_stats[1] - rpc_stats[0])
e19a30
+        kilobytes = float(rpc_stats[3] + rpc_stats[4]) / 1024
e19a30
+        rtt = float(rpc_stats[6])
e19a30
+        exe = float(rpc_stats[7])
e19a30
+
e19a30
+        # prevent floating point exceptions
e19a30
+        if ops != 0:
e19a30
+            kb_per_op = kilobytes / ops
e19a30
+            retrans_percent = (retrans * 100) / ops
e19a30
+            rtt_per_op = rtt / ops
e19a30
+            exe_per_op = exe / ops
e19a30
+        else:
e19a30
+            kb_per_op = 0.0
e19a30
+            retrans_percent = 0.0
e19a30
+            rtt_per_op = 0.0
e19a30
+            exe_per_op = 0.0
e19a30
+
e19a30
+        op += ':'
e19a30
+        print(format(op.lower(), '<16s'), end='')
e19a30
+        print(format('ops/s', '>8s'), end='')
e19a30
+        print(format('kB/s', '>16s'), end='')
e19a30
+        print(format('kB/op', '>16s'), end='')
e19a30
+        print(format('retrans', '>16s'), end='')
e19a30
+        print(format('avg RTT (ms)', '>16s'), end='')
e19a30
+        print(format('avg exe (ms)', '>16s'))
e19a30
+
e19a30
+        print(format((ops / sample_time), '>24.3f'), end='')
e19a30
+        print(format((kilobytes / sample_time), '>16.3f'), end='')
e19a30
+        print(format(kb_per_op, '>16.3f'), end='')
e19a30
+        retransmits = '{0:>10.0f} ({1:>3.1f}%)'.format(retrans, retrans_percent).strip()
e19a30
+        print(format(retransmits, '>16'), end='')
e19a30
+        print(format(rtt_per_op, '>16.3f'), end='')
e19a30
+        print(format(exe_per_op, '>16.3f'))
e19a30
+
e19a30
     def display_iostats(self, sample_time):
e19a30
         """Display NFS and RPC stats in an iostat-like way
e19a30
         """
e19a30
         sends = float(self.__rpc_data['rpcsends'])
e19a30
         if sample_time == 0:
e19a30
             sample_time = float(self.__nfs_data['age'])
e19a30
+        #  sample_time could still be zero if the export was just mounted.
e19a30
+        #  Set it to 1 to avoid divide by zero errors in this case since we'll
e19a30
+        #  likely still have relevant mount statistics to show.
e19a30
+        #
e19a30
+        if sample_time == 0:
e19a30
+            sample_time = 1;
e19a30
+        if sends != 0:
e19a30
+            backlog = (float(self.__rpc_data['backlogutil']) / sends) / sample_time
e19a30
+        else:
e19a30
+            backlog = 0.0
e19a30
 
e19a30
         print()
e19a30
         print('%s mounted on %s:' % \
e19a30
             (self.__nfs_data['export'], self.__nfs_data['mountpoint']))
e19a30
+        print()
e19a30
 
e19a30
-        print('\top/s\trpc bklog')
e19a30
-        print('\t%.2f' % (sends / sample_time), end=' ')
e19a30
-        if sends != 0:
e19a30
-            print('\t%.2f' % \
e19a30
-                ((float(self.__rpc_data['backlogutil']) / sends) / sample_time))
e19a30
-        else:
e19a30
-            print('\t0.00')
e19a30
-
e19a30
-        # reads:  ops/s, kB/s, avg rtt, and avg exe
e19a30
-        # XXX: include avg xfer size and retransmits?
e19a30
-        read_rpc_stats = self.__rpc_data['READ']
e19a30
-        ops = float(read_rpc_stats[0])
e19a30
-        kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024
e19a30
-        rtt = float(read_rpc_stats[6])
e19a30
-        exe = float(read_rpc_stats[7])
e19a30
-
e19a30
-        print('\treads:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)')
e19a30
-        print('\t\t%.2f' % (ops / sample_time), end=' ')
e19a30
-        print('\t\t%.2f' % (kilobytes / sample_time), end=' ')
e19a30
-        if ops != 0:
e19a30
-            print('\t\t%.2f' % (rtt / ops), end=' ')
e19a30
-            print('\t\t%.2f' % (exe / ops))
e19a30
-        else:
e19a30
-            print('\t\t0.00', end=' ')
e19a30
-            print('\t\t0.00')
e19a30
+        print(format('ops/s', '>16') + format('rpc bklog', '>16'))
e19a30
+        print(format((sends / sample_time), '>16.3f'), end='')
e19a30
+        print(format(backlog, '>16.3f'))
e19a30
+        print()
e19a30
 
e19a30
-        # writes:  ops/s, kB/s, avg rtt, and avg exe
e19a30
-        # XXX: include avg xfer size and retransmits?
e19a30
-        write_rpc_stats = self.__rpc_data['WRITE']
e19a30
-        ops = float(write_rpc_stats[0])
e19a30
-        kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024
e19a30
-        rtt = float(write_rpc_stats[6])
e19a30
-        exe = float(write_rpc_stats[7])
e19a30
-
e19a30
-        print('\twrites:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)')
e19a30
-        print('\t\t%.2f' % (ops / sample_time), end=' ')
e19a30
-        print('\t\t%.2f' % (kilobytes / sample_time), end=' ')
e19a30
-        if ops != 0:
e19a30
-            print('\t\t%.2f' % (rtt / ops), end=' ')
e19a30
-            print('\t\t%.2f' % (exe / ops))
e19a30
-        else:
e19a30
-            print('\t\t0.00', end=' ')
e19a30
-            print('\t\t0.00')
e19a30
+        self.__print_rpc_op_stats('READ', sample_time)
e19a30
+        self.__print_rpc_op_stats('WRITE', sample_time)
e19a30
+        sys.stdout.flush()
e19a30
 
e19a30
-def parse_stats_file(filename):
e19a30
+def parse_stats_file(f):
e19a30
     """pop the contents of a mountstats file into a dictionary,
e19a30
     keyed by mount point.  each value object is a list of the
e19a30
     lines in the mountstats file corresponding to the mount
e19a30
@@ -371,7 +647,7 @@ def parse_stats_file(filename):
e19a30
     ms_dict = dict()
e19a30
     key = ''
e19a30
 
e19a30
-    f = file(filename)
e19a30
+    f.seek(0)
e19a30
     for line in f.readlines():
e19a30
         words = line.split()
e19a30
         if len(words) == 0:
e19a30
@@ -385,132 +661,156 @@ def parse_stats_file(filename):
e19a30
         else:
e19a30
             new += [ line.strip() ]
e19a30
         ms_dict[key] = new
e19a30
-    f.close
e19a30
 
e19a30
     return ms_dict
e19a30
 
e19a30
-def print_mountstats_help(name):
e19a30
-    print('usage: %s [ options ] <mount point>' % name)
e19a30
-    print()
e19a30
-    print(' Version %s' % Mountstats_version)
e19a30
-    print()
e19a30
-    print(' Display NFS client per-mount statistics.')
e19a30
-    print()
e19a30
-    print('  --version    display the version of this command')
e19a30
-    print('  --nfs        display only the NFS statistics')
e19a30
-    print('  --rpc        display only the RPC statistics')
e19a30
-    print('  --start      sample and save statistics')
e19a30
-    print('  --end        resample statistics and compare them with saved')
e19a30
+def print_mountstats(stats, nfs_only, rpc_only, raw):
e19a30
+    if nfs_only:
e19a30
+       stats.display_nfs_options()
e19a30
+       stats.display_nfs_events()
e19a30
+       stats.display_nfs_bytes()
e19a30
+    elif rpc_only:
e19a30
+       stats.display_stats_header()
e19a30
+       stats.display_rpc_generic_stats()
e19a30
+       stats.display_rpc_op_stats()
e19a30
+    elif raw:
e19a30
+       stats.display_raw_stats()
e19a30
+    else:
e19a30
+       stats.display_nfs_options()
e19a30
+       stats.display_nfs_bytes()
e19a30
+       stats.display_rpc_generic_stats()
e19a30
+       stats.display_rpc_op_stats()
e19a30
     print()
e19a30
 
e19a30
-def mountstats_command():
e19a30
+def mountstats_command(args):
e19a30
     """Mountstats command
e19a30
     """
e19a30
-    mountpoints = []
e19a30
-    nfs_only = False
e19a30
-    rpc_only = False
e19a30
-
e19a30
-    for arg in sys.argv:
e19a30
-        if arg in ['-h', '--help', 'help', 'usage']:
e19a30
-            print_mountstats_help(prog)
e19a30
-            return
e19a30
+    mountstats = parse_stats_file(args.infile)
e19a30
+    mountpoints = [os.path.normpath(mp) for mp in args.mountpoints]
e19a30
 
e19a30
-        if arg in ['-v', '--version', 'version']:
e19a30
-            print('%s version %s' % (sys.argv[0], Mountstats_version))
e19a30
-            sys.exit(0)
e19a30
-
e19a30
-        if arg in ['-n', '--nfs']:
e19a30
-            nfs_only = True
e19a30
-            continue
e19a30
-
e19a30
-        if arg in ['-r', '--rpc']:
e19a30
-            rpc_only = True
e19a30
-            continue
e19a30
-
e19a30
-        if arg in ['-s', '--start']:
e19a30
-            raise Exception('Sampling is not yet implemented')
e19a30
+    # make certain devices contains only NFS mount points
e19a30
+    if len(mountpoints) > 0:
e19a30
+        check = []
e19a30
+        for device in mountpoints:
e19a30
+            stats = DeviceData()
e19a30
+            try:
e19a30
+                stats.parse_stats(mountstats[device])
e19a30
+                if stats.is_nfs_mountpoint():
e19a30
+                    check += [device]
e19a30
+            except KeyError:
e19a30
+                continue
e19a30
+        mountpoints = check
e19a30
+    else:
e19a30
+        for device, descr in mountstats.items():
e19a30
+            stats = DeviceData()
e19a30
+            stats.parse_stats(descr)
e19a30
+            if stats.is_nfs_mountpoint():
e19a30
+                mountpoints += [device]
e19a30
+    if len(mountpoints) == 0:
e19a30
+        print('No NFS mount points were found')
e19a30
+        return 1
e19a30
 
e19a30
-        if arg in ['-e', '--end']:
e19a30
-            raise Exception('Sampling is not yet implemented')
e19a30
+    if args.since:
e19a30
+        old_mountstats = parse_stats_file(args.since)
e19a30
 
e19a30
-        if arg == sys.argv[0]:
e19a30
-            continue
e19a30
+    for mp in mountpoints:
e19a30
+        stats = DeviceData()
e19a30
+        stats.parse_stats(mountstats[mp])
e19a30
+        if not args.since:
e19a30
+            print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw)
e19a30
+        elif args.since and mp not in old_mountstats:
e19a30
+            print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw)
e19a30
+        else:
e19a30
+            old_stats = DeviceData()
e19a30
+            old_stats.parse_stats(old_mountstats[mp])
e19a30
+            diff_stats = stats.compare_iostats(old_stats)
e19a30
+            print_mountstats(diff_stats, args.nfs_only, args.rpc_only, args.raw)
e19a30
 
e19a30
-        mountpoints += [arg]
e19a30
+    args.infile.close()
e19a30
+    if args.since:
e19a30
+        args.since.close()
e19a30
+    return 0
e19a30
 
e19a30
-    if mountpoints == []:
e19a30
-        print_mountstats_help(prog)
e19a30
-        return
e19a30
+def nfsstat_command(args):
e19a30
+    """nfsstat-like command for NFS mount points
e19a30
+    """
e19a30
+    mountstats = parse_stats_file(args.infile)
e19a30
+    mountpoints = [os.path.normpath(mp) for mp in args.mountpoints]
e19a30
+    v3stats = DeviceData()
e19a30
+    v3stats.setup_accumulator(Nfsv3ops)
e19a30
+    v4stats = DeviceData()
e19a30
+    v4stats.setup_accumulator(Nfsv4ops)
e19a30
+
e19a30
+    # ensure stats get printed if neither v3 nor v4 was specified
e19a30
+    if args.show_v3 or args.show_v4:
e19a30
+        show_both = False
e19a30
+    else:
e19a30
+        show_both = True
e19a30
 
e19a30
-    if rpc_only == True and nfs_only == True:
e19a30
-        print_mountstats_help(prog)
e19a30
-        return
e19a30
+    # make certain devices contains only NFS mount points
e19a30
+    if len(mountpoints) > 0:
e19a30
+        check = []
e19a30
+        for device in mountpoints:
e19a30
+            stats = DeviceData()
e19a30
+            try:
e19a30
+                stats.parse_stats(mountstats[device])
e19a30
+                if stats.is_nfs_mountpoint():
e19a30
+                    check += [device]
e19a30
+            except KeyError:
e19a30
+                continue
e19a30
+        mountpoints = check
e19a30
+    else:
e19a30
+        for device, descr in mountstats.items():
e19a30
+            stats = DeviceData()
e19a30
+            stats.parse_stats(descr)
e19a30
+            if stats.is_nfs_mountpoint():
e19a30
+                mountpoints += [device]
e19a30
+    if len(mountpoints) == 0:
e19a30
+        print('No NFS mount points were found')
e19a30
+        return 1
e19a30
 
e19a30
-    mountstats = parse_stats_file('/proc/self/mountstats')
e19a30
+    if args.since:
e19a30
+        old_mountstats = parse_stats_file(args.since)
e19a30
 
e19a30
     for mp in mountpoints:
e19a30
-        if mp not in mountstats:
e19a30
-            print('Statistics for mount point %s not found' % mp)
e19a30
-            continue
e19a30
-
e19a30
         stats = DeviceData()
e19a30
         stats.parse_stats(mountstats[mp])
e19a30
+        vers = stats.nfs_version()
e19a30
 
e19a30
-        if not stats.is_nfs_mountpoint():
e19a30
-            print('Mount point %s exists but is not an NFS mount' % mp)
e19a30
-            continue
e19a30
-
e19a30
-        if nfs_only:
e19a30
-           stats.display_nfs_options()
e19a30
-           stats.display_nfs_events()
e19a30
-           stats.display_nfs_bytes()
e19a30
-        elif rpc_only:
e19a30
-           stats.display_rpc_generic_stats()
e19a30
-           stats.display_rpc_op_stats()
e19a30
-        else:
e19a30
-           stats.display_nfs_options()
e19a30
-           stats.display_nfs_bytes()
e19a30
-           stats.display_rpc_generic_stats()
e19a30
-           stats.display_rpc_op_stats()
e19a30
-
e19a30
-def print_nfsstat_help(name):
e19a30
-    print('usage: %s [ options ]' % name)
e19a30
-    print()
e19a30
-    print(' Version %s' % Mountstats_version)
e19a30
-    print()
e19a30
-    print(' nfsstat-like program that uses NFS client per-mount statistics.')
e19a30
-    print()
e19a30
-
e19a30
-def nfsstat_command():
e19a30
-    print_nfsstat_help(prog)
e19a30
+        if not args.since:
e19a30
+            acc_stats = stats
e19a30
+        elif args.since and mp not in old_mountstats:
e19a30
+            acc_stats = stats
e19a30
+        else:
e19a30
+            old_stats = DeviceData()
e19a30
+            old_stats.parse_stats(old_mountstats[mp])
e19a30
+            acc_stats = stats.compare_iostats(old_stats)
e19a30
 
e19a30
-def print_iostat_help(name):
e19a30
-    print('usage: %s [ <interval> [ <count> ] ] [ <mount point> ] ' % name)
e19a30
-    print()
e19a30
-    print(' Version %s' % Mountstats_version)
e19a30
-    print()
e19a30
-    print(' iostat-like program to display NFS client per-mount statistics.')
e19a30
-    print()
e19a30
-    print(' The <interval> parameter specifies the amount of time in seconds between')
e19a30
-    print(' each report.  The first report contains statistics for the time since each')
e19a30
-    print(' file system was mounted.  Each subsequent report contains statistics')
e19a30
-    print(' collected during the interval since the previous report.')
e19a30
-    print()
e19a30
-    print(' If the <count> parameter is specified, the value of <count> determines the')
e19a30
-    print(' number of reports generated at <interval> seconds apart.  If the interval')
e19a30
-    print(' parameter is specified without the <count> parameter, the command generates')
e19a30
-    print(' reports continuously.')
e19a30
-    print()
e19a30
-    print(' If one or more <mount point> names are specified, statistics for only these')
e19a30
-    print(' mount points will be displayed.  Otherwise, all NFS mount points on the')
e19a30
-    print(' client are listed.')
e19a30
-    print()
e19a30
+        if vers == 3 and (show_both or args.show_v3):
e19a30
+           v3stats.accumulate_iostats(acc_stats)
e19a30
+        elif vers == 4 and (show_both or args.show_v4):
e19a30
+           v4stats.accumulate_iostats(acc_stats)
e19a30
+
e19a30
+    sends, retrans, authrefrsh = map(add, v3stats.client_rpc_stats(), v4stats.client_rpc_stats())
e19a30
+    print('Client rpc stats:')
e19a30
+    print('calls      retrans    authrefrsh')
e19a30
+    print('%-11u%-11u%-11u' % (sends, retrans, authrefrsh))
e19a30
+
e19a30
+    if show_both or args.show_v3:
e19a30
+        v3stats.display_nfsstat_stats()
e19a30
+    if show_both or args.show_v4:
e19a30
+        v4stats.display_nfsstat_stats()
e19a30
+
e19a30
+    args.infile.close()
e19a30
+    if args.since:
e19a30
+        args.since.close()
e19a30
+    return 0
e19a30
 
e19a30
 def print_iostat_summary(old, new, devices, time):
e19a30
     for device in devices:
e19a30
         stats = DeviceData()
e19a30
         stats.parse_stats(new[device])
e19a30
-        if not old:
e19a30
+        if not old or device not in old:
e19a30
             stats.display_iostats(time)
e19a30
         else:
e19a30
             old_stats = DeviceData()
e19a30
@@ -518,51 +818,28 @@ def print_iostat_summary(old, new, devic
e19a30
             diff_stats = stats.compare_iostats(old_stats)
e19a30
             diff_stats.display_iostats(time)
e19a30
 
e19a30
-def iostat_command():
e19a30
+def iostat_command(args):
e19a30
     """iostat-like command for NFS mount points
e19a30
     """
e19a30
-    mountstats = parse_stats_file('/proc/self/mountstats')
e19a30
-    devices = []
e19a30
-    interval_seen = False
e19a30
-    count_seen = False
e19a30
-
e19a30
-    for arg in sys.argv:
e19a30
-        if arg in ['-h', '--help', 'help', 'usage']:
e19a30
-            print_iostat_help(prog)
e19a30
-            return
e19a30
+    mountstats = parse_stats_file(args.infile)
e19a30
+    devices = [os.path.normpath(mp) for mp in args.mountpoints]
e19a30
 
e19a30
-        if arg in ['-v', '--version', 'version']:
e19a30
-            print('%s version %s' % (sys.argv[0], Mountstats_version))
e19a30
-            return
e19a30
-
e19a30
-        if arg == sys.argv[0]:
e19a30
-            continue
e19a30
-
e19a30
-        if arg in mountstats:
e19a30
-            devices += [arg]
e19a30
-        elif not interval_seen:
e19a30
-            interval = int(arg)
e19a30
-            if interval > 0:
e19a30
-                interval_seen = True
e19a30
-            else:
e19a30
-                print('Illegal <interval> value')
e19a30
-                return
e19a30
-        elif not count_seen:
e19a30
-            count = int(arg)
e19a30
-            if count > 0:
e19a30
-                count_seen = True
e19a30
-            else:
e19a30
-                print('Illegal <count> value')
e19a30
-                return
e19a30
+    if args.since:
e19a30
+        old_mountstats = parse_stats_file(args.since)
e19a30
+    else:
e19a30
+        old_mountstats = None
e19a30
 
e19a30
     # make certain devices contains only NFS mount points
e19a30
     if len(devices) > 0:
e19a30
         check = []
e19a30
         for device in devices:
e19a30
             stats = DeviceData()
e19a30
-            stats.parse_stats(mountstats[device])
e19a30
-            if stats.is_nfs_mountpoint():
e19a30
-                check += [device]
e19a30
+            try:
e19a30
+                stats.parse_stats(mountstats[device])
e19a30
+                if stats.is_nfs_mountpoint():
e19a30
+                    check += [device]
e19a30
+            except KeyError:
e19a30
+                continue
e19a30
         devices = check
e19a30
     else:
e19a30
         for device, descr in mountstats.items():
e19a30
@@ -572,45 +849,148 @@ def iostat_command():
e19a30
                 devices += [device]
e19a30
     if len(devices) == 0:
e19a30
         print('No NFS mount points were found')
e19a30
-        return
e19a30
+        return 1
e19a30
 
e19a30
-    old_mountstats = None
e19a30
     sample_time = 0
e19a30
 
e19a30
-    if not interval_seen:
e19a30
+    if args.interval is None:
e19a30
         print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
e19a30
         return
e19a30
 
e19a30
-    if count_seen:
e19a30
+    if args.count is not None:
e19a30
+        count = args.count
e19a30
         while count != 0:
e19a30
             print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
e19a30
             old_mountstats = mountstats
e19a30
-            time.sleep(interval)
e19a30
-            sample_time = interval
e19a30
-            mountstats = parse_stats_file('/proc/self/mountstats')
e19a30
+            time.sleep(args.interval)
e19a30
+            sample_time = args.interval
e19a30
+            mountstats = parse_stats_file(args.infile)
e19a30
             count -= 1
e19a30
     else: 
e19a30
         while True:
e19a30
             print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
e19a30
             old_mountstats = mountstats
e19a30
-            time.sleep(interval)
e19a30
-            sample_time = interval
e19a30
-            mountstats = parse_stats_file('/proc/self/mountstats')
e19a30
-
e19a30
-#
e19a30
-# Main
e19a30
-#
e19a30
-prog = os.path.basename(sys.argv[0])
e19a30
+            time.sleep(args.interval)
e19a30
+            sample_time = args.interval
e19a30
+            mountstats = parse_stats_file(args.infile)
e19a30
+
e19a30
+    args.infile.close()
e19a30
+    if args.since:
e19a30
+        args.since.close()
e19a30
+    return 0
e19a30
+
e19a30
+class ICMAction(argparse.Action):
e19a30
+    """Custom action to deal with interval, count, and mountpoints.
e19a30
+    """
e19a30
+    def __call__(self, parser, namespace, values, option_string=None):
e19a30
+        if namespace.mountpoints is None:
e19a30
+            namespace.mountpoints = []
e19a30
+        if values is None:
e19a30
+            return
e19a30
+        elif (type(values) == type([])):
e19a30
+            for value in values:
e19a30
+                self._handle_one(namespace, value)
e19a30
+        else:
e19a30
+            self._handle_one(namespace, values)
e19a30
+
e19a30
+    def _handle_one(self, namespace, value):
e19a30
+        try:
e19a30
+            intval = int(value)
e19a30
+            if namespace.infile.name != '/proc/self/mountstats':
e19a30
+                raise argparse.ArgumentError(self, "not allowed with argument -f/--file or -S/--since")
e19a30
+            self._handle_int(namespace, intval)
e19a30
+        except ValueError:
e19a30
+            namespace.mountpoints.append(value)
e19a30
+
e19a30
+    def _handle_int(self, namespace, value):
e19a30
+        if namespace.interval is None:
e19a30
+            namespace.interval = value
e19a30
+        elif namespace.count is None:
e19a30
+            namespace.count = value
e19a30
+        else:
e19a30
+            raise argparse.ArgumentError(self, "too many integer arguments")
e19a30
+
e19a30
+def main():
e19a30
+    parser = argparse.ArgumentParser(epilog='For specific sub-command help, '
e19a30
+        'run \'mountstats SUB-COMMAND -h|--help\'')
e19a30
+    subparsers = parser.add_subparsers(help='sub-command help')
e19a30
+
e19a30
+    common_parser = argparse.ArgumentParser(add_help=False)
e19a30
+    common_parser.add_argument('-v', '--version', action='version',
e19a30
+        version='mountstats ' + Mountstats_version)
e19a30
+    common_parser.add_argument('-f', '--file', default=open('/proc/self/mountstats', 'r'),
e19a30
+        type=argparse.FileType('r'), dest='infile',
e19a30
+        help='Read stats from %(dest)s instead of /proc/self/mountstats')
e19a30
+    common_parser.add_argument('-S', '--since', type=argparse.FileType('r'),
e19a30
+        metavar='SINCEFILE',
e19a30
+        help='Show difference between current stats and those in SINCEFILE')
e19a30
+
e19a30
+    mountstats_parser = subparsers.add_parser('mountstats',
e19a30
+        parents=[common_parser],
e19a30
+        help='Display a combination of per-op RPC statistics, NFS event counts, and NFS byte counts. '
e19a30
+            'This is the default sub-command if no sub-command is given.')
e19a30
+    group = mountstats_parser.add_mutually_exclusive_group()
e19a30
+    group.add_argument('-n', '--nfs', action='store_true', dest='nfs_only',
e19a30
+        help='Display only the NFS statistics')
e19a30
+    group.add_argument('-r', '--rpc', action='store_true', dest='rpc_only',
e19a30
+        help='Display only the RPC statistics')
e19a30
+    group.add_argument('-R', '--raw', action='store_true',
e19a30
+        help='Display only the raw statistics')
e19a30
+    # The mountpoints argument cannot be moved into the common_parser because
e19a30
+    # it will screw up the parsing of the iostat arguments (interval and count)
e19a30
+    mountstats_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint',
e19a30
+        help='Display statistics for this mountpoint. More than one may be specified. '
e19a30
+            'If absent, statistics for all NFS mountpoints will be generated.')
e19a30
+    mountstats_parser.set_defaults(func=mountstats_command)
e19a30
+
e19a30
+    nfsstat_parser = subparsers.add_parser('nfsstat',
e19a30
+        parents=[common_parser],
e19a30
+        help='Display nfsstat-like statistics.')
e19a30
+    nfsstat_parser.add_argument('-3', action='store_true', dest='show_v3',
e19a30
+        help='Show NFS version 3 statistics')
e19a30
+    nfsstat_parser.add_argument('-4', action='store_true', dest='show_v4',
e19a30
+        help='Show NFS version 4 statistics')
e19a30
+    # The mountpoints argument cannot be moved into the common_parser because
e19a30
+    # it will screw up the parsing of the iostat arguments (interval and count)
e19a30
+    nfsstat_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint',
e19a30
+        help='Display statistics for this mountpoint. More than one may be specified. '
e19a30
+            'If absent, statistics for all NFS mountpoints will be generated.')
e19a30
+    nfsstat_parser.set_defaults(func=nfsstat_command)
e19a30
+
e19a30
+    iostat_parser = subparsers.add_parser('iostat',
e19a30
+        parents=[common_parser],
e19a30
+        help='Display iostat-like statistics.')
e19a30
+    iostat_parser.add_argument('interval', nargs='?', action=ICMAction,
e19a30
+        help='Number of seconds between reports. If absent, only one report will '
e19a30
+            'be generated.')
e19a30
+    iostat_parser.add_argument('count', nargs='?', action=ICMAction,
e19a30
+        help='Number of reports generated at <interval> seconds apart. If absent, '
e19a30
+            'reports will be generated continuously.')
e19a30
+    # The mountpoints argument cannot be moved into the common_parser because
e19a30
+    # it will screw up the parsing of the iostat arguments (interval and count)
e19a30
+    iostat_parser.add_argument('mountpoints', nargs='*', action=ICMAction, metavar='mountpoint',
e19a30
+        help='Display statsistics for this mountpoint. More than one may be specified. '
e19a30
+            'If absent, statistics for all NFS mountpoints will be generated.')
e19a30
+    iostat_parser.set_defaults(func=iostat_command)
e19a30
+ 
e19a30
+    args = parser.parse_args()
e19a30
+    return args.func(args)
e19a30
 
e19a30
 try:
e19a30
-    if prog == 'mountstats':
e19a30
-        mountstats_command()
e19a30
-    elif prog == 'ms-nfsstat':
e19a30
-        nfsstat_command()
e19a30
-    elif prog == 'ms-iostat':
e19a30
-        iostat_command()
e19a30
-except KeyboardInterrupt:
e19a30
-    print('Caught ^C... exiting')
e19a30
+    if __name__ == '__main__':
e19a30
+        # Run the mounstats sub-command if no sub-command (or the help flag)
e19a30
+        # is given.  If the argparse module ever gets support for optional
e19a30
+        # (default) sub-commands, then this can be changed.
e19a30
+        if len(sys.argv) == 1:
e19a30
+            sys.argv.insert(1, 'mountstats')
e19a30
+        elif sys.argv[1] not in ['-h', '--help', 'mountstats', 'iostat', 'nfsstat']:
e19a30
+            sys.argv.insert(1, 'mountstats')
e19a30
+        res = main()
e19a30
+        sys.stdout.close()
e19a30
+        sys.stderr.close()
e19a30
+        sys.exit(res)
e19a30
+except (KeyboardInterrupt, RuntimeError):
e19a30
     sys.exit(1)
e19a30
+except IOError:
e19a30
+    pass
e19a30
 
e19a30
-sys.exit(0)
e19a30
diff -up nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py.good nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py
e19a30
--- nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py.good	2015-07-30 15:16:58.774761466 -0400
e19a30
+++ nfs-utils-1.3.0/tools/nfs-iostat/nfs-iostat.py	2015-07-30 15:16:20.391062604 -0400
e19a30
@@ -213,7 +213,8 @@ class DeviceData:
e19a30
         # the reference to them.  so we build new lists here
e19a30
         # for the result object.
e19a30
         for op in result.__rpc_data['ops']:
e19a30
-            result.__rpc_data[op] = map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])
e19a30
+            result.__rpc_data[op] = list(map(
e19a30
+                difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
e19a30
 
e19a30
         # update the remaining keys we care about
e19a30
         result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']