diff options
Diffstat (limited to '')
-rwxr-xr-x | tools/mountstats/mountstats.py | 1145 |
1 files changed, 1145 insertions, 0 deletions
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py new file mode 100755 index 0000000..8e129c8 --- /dev/null +++ b/tools/mountstats/mountstats.py @@ -0,0 +1,1145 @@ +#!/usr/bin/python3 +# -*- python-mode -*- +"""Parse /proc/self/mountstats and display it in human readable form +""" + +from __future__ import print_function +import datetime as datetime + +__copyright__ = """ +Copyright (C) 2005, Chuck Lever <cel@netapp.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +MA 02110-1301 USA +""" + +import sys, os, time +from operator import itemgetter, add +try: + import argparse +except ImportError: + print('%s: Failed to import argparse - make sure argparse is installed!' + % sys.argv[0]) + sys.exit(1) + +Mountstats_version = '0.3' + +def difference(x, y): + """Used for a map() function + """ + 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', + 'maxslots', + 'sendutil', + 'pendutil' +] + +XprtTcpCounters = [ + 'port', + 'bind_count', + 'connect_count', + 'connect_time', + 'idle_time', + 'rpcsends', + 'rpcreceives', + 'badxids', + 'inflightsends', + 'backlogutil', + 'maxslots', + 'sendutil', + 'pendutil' +] + +XprtRdmaCounters = [ + 'port', + 'bind_count', + 'connect_count', + 'connect_time', + 'idle_time', + 'rpcsends', + 'rpcreceives', + 'badxids', + 'inflightsends', + 'backlogutil', + 'read_segments', + 'write_segments', + 'reply_segments', + 'total_rdma_req', + 'total_rdma_rep', + 'pullup', + 'fixup', + 'hardway', + 'failed_marshal', + 'bad_reply', + 'nomsg_calls', + 'recovered_mrs', + 'orphaned_mrs', + 'allocated_mrs', + 'local_invalidates', + 'empty_sendctx_q', + 'reply_waits_for_send', +] + +Nfsv3ops = [ + 'NULL', + 'GETATTR', + 'SETATTR', + 'LOOKUP', + 'ACCESS', + 'READLINK', + 'READ', + 'WRITE', + 'CREATE', + 'MKDIR', + 'SYMLINK', + 'MKNOD', + 'REMOVE', + 'RMDIR', + 'RENAME', + 'LINK', + 'READDIR', + 'READDIRPLUS', + 'FSSTAT', + 'FSINFO', + 'PATHCONF', + 'COMMIT' +] + +# This list should be kept in-sync with the NFSPROC4_CLNT_* enum in +# include/linux/nfs4.h in the kernel. +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', + 'SEEK', + 'ALLOCATE', + 'DEALLOCATE', + 'LAYOUTSTATS', + 'CLONE', + 'COPY', + 'OFFLOAD_CANCEL', + 'LOOKUPP', + 'LAYOUTERROR', + 'COPY_NOTIFY' +] + +class DeviceData: + """DeviceData objects provide methods for parsing and displaying + data for a single mount grabbed from /proc/self/mountstats + """ + def __init__(self): + self.__nfs_data = dict() + self.__rpc_data = dict() + self.__rpc_data['ops'] = [] + + def __parse_nfs_line(self, words): + if words[0] == 'device': + self.__nfs_data['export'] = words[1] + self.__nfs_data['mountpoint'] = words[4] + self.__nfs_data['fstype'] = words[7] + 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 and words[6] != 'nfsd': + self.__nfs_data['statvers'] = words[7] + elif words[0] == 'age:': + self.__nfs_data['age'] = int(words[1]) + elif words[0] == 'opts:': + self.__nfs_data['mountoptions'] = ''.join(words[1:]).split(',') + elif words[0] == 'caps:': + self.__nfs_data['servercapabilities'] = ''.join(words[1:]).split(',') + elif words[0] == 'nfsv4:': + self.__nfs_data['nfsv4flags'] = ''.join(words[1:]).split(',') + elif words[0] == 'sec:': + keys = ''.join(words[1:]).split(',') + self.__nfs_data['flavor'] = int(keys[0].split('=')[1]) + self.__nfs_data['pseudoflavor'] = 0 + if self.__nfs_data['flavor'] == 6: + self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1]) + elif words[0] == 'events:': + 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:': + 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': + self.__rpc_data['statsvers'] = float(words[3]) + self.__rpc_data['programversion'] = words[5] + elif words[0] == 'xprt:': + self.__rpc_data['protocol'] = words[1] + if words[1] == 'udp': + i = 2 + for key in XprtUdpCounters: + if i < len(words): + self.__rpc_data[key] = int(words[i]) + i += 1 + elif words[1] == 'tcp': + i = 2 + for key in XprtTcpCounters: + if i < len(words): + self.__rpc_data[key] = int(words[i]) + i += 1 + elif words[1] == 'rdma': + i = 2 + for key in XprtRdmaCounters: + if i < len(words): + self.__rpc_data[key] = int(words[i]) + i += 1 + elif words[0] == 'per-op': + self.__rpc_data['per-op'] = words + else: + op = words[0][:-1] + self.__rpc_data['ops'] += [op] + self.__rpc_data[op] = [int(word) for word in words[1:]] + if len(self.__rpc_data[op]) < 9: + self.__rpc_data[op] += [0] + + def parse_stats(self, lines): + """Turn a list of lines from a mount stat file into a + dictionary full of stats, keyed by name + """ + found = False + for line in lines: + words = line.split() + if len(words) == 0: + continue + if (not found and words[0] != 'RPC'): + self.__parse_nfs_line(words) + continue + + found = True + self.__parse_rpc_line(words) + + def is_nfs_mountpoint(self): + """Return True if this is an NFS or NFSv4 mountpoint, + otherwise return False + """ + if self.__nfs_data['fstype'] == 'nfs': + return True + elif self.__nfs_data['fstype'] == 'nfs4': + 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 + """ + 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: + try: + print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op]))) + except KeyError: + continue + 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'])) + print() + + def display_nfs_options(self): + """Pretty-print the NFS options + """ + print(' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])) + print(' NFS mount age: %s' % datetime.timedelta(seconds = self.__nfs_data['age'])) + print(' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])) + if 'nfsv4flags' in self.__nfs_data: + print(' NFSv4 capability flags: %s' % ','.join(self.__nfs_data['nfsv4flags'])) + if 'pseudoflavor' in self.__nfs_data: + print(' NFS security flavor: %d pseudoflavor: %d' % \ + (self.__nfs_data['flavor'], self.__nfs_data['pseudoflavor'])) + else: + print(' NFS security flavor: %d' % self.__nfs_data['flavor']) + + def display_nfs_events(self): + """Pretty-print the NFS event counters + """ + print() + print('Cache events:') + print(' data cache invalidated %d times' % self.__nfs_data['datainvalidates']) + print(' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates']) + print() + print('VFS calls:') + print(' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates']) + print(' VFS requested %d dentry revalidations' % self.__nfs_data['dentryrevalidates']) + print() + print(' VFS called nfs_readdir() %d times' % self.__nfs_data['vfsreaddir']) + print(' VFS called nfs_lookup() %d times' % self.__nfs_data['vfslookup']) + print(' VFS called nfs_permission() %d times' % self.__nfs_data['vfspermission']) + print(' VFS called nfs_file_open() %d times' % self.__nfs_data['vfsopen']) + print(' VFS called nfs_file_flush() %d times' % self.__nfs_data['vfsflush']) + print(' VFS called nfs_lock() %d times' % self.__nfs_data['vfslock']) + print(' VFS called nfs_fsync() %d times' % self.__nfs_data['vfsfsync']) + print(' VFS called nfs_file_release() %d times' % self.__nfs_data['vfsrelease']) + print() + print('VM calls:') + print(' VFS called nfs_readpage() %d times' % self.__nfs_data['vfsreadpage']) + print(' VFS called nfs_readpages() %d times' % self.__nfs_data['vfsreadpages']) + print(' VFS called nfs_writepage() %d times' % self.__nfs_data['vfswritepage']) + print(' VFS called nfs_writepages() %d times' % self.__nfs_data['vfswritepages']) + print() + print('Generic NFS counters:') + print(' File size changing operations:') + print(' truncating SETATTRs: %d extending WRITEs: %d' % \ + (self.__nfs_data['setattrtrunc'], self.__nfs_data['extendwrite'])) + print(' %d silly renames' % self.__nfs_data['sillyrenames']) + print(' short reads: %d short writes: %d' % \ + (self.__nfs_data['shortreads'], self.__nfs_data['shortwrites'])) + print(' NFSERR_DELAYs from server: %d' % self.__nfs_data['delay']) + print(' pNFS READs: %d' % self.__nfs_data['pnfsreads']) + print(' pNFS WRITEs: %d' % self.__nfs_data['pnfswrites']) + + def display_nfs_bytes(self): + """Pretty-print the NFS event counters + """ + print() + print('NFS byte counts:') + print(' applications read %d bytes via read(2)' % self.__nfs_data['normalreadbytes']) + print(' applications wrote %d bytes via write(2)' % self.__nfs_data['normalwritebytes']) + print(' applications read %d bytes via O_DIRECT read(2)' % self.__nfs_data['directreadbytes']) + print(' applications wrote %d bytes via O_DIRECT write(2)' % self.__nfs_data['directwritebytes']) + print(' client read %d bytes via NFS READ' % self.__nfs_data['serverreadbytes']) + print(' client wrote %d bytes via NFS WRITE' % self.__nfs_data['serverwritebytes']) + + def display_rpc_generic_stats(self): + """Pretty-print the generic RPC stats + """ + sends = self.__rpc_data['rpcsends'] + + print('RPC statistics:') + + print(' %d RPC requests sent, %d RPC replies received (%d XIDs not found)' % \ + (sends, self.__rpc_data['rpcreceives'], self.__rpc_data['badxids'])) + if sends != 0: + print(' average backlog queue length: %d' % \ + (float(self.__rpc_data['backlogutil']) / sends)) + + def display_rpc_op_stats(self): + """Pretty-print the per-op stats + """ + sends = self.__rpc_data['rpcsends'] + + allstats = [] + for op in self.__rpc_data['ops']: + allstats.append([op] + self.__rpc_data[op]) + + print() + for stats in sorted(allstats, key=itemgetter(1), reverse=True): + count = stats[1] + if count != 0: + print('%s:' % stats[0]) + ops_pcnt = 0 + if sends != 0: + ops_pcnt = (count * 100) / sends + print('\t%d ops (%d%%)' % \ + (count, ops_pcnt), end=' ') + retrans = stats[2] - count + if retrans != 0: + print('\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)), end=' ') + print('\t%d major timeouts' % stats[3], end='') + if len(stats) >= 10 and stats[9] != 0: + print('\t%d errors (%d%%)' % (stats[9], ((stats[9] * 100) / count))) + else: + print('') + print('\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \ + (stats[4] / count, stats[5] / count)) + print('\tbacklog wait: %f' % (float(stats[6]) / count), end=' ') + print('\tRTT: %f' % (float(stats[7]) / count), end=' ') + print('\ttotal execute time: %f (milliseconds)' % \ + (float(stats[8]) / 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, retrans, 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 + """ + 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(): + result.__nfs_data[key] = value + for key, value in self.__rpc_data.items(): + result.__rpc_data[key] = value + + # compute the difference of each item in the list + # note the copy loop above does not copy the lists, just + # the reference to them. so we build new lists here + # for the result object. + for op in result.__rpc_data['ops']: + try: + result.__rpc_data[op] = list(map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])) + except KeyError: + continue + + # 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 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(9)] + + 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']: + try: + self.__rpc_data[op] = list(map(add, self.__rpc_data[op], new_stats.__rpc_data[op])) + except KeyError: + continue + + 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 + queued_for = float(rpc_stats[5]) + rtt = float(rpc_stats[6]) + exe = float(rpc_stats[7]) + if len(rpc_stats) >= 9: + errs = int(rpc_stats[8]) + + # 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 + queued_for_per_op = queued_for / ops + if len(rpc_stats) >= 9: + errs_percent = (errs * 100) / ops + else: + kb_per_op = 0.0 + retrans_percent = 0.0 + rtt_per_op = 0.0 + exe_per_op = 0.0 + queued_for_per_op = 0.0 + errs_percent = 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'), end='') + print(format('avg queue (ms)', '>16s'), end='') + if len(rpc_stats) >= 9: + print(format('errors', '>16s'), end='') + print() + + 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'), end='') + print(format(queued_for_per_op, '>16.3f'), end='') + if len(rpc_stats) >= 9: + errors = '{0:>10.0f} ({1:>3.1f}%)'.format(errs, errs_percent).strip() + print(format(errors, '>16'), end='') + print() + + 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(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 display_xprt_stats(self): + """Pretty-print the xprt statistics + """ + if self.__rpc_data['protocol'] == 'udp': + print('\tTransport protocol: udp') + print('\tSource port: %d' % self.__rpc_data['port']) + print('\tBind count: %d' % self.__rpc_data['bind_count']) + print('\tRPC requests: %d' % self.__rpc_data['rpcsends']) + print('\tRPC replies: %d' % self.__rpc_data['rpcreceives']) + print('\tXIDs not found: %d' % self.__rpc_data['badxids']) + print('\tMax slots: %d' % self.__rpc_data['maxslots']) + if self.__rpc_data['rpcsends'] != 0: + print('\tAvg backlog length: %d' % \ + (float(self.__rpc_data['backlogutil']) / self.__rpc_data['rpcsends'])) + print('\tAvg send queue length: %d' % \ + (float(self.__rpc_data['sendutil']) / self.__rpc_data['rpcsends'])) + print('\tAvg pending queue length: %d' % \ + (float(self.__rpc_data['pendutil']) / self.__rpc_data['rpcsends'])) + elif self.__rpc_data['protocol'] == 'tcp': + print('\tTransport protocol: tcp') + print('\tSource port: %d' % self.__rpc_data['port']) + print('\tBind count: %d' % self.__rpc_data['bind_count']) + print('\tConnect count: %d' % self.__rpc_data['connect_count']) + print('\tConnect time: %d seconds' % self.__rpc_data['connect_time']) + print('\tIdle time: %d seconds' % self.__rpc_data['idle_time']) + print('\tRPC requests: %d' % self.__rpc_data['rpcsends']) + print('\tRPC replies: %d' % self.__rpc_data['rpcreceives']) + print('\tXIDs not found: %d' % self.__rpc_data['badxids']) + print('\tMax slots: %d' % self.__rpc_data['maxslots']) + if self.__rpc_data['rpcsends'] != 0: + print('\tAvg backlog length: %d' % \ + (float(self.__rpc_data['backlogutil']) / self.__rpc_data['rpcsends'])) + print('\tAvg send queue length: %d' % \ + (float(self.__rpc_data['sendutil']) / self.__rpc_data['rpcsends'])) + print('\tAvg pending queue length: %d' % \ + (float(self.__rpc_data['pendutil']) / self.__rpc_data['rpcsends'])) + elif self.__rpc_data['protocol'] == 'rdma': + print('\tTransport protocol: rdma') + print('\tConnect count: %d' % self.__rpc_data['connect_count']) + print('\tConnect time: %d seconds' % self.__rpc_data['connect_time']) + print('\tIdle time: %d seconds' % self.__rpc_data['idle_time']) + sends = self.__rpc_data['rpcsends'] + print('\tRPC requests: %d' % self.__rpc_data['rpcsends']) + print('\tRPC replies: %d' % self.__rpc_data['rpcreceives']) + print('\tXIDs not found: %d' % self.__rpc_data['badxids']) + if self.__rpc_data['rpcsends'] != 0: + print('\tAvg backlog length: %d' % \ + (float(self.__rpc_data['backlogutil']) / self.__rpc_data['rpcsends'])) + print('\tRead segments: %d' % self.__rpc_data['read_segments']) + print('\tWrite segments: %d' % self.__rpc_data['write_segments']) + print('\tReply segments: %d' % self.__rpc_data['reply_segments']) + print('\tRegistered: %d bytes' % self.__rpc_data['total_rdma_req']) + print('\tRDMA received: %d bytes' % self.__rpc_data['total_rdma_rep']) + print('\tTotal pull-up: %d bytes' % self.__rpc_data['pullup']) + print('\tTotal fix-up: %d bytes' % self.__rpc_data['fixup']) + print('\tHardway allocations: %d bytes' % self.__rpc_data['hardway']) + print('\tFailed marshals: %d' % self.__rpc_data['failed_marshal']) + print('\tBad replies: %d' % self.__rpc_data['bad_reply']) + + """ Counters not present in all kernels """ + if 'nomsg_calls' in self.__rpc_data: + print('\tRDMA_NOMSG calls: %d' % self.__rpc_data['nomsg_calls']) + if 'allocated_mrs' in self.__rpc_data: + print('\tAllocated MRs: %d' % self.__rpc_data['allocated_mrs']) + if 'recovered_mrs' in self.__rpc_data: + print('\tRecovered MRs: %d' % self.__rpc_data['recovered_mrs']) + if 'orphaned_mrs' in self.__rpc_data: + print('\tOrphaned MRs: %d' % self.__rpc_data['orphaned_mrs']) + if 'local_invalidates' in self.__rpc_data: + print('\tLocal Invalidates needed: %d' % self.__rpc_data['local_invalidates']) + if 'empty_sendctx_q' in self.__rpc_data: + print('\tEmpty sendctx queue count: %d' % self.__rpc_data['empty_sendctx_q']) + if 'reply_waits_for_send' in self.__rpc_data: + print('\tReplies that waited for Send completion: %d' % self.__rpc_data['reply_waits_for_send']) + else: + raise Exception('Unknown RPC transport protocol %s' % self.__rpc_data['protocol']) + +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 + point named in the key. + """ + ms_dict = dict() + key = '' + + f.seek(0) + for line in f.readlines(): + words = line.split() + if len(words) == 0: + continue + if words[0] == 'device': + key = words[4] + new = [ line.strip() ] + elif 'nfs' in words or 'nfs4' in words: + key = words[3] + new = [ line.strip() ] + else: + new += [ line.strip() ] + ms_dict[key] = new + + return ms_dict + +def print_mountstats(stats, nfs_only, rpc_only, raw, xprt_only): + if nfs_only: + stats.display_stats_header() + stats.display_nfs_options() + 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() + elif raw: + stats.display_raw_stats() + elif xprt_only: + stats.display_stats_header() + stats.display_xprt_stats() + else: + stats.display_stats_header() + stats.display_nfs_options() + 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 = [os.path.normpath(mp) for mp in 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 1 + + if args.since: + old_mountstats = parse_stats_file(args.since) + + for mp in mountpoints: + stats = DeviceData() + stats.parse_stats(mountstats[mp]) + if not args.since: + print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw, args.xprt_only) + elif args.since and mp not in old_mountstats: + print_mountstats(stats, args.nfs_only, args.rpc_only, args.raw, args.xprt_only) + 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, args.raw, args.xprt_only) + + args.infile.close() + if args.since: + args.since.close() + return 0 + +def nfsstat_command(args): + """nfsstat-like command for NFS mount points + """ + mountstats = parse_stats_file(args.infile) + mountpoints = [os.path.normpath(mp) for mp in 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 1 + + 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(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() + return 0 + +def print_iostat_summary(old, new, devices, time): + for device in devices: + stats = DeviceData() + stats.parse_stats(new[device]) + if not old or device not in old: + stats.display_iostats(time) + else: + if ("fstype autofs" not in str(old[device])) and ("fstype autofs" not in str(new[device])): + old_stats = DeviceData() + old_stats.parse_stats(old[device]) + diff_stats = stats.compare_iostats(old_stats) + diff_stats.display_iostats(time) + +def iostat_command(args): + """iostat-like command for NFS mount points + """ + mountstats = parse_stats_file(args.infile) + devices = [os.path.normpath(mp) for mp in 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 = [] + for device in devices: + stats = DeviceData() + 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(): + stats = DeviceData() + stats.parse_stats(descr) + if stats.is_nfs_mountpoint(): + devices += [device] + if len(devices) == 0: + print('No NFS mount points were found') + return 1 + + sample_time = 0 + + if args.interval is None: + print_iostat_summary(old_mountstats, mountstats, devices, sample_time) + return + + 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(args.interval) + sample_time = args.interval + mountstats = parse_stats_file(args.infile) + count -= 1 + else: + while True: + print_iostat_summary(old_mountstats, mountstats, devices, sample_time) + old_mountstats = mountstats + time.sleep(args.interval) + sample_time = args.interval + mountstats = parse_stats_file(args.infile) + + args.infile.close() + if args.since: + args.since.close() + return 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) + if namespace.infile.name != '/proc/self/mountstats': + raise argparse.ArgumentError(self, "not allowed with argument -f/--file or -S/--since") + 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 integer arguments") + +def main(): + parser = argparse.ArgumentParser(epilog='For specific sub-command help, ' + 'run \'mountstats SUB-COMMAND -h|--help\'') + subparsers = parser.add_subparsers(help='sub-command help') + + common_parser = argparse.ArgumentParser(add_help=False) + common_parser.add_argument('-v', '--version', action='version', + version='mountstats ' + Mountstats_version) + common_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') + common_parser.add_argument('-S', '--since', type=argparse.FileType('r'), + metavar='SINCEFILE', + help='Show difference between current stats and those in SINCEFILE') + + mountstats_parser = subparsers.add_parser('mountstats', + parents=[common_parser], + help='Display a combination of per-op RPC statistics, NFS event counts, and NFS byte counts. ' + 'This is the default sub-command if no sub-command is given.') + 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') + group.add_argument('-R', '--raw', action='store_true', + help='Display only the raw statistics') + group.add_argument('-x', '--xprt', action='store_true', dest='xprt_only', + help='Display only the xprt statistics') + # The mountpoints argument cannot be moved into the common_parser because + # it will screw up the parsing of the iostat arguments (interval and count) + 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) + + nfsstat_parser = subparsers.add_parser('nfsstat', + parents=[common_parser], + help='Display nfsstat-like statistics.') + 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') + # The mountpoints argument cannot be moved into the common_parser because + # it will screw up the parsing of the iostat arguments (interval and count) + nfsstat_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.') + nfsstat_parser.set_defaults(func=nfsstat_command) + + iostat_parser = subparsers.add_parser('iostat', + parents=[common_parser], + help='Display iostat-like statistics.') + 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.') + # The mountpoints argument cannot be moved into the common_parser because + # it will screw up the parsing of the iostat arguments (interval and count) + 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 = parser.parse_args() + return args.func(args) + +try: + if __name__ == '__main__': + # Run the mounstats sub-command if no sub-command (or the help flag) + # is given. If the argparse module ever gets support for optional + # (default) sub-commands, then this can be changed. + if len(sys.argv) == 1: + sys.argv.insert(1, 'mountstats') + elif sys.argv[1] not in ['-h', '--help', 'mountstats', 'iostat', 'nfsstat']: + sys.argv.insert(1, 'mountstats') + res = main() + sys.stdout.close() + sys.stderr.close() + sys.exit(res) +except (KeyboardInterrupt, RuntimeError): + sys.exit(1) +except IOError: + pass + |