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

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