]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - tools/nfs-iostat/nfs-iostat.py
Merge branch 'sid'
[nfs-utils.git] / tools / nfs-iostat / nfs-iostat.py
index 5c46f9261cff998755ea3333026bb92a9b666ca4..dfbef87ed06c67b05d5e53fa14481c6f9d502a59 100644 (file)
@@ -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 <cel@netapp.com>
 
@@ -17,7 +19,8 @@ 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
@@ -85,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:':
@@ -194,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
@@ -226,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
@@ -248,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
@@ -270,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
@@ -290,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
@@ -344,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
@@ -360,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)
@@ -405,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:
@@ -413,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
@@ -420,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
@@ -429,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
@@ -452,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():
@@ -485,10 +529,10 @@ client are listed.
         usage="usage: %prog [ <interval> [ <count> ] ] [ <options> ] [ <mount point> ]",
         description=mydescription,
         version='version %s' % Iostats_version)
-    parser.set_defaults(which=0)
+    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:')
+                            'File I/O is displayed unless one of the following is specified:')
     statgroup.add_option('-a', '--attr',
                             action="store_const",
                             dest="which",
@@ -505,9 +549,20 @@ client are listed.
                             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]:
@@ -519,29 +574,29 @@ client are listed.
             try:
                 interval = int(arg)
             except:
-                print 'Illegal <interval> value %s' % arg
+                print('Illegal <interval> value %s' % arg)
                 return
             if interval > 0:
                 interval_seen = True
             else:
-                print 'Illegal <interval> value %s' % arg
+                print('Illegal <interval> value %s' % arg)
                 return
         elif not count_seen:
             try:
                 count = int(arg)
             except:
-                print 'Ilegal <count> value %s' % arg
+                print('Ilegal <count> value %s' % arg)
                 return
             if count > 0:
                 count_seen = True
             else:
-                print 'Illegal <count> value %s' % arg
+                print('Illegal <count> 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
 
 
@@ -549,12 +604,12 @@ client are listed.
     sample_time = 0.0
 
     if not interval_seen:
-        print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options.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, options.which)
+            print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
             old_mountstats = mountstats
             time.sleep(interval)
             sample_time = interval
@@ -563,12 +618,12 @@ client are listed.
             # 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, options.which)
+            print_iostat_summary(old_mountstats, mountstats, devices, sample_time, options)
             old_mountstats = mountstats
             time.sleep(interval)
             sample_time = interval
@@ -577,7 +632,7 @@ client are listed.
             # 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
 
 #
@@ -588,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)