X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=tools%2Fnfs-iostat%2Fnfs-iostat.py;h=d9096321f59aae9eb131340cb8a5cff5b38d7ccd;hp=794d4a82b3cbb6d9fa61660e3abf6adb4cd7cc0e;hb=3ad7dea527170ae12c9cdc2e428d0676ac261144;hpb=ba18e469a8507befdf8969c5ce7a25564744ae01 diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py index 794d4a8..d909632 100644 --- a/tools/nfs-iostat/nfs-iostat.py +++ b/tools/nfs-iostat/nfs-iostat.py @@ -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 [ [ ] ] [ ] [ ] ' % 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 @@ -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 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.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 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 - 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