X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=tools%2Fnfs-iostat%2Fnfs-iostat.py;h=dfbef87ed06c67b05d5e53fa14481c6f9d502a59;hp=2e5ac3dcfd3b8a5248ba0cc72c9ff66fc0f5f9d5;hb=adabe938c17b22aec5e89c24e1856de9fffe9f0c;hpb=88deba4a8db06d371659aa85b668460b85900d48 diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py index 2e5ac3d..dfbef87 100644 --- a/tools/nfs-iostat/nfs-iostat.py +++ b/tools/nfs-iostat/nfs-iostat.py @@ -3,6 +3,8 @@ """Emulate iostat for NFS mount points using /proc/self/mountstats """ +from __future__ import print_function + __copyright__ = """ Copyright (C) 2005, Chuck Lever @@ -17,10 +19,12 @@ 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 +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +MA 02110-1301 USA """ import sys, os, time +from optparse import OptionParser, OptionGroup Iostats_version = '0.2' @@ -84,6 +88,12 @@ class DeviceData: self.__nfs_data['fstype'] = words[7] if words[7] == 'nfs': 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] == 'nfs': + self.__nfs_data['statvers'] = words[7] elif words[0] == 'age:': self.__nfs_data['age'] = long(words[1]) elif words[0] == 'opts:': @@ -193,9 +203,9 @@ class DeviceData: result = DeviceData() # copy self into result - for key, value in self.__nfs_data.iteritems(): + for key, value in self.__nfs_data.items(): result.__nfs_data[key] = value - for key, value in self.__rpc_data.iteritems(): + for key, value in self.__rpc_data.items(): result.__rpc_data[key] = value # compute the difference of each item in the list @@ -225,9 +235,9 @@ class DeviceData: client_bytes_read = float(nfs_stats['serverreadbytes'] - nfs_stats['directreadbytes']) ratio = ((app_bytes_read - client_bytes_read) * 100) / app_bytes_read - print - print 'app bytes: %f client bytes %f' % (app_bytes_read, client_bytes_read) - print 'Data cache hit ratio: %4.2f%%' % ratio + print() + print('app bytes: %f client bytes %f' % (app_bytes_read, client_bytes_read)) + print('Data cache hit ratio: %4.2f%%' % ratio) def __print_attr_cache_stats(self, sample_time): """Print attribute cache efficiency stats @@ -247,13 +257,13 @@ class DeviceData: data_invalidates = float(nfs_stats['datainvalidates']) attr_invalidates = float(nfs_stats['attrinvalidates']) - print - print '%d inode revalidations, hitting in cache %4.2f%% of the time' % \ - (revalidates, ratio) - print '%d open operations (mandatory GETATTR requests)' % opens + print() + print('%d inode revalidations, hitting in cache %4.2f%% of the time' % \ + (revalidates, ratio)) + print('%d open operations (mandatory GETATTR requests)' % opens) if getattr_ops != 0: - print '%4.2f%% of GETATTRs resulted in data cache invalidations' % \ - ((data_invalidates * 100) / getattr_ops) + print('%4.2f%% of GETATTRs resulted in data cache invalidations' % \ + ((data_invalidates * 100) / getattr_ops)) def __print_dir_cache_stats(self, sample_time): """Print directory stats @@ -269,13 +279,13 @@ class DeviceData: lookups = nfs_stats['vfslookup'] getdents = nfs_stats['vfsreaddir'] - print - print '%d open operations (pathname lookups)' % opens - print '%d dentry revalidates and %d vfs lookup requests' % \ - (dentry_revals, lookups), - print 'resulted in %d LOOKUPs on the wire' % lookup_ops - print '%d vfs getdents calls resulted in %d READDIRs on the wire' % \ - (getdents, readdir_ops) + print() + print('%d open operations (pathname lookups)' % opens) + print('%d dentry revalidates and %d vfs lookup requests' % \ + (dentry_revals, lookups)) + print('resulted in %d LOOKUPs on the wire' % lookup_ops) + print('%d vfs getdents calls resulted in %d READDIRs on the wire' % \ + (getdents, readdir_ops)) def __print_page_stats(self, sample_time): """Print page cache stats @@ -289,33 +299,33 @@ class DeviceData: vfswritepages = nfs_stats['vfswritepages'] pages_written = nfs_stats['writepages'] - print - print '%d nfs_readpage() calls read %d pages' % \ - (vfsreadpage, vfsreadpage) - print '%d nfs_readpages() calls read %d pages' % \ - (vfsreadpages, pages_read - vfsreadpage), + print() + print('%d nfs_readpage() calls read %d pages' % \ + (vfsreadpage, vfsreadpage)) + print('%d nfs_readpages() calls read %d pages' % \ + (vfsreadpages, pages_read - vfsreadpage)) if vfsreadpages != 0: - print '(%.1f pages per call)' % \ - (float(pages_read - vfsreadpage) / vfsreadpages) + print('(%.1f pages per call)' % \ + (float(pages_read - vfsreadpage) / vfsreadpages)) else: - print - - print - print '%d nfs_updatepage() calls' % nfs_stats['vfsupdatepage'] - print '%d nfs_writepage() calls wrote %d pages' % \ - (vfswritepage, vfswritepage) - print '%d nfs_writepages() calls wrote %d pages' % \ - (vfswritepages, pages_written - vfswritepage), + print() + + print() + print('%d nfs_updatepage() calls' % nfs_stats['vfsupdatepage']) + print('%d nfs_writepage() calls wrote %d pages' % \ + (vfswritepage, vfswritepage)) + print('%d nfs_writepages() calls wrote %d pages' % \ + (vfswritepages, pages_written - vfswritepage)) if (vfswritepages) != 0: - print '(%.1f pages per call)' % \ - (float(pages_written - vfswritepage) / vfswritepages) + print('(%.1f pages per call)' % \ + (float(pages_written - vfswritepage) / vfswritepages)) else: - print + print() congestionwaits = nfs_stats['congestionwait'] if congestionwaits != 0: - print - print '%d congestion waits' % congestionwaits + print() + print('%d congestion waits' % congestionwaits) def __print_rpc_op_stats(self, op, sample_time): """Print generic stats for one RPC op @@ -343,15 +353,21 @@ class DeviceData: exe_per_op = 0.0 op += ':' - print '%s' % op.lower().ljust(15), - print ' ops/s\t\t kB/s\t\t kB/op\t\tretrans\t\tavg RTT (ms)\tavg exe (ms)' + print('%s' % op.lower().ljust(15)) + print(' ops/s\t\t kB/s\t\t kB/op\t\tretrans\t\tavg RTT (ms)\tavg exe (ms)') + + print('\t\t%7.3f' % (ops / sample_time)) + print('\t%7.3f' % (kilobytes / sample_time)) + print('\t%7.3f' % kb_per_op) + print(' %7d (%3.1f%%)' % (retrans, retrans_percent)) + print('\t%7.3f' % rtt_per_op) + print('\t%7.3f' % exe_per_op) - print '\t\t%7.3f' % (ops / sample_time), - print '\t%7.3f' % (kilobytes / sample_time), - print '\t%7.3f' % kb_per_op, - print ' %7d (%3.1f%%)' % (retrans, retrans_percent), - print '\t%7.3f' % rtt_per_op, - print '\t%7.3f' % exe_per_op + def ops(self, sample_time): + sends = float(self.__rpc_data['rpcsends']) + if sample_time == 0: + sample_time = float(self.__nfs_data['age']) + return (sends / sample_time) def display_iostats(self, sample_time, which): """Display NFS and RPC stats in an iostat-like way @@ -359,19 +375,25 @@ class DeviceData: 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() + print('%s mounted on %s:' % \ + (self.__nfs_data['export'], self.__nfs_data['mountpoint'])) + print() - print ' op/s\t\trpc bklog' - print '%7.2f' % (sends / sample_time), - print '\t%7.2f' % backlog + print(' op/s\t\trpc bklog') + print('%7.2f' % (sends / sample_time)) + print('\t%7.2f' % backlog) if which == 0: self.__print_rpc_op_stats('READ', sample_time) @@ -395,33 +417,6 @@ class DeviceData: # Functions # -def print_iostat_help(name): - print 'usage: %s [ [ ] ] [ ] [ ] ' % name - print - print ' Version %s' % Iostats_version - print - print ' Sample iostat-like program to display NFS client per-mount statistics.' - print - print ' The 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 parameter is specified, the value of determines the' - print ' number of reports generated at seconds apart. If the interval' - print ' parameter is specified without the parameter, the command generates' - print ' reports continuously.' - print - print ' Options include "--attr", which displays statistics related to the attribute' - print ' cache, "--dir", which displays statistics related to directory operations,' - print ' and "--page", which displays statistics related to the page cache.' - print ' By default, if no option is specified, statistics related to file I/O are' - print ' displayed.' - print - print ' If one or more names are specified, statistics for only these' - print ' mount points will be displayed. Otherwise, all NFS mount points on the' - print ' client are listed.' - 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 @@ -431,7 +426,7 @@ def parse_stats_file(filename): ms_dict = dict() key = '' - f = file(filename) + f = open(filename) for line in f.readlines(): words = line.split() if len(words) == 0: @@ -439,6 +434,9 @@ def parse_stats_file(filename): 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 @@ -446,7 +444,9 @@ def parse_stats_file(filename): return ms_dict -def print_iostat_summary(old, new, devices, time, ac): +def print_iostat_summary(old, new, devices, time, options): + stats = {} + diff_stats = {} if old: # Trim device list to only include intersection of old and new data, # this addresses umounts due to autofs mountpoints @@ -455,15 +455,33 @@ def print_iostat_summary(old, new, devices, time, ac): devicelist = devices for device in devicelist: - stats = DeviceData() - stats.parse_stats(new[device]) - if not old: - stats.display_iostats(time, ac) - else: + stats[device] = DeviceData() + stats[device].parse_stats(new[device]) + if old: old_stats = DeviceData() old_stats.parse_stats(old[device]) - diff_stats = stats.compare_iostats(old_stats) - diff_stats.display_iostats(time, ac) + diff_stats[device] = stats[device].compare_iostats(old_stats) + + if options.sort: + if old: + # We now have compared data and can print a comparison + # ordered by mountpoint ops per second + devicelist.sort(key=lambda x: diff_stats[x].ops(time), reverse=True) + else: + # First iteration, just sort by newly parsed ops/s + devicelist.sort(key=lambda x: stats[x].ops(time), reverse=True) + + count = 1 + for device in devicelist: + if old: + diff_stats[device].display_iostats(time, options.which) + else: + stats[device].display_iostats(time, options.which) + + count += 1 + if (count > options.list): + return + def list_nfs_mounts(givenlist, mountstats): """return a list of NFS mounts given a list to validate or @@ -478,7 +496,7 @@ def list_nfs_mounts(givenlist, mountstats): if stats.is_nfs_mountpoint(): list += [device] else: - for device, descr in mountstats.iteritems(): + for device, descr in mountstats.items(): stats = DeviceData() stats.parse_stats(descr) if stats.is_nfs_mountpoint(): @@ -491,30 +509,61 @@ def iostat_command(name): mountstats = parse_stats_file('/proc/self/mountstats') devices = [] origdevices = [] - which = 0 interval_seen = False count_seen = False - for arg in sys.argv: - if arg in ['-h', '--help', 'help', 'usage']: - print_iostat_help(name) - return - - if arg in ['-v', '--version', 'version']: - print '%s version %s' % (name, Iostats_version) - return - - if arg in ['-a', '--attr']: - which = 1 - continue - - if arg in ['-d', '--dir']: - which = 2 - continue - - if arg in ['-p', '--page']: - which = 3 - continue + mydescription= """ +Sample iostat-like program to display NFS client per-mount' +statistics. The 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. If the +parameter is specified, the value of determines the number of reports +generated at seconds apart. If the interval parameter is specified +without the parameter, the command generates reports continuously. +If one or more names are specified, statistics for only these +mount points will be displayed. Otherwise, all NFS mount points on the +client are listed. +""" + parser = OptionParser( + usage="usage: %prog [ [ ] ] [ ] [ ]", + description=mydescription, + version='version %s' % Iostats_version) + parser.set_defaults(which=0, sort=False, list=sys.maxsize) + + statgroup = OptionGroup(parser, "Statistics Options", + 'File I/O is displayed unless one of the following is specified:') + statgroup.add_option('-a', '--attr', + action="store_const", + dest="which", + const=1, + help='displays statistics related to the attribute cache') + statgroup.add_option('-d', '--dir', + action="store_const", + dest="which", + const=2, + help='displays statistics related to directory operations') + statgroup.add_option('-p', '--page', + action="store_const", + dest="which", + const=3, + help='displays statistics related to the page cache') + parser.add_option_group(statgroup) + displaygroup = OptionGroup(parser, "Display Options", + 'Options affecting display format:') + displaygroup.add_option('-s', '--sort', + action="store_true", + dest="sort", + help="Sort NFS mount points by ops/second") + displaygroup.add_option('-l','--list', + action="store", + type="int", + dest="list", + help="only print stats for first LIST mount points") + parser.add_option_group(displaygroup) + + (options, args) = parser.parse_args(sys.argv) + for arg in args: if arg == sys.argv[0]: continue @@ -522,24 +571,32 @@ def iostat_command(name): if arg in mountstats: origdevices += [arg] elif not interval_seen: - interval = int(arg) + try: + interval = int(arg) + except: + print('Illegal value %s' % arg) + return if interval > 0: interval_seen = True else: - print 'Illegal value' + print('Illegal value %s' % arg) return elif not count_seen: - count = int(arg) + try: + count = int(arg) + except: + print('Ilegal value %s' % arg) + return if count > 0: count_seen = True else: - print 'Illegal value' + print('Illegal value %s' % arg) return # make certain devices contains only NFS mount points devices = list_nfs_mounts(origdevices, mountstats) if len(devices) == 0: - print 'No NFS mount points were found' + print('No NFS mount points were found') return @@ -547,12 +604,12 @@ def iostat_command(name): sample_time = 0.0 if not interval_seen: - print_iostat_summary(old_mountstats, mountstats, devices, sample_time, which) + print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options) return if count_seen: while count != 0: - print_iostat_summary(old_mountstats, mountstats, devices, sample_time, which) + print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options) old_mountstats = mountstats time.sleep(interval) sample_time = interval @@ -561,12 +618,12 @@ def iostat_command(name): # we need to recheck the devices list when reparsing devices = list_nfs_mounts(origdevices,mountstats) if len(devices) == 0: - print 'No NFS mount points were found' + print('No NFS mount points were found') return count -= 1 else: while True: - print_iostat_summary(old_mountstats, mountstats, devices, sample_time, which) + print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options) old_mountstats = mountstats time.sleep(interval) sample_time = interval @@ -575,7 +632,7 @@ def iostat_command(name): # we need to recheck the devices list when reparsing devices = list_nfs_mounts(origdevices,mountstats) if len(devices) == 0: - print 'No NFS mount points were found' + print('No NFS mount points were found') return # @@ -586,7 +643,7 @@ prog = os.path.basename(sys.argv[0]) try: iostat_command(prog) except KeyboardInterrupt: - print 'Caught ^C... exiting' + print('Caught ^C... exiting') sys.exit(1) sys.exit(0)