Changes since v1:
- Changed the parsing to use the argparse module instead of optparse
- Added per-mountpoint headers to the output of 'mountstats --rpc'
- Revamped the ms-nfsstat command to take a variable list of mountpoints
(so now mountstats, ms-iostat, and ms-nfsstat all behave in the same
manner). Added -3 and -4 options which behave the same way they do in
nfsstat.c. The output doesn't still doesn't match up 100% with that
of nfsstat.c though (I'm just taking the first 12 characters of the
operation name and converting them to lowercase to use as labels, while
nfsstat.c defines its own labels, but also it looks to me like nfsstat.c
is missing some operations altoegether).
- Updated the man page for mountstats and added man pages for ms-iostat
and ms-nfsstat.
These are also available in the 'mountstats-improvements-v2' branch at
https://github.com/scottmayhew/nfs-utils.git
Original cover letter:
-------------------8<------------------
The following patches add a couple of enhancements to mountstats.py. I
also fixed a few bugs I encountered along the way. Highlights include:
- added support for -f/--file to allow stats to be parsed from an
aritrary input file instead of /proc/self/mountstats
- added support for -S/--since to show just the changes that have
occurred between the current and a previous set of statisics (works
with and without the -f option)
- added support for -R/--raw to generate 'raw' statistics (i.e. in the
same format as /proc/self/mountstats). It's intended to be used with
the -f and -S options.
- implemented the ms-nfsstat command to generate client-side
nfsstat-like statisics (only works with a single mountpoint)
My motivation for these changes was so that I could take various copies
of /proc/self/mountstats and massage them into data that I could feed
into the 'report' option of Dros's nfsometer tool for scenarios where
it's not feasible to run nfsometer itself (e.g. systems where we can't
start with an 'idle' state (i.e. no NFS filesystems initially
mounted), systems with multiple NFS filesystems mounted, and workloads
that can't easily be boiled down into an nfsometer workload file or run
via the custom workload environment variables).
Scott Mayhew (17):
mountstats: Fix up NFS event counters
mountstats: Add lists of various counters
mountstats: Refactor __parse_nfs_line and __parse_rpc_line
mountstats: Refactor compare_iostats
mountstats: Convert existing option parsing to use the argparse module
mountstats: Make ms-iostat output match that of nfs-iostat.py
mountstats: Make print_iostat_summary handle newly appearing mounts
mountstats: Add support for -f/--file to the mountstats and ms-iostat
commands
mountstats: Add support for -S/--since to the mountstats and ms-iostat
commands
mountstats: Fix IndexError in __parse_nfs_line
mountstats: Allow mountstats_command to take a variable number of
mountpoints
mountstats: Add support for -R/--raw to mountstats_command
mountstats: Implement nfsstat_command
mountstats: Updated the mountstats(8) man page.
mountstats: Added man page for ms-iostat(8)
mountstats: Added man page for ms-nfsstat(8)
mountstats: add ms-iostat and ms-nfsstat to Makefile.am
tools/mountstats/Makefile.am | 4 +-
tools/mountstats/mountstats.man | 45 +-
tools/mountstats/mountstats.py | 904 ++++++++++++++++++++++++++++------------
tools/mountstats/ms-iostat.man | 64 +++
tools/mountstats/ms-nfsstat.man | 49 +++
5 files changed, 777 insertions(+), 289 deletions(-)
create mode 100644 tools/mountstats/ms-iostat.man
create mode 100644 tools/mountstats/ms-nfsstat.man
--
1.9.3
The event counters in the mountstats program aren't in sync with the
event counters in the kernel. Removed syncinodes and added
vfsupdatepage, vfssetattr, congestionwait, pnfsreads, and pnfswrites.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 33 ++++++++++++++++++---------------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index e6a456c..ab971a4 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -73,25 +73,29 @@ class DeviceData:
self.__nfs_data['dentryrevalidates'] = int(words[2])
self.__nfs_data['datainvalidates'] = int(words[3])
self.__nfs_data['attrinvalidates'] = int(words[4])
- self.__nfs_data['syncinodes'] = int(words[5])
- self.__nfs_data['vfsopen'] = int(words[6])
- self.__nfs_data['vfslookup'] = int(words[7])
- self.__nfs_data['vfspermission'] = int(words[8])
+ self.__nfs_data['vfsopen'] = int(words[5])
+ self.__nfs_data['vfslookup'] = int(words[6])
+ self.__nfs_data['vfspermission'] = int(words[7])
+ self.__nfs_data['vfsupdatepage'] = int(words[8])
self.__nfs_data['vfsreadpage'] = int(words[9])
self.__nfs_data['vfsreadpages'] = int(words[10])
self.__nfs_data['vfswritepage'] = int(words[11])
self.__nfs_data['vfswritepages'] = int(words[12])
self.__nfs_data['vfsreaddir'] = int(words[13])
- self.__nfs_data['vfsflush'] = int(words[14])
- self.__nfs_data['vfsfsync'] = int(words[15])
- self.__nfs_data['vfslock'] = int(words[16])
- self.__nfs_data['vfsrelease'] = int(words[17])
- self.__nfs_data['setattrtrunc'] = int(words[18])
- self.__nfs_data['extendwrite'] = int(words[19])
- self.__nfs_data['sillyrenames'] = int(words[20])
- self.__nfs_data['shortreads'] = int(words[21])
- self.__nfs_data['shortwrites'] = int(words[22])
- self.__nfs_data['delay'] = int(words[23])
+ self.__nfs_data['vfssetattr'] = int(words[14])
+ self.__nfs_data['vfsflush'] = int(words[15])
+ self.__nfs_data['vfsfsync'] = int(words[16])
+ self.__nfs_data['vfslock'] = int(words[17])
+ self.__nfs_data['vfsrelease'] = int(words[18])
+ self.__nfs_data['congestionwait'] = int(words[19])
+ self.__nfs_data['setattrtrunc'] = int(words[20])
+ self.__nfs_data['extendwrite'] = int(words[21])
+ self.__nfs_data['sillyrenames'] = int(words[22])
+ self.__nfs_data['shortreads'] = int(words[23])
+ self.__nfs_data['shortwrites'] = int(words[24])
+ self.__nfs_data['delay'] = int(words[25])
+ self.__nfs_data['pnfsreads'] = int(words[26])
+ self.__nfs_data['pnfswrites'] = int(words[27])
elif words[0] == 'bytes:':
self.__nfs_data['normalreadbytes'] = int(words[1])
self.__nfs_data['normalwritebytes'] = int(words[2])
@@ -201,7 +205,6 @@ class DeviceData:
print('Cache events:')
print(' data cache invalidated %d times' % self.__nfs_data['datainvalidates'])
print(' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates'])
- print(' inodes synced %d times' % self.__nfs_data['syncinodes'])
print()
print('VFS calls:')
print(' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates'])
--
1.9.3
The NfsEventCounters and NfsByteCounters were lifted straight from
nfs-iostat.py. Also added counters for the xprt line (for udp, tcp, and
rdma) as well as for v3 and v4 NFS ops. These will allow for more
compact code in a couple of places.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 168 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 168 insertions(+)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index ab971a4..e36b9ef 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -32,6 +32,174 @@ def difference(x, y):
"""
return x - y
+NfsEventCounters = [
+ 'inoderevalidates',
+ 'dentryrevalidates',
+ 'datainvalidates',
+ 'attrinvalidates',
+ 'vfsopen',
+ 'vfslookup',
+ 'vfspermission',
+ 'vfsupdatepage',
+ 'vfsreadpage',
+ 'vfsreadpages',
+ 'vfswritepage',
+ 'vfswritepages',
+ 'vfsreaddir',
+ 'vfssetattr',
+ 'vfsflush',
+ 'vfsfsync',
+ 'vfslock',
+ 'vfsrelease',
+ 'congestionwait',
+ 'setattrtrunc',
+ 'extendwrite',
+ 'sillyrenames',
+ 'shortreads',
+ 'shortwrites',
+ 'delay',
+ 'pnfsreads',
+ 'pnfswrites'
+]
+
+NfsByteCounters = [
+ 'normalreadbytes',
+ 'normalwritebytes',
+ 'directreadbytes',
+ 'directwritebytes',
+ 'serverreadbytes',
+ 'serverwritebytes',
+ 'readpages',
+ 'writepages'
+]
+
+XprtUdpCounters = [
+ 'port',
+ 'bind_count',
+ 'rpcsends',
+ 'rpcreceives',
+ 'badxids',
+ 'inflightsends',
+ 'backlogutil'
+]
+
+XprtTcpCounters = [
+ 'port',
+ 'bind_count',
+ 'connect_count',
+ 'connect_time',
+ 'idle_time',
+ 'rpcsends',
+ 'rpcreceives',
+ 'badxids',
+ 'inflightsends',
+ 'backlogutil'
+]
+
+XprtRdmaCounters = [
+ 'port',
+ 'bind_count',
+ 'connect_count',
+ 'connect_time',
+ 'idle_time',
+ 'rpcsends',
+ 'rpcreceives',
+ 'badxids',
+ 'backlogutil',
+ 'read_chunks',
+ 'write_chunks',
+ 'reply_chunks',
+ 'total_rdma_req',
+ 'total_rdma_rep',
+ 'pullup',
+ 'fixup',
+ 'hardway',
+ 'failed_marshal',
+ 'bad_reply'
+]
+
+Nfsv3ops = [
+ 'NULL',
+ 'GETATTR',
+ 'SETATTR',
+ 'LOOKUP',
+ 'ACCESS',
+ 'READLINK',
+ 'READ',
+ 'WRITE',
+ 'CREATE',
+ 'MKDIR',
+ 'SYMLINK',
+ 'MKNOD',
+ 'REMOVE',
+ 'RMDIR',
+ 'RENAME',
+ 'LINK',
+ 'READDIR',
+ 'READDIRPLUS',
+ 'FSSTAT',
+ 'FSINFO',
+ 'PATHCONF',
+ 'COMMIT'
+]
+
+Nfsv4ops = [
+ 'NULL',
+ 'READ',
+ 'WRITE',
+ 'COMMIT',
+ 'OPEN',
+ 'OPEN_CONFIRM',
+ 'OPEN_NOATTR',
+ 'OPEN_DOWNGRADE',
+ 'CLOSE',
+ 'SETATTR',
+ 'FSINFO',
+ 'RENEW',
+ 'SETCLIENTID',
+ 'SETCLIENTID_CONFIRM',
+ 'LOCK',
+ 'LOCKT',
+ 'LOCKU',
+ 'ACCESS',
+ 'GETATTR',
+ 'LOOKUP',
+ 'LOOKUP_ROOT',
+ 'REMOVE',
+ 'RENAME',
+ 'LINK',
+ 'SYMLINK',
+ 'CREATE',
+ 'PATHCONF',
+ 'STATFS',
+ 'READLINK',
+ 'READDIR',
+ 'SERVER_CAPS',
+ 'DELEGRETURN',
+ 'GETACL',
+ 'SETACL',
+ 'FS_LOCATIONS',
+ 'RELEASE_LOCKOWNER',
+ 'SECINFO',
+ 'FSID_PRESENT',
+ 'EXCHANGE_ID',
+ 'CREATE_SESSION',
+ 'DESTROY_SESSION',
+ 'SEQUENCE',
+ 'GET_LEASE_TIME',
+ 'RECLAIM_COMPLETE',
+ 'LAYOUTGET',
+ 'GETDEVICEINFO',
+ 'LAYOUTCOMMIT',
+ 'LAYOUTRETURN',
+ 'SECINFO_NO_NAME',
+ 'TEST_STATEID',
+ 'FREE_STATEID',
+ 'GETDEVICELIST',
+ 'BIND_CONN_TO_SESSION',
+ 'DESTROY_CLIENTID'
+]
+
class DeviceData:
"""DeviceData objects provide methods for parsing and displaying
data for a single mount grabbed from /proc/self/mountstats
--
1.9.3
Iterate over the newly added counter lists instead of having a ton of
assignment statements.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 92 +++++++++++-------------------------------
1 file changed, 23 insertions(+), 69 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index e36b9ef..5fe99b5 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -237,40 +237,18 @@ class DeviceData:
if self.__nfs_data['flavor'] == 6:
self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1])
elif words[0] == 'events:':
- self.__nfs_data['inoderevalidates'] = int(words[1])
- self.__nfs_data['dentryrevalidates'] = int(words[2])
- self.__nfs_data['datainvalidates'] = int(words[3])
- self.__nfs_data['attrinvalidates'] = int(words[4])
- self.__nfs_data['vfsopen'] = int(words[5])
- self.__nfs_data['vfslookup'] = int(words[6])
- self.__nfs_data['vfspermission'] = int(words[7])
- self.__nfs_data['vfsupdatepage'] = int(words[8])
- self.__nfs_data['vfsreadpage'] = int(words[9])
- self.__nfs_data['vfsreadpages'] = int(words[10])
- self.__nfs_data['vfswritepage'] = int(words[11])
- self.__nfs_data['vfswritepages'] = int(words[12])
- self.__nfs_data['vfsreaddir'] = int(words[13])
- self.__nfs_data['vfssetattr'] = int(words[14])
- self.__nfs_data['vfsflush'] = int(words[15])
- self.__nfs_data['vfsfsync'] = int(words[16])
- self.__nfs_data['vfslock'] = int(words[17])
- self.__nfs_data['vfsrelease'] = int(words[18])
- self.__nfs_data['congestionwait'] = int(words[19])
- self.__nfs_data['setattrtrunc'] = int(words[20])
- self.__nfs_data['extendwrite'] = int(words[21])
- self.__nfs_data['sillyrenames'] = int(words[22])
- self.__nfs_data['shortreads'] = int(words[23])
- self.__nfs_data['shortwrites'] = int(words[24])
- self.__nfs_data['delay'] = int(words[25])
- self.__nfs_data['pnfsreads'] = int(words[26])
- self.__nfs_data['pnfswrites'] = int(words[27])
+ i = 1
+ for key in NfsEventCounters:
+ try:
+ self.__nfs_data[key] = int(words[i])
+ except IndexError as err:
+ self.__nfs_data[key] = 0
+ i += 1
elif words[0] == 'bytes:':
- self.__nfs_data['normalreadbytes'] = int(words[1])
- self.__nfs_data['normalwritebytes'] = int(words[2])
- self.__nfs_data['directreadbytes'] = int(words[3])
- self.__nfs_data['directwritebytes'] = int(words[4])
- self.__nfs_data['serverreadbytes'] = int(words[5])
- self.__nfs_data['serverwritebytes'] = int(words[6])
+ i = 1
+ for key in NfsByteCounters:
+ self.__nfs_data[key] = int(words[i])
+ i += 1
def __parse_rpc_line(self, words):
if words[0] == 'RPC':
@@ -279,44 +257,20 @@ class DeviceData:
elif words[0] == 'xprt:':
self.__rpc_data['protocol'] = words[1]
if words[1] == 'udp':
- self.__rpc_data['port'] = int(words[2])
- self.__rpc_data['bind_count'] = int(words[3])
- self.__rpc_data['rpcsends'] = int(words[4])
- self.__rpc_data['rpcreceives'] = int(words[5])
- self.__rpc_data['badxids'] = int(words[6])
- self.__rpc_data['inflightsends'] = int(words[7])
- self.__rpc_data['backlogutil'] = int(words[8])
+ i = 2
+ for key in XprtUdpCounters:
+ self.__rpc_data[key] = int(words[i])
+ i += 1
elif words[1] == 'tcp':
- self.__rpc_data['port'] = words[2]
- self.__rpc_data['bind_count'] = int(words[3])
- self.__rpc_data['connect_count'] = int(words[4])
- self.__rpc_data['connect_time'] = int(words[5])
- self.__rpc_data['idle_time'] = int(words[6])
- self.__rpc_data['rpcsends'] = int(words[7])
- self.__rpc_data['rpcreceives'] = int(words[8])
- self.__rpc_data['badxids'] = int(words[9])
- self.__rpc_data['inflightsends'] = int(words[10])
- self.__rpc_data['backlogutil'] = int(words[11])
+ i = 2
+ for key in XprtTcpCounters:
+ self.__rpc_data[key] = int(words[i])
+ i += 1
elif words[1] == 'rdma':
- self.__rpc_data['port'] = words[2]
- self.__rpc_data['bind_count'] = int(words[3])
- self.__rpc_data['connect_count'] = int(words[4])
- self.__rpc_data['connect_time'] = int(words[5])
- self.__rpc_data['idle_time'] = int(words[6])
- self.__rpc_data['rpcsends'] = int(words[7])
- self.__rpc_data['rpcreceives'] = int(words[8])
- self.__rpc_data['badxids'] = int(words[9])
- self.__rpc_data['backlogutil'] = int(words[10])
- self.__rpc_data['read_chunks'] = int(words[11])
- self.__rpc_data['write_chunks'] = int(words[12])
- self.__rpc_data['reply_chunks'] = int(words[13])
- self.__rpc_data['total_rdma_req'] = int(words[14])
- self.__rpc_data['total_rdma_rep'] = int(words[15])
- self.__rpc_data['pullup'] = int(words[16])
- self.__rpc_data['fixup'] = int(words[17])
- self.__rpc_data['hardway'] = int(words[18])
- self.__rpc_data['failed_marshal'] = int(words[19])
- self.__rpc_data['bad_reply'] = int(words[20])
+ i = 2
+ for key in XprtRdmaCounters:
+ self.__rpc_data[key] = int(words[i])
+ i += 1
elif words[0] == 'per-op':
self.__rpc_data['per-op'] = words
else:
--
1.9.3
Iterate over the newly added counters instead of using repeated
assignment statements. Also compute the difference of every counter
where it makes sense -- this will allow support for -S/--since to be
implemented in the future.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 25 +++++++++++++++++++------
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 5fe99b5..f633bbc 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -412,7 +412,11 @@ class DeviceData:
def compare_iostats(self, old_stats):
"""Return the difference between two sets of stats
"""
+ if old_stats.__nfs_data['age'] > self.__nfs_data['age']:
+ return self
+
result = DeviceData()
+ protocol = self.__rpc_data['protocol']
# copy self into result
for key, value in self.__nfs_data.items():
@@ -427,12 +431,21 @@ class DeviceData:
for op in result.__rpc_data['ops']:
result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op]))
- # update the remaining keys we care about
- result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
- result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil']
- result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes']
- result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes']
-
+ # update the remaining keys
+ if protocol == 'udp':
+ for key in XprtUdpCounters:
+ result.__rpc_data[key] -= old_stats.__rpc_data[key]
+ elif protocol == 'tcp':
+ for key in XprtTcpCounters:
+ result.__rpc_data[key] -= old_stats.__rpc_data[key]
+ elif protocol == 'rdma':
+ for key in XprtRdmaCounters:
+ result.__rpc_data[key] -= old_stats.__rpc_data[key]
+ result.__nfs_data['age'] -= old_stats.__nfs_data['age']
+ for key in NfsEventCounters:
+ result.__nfs_data[key] -= old_stats.__nfs_data[key]
+ for key in NfsByteCounters:
+ result.__nfs_data[key] -= old_stats.__nfs_data[key]
return result
def display_iostats(self, sample_time):
--
1.9.3
Also removed the --start and --end options since all they do is throw
exceptions.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 235 ++++++++++++++++-------------------------
1 file changed, 93 insertions(+), 142 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index f633bbc..cc5c6e5 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -24,6 +24,7 @@ MA 02110-1301 USA
"""
import sys, os, time
+import argparse
Mountstats_version = '0.2'
@@ -530,66 +531,12 @@ def parse_stats_file(filename):
return ms_dict
-def print_mountstats_help(name):
- print('usage: %s [ options ] <mount point>' % name)
- print()
- print(' Version %s' % Mountstats_version)
- print()
- print(' Display NFS client per-mount statistics.')
- print()
- print(' --version display the version of this command')
- print(' --nfs display only the NFS statistics')
- print(' --rpc display only the RPC statistics')
- print(' --start sample and save statistics')
- print(' --end resample statistics and compare them with saved')
- print()
-
-def mountstats_command():
+def mountstats_command(args):
"""Mountstats command
"""
- mountpoints = []
- nfs_only = False
- rpc_only = False
-
- for arg in sys.argv:
- if arg in ['-h', '--help', 'help', 'usage']:
- print_mountstats_help(prog)
- return
-
- if arg in ['-v', '--version', 'version']:
- print('%s version %s' % (sys.argv[0], Mountstats_version))
- sys.exit(0)
-
- if arg in ['-n', '--nfs']:
- nfs_only = True
- continue
-
- if arg in ['-r', '--rpc']:
- rpc_only = True
- continue
-
- if arg in ['-s', '--start']:
- raise Exception('Sampling is not yet implemented')
-
- if arg in ['-e', '--end']:
- raise Exception('Sampling is not yet implemented')
-
- if arg == sys.argv[0]:
- continue
-
- mountpoints += [arg]
-
- if mountpoints == []:
- print_mountstats_help(prog)
- return
-
- if rpc_only == True and nfs_only == True:
- print_mountstats_help(prog)
- return
-
mountstats = parse_stats_file('/proc/self/mountstats')
- for mp in mountpoints:
+ for mp in args.mountpoints:
if mp not in mountstats:
print('Statistics for mount point %s not found' % mp)
continue
@@ -601,11 +548,11 @@ def mountstats_command():
print('Mount point %s exists but is not an NFS mount' % mp)
continue
- if nfs_only:
+ if args.nfs_only:
stats.display_nfs_options()
stats.display_nfs_events()
stats.display_nfs_bytes()
- elif rpc_only:
+ elif args.rpc_only:
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
else:
@@ -614,38 +561,8 @@ def mountstats_command():
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
-def print_nfsstat_help(name):
- print('usage: %s [ options ]' % name)
- print()
- print(' Version %s' % Mountstats_version)
- print()
- print(' nfsstat-like program that uses NFS client per-mount statistics.')
- print()
-
-def nfsstat_command():
- print_nfsstat_help(prog)
-
-def print_iostat_help(name):
- print('usage: %s [ <interval> [ <count> ] ] [ <mount point> ] ' % name)
- print()
- print(' Version %s' % Mountstats_version)
- print()
- print(' iostat-like program to display NFS client per-mount statistics.')
- print()
- print(' The <interval> parameter specifies the amount of time in seconds between')
- print(' each report. The first report contains statistics for the time since each')
- print(' file system was mounted. Each subsequent report contains statistics')
- print(' collected during the interval since the previous report.')
- print()
- print(' If the <count> parameter is specified, the value of <count> determines the')
- print(' number of reports generated at <interval> seconds apart. If the interval')
- print(' parameter is specified without the <count> parameter, the command generates')
- print(' reports continuously.')
- print()
- print(' If one or more <mount point> names are specified, statistics for only these')
- print(' mount points will be displayed. Otherwise, all NFS mount points on the')
- print(' client are listed.')
- print()
+def nfsstat_command(args):
+ return
def print_iostat_summary(old, new, devices, time):
for device in devices:
@@ -659,42 +576,11 @@ def print_iostat_summary(old, new, devices, time):
diff_stats = stats.compare_iostats(old_stats)
diff_stats.display_iostats(time)
-def iostat_command():
+def iostat_command(args):
"""iostat-like command for NFS mount points
"""
mountstats = parse_stats_file('/proc/self/mountstats')
- devices = []
- interval_seen = False
- count_seen = False
-
- for arg in sys.argv:
- if arg in ['-h', '--help', 'help', 'usage']:
- print_iostat_help(prog)
- return
-
- if arg in ['-v', '--version', 'version']:
- print('%s version %s' % (sys.argv[0], Mountstats_version))
- return
-
- if arg == sys.argv[0]:
- continue
-
- if arg in mountstats:
- devices += [arg]
- elif not interval_seen:
- interval = int(arg)
- if interval > 0:
- interval_seen = True
- else:
- print('Illegal <interval> value')
- return
- elif not count_seen:
- count = int(arg)
- if count > 0:
- count_seen = True
- else:
- print('Illegal <count> value')
- return
+ devices = args.mountpoints
# make certain devices contains only NFS mount points
if len(devices) > 0:
@@ -718,44 +604,109 @@ def iostat_command():
old_mountstats = None
sample_time = 0
- if not interval_seen:
+ if args.interval is None:
print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
return
- if count_seen:
+ if args.count is not None:
+ count = args.count
while count != 0:
print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
old_mountstats = mountstats
- time.sleep(interval)
- sample_time = interval
+ time.sleep(args.interval)
+ sample_time = args.interval
mountstats = parse_stats_file('/proc/self/mountstats')
count -= 1
else:
while True:
print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
old_mountstats = mountstats
- time.sleep(interval)
- sample_time = interval
+ time.sleep(args.interval)
+ sample_time = args.interval
mountstats = parse_stats_file('/proc/self/mountstats')
-#
-# Main
-#
-prog = os.path.basename(sys.argv[0])
+class ICMAction(argparse.Action):
+ """Custom action to deal with interval, count, and mountpoints.
+ """
+ def __call__(self, parser, namespace, values, option_string=None):
+ if namespace.mountpoints is None:
+ namespace.mountpoints = []
+ if values is None:
+ return
+ elif (type(values) == type([])):
+ for value in values:
+ self._handle_one(namespace, value)
+ else:
+ self._handle_one(namespace, values)
+
+ def _handle_one(self, namespace, value):
+ try:
+ intval = int(value)
+ self._handle_int(namespace, intval)
+ except ValueError:
+ namespace.mountpoints.append(value)
+
+ def _handle_int(self, namespace, value):
+ if namespace.interval is None:
+ namespace.interval = value
+ elif namespace.count is None:
+ namespace.count = value
+ else:
+ raise argparse.ArgumentError(self, "too many ints")
+
+def main():
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + Mountstats_version)
+
+ prog = os.path.basename(sys.argv[0])
-try:
if prog == 'mountstats':
- mountstats_command()
+ mountstats_parser = argparse.ArgumentParser(
+ description='Display NFS client per-mount statistics.',
+ parents=[parser])
+ group = mountstats_parser.add_mutually_exclusive_group()
+ group.add_argument('-n', '--nfs', action='store_true', dest='nfs_only',
+ help='Display only the NFS statistics')
+ group.add_argument('-r', '--rpc', action='store_true', dest='rpc_only',
+ help='Display only the RPC statistics')
+ mountstats_parser.add_argument('mountpoints', nargs='+', metavar='mountpoint',
+ help='Display statistics for this mountpoint. More than one may be specified.')
+ mountstats_parser.set_defaults(func=mountstats_command)
+ args = mountstats_parser.parse_args()
+
elif prog == 'ms-nfsstat':
- nfsstat_command()
+ nfsstat_parser = argparse.ArgumentParser(
+ description='nfsstat-like program that uses NFS client per-mount statistics.',
+ parents=[parser])
+ nfsstat_parser.set_defaults(func=nfsstat_command)
+ args = nfsstat_parser.parse_args()
+ nfsstat_parser.print_help()
+
elif prog == 'ms-iostat':
- iostat_command()
- sys.stdout.close()
- sys.stderr.close()
-except KeyboardInterrupt:
- print('Caught ^C... exiting')
+ iostat_parser = argparse.ArgumentParser(
+ description='iostat-like program to display NFS client per-mount statistics.',
+ parents=[parser])
+ iostat_parser.add_argument('interval', nargs='?', action=ICMAction,
+ help='Number of seconds between reports. If absent, only one report will '
+ 'be generated.')
+ iostat_parser.add_argument('count', nargs='?', action=ICMAction,
+ help='Number of reports generated at <interval> seconds apart. If absent, '
+ 'reports will be generated continuously.')
+ iostat_parser.add_argument('mountpoints', nargs='*', action=ICMAction, metavar='mountpoint',
+ help='Display statsistics for this mountpoint. More than one may be specified. '
+ 'If absent, statistics for all NFS mountpoints will be generated.')
+ iostat_parser.set_defaults(func=iostat_command)
+ args = iostat_parser.parse_args()
+ return args.func(args)
+
+try:
+ if __name__ == '__main__':
+ res = main()
+ sys.stdout.close()
+ sys.stderr.close()
+ sys.exit(res)
+except (SystemExit, KeyboardInterrupt, RuntimeError):
sys.exit(1)
except IOError:
pass
-sys.exit(0)
--
1.9.3
Changes mostly lifted straight from nfs-iostat.py.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 104 ++++++++++++++++++++++++-----------------
1 file changed, 61 insertions(+), 43 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index cc5c6e5..729e01c 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -449,60 +449,78 @@ class DeviceData:
result.__nfs_data[key] -= old_stats.__nfs_data[key]
return result
+ def __print_rpc_op_stats(self, op, sample_time):
+ """Print generic stats for one RPC op
+ """
+ if op not in self.__rpc_data:
+ return
+
+ rpc_stats = self.__rpc_data[op]
+ ops = float(rpc_stats[0])
+ retrans = float(rpc_stats[1] - rpc_stats[0])
+ kilobytes = float(rpc_stats[3] + rpc_stats[4]) / 1024
+ rtt = float(rpc_stats[6])
+ exe = float(rpc_stats[7])
+
+ # prevent floating point exceptions
+ if ops != 0:
+ kb_per_op = kilobytes / ops
+ retrans_percent = (retrans * 100) / ops
+ rtt_per_op = rtt / ops
+ exe_per_op = exe / ops
+ else:
+ kb_per_op = 0.0
+ retrans_percent = 0.0
+ rtt_per_op = 0.0
+ exe_per_op = 0.0
+
+ op += ':'
+ print(format(op.lower(), '<16s'), end='')
+ print(format('ops/s', '>8s'), end='')
+ print(format('kB/s', '>16s'), end='')
+ print(format('kB/op', '>16s'), end='')
+ print(format('retrans', '>16s'), end='')
+ print(format('avg RTT (ms)', '>16s'), end='')
+ print(format('avg exe (ms)', '>16s'))
+
+ print(format((ops / sample_time), '>24.3f'), end='')
+ print(format((kilobytes / sample_time), '>16.3f'), end='')
+ print(format(kb_per_op, '>16.3f'), end='')
+ retransmits = '{0:>10.0f} ({1:>3.1f}%)'.format(retrans, retrans_percent).strip()
+ print(format(retransmits, '>16'), end='')
+ print(format(rtt_per_op, '>16.3f'), end='')
+ print(format(exe_per_op, '>16.3f'))
+
def display_iostats(self, sample_time):
"""Display NFS and RPC stats in an iostat-like way
"""
sends = float(self.__rpc_data['rpcsends'])
if sample_time == 0:
sample_time = float(self.__nfs_data['age'])
+ # sample_time could still be zero if the export was just mounted.
+ # Set it to 1 to avoid divide by zero errors in this case since we'll
+ # likely still have relevant mount statistics to show.
+ #
+ if sample_time == 0:
+ sample_time = 1;
+ if sends != 0:
+ backlog = (float(self.__rpc_data['backlogutil']) / sends) / sample_time
+ else:
+ backlog = 0.0
print()
print('%s mounted on %s:' % \
(self.__nfs_data['export'], self.__nfs_data['mountpoint']))
+ print()
- print('\top/s\trpc bklog')
- print('\t%.2f' % (sends / sample_time), end=' ')
- if sends != 0:
- print('\t%.2f' % \
- ((float(self.__rpc_data['backlogutil']) / sends) / sample_time))
- else:
- print('\t0.00')
-
- # reads: ops/s, kB/s, avg rtt, and avg exe
- # XXX: include avg xfer size and retransmits?
- read_rpc_stats = self.__rpc_data['READ']
- ops = float(read_rpc_stats[0])
- kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024
- rtt = float(read_rpc_stats[6])
- exe = float(read_rpc_stats[7])
-
- print('\treads:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)')
- print('\t\t%.2f' % (ops / sample_time), end=' ')
- print('\t\t%.2f' % (kilobytes / sample_time), end=' ')
- if ops != 0:
- print('\t\t%.2f' % (rtt / ops), end=' ')
- print('\t\t%.2f' % (exe / ops))
- else:
- print('\t\t0.00', end=' ')
- print('\t\t0.00')
-
- # writes: ops/s, kB/s, avg rtt, and avg exe
- # XXX: include avg xfer size and retransmits?
- write_rpc_stats = self.__rpc_data['WRITE']
- ops = float(write_rpc_stats[0])
- kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024
- rtt = float(write_rpc_stats[6])
- exe = float(write_rpc_stats[7])
-
- print('\twrites:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)')
- print('\t\t%.2f' % (ops / sample_time), end=' ')
- print('\t\t%.2f' % (kilobytes / sample_time), end=' ')
- if ops != 0:
- print('\t\t%.2f' % (rtt / ops), end=' ')
- print('\t\t%.2f' % (exe / ops))
- else:
- print('\t\t0.00', end=' ')
- print('\t\t0.00')
+ print(format('ops/s', '>16') + format('rpc bklog', '>16'))
+ print(format((sends / sample_time), '>16.3f'), end='')
+ print(format(backlog, '>16.3f'))
+ print()
+
+ self.__print_rpc_op_stats('READ', sample_time)
+ self.__print_rpc_op_stats('WRITE', sample_time)
+ sys.stdout.flush()
def parse_stats_file(filename):
"""pop the contents of a mountstats file into a dictionary,
--
1.9.3
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 729e01c..3419c7a 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -586,7 +586,7 @@ def print_iostat_summary(old, new, devices, time):
for device in devices:
stats = DeviceData()
stats.parse_stats(new[device])
- if not old:
+ if not old or device not in old:
stats.display_iostats(time)
else:
old_stats = DeviceData()
--
1.9.3
Add support for the -f/--file option to allow parsing of data from an
arbitrary file instead of /proc/self/mountstats.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 31 +++++++++++++++++++++----------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 3419c7a..ddaa6c4 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -522,7 +522,7 @@ class DeviceData:
self.__print_rpc_op_stats('WRITE', sample_time)
sys.stdout.flush()
-def parse_stats_file(filename):
+def parse_stats_file(f):
"""pop the contents of a mountstats file into a dictionary,
keyed by mount point. each value object is a list of the
lines in the mountstats file corresponding to the mount
@@ -531,7 +531,7 @@ def parse_stats_file(filename):
ms_dict = dict()
key = ''
- f = open(filename)
+ f.seek(0)
for line in f.readlines():
words = line.split()
if len(words) == 0:
@@ -545,14 +545,13 @@ def parse_stats_file(filename):
else:
new += [ line.strip() ]
ms_dict[key] = new
- f.close
return ms_dict
def mountstats_command(args):
"""Mountstats command
"""
- mountstats = parse_stats_file('/proc/self/mountstats')
+ mountstats = parse_stats_file(args.infile)
for mp in args.mountpoints:
if mp not in mountstats:
@@ -579,6 +578,8 @@ def mountstats_command(args):
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
+ args.infile.close()
+
def nfsstat_command(args):
return
@@ -597,7 +598,7 @@ def print_iostat_summary(old, new, devices, time):
def iostat_command(args):
"""iostat-like command for NFS mount points
"""
- mountstats = parse_stats_file('/proc/self/mountstats')
+ mountstats = parse_stats_file(args.infile)
devices = args.mountpoints
# make certain devices contains only NFS mount points
@@ -605,9 +606,12 @@ def iostat_command(args):
check = []
for device in devices:
stats = DeviceData()
- stats.parse_stats(mountstats[device])
- if stats.is_nfs_mountpoint():
- check += [device]
+ try:
+ stats.parse_stats(mountstats[device])
+ if stats.is_nfs_mountpoint():
+ check += [device]
+ except KeyError:
+ continue
devices = check
else:
for device, descr in mountstats.items():
@@ -633,7 +637,7 @@ def iostat_command(args):
old_mountstats = mountstats
time.sleep(args.interval)
sample_time = args.interval
- mountstats = parse_stats_file('/proc/self/mountstats')
+ mountstats = parse_stats_file(args.infile)
count -= 1
else:
while True:
@@ -641,7 +645,9 @@ def iostat_command(args):
old_mountstats = mountstats
time.sleep(args.interval)
sample_time = args.interval
- mountstats = parse_stats_file('/proc/self/mountstats')
+ mountstats = parse_stats_file(args.infile)
+
+ args.infile.close()
class ICMAction(argparse.Action):
"""Custom action to deal with interval, count, and mountpoints.
@@ -660,6 +666,8 @@ class ICMAction(argparse.Action):
def _handle_one(self, namespace, value):
try:
intval = int(value)
+ if namespace.infile.name != '/proc/self/mountstats':
+ raise argparse.ArgumentError(self, "not allowed with argument -f/--file")
self._handle_int(namespace, intval)
except ValueError:
namespace.mountpoints.append(value)
@@ -675,6 +683,9 @@ class ICMAction(argparse.Action):
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + Mountstats_version)
+ parser.add_argument('-f', '--file', default=open('/proc/self/mountstats', 'r'),
+ type=argparse.FileType('r'), dest='infile',
+ help='Read stats from %(dest)s instead of /proc/self/mountstats')
prog = os.path.basename(sys.argv[0])
--
1.9.3
Add support for -S/--since to the mountstats and ms-iostat commands to
display the delta between a previous copy of /proc/self/mountstats and
the current /proc/self/mountstats file. Can be combined with the -f
option to show the statistics between two different points in time.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 50 +++++++++++++++++++++++++++++++-----------
1 file changed, 37 insertions(+), 13 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index ddaa6c4..fac7853 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -548,11 +548,28 @@ def parse_stats_file(f):
return ms_dict
+def print_mountstats(stats, nfs_only, rpc_only):
+ if nfs_only:
+ stats.display_nfs_options()
+ stats.display_nfs_events()
+ stats.display_nfs_bytes()
+ elif rpc_only:
+ stats.display_rpc_generic_stats()
+ stats.display_rpc_op_stats()
+ else:
+ stats.display_nfs_options()
+ stats.display_nfs_bytes()
+ stats.display_rpc_generic_stats()
+ stats.display_rpc_op_stats()
+
def mountstats_command(args):
"""Mountstats command
"""
mountstats = parse_stats_file(args.infile)
+ if args.since:
+ old_mountstats = parse_stats_file(args.since)
+
for mp in args.mountpoints:
if mp not in mountstats:
print('Statistics for mount point %s not found' % mp)
@@ -565,20 +582,19 @@ def mountstats_command(args):
print('Mount point %s exists but is not an NFS mount' % mp)
continue
- if args.nfs_only:
- stats.display_nfs_options()
- stats.display_nfs_events()
- stats.display_nfs_bytes()
- elif args.rpc_only:
- stats.display_rpc_generic_stats()
- stats.display_rpc_op_stats()
+ if not args.since:
+ print_mountstats(stats, args.nfs_only, args.rpc_only)
+ elif args.since and mp not in old_mountstats:
+ print_mountstats(stats, args.nfs_only, args.rpc_only)
else:
- stats.display_nfs_options()
- stats.display_nfs_bytes()
- stats.display_rpc_generic_stats()
- stats.display_rpc_op_stats()
+ old_stats = DeviceData()
+ old_stats.parse_stats(old_mountstats[mp])
+ diff_stats = stats.compare_iostats(old_stats)
+ print_mountstats(diff_stats, args.nfs_only, args.rpc_only)
args.infile.close()
+ if args.since:
+ args.since.close()
def nfsstat_command(args):
return
@@ -601,6 +617,11 @@ def iostat_command(args):
mountstats = parse_stats_file(args.infile)
devices = args.mountpoints
+ if args.since:
+ old_mountstats = parse_stats_file(args.since)
+ else:
+ old_mountstats = None
+
# make certain devices contains only NFS mount points
if len(devices) > 0:
check = []
@@ -623,7 +644,6 @@ def iostat_command(args):
print('No NFS mount points were found')
return
- old_mountstats = None
sample_time = 0
if args.interval is None:
@@ -648,6 +668,8 @@ def iostat_command(args):
mountstats = parse_stats_file(args.infile)
args.infile.close()
+ if args.since:
+ args.since.close()
class ICMAction(argparse.Action):
"""Custom action to deal with interval, count, and mountpoints.
@@ -667,7 +689,7 @@ class ICMAction(argparse.Action):
try:
intval = int(value)
if namespace.infile.name != '/proc/self/mountstats':
- raise argparse.ArgumentError(self, "not allowed with argument -f/--file")
+ raise argparse.ArgumentError(self, "not allowed with argument -f/--file or -S/--since")
self._handle_int(namespace, intval)
except ValueError:
namespace.mountpoints.append(value)
@@ -686,6 +708,8 @@ def main():
parser.add_argument('-f', '--file', default=open('/proc/self/mountstats', 'r'),
type=argparse.FileType('r'), dest='infile',
help='Read stats from %(dest)s instead of /proc/self/mountstats')
+ parser.add_argument('-S', '--since', type=argparse.FileType('r'),
+ help='Show difference between current stats and those in SINCE')
prog = os.path.basename(sys.argv[0])
--
1.9.3
If __parse_nfs_line is is called on a line that has 'fstype nfsd', it'll
raise an IndexError trying to parse a nonexistent statvers entry.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index fac7853..26237a8 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -215,13 +215,13 @@ class DeviceData:
self.__nfs_data['export'] = words[1]
self.__nfs_data['mountpoint'] = words[4]
self.__nfs_data['fstype'] = words[7]
- if words[7].find('nfs') != -1:
+ if words[7].find('nfs') != -1 and words[7] != 'nfsd':
self.__nfs_data['statvers'] = words[8]
elif 'nfs' in words or 'nfs4' in words:
self.__nfs_data['export'] = words[0]
self.__nfs_data['mountpoint'] = words[3]
self.__nfs_data['fstype'] = words[6]
- if words[6].find('nfs') != -1:
+ if words[6].find('nfs') != -1 and words[6] != 'nfsd':
self.__nfs_data['statvers'] = words[7]
elif words[0] == 'age:':
self.__nfs_data['age'] = int(words[1])
--
1.9.3
Allow the mountstats command to take a variable number of mountpoints
(including none, in which case it will print stats for all NFS
mountpoints it finds).
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 48 ++++++++++++++++++++++++++++++------------
1 file changed, 34 insertions(+), 14 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 26237a8..d09096f 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -305,11 +305,14 @@ class DeviceData:
return True
return False
+ def display_stats_header(self):
+ print('Stats for %s mounted on %s:' % \
+ (self.__nfs_data['export'], self.__nfs_data['mountpoint']))
+
def display_nfs_options(self):
"""Pretty-print the NFS options
"""
- print('Stats for %s mounted on %s:' % \
- (self.__nfs_data['export'], self.__nfs_data['mountpoint']))
+ self.display_stats_header()
print(' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions']))
print(' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities']))
@@ -554,6 +557,7 @@ def print_mountstats(stats, nfs_only, rpc_only):
stats.display_nfs_events()
stats.display_nfs_bytes()
elif rpc_only:
+ stats.display_stats_header()
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
else:
@@ -561,27 +565,42 @@ def print_mountstats(stats, nfs_only, rpc_only):
stats.display_nfs_bytes()
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
+ print()
def mountstats_command(args):
"""Mountstats command
"""
mountstats = parse_stats_file(args.infile)
+ mountpoints = args.mountpoints
+
+ # make certain devices contains only NFS mount points
+ if len(mountpoints) > 0:
+ check = []
+ for device in mountpoints:
+ stats = DeviceData()
+ try:
+ stats.parse_stats(mountstats[device])
+ if stats.is_nfs_mountpoint():
+ check += [device]
+ except KeyError:
+ continue
+ mountpoints = check
+ else:
+ for device, descr in mountstats.items():
+ stats = DeviceData()
+ stats.parse_stats(descr)
+ if stats.is_nfs_mountpoint():
+ mountpoints += [device]
+ if len(mountpoints) == 0:
+ print('No NFS mount points were found')
+ return
if args.since:
old_mountstats = parse_stats_file(args.since)
- for mp in args.mountpoints:
- if mp not in mountstats:
- print('Statistics for mount point %s not found' % mp)
- continue
-
+ for mp in mountpoints:
stats = DeviceData()
stats.parse_stats(mountstats[mp])
-
- if not stats.is_nfs_mountpoint():
- print('Mount point %s exists but is not an NFS mount' % mp)
- continue
-
if not args.since:
print_mountstats(stats, args.nfs_only, args.rpc_only)
elif args.since and mp not in old_mountstats:
@@ -722,8 +741,9 @@ def main():
help='Display only the NFS statistics')
group.add_argument('-r', '--rpc', action='store_true', dest='rpc_only',
help='Display only the RPC statistics')
- mountstats_parser.add_argument('mountpoints', nargs='+', metavar='mountpoint',
- help='Display statistics for this mountpoint. More than one may be specified.')
+ mountstats_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint',
+ help='Display statistics for this mountpoint. More than one may be specified.'
+ 'If absent, statistics for all NFS mountpoints will be generated.')
mountstats_parser.set_defaults(func=mountstats_command)
args = mountstats_parser.parse_args()
--
1.9.3
This option displays the mountstats in raw format (i.e. in the same
format as /proc/self/mountstats). It's intended to be used mainly with
the -S/--since option.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 47 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 43 insertions(+), 4 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index d09096f..a1ce8f4 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -305,6 +305,41 @@ class DeviceData:
return True
return False
+ def display_raw_stats(self):
+ """Prints out stats in the same format as /proc/self/mountstats
+ """
+ print('device %s mounted on %s with fstype %s %s' % \
+ (self.__nfs_data['export'], self.__nfs_data['mountpoint'], \
+ self.__nfs_data['fstype'], self.__nfs_data['statvers']))
+ print('\topts:\t%s' % ','.join(self.__nfs_data['mountoptions']))
+ print('\tage:\t%d' % self.__nfs_data['age'])
+ print('\tcaps:\t%s' % ','.join(self.__nfs_data['servercapabilities']))
+ print('\tsec:\tflavor=%d,pseudoflavor=%d' % (self.__nfs_data['flavor'], \
+ self.__nfs_data['pseudoflavor']))
+ print('\tevents:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsEventCounters]))
+ print('\tbytes:\t%s' % " ".join([str(self.__nfs_data[key]) for key in NfsByteCounters]))
+ print('\tRPC iostats version: %1.1f p/v: %s (nfs)' % (self.__rpc_data['statsvers'], \
+ self.__rpc_data['programversion']))
+ if self.__rpc_data['protocol'] == 'udp':
+ print('\txprt:\tudp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtUdpCounters]))
+ elif self.__rpc_data['protocol'] == 'tcp':
+ print('\txprt:\ttcp %s' % " ".join([str(self.__rpc_data[key]) for key in XprtTcpCounters]))
+ elif self.__rpc_data['protocol'] == 'rdma':
+ print('\txprt:\trdma %s' % " ".join([str(self.__rpc_data[key]) for key in XprtRdmaCounters]))
+ else:
+ raise Exception('Unknown RPC transport protocol %s' % self.__rpc_data['protocol'])
+ print('\tper-op statistics')
+ prog, vers = self.__rpc_data['programversion'].split('/')
+ if vers == '3':
+ for op in Nfsv3ops:
+ print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
+ elif vers == '4':
+ for op in Nfsv4ops:
+ print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
+ else:
+ print('\tnot implemented for version %d' % vers)
+ print()
+
def display_stats_header(self):
print('Stats for %s mounted on %s:' % \
(self.__nfs_data['export'], self.__nfs_data['mountpoint']))
@@ -551,7 +586,7 @@ def parse_stats_file(f):
return ms_dict
-def print_mountstats(stats, nfs_only, rpc_only):
+def print_mountstats(stats, nfs_only, rpc_only, raw):
if nfs_only:
stats.display_nfs_options()
stats.display_nfs_events()
@@ -560,6 +595,8 @@ def print_mountstats(stats, nfs_only, rpc_only):
stats.display_stats_header()
stats.display_rpc_generic_stats()
stats.display_rpc_op_stats()
+ elif raw:
+ stats.display_raw_stats()
else:
stats.display_nfs_options()
stats.display_nfs_bytes()
@@ -602,14 +639,14 @@ def mountstats_command(args):
stats = DeviceData()
stats.parse_stats(mountstats[mp])
if not args.since:
- print_mountstats(stats, args.nfs_only, args.rpc_only)
+ print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw)
elif args.since and mp not in old_mountstats:
- print_mountstats(stats, args.nfs_only, args.rpc_only)
+ print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw)
else:
old_stats = DeviceData()
old_stats.parse_stats(old_mountstats[mp])
diff_stats = stats.compare_iostats(old_stats)
- print_mountstats(diff_stats, args.nfs_only, args.rpc_only)
+ print_mountstats(diff_stats, args.nfs_only, args.rpc_only, args.raw)
args.infile.close()
if args.since:
@@ -741,6 +778,8 @@ def main():
help='Display only the NFS statistics')
group.add_argument('-r', '--rpc', action='store_true', dest='rpc_only',
help='Display only the RPC statistics')
+ group.add_argument('-R', '--raw', action='store_true',
+ help='Display only the raw statistics')
mountstats_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint',
help='Display statistics for this mountpoint. More than one may be specified.'
'If absent, statistics for all NFS mountpoints will be generated.')
--
1.9.3
Displays nfssstat-like statistics (client statistics only).
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.py | 153 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 150 insertions(+), 3 deletions(-)
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index a1ce8f4..b886e5a 100644
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -25,8 +25,9 @@ MA 02110-1301 USA
import sys, os, time
import argparse
+import operator
-Mountstats_version = '0.2'
+Mountstats_version = '0.3'
def difference(x, y):
"""Used for a map() function
@@ -305,6 +306,11 @@ class DeviceData:
return True
return False
+ def nfs_version(self):
+ if self.is_nfs_mountpoint():
+ prog, vers = self.__rpc_data['programversion'].split('/')
+ return int(vers)
+
def display_raw_stats(self):
"""Prints out stats in the same format as /proc/self/mountstats
"""
@@ -448,6 +454,50 @@ class DeviceData:
print('\ttotal execute time: %f (milliseconds)' % \
(float(stats[7]) / count))
+ def client_rpc_stats(self):
+ """Tally high-level rpc stats for the nfsstat command
+ """
+ sends = 0
+ trans = 0
+ authrefrsh = 0
+ for op in self.__rpc_data['ops']:
+ sends += self.__rpc_data[op][0]
+ trans += self.__rpc_data[op][1]
+ retrans = trans - sends
+ # authrefresh stats don't actually get captured in
+ # /proc/self/mountstats, so we fudge it here
+ authrefrsh = sends
+ return (sends, trans, authrefrsh)
+
+ def display_nfsstat_stats(self):
+ """Pretty-print nfsstat-style stats
+ """
+ sends = 0
+ for op in self.__rpc_data['ops']:
+ sends += self.__rpc_data[op][0]
+ if sends == 0:
+ return
+ print()
+ vers = self.nfs_version()
+ print('Client nfs v%d' % vers)
+ info = []
+ for op in self.__rpc_data['ops']:
+ print('%-13s' % str.lower(op)[:12], end='')
+ count = self.__rpc_data[op][0]
+ pct = (count * 100) / sends
+ info.append((count, pct))
+ if (self.__rpc_data['ops'].index(op) + 1) % 6 == 0:
+ print()
+ for (count, pct) in info:
+ print('%-8u%3u%% ' % (count, pct), end='')
+ print()
+ info = []
+ print()
+ if len(info) > 0:
+ for (count, pct) in info:
+ print('%-8u%3u%% ' % (count, pct), end='')
+ print()
+
def compare_iostats(self, old_stats):
"""Return the difference between two sets of stats
"""
@@ -487,6 +537,27 @@ class DeviceData:
result.__nfs_data[key] -= old_stats.__nfs_data[key]
return result
+ def setup_accumulator(self, ops):
+ """Initialize a DeviceData instance to tally stats for all mountpoints
+ with the same major version. This is for the nfsstat command.
+ """
+ if ops == Nfsv3ops:
+ self.__rpc_data['programversion'] = '100003/3'
+ self.__nfs_data['fstype'] = 'nfs'
+ elif ops == Nfsv4ops:
+ self.__rpc_data['programversion'] = '100003/4'
+ self.__nfs_data['fstype'] = 'nfs4'
+ self.__rpc_data['ops'] = ops
+ for op in ops:
+ self.__rpc_data[op] = [0 for i in range(8)]
+
+ def accumulate_iostats(self, new_stats):
+ """Accumulate counters from all RPC op buckets in new_stats. This is
+ for the nfsstat command.
+ """
+ for op in new_stats.__rpc_data['ops']:
+ self.__rpc_data[op] = list(map(operator.add, self.__rpc_data[op], new_stats.__rpc_data[op]))
+
def __print_rpc_op_stats(self, op, sample_time):
"""Print generic stats for one RPC op
"""
@@ -653,7 +724,78 @@ def mountstats_command(args):
args.since.close()
def nfsstat_command(args):
- return
+ """nfsstat-like command for NFS mount points
+ """
+ mountstats = parse_stats_file(args.infile)
+ mountpoints = args.mountpoints
+ v3stats = DeviceData()
+ v3stats.setup_accumulator(Nfsv3ops)
+ v4stats = DeviceData()
+ v4stats.setup_accumulator(Nfsv4ops)
+
+ # ensure stats get printed if neither v3 nor v4 was specified
+ if args.show_v3 or args.show_v4:
+ show_both = False
+ else:
+ show_both = True
+
+ # make certain devices contains only NFS mount points
+ if len(mountpoints) > 0:
+ check = []
+ for device in mountpoints:
+ stats = DeviceData()
+ try:
+ stats.parse_stats(mountstats[device])
+ if stats.is_nfs_mountpoint():
+ check += [device]
+ except KeyError:
+ continue
+ mountpoints = check
+ else:
+ for device, descr in mountstats.items():
+ stats = DeviceData()
+ stats.parse_stats(descr)
+ if stats.is_nfs_mountpoint():
+ mountpoints += [device]
+ if len(mountpoints) == 0:
+ print('No NFS mount points were found')
+ return
+
+ if args.since:
+ old_mountstats = parse_stats_file(args.since)
+
+ for mp in mountpoints:
+ stats = DeviceData()
+ stats.parse_stats(mountstats[mp])
+ vers = stats.nfs_version()
+
+ if not args.since:
+ acc_stats = stats
+ elif args.since and mp not in old_mountstats:
+ acc_stats = stats
+ else:
+ old_stats = DeviceData()
+ old_stats.parse_stats(old_mountstats[mp])
+ acc_stats = stats.compare_iostats(old_stats)
+
+ if vers == 3 and (show_both or args.show_v3):
+ v3stats.accumulate_iostats(acc_stats)
+ elif vers == 4 and (show_both or args.show_v4):
+ v4stats.accumulate_iostats(acc_stats)
+
+ sends, retrans, authrefrsh = map(operator.add, v3stats.client_rpc_stats(), v4stats.client_rpc_stats())
+ print('Client rpc stats:')
+ print('calls retrans authrefrsh')
+ print('%-11u%-11u%-11u' % (sends, retrans, authrefrsh))
+
+ if show_both or args.show_v3:
+ v3stats.display_nfsstat_stats()
+ if show_both or args.show_v4:
+ v4stats.display_nfsstat_stats()
+
+ args.infile.close()
+ if args.since:
+ args.since.close()
def print_iostat_summary(old, new, devices, time):
for device in devices:
@@ -790,9 +932,14 @@ def main():
nfsstat_parser = argparse.ArgumentParser(
description='nfsstat-like program that uses NFS client per-mount statistics.',
parents=[parser])
+ nfsstat_parser.add_argument('-3', action='store_true', dest='show_v3',
+ help='Show NFS version 3 statistics')
+ nfsstat_parser.add_argument('-4', action='store_true', dest='show_v4',
+ help='Show NFS version 4 statistics')
+ nfsstat_parser.add_argument('mountpoints', nargs='*', metavar='mountpoint',
+ help='Display statistics for this mountpoint')
nfsstat_parser.set_defaults(func=nfsstat_command)
args = nfsstat_parser.parse_args()
- nfsstat_parser.print_help()
elif prog == 'ms-iostat':
iostat_parser = argparse.ArgumentParser(
--
1.9.3
Updated the mounstats(8) man page to match the --help output.
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/mountstats.man | 45 ++++++++++++++++++++++++++++++++---------
1 file changed, 35 insertions(+), 10 deletions(-)
diff --git a/tools/mountstats/mountstats.man b/tools/mountstats/mountstats.man
index 0de31b7..1269a8c 100644
--- a/tools/mountstats/mountstats.man
+++ b/tools/mountstats/mountstats.man
@@ -1,32 +1,57 @@
.\"
.\" mountstats(8)
.\"
-.TH mountstats 8 "15 Apr 2010"
+.TH mountstats 8 "25 Nov 2014"
.SH NAME
mountstats \- Displays NFS client per-mount statistics
.SH SYNOPSIS
-.BI "mountstats ["<options> "] " <mount_point> " [ " <mount_point> "]"
+.BI "mountstats [" OPTIONS "] [" mountpoint " [" mountpoint "] " ... " ]"
.SH DESCRIPTION
The
.B mountstats
command displays NFS client statisitics on each given
-.I <mount_point>
+.IR mountpoint .
+.P
+If no
+.I mountpoint
+is given, statistics will be displayed for all NFS mountpoints on the client.
.SH OPTIONS
.TP
-.B " \-\-nfs
-display only the NFS statistics
+.B \-h, \-\-help
+show the help message and exit
.TP
-.B " \-\-rpc
-display only the RPC statistics
+.B \-v, \-\-version
+show program's version number and exit
.TP
-.B " \-\-version
-display the version of this command
+\fB\-f \fIINFILE\fR, \fB\-\-file \fIINFILE
+Read stats from
+.I INFILE
+instead of /proc/self/mountstats
+.TP
+\fB\-S \fISINCE\fR, \fB\-\-since \fISINCE
+Show difference between current stats and those in
+.I SINCE
+.TP
+.B \-n, \-\-nfs
+Display only the NFS statistics
+.TP
+.B \-r, \-\-rpc
+Display only the RPC statistics
+.TP
+.B \-R, \-\-raw
+Display only the raw statistics. This is intended for use with the
+.B \-f/\-\-file
+and
+.B \-S/\-\-since
+options.
.SH FILES
.TP
.B /proc/self/mountstats
.SH SEE ALSO
.BR iostat (8),
.BR nfsiostat (8),
-.BR nfsstat(8)
+.BR nfsstat (8),
+.BR ms-iostat (8),
+.BR ms-nfsstat (8)
.SH AUTHOR
Chuck Lever <[email protected]>
--
1.9.3
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/ms-iostat.man | 64 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 tools/mountstats/ms-iostat.man
diff --git a/tools/mountstats/ms-iostat.man b/tools/mountstats/ms-iostat.man
new file mode 100644
index 0000000..73002c2
--- /dev/null
+++ b/tools/mountstats/ms-iostat.man
@@ -0,0 +1,64 @@
+.\"
+.\" ms-iostat(8)
+.\"
+.TH ms-iostat 8 "25 Nov 2014"
+.SH NAME
+ms-iostat \- iostat-like program to display NFS client per-mount statistics
+.SH SYNOPSIS
+.BI "ms-iostat [" OPTIONS "] [" interval "] [" count "] [" mountpoint " [" mountpoint "] " ... " ]"
+.SH DESCRIPTION
+The
+.B ms-iostat
+command is an iostat-like program to display NFS client per-mount statistics.
+.P
+The
+.I interval
+parameter 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.
+.P
+If the
+.I count
+parameter is specified, the value of
+.I count
+determines the number of reports generated at
+.I interval
+seconds apart. If the
+.I interval
+parameter is specified without the
+.I count
+parameter, the command generates reports continuously.
+.P
+If one or more
+.I mountpoint
+names are specified, statistics for only these mount points will be displayed. Otherwise, all NFS mount points on the client are listed.
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+show the help message and exit
+.TP
+.B \-v, \-\-version
+show program's version number and exit
+.TP
+\fB\-f \fIINFILE\fR, \fB\-\-file \fIINFILE
+Read stats from
+.I INFILE
+instead of /proc/self/mountstats. This may not be used with the
+.IR interval " or " count
+options.
+.TP
+\fB\-S \fISINCE\fR, \fB\-\-since \fISINCE
+Show difference between current stats and those in
+.IR SINCE .
+This may not be used with the
+.IR interval " or " count
+options.
+.SH FILES
+.TP
+.B /proc/self/mountstats
+.SH SEE ALSO
+.BR iostat (8),
+.BR nfsiostat (8),
+.BR nfsstat (8),
+.BR ms-iostat (8),
+.BR ms-nfsstat (8)
+.SH AUTHOR
+Chuck Lever <[email protected]>
--
1.9.3
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/ms-nfsstat.man | 49 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 tools/mountstats/ms-nfsstat.man
diff --git a/tools/mountstats/ms-nfsstat.man b/tools/mountstats/ms-nfsstat.man
new file mode 100644
index 0000000..53bfb30
--- /dev/null
+++ b/tools/mountstats/ms-nfsstat.man
@@ -0,0 +1,49 @@
+.\"
+.\" ms-nfsstat(8)
+.\"
+.TH ms-nfsstat 8 "25 Nov 2014"
+.SH NAME
+ms-nfsstat \- nfsstat-like program that uses NFS client per-mount statistics
+.SH SYNOPSIS
+.BI "ms-nfsstat [" OPTIONS "] [" mountpoint " [" mountpoint "] " ... " ]"
+.SH DESCRIPTION
+The
+.B ms-nfsstat
+command is an nfsstat-like program to display NFS client statistics generated from /proc/self/mountstats instead of from /proc/net/rpc/nfs.
+.P
+If one or more
+.I mountpoint
+names are specified, statistics for only these mount points will be displayed. Otherwise, statistics for all NFS mount points on the client are listed.
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+show the help message and exit
+.TP
+.B \-v, \-\-version
+show program's version number and exit
+.TP
+\fB\-f \fIINFILE\fR, \fB\-\-file \fIINFILE
+Read stats from
+.I INFILE
+instead of /proc/self/mountstats.
+.TP
+\fB\-S \fISINCE\fR, \fB\-\-since \fISINCE
+Show difference between current stats and those in
+.IR SINCE .
+.TP
+.B -3
+Show only NFS version 3 statistics. The default is to show both version 3 and version 4 statistics.
+.TP
+.B -4
+Show only NFS version 4 statistics. The default is to show both version 3 and version 4 statistics.
+.SH FILES
+.TP
+.B /proc/self/mountstats
+.SH SEE ALSO
+.BR iostat (8),
+.BR nfsiostat (8),
+.BR nfsstat (8),
+.BR mountstats (8),
+.BR ms-iostat (8)
+.SH AUTHOR
+Chuck Lever <[email protected]>
--
1.9.3
Signed-off-by: Scott Mayhew <[email protected]>
---
tools/mountstats/Makefile.am | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/mountstats/Makefile.am b/tools/mountstats/Makefile.am
index c2e9f99..05cc4e5 100644
--- a/tools/mountstats/Makefile.am
+++ b/tools/mountstats/Makefile.am
@@ -1,7 +1,7 @@
## Process this file with automake to produce Makefile.in
PYTHON_FILES = mountstats.py
-man8_MANS = mountstats.man
+man8_MANS = mountstats.man ms-iostat.man ms-nfsstat.man
EXTRA_DIST = $(man8_MANS) $(PYTHON_FILES)
@@ -9,5 +9,7 @@ all-local: $(PYTHON_FILES)
install-data-hook:
$(INSTALL) -m 755 mountstats.py $(DESTDIR)$(sbindir)/mountstats
+ ln -sf $(DESTDIR)$(sbindir)/mountstats $(DESTDIR)$(sbindir)/ms-iostat
+ ln -sf $(DESTDIR)$(sbindir)/mountstats $(DESTDIR)$(sbindir)/ms-nfsstat
MAINTAINERCLEANFILES=Makefile.in
--
1.9.3
Hi Scott-
On Nov 26, 2014, at 2:13 PM, Scott Mayhew <[email protected]> wrote:
> Changes since v1:
>
> - Changed the parsing to use the argparse module instead of optparse
>
> - Added per-mountpoint headers to the output of 'mountstats --rpc'
>
> - Revamped the ms-nfsstat command to take a variable list of mountpoints
> (so now mountstats, ms-iostat, and ms-nfsstat all behave in the same
> manner). Added -3 and -4 options which behave the same way they do in
> nfsstat.c. The output doesn't still doesn't match up 100% with that
> of nfsstat.c though (I'm just taking the first 12 characters of the
> operation name and converting them to lowercase to use as labels, while
> nfsstat.c defines its own labels, but also it looks to me like nfsstat.c
> is missing some operations altoegether).
>
> - Updated the man page for mountstats and added man pages for ms-iostat
> and ms-nfsstat.
>
> These are also available in the 'mountstats-improvements-v2' branch at
> https://github.com/scottmayhew/nfs-utils.git
Tried this out. The features I normally use seem to be
working OK.
It appears that python-argparse is not installed by
default on some systems. That would have to be added
as an RPM dependency. No big deal, but it might be
nice to add exception processing in the scripts to deal
with not finding it (print a polite error message
instead of spewing a backtrace).
A couple of nits with the man pages:
Filenames (like /proc/self/mountstats) are typically
italicized.
In the synopses, the closing square brace is unmatched.
Either use just an ellipsis, or add a matching opening
brace. Have a look at how this is done in other man
pages.
At this point I wouldn?t call it a full ?review? or
?test? but I?ll keep playing with it. I don?t have any
objection to merging these, and you could address my
nit-picks with subsequent patches if you want.
> Original cover letter:
>
> -------------------8<------------------
>
> The following patches add a couple of enhancements to mountstats.py. I
> also fixed a few bugs I encountered along the way. Highlights include:
>
> - added support for -f/--file to allow stats to be parsed from an
> aritrary input file instead of /proc/self/mountstats
>
> - added support for -S/--since to show just the changes that have
> occurred between the current and a previous set of statisics (works
> with and without the -f option)
>
> - added support for -R/--raw to generate 'raw' statistics (i.e. in the
> same format as /proc/self/mountstats). It's intended to be used with
> the -f and -S options.
>
> - implemented the ms-nfsstat command to generate client-side
> nfsstat-like statisics (only works with a single mountpoint)
>
> My motivation for these changes was so that I could take various copies
> of /proc/self/mountstats and massage them into data that I could feed
> into the 'report' option of Dros's nfsometer tool for scenarios where
> it's not feasible to run nfsometer itself (e.g. systems where we can't
> start with an 'idle' state (i.e. no NFS filesystems initially
> mounted), systems with multiple NFS filesystems mounted, and workloads
> that can't easily be boiled down into an nfsometer workload file or run
> via the custom workload environment variables).
>
> Scott Mayhew (17):
> mountstats: Fix up NFS event counters
> mountstats: Add lists of various counters
> mountstats: Refactor __parse_nfs_line and __parse_rpc_line
> mountstats: Refactor compare_iostats
> mountstats: Convert existing option parsing to use the argparse module
> mountstats: Make ms-iostat output match that of nfs-iostat.py
> mountstats: Make print_iostat_summary handle newly appearing mounts
> mountstats: Add support for -f/--file to the mountstats and ms-iostat
> commands
> mountstats: Add support for -S/--since to the mountstats and ms-iostat
> commands
> mountstats: Fix IndexError in __parse_nfs_line
> mountstats: Allow mountstats_command to take a variable number of
> mountpoints
> mountstats: Add support for -R/--raw to mountstats_command
> mountstats: Implement nfsstat_command
> mountstats: Updated the mountstats(8) man page.
> mountstats: Added man page for ms-iostat(8)
> mountstats: Added man page for ms-nfsstat(8)
> mountstats: add ms-iostat and ms-nfsstat to Makefile.am
>
> tools/mountstats/Makefile.am | 4 +-
> tools/mountstats/mountstats.man | 45 +-
> tools/mountstats/mountstats.py | 904 ++++++++++++++++++++++++++++------------
> tools/mountstats/ms-iostat.man | 64 +++
> tools/mountstats/ms-nfsstat.man | 49 +++
> 5 files changed, 777 insertions(+), 289 deletions(-)
> create mode 100644 tools/mountstats/ms-iostat.man
> create mode 100644 tools/mountstats/ms-nfsstat.man
>
> --
> 1.9.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-nfs" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Chuck Lever
chuck[dot]lever[at]oracle[dot]com