]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - tools/nfs-iostat/nfs-iostat.py
Merge branch 'upstream'
[nfs-utils.git] / tools / nfs-iostat / nfs-iostat.py
index 794d4a82b3cbb6d9fa61660e3abf6adb4cd7cc0e..d9096321f59aae9eb131340cb8a5cff5b38d7ccd 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python
 # -*- python-mode -*-
 """Emulate iostat for NFS mount points using /proc/self/mountstats
 """
@@ -17,10 +17,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 +86,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:':
@@ -134,6 +142,26 @@ class DeviceData:
                 self.__rpc_data['badxids'] = int(words[9])
                 self.__rpc_data['inflightsends'] = long(words[10])
                 self.__rpc_data['backlogutil'] = long(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:
@@ -324,7 +352,7 @@ class DeviceData:
 
         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 '  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),
@@ -333,12 +361,24 @@ class DeviceData:
         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
         """
         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:
@@ -375,33 +415,6 @@ class DeviceData:
 # Functions
 #
 
-def print_iostat_help(name):
-    print 'usage: %s [ <interval> [ <count> ] ] [ <options> ] [ <mount point> ] ' % name
-    print
-    print ' Version %s' % Iostats_version
-    print
-    print ' Sample 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 ' 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 <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.'
-
 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
@@ -419,6 +432,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
@@ -426,109 +442,196 @@ def parse_stats_file(filename):
 
     return ms_dict
 
-def print_iostat_summary(old, new, devices, time, ac):
-    for device in devices:
-        stats = DeviceData()
-        stats.parse_stats(new[device])
-        if not old:
-            stats.display_iostats(time, ac)
-        else:
+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
+        devicelist = filter(lambda x:x in devices,old)
+    else:
+        devicelist = devices
+
+    for device in devicelist:
+        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
+       return a full list if the given list is empty -
+       may return an empty list if none found
+    """
+    list = []
+    if len(givenlist) > 0:
+        for device in givenlist:
+            stats = DeviceData()
+            stats.parse_stats(mountstats[device])
+            if stats.is_nfs_mountpoint():
+                list += [device]
+    else:
+        for device, descr in mountstats.iteritems():
+            stats = DeviceData()
+            stats.parse_stats(descr)
+            if stats.is_nfs_mountpoint():
+                list += [device]
+    return list
 
 def iostat_command(name):
     """iostat-like command for NFS mount points
     """
     mountstats = parse_stats_file('/proc/self/mountstats')
     devices = []
-    which = 0
+    origdevices = []
     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 <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.  If the <count>
+parameter is specified, the value of <count> determines the number of reports
+generated at <interval> seconds apart.  If the interval parameter is specified
+without the <count> parameter, the command generates reports continuously.
+If one or more <mount point> 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 [ <interval> [ <count> ] ] [ <options> ] [ <mount point> ]",
+        description=mydescription,
+        version='version %s' % Iostats_version)
+    parser.set_defaults(which=0, sort=False, list=sys.maxint)
+
+    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
 
         if arg in mountstats:
-            devices += [arg]
+            origdevices += [arg]
         elif not interval_seen:
-            interval = int(arg)
+            try:
+                interval = int(arg)
+            except:
+                print 'Illegal <interval> value %s' % arg
+                return
             if interval > 0:
                 interval_seen = True
             else:
-                print 'Illegal <interval> value'
+                print 'Illegal <interval> value %s' % arg
                 return
         elif not count_seen:
-            count = int(arg)
+            try:
+                count = int(arg)
+            except:
+                print 'Ilegal <count> value %s' % arg
+                return
             if count > 0:
                 count_seen = True
             else:
-                print 'Illegal <count> value'
+                print 'Illegal <count> value %s' % arg
                 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]
+    devices = list_nfs_mounts(origdevices, mountstats)
     if len(devices) == 0:
         print 'No NFS mount points were found'
         return
 
+
     old_mountstats = None
     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
             mountstats = parse_stats_file('/proc/self/mountstats')
+            # automount mountpoints add and drop, if automount is involved
+            # 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'
+                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
             mountstats = parse_stats_file('/proc/self/mountstats')
+            # automount mountpoints add and drop, if automount is involved
+            # 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'
+                return
 
 #
 # Main