blob: c475c9efde12bed74abb8205619d2e04d7b46a3a [file] [log] [blame]
#!/usr/bin/env python
# -*- python-mode -*-
"""Parse /proc/self/mountstats and display it in human readable form
"""
__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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys, os, time
Mountstats_version = '0.2'
def difference(x, y):
"""Used for a map() function
"""
return x - y
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:
self.__nfs_data['statvers'] = words[8]
elif words[0] == 'age:':
self.__nfs_data['age'] = long(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:':
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['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['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])
elif words[0] == 'bytes:':
self.__nfs_data['normalreadbytes'] = long(words[1])
self.__nfs_data['normalwritebytes'] = long(words[2])
self.__nfs_data['directreadbytes'] = long(words[3])
self.__nfs_data['directwritebytes'] = long(words[4])
self.__nfs_data['serverreadbytes'] = long(words[5])
self.__nfs_data['serverwritebytes'] = long(words[6])
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':
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'] = long(words[7])
self.__rpc_data['backlogutil'] = long(words[8])
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'] = long(words[10])
self.__rpc_data['backlogutil'] = int(words[11])
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])
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] = [long(word) for word in words[1:]]
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 display_nfs_options(self):
"""Pretty-print the NFS options
"""
print 'Stats for %s mounted on %s:' % \
(self.__nfs_data['export'], self.__nfs_data['mountpoint'])
print ' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])
print ' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])
if self.__nfs_data.has_key('nfsv4flags'):
print ' NFSv4 capability flags: %s' % ','.join(self.__nfs_data['nfsv4flags'])
if self.__nfs_data.has_key('pseudoflavor'):
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 ' inodes synced %d times' % self.__nfs_data['syncinodes']
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']
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
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']
# XXX: these should be sorted by 'count'
print
for op in self.__rpc_data['ops']:
stats = self.__rpc_data[op]
count = stats[0]
retrans = stats[1] - count
if count != 0:
print '%s:' % op
print '\t%d ops (%d%%)' % \
(count, ((count * 100) / sends)),
print '\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)),
print '\t%d major timeouts' % stats[2]
print '\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \
(stats[3] / count, stats[4] / count)
print '\tbacklog wait: %f' % (float(stats[5]) / count),
print '\tRTT: %f' % (float(stats[6]) / count),
print '\ttotal execute time: %f (milliseconds)' % \
(float(stats[7]) / count)
def compare_iostats(self, old_stats):
"""Return the difference between two sets of stats
"""
result = DeviceData()
# copy self into result
for key, value in self.__nfs_data.iteritems():
result.__nfs_data[key] = value
for key, value in self.__rpc_data.iteritems():
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']:
result.__rpc_data[op] = 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']
return result
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'])
print
print '%s mounted on %s:' % \
(self.__nfs_data['export'], self.__nfs_data['mountpoint'])
print '\top/s\trpc bklog'
print '\t%.2f' % (sends / sample_time),
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),
print '\t\t%.2f' % (kilobytes / sample_time),
if ops != 0:
print '\t\t%.2f' % (rtt / ops),
print '\t\t%.2f' % (exe / ops)
else:
print '\t\t0.00',
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),
print '\t\t%.2f' % (kilobytes / sample_time),
if ops != 0:
print '\t\t%.2f' % (rtt / ops),
print '\t\t%.2f' % (exe / ops)
else:
print '\t\t0.00',
print '\t\t0.00'
def parse_stats_file(filename):
"""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 = file(filename)
for line in f.readlines():
words = line.split()
if len(words) == 0:
continue
if words[0] == 'device':
key = words[4]
new = [ line.strip() ]
else:
new += [ line.strip() ]
ms_dict[key] = new
f.close
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():
"""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:
if mp not in mountstats:
print 'Statistics for mount point %s not found' % mp
continue
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 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 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 print_iostat_summary(old, new, devices, time):
for device in devices:
stats = DeviceData()
stats.parse_stats(new[device])
if not old:
stats.display_iostats(time)
else:
old_stats = DeviceData()
old_stats.parse_stats(old[device])
diff_stats = stats.compare_iostats(old_stats)
diff_stats.display_iostats(time)
def iostat_command():
"""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
# make certain devices contains only NFS mount points
if len(devices) > 0:
check = []
for device in devices:
stats = DeviceData()
stats.parse_stats(mountstats[device])
if stats.is_nfs_mountpoint():
check += [device]
devices = check
else:
for device, descr in mountstats.iteritems():
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
old_mountstats = None
sample_time = 0
if not interval_seen:
print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
return
if count_seen:
while count != 0:
print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
old_mountstats = mountstats
time.sleep(interval)
sample_time = 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
mountstats = parse_stats_file('/proc/self/mountstats')
#
# Main
#
prog = os.path.basename(sys.argv[0])
try:
if prog == 'mountstats':
mountstats_command()
elif prog == 'ms-nfsstat':
nfsstat_command()
elif prog == 'ms-iostat':
iostat_command()
except KeyboardInterrupt:
print 'Caught ^C... exiting'
sys.exit(1)
sys.exit(0)