From 4f0634d672e7b1d789ee331dc418d07ea2206a1b Mon Sep 17 00:00:00 2001 From: Steve Dickson Date: Mon, 23 Jun 2008 18:03:20 +0000 Subject: [PATCH] - Added -D_FILE_OFFSET_BITS=64 to CFLAGS - make nfsstat read and print stats as unsigned integers - Added (but not installed) the mountstats and nfs-iostat python scripts. --- nfs-utils-1.1.2-mountstats-rdma.patch | 42 ++ nfs-utils-1.1.2-mountstats.patch | 605 +++++++++++++++++++++++++ nfs-utils-1.1.2-nfs-iostat-rdma.patch | 42 ++ nfs-utils-1.1.2-nfs-iostat.patch | 560 +++++++++++++++++++++++ nfs-utils-1.1.2-nfsstat-counters.patch | 60 +++ nfs-utils.spec | 20 +- 6 files changed, 1326 insertions(+), 3 deletions(-) create mode 100644 nfs-utils-1.1.2-mountstats-rdma.patch create mode 100644 nfs-utils-1.1.2-mountstats.patch create mode 100644 nfs-utils-1.1.2-nfs-iostat-rdma.patch create mode 100644 nfs-utils-1.1.2-nfs-iostat.patch create mode 100644 nfs-utils-1.1.2-nfsstat-counters.patch diff --git a/nfs-utils-1.1.2-mountstats-rdma.patch b/nfs-utils-1.1.2-mountstats-rdma.patch new file mode 100644 index 0000000..f9a7f11 --- /dev/null +++ b/nfs-utils-1.1.2-mountstats-rdma.patch @@ -0,0 +1,42 @@ +commit 589a913e42476a965b686c9f2656b786eaae399e +Author: Tom Talpey +Date: Mon Jun 23 12:54:08 2008 -0400 + + Add RDMA as a supported transport for reporting the + mountstats statistics + + Signed-off-by: Tom Talpey + Acked-by: Chuck Lever + Signed-off-by: Steve Dickson + +diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py +index 5f20db6..f55595e 100644 +--- a/tools/mountstats/mountstats.py ++++ b/tools/mountstats/mountstats.py +@@ -116,6 +116,26 @@ class DeviceData: + 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: diff --git a/nfs-utils-1.1.2-mountstats.patch b/nfs-utils-1.1.2-mountstats.patch new file mode 100644 index 0000000..12535ea --- /dev/null +++ b/nfs-utils-1.1.2-mountstats.patch @@ -0,0 +1,605 @@ +commit c761709ad3abb9c36a68c269f78118bf49d79639 +Author: Chuck Lever +Date: Mon Jun 23 12:52:33 2008 -0400 + + The "mountstats" utility is a Python program that extracts and displays NFS + client performance information from /proc/self/mountstats. + + Note that if mountstats is named 'ms-nfsstat' or 'ms-iostat' it offers + slightly different functionality. It needs two man pages and the install + script should provide both commands by installing the script and providing the + other command via a symlink. + + Signed-off-by: Chuck Lever + Signed-off-by: Steve Dickson + +diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py +new file mode 100644 +index 0000000..5f20db6 +--- /dev/null ++++ b/tools/mountstats/mountstats.py +@@ -0,0 +1,584 @@ ++#!/usr/bin/env python ++# -*- python-mode -*- ++"""Parse /proc/self/mountstats and display it in human readable form ++""" ++ ++__copyright__ = """ ++Copyright (C) 2005, Chuck Lever ++ ++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[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 ] ' % 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 [ [ ] ] [ ] ' % name ++ print ++ print ' Version %s' % Mountstats_version ++ print ++ print ' 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 ' 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.' ++ 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 value' ++ return ++ elif not count_seen: ++ count = int(arg) ++ if count > 0: ++ count_seen = True ++ else: ++ print 'Illegal 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) diff --git a/nfs-utils-1.1.2-nfs-iostat-rdma.patch b/nfs-utils-1.1.2-nfs-iostat-rdma.patch new file mode 100644 index 0000000..6e8c8ca --- /dev/null +++ b/nfs-utils-1.1.2-nfs-iostat-rdma.patch @@ -0,0 +1,42 @@ +commit 2ef57222b10a91f4b96a06808d05a47e8f4c14f7 +Author: Tom Talpey +Date: Mon Jun 23 12:57:29 2008 -0400 + + Add RDMA as a supported transport for reporting + the mountstats statistics + + Signed-off-by: Tom Talpey + Acked-by: Chuck Lever + Signed-off-by: Steve Dickson + +diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py +index 794d4a8..649c1bd 100644 +--- a/tools/nfs-iostat/nfs-iostat.py ++++ b/tools/nfs-iostat/nfs-iostat.py +@@ -134,6 +134,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: diff --git a/nfs-utils-1.1.2-nfs-iostat.patch b/nfs-utils-1.1.2-nfs-iostat.patch new file mode 100644 index 0000000..9be18ff --- /dev/null +++ b/nfs-utils-1.1.2-nfs-iostat.patch @@ -0,0 +1,560 @@ +commit ba18e469a8507befdf8969c5ce7a25564744ae01 +Author: Chuck Lever +Date: Mon Jun 23 12:56:14 2008 -0400 + + The "nfs-iostat" utility is a Python program that extracts and displays NFS + client performance information from /proc/self/mountstats. + + Signed-off-by: Chuck Lever + Signed-off-by: Steve Dickson + +diff --git a/tools/nfs-iostat/nfs-iostat.py b/tools/nfs-iostat/nfs-iostat.py +new file mode 100644 +index 0000000..794d4a8 +--- /dev/null ++++ b/tools/nfs-iostat/nfs-iostat.py +@@ -0,0 +1,544 @@ ++#!/usr/bin/env python ++# -*- python-mode -*- ++"""Emulate iostat for NFS mount points using /proc/self/mountstats ++""" ++ ++__copyright__ = """ ++Copyright (C) 2005, Chuck Lever ++ ++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 ++ ++Iostats_version = '0.2' ++ ++def difference(x, y): ++ """Used for a map() function ++ """ ++ return x - y ++ ++NfsEventCounters = [ ++ 'inoderevalidates', ++ 'dentryrevalidates', ++ 'datainvalidates', ++ 'attrinvalidates', ++ 'vfsopen', ++ 'vfslookup', ++ 'vfspermission', ++ 'vfsupdatepage', ++ 'vfsreadpage', ++ 'vfsreadpages', ++ 'vfswritepage', ++ 'vfswritepages', ++ 'vfsreaddir', ++ 'vfssetattr', ++ 'vfsflush', ++ 'vfsfsync', ++ 'vfslock', ++ 'vfsrelease', ++ 'congestionwait', ++ 'setattrtrunc', ++ 'extendwrite', ++ 'sillyrenames', ++ 'shortreads', ++ 'shortwrites', ++ 'delay' ++] ++ ++NfsByteCounters = [ ++ 'normalreadbytes', ++ 'normalwritebytes', ++ 'directreadbytes', ++ 'directwritebytes', ++ 'serverreadbytes', ++ 'serverwritebytes', ++ 'readpages', ++ 'writepages' ++] ++ ++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] == 'nfs': ++ 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:': ++ i = 1 ++ for key in NfsEventCounters: ++ self.__nfs_data[key] = int(words[i]) ++ i += 1 ++ elif words[0] == 'bytes:': ++ i = 1 ++ for key in NfsByteCounters: ++ self.__nfs_data[key] = long(words[i]) ++ i += 1 ++ ++ 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'] = long(words[11]) ++ 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 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'] ++ ++ for key in NfsEventCounters: ++ result.__nfs_data[key] -= old_stats.__nfs_data[key] ++ for key in NfsByteCounters: ++ result.__nfs_data[key] -= old_stats.__nfs_data[key] ++ ++ return result ++ ++ def __print_data_cache_stats(self): ++ """Print the data cache hit rate ++ """ ++ nfs_stats = self.__nfs_data ++ app_bytes_read = float(nfs_stats['normalreadbytes']) ++ if app_bytes_read != 0: ++ 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 ++ ++ def __print_attr_cache_stats(self, sample_time): ++ """Print attribute cache efficiency stats ++ """ ++ nfs_stats = self.__nfs_data ++ getattr_stats = self.__rpc_data['GETATTR'] ++ ++ if nfs_stats['inoderevalidates'] != 0: ++ getattr_ops = float(getattr_stats[1]) ++ opens = float(nfs_stats['vfsopen']) ++ revalidates = float(nfs_stats['inoderevalidates']) - opens ++ if revalidates != 0: ++ ratio = ((revalidates - getattr_ops) * 100) / revalidates ++ else: ++ ratio = 0.0 ++ ++ 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 ++ if getattr_ops != 0: ++ 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 ++ """ ++ nfs_stats = self.__nfs_data ++ lookup_ops = self.__rpc_data['LOOKUP'][0] ++ readdir_ops = self.__rpc_data['READDIR'][0] ++ if self.__rpc_data.has_key('READDIRPLUS'): ++ readdir_ops += self.__rpc_data['READDIRPLUS'][0] ++ ++ dentry_revals = nfs_stats['dentryrevalidates'] ++ opens = nfs_stats['vfsopen'] ++ 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) ++ ++ def __print_page_stats(self, sample_time): ++ """Print page cache stats ++ """ ++ nfs_stats = self.__nfs_data ++ ++ vfsreadpage = nfs_stats['vfsreadpage'] ++ vfsreadpages = nfs_stats['vfsreadpages'] ++ pages_read = nfs_stats['readpages'] ++ vfswritepage = nfs_stats['vfswritepage'] ++ 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), ++ if vfsreadpages != 0: ++ 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), ++ if (vfswritepages) != 0: ++ print '(%.1f pages per call)' % \ ++ (float(pages_written - vfswritepage) / vfswritepages) ++ else: ++ print ++ ++ congestionwaits = nfs_stats['congestionwait'] ++ if congestionwaits != 0: ++ print ++ print '%d congestion waits' % congestionwaits ++ ++ def __print_rpc_op_stats(self, op, sample_time): ++ """Print generic stats for one RPC op ++ """ ++ if not self.__rpc_data.has_key(op): ++ return ++ ++ rpc_stats = self.__rpc_data[op] ++ ops = float(rpc_stats[0]) ++ retrans = float(rpc_stats[1] - rpc_stats[0]) ++ kilobytes = float(rpc_stats[3] + rpc_stats[4]) / 1024 ++ rtt = float(rpc_stats[6]) ++ exe = float(rpc_stats[7]) ++ ++ # prevent floating point exceptions ++ if ops != 0: ++ kb_per_op = kilobytes / ops ++ retrans_percent = (retrans * 100) / ops ++ rtt_per_op = rtt / ops ++ exe_per_op = exe / ops ++ else: ++ kb_per_op = 0.0 ++ retrans_percent = 0.0 ++ rtt_per_op = 0.0 ++ 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 '\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 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']) ++ 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 ' 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) ++ self.__print_rpc_op_stats('WRITE', sample_time) ++ elif which == 1: ++ self.__print_rpc_op_stats('GETATTR', sample_time) ++ self.__print_rpc_op_stats('ACCESS', sample_time) ++ self.__print_attr_cache_stats(sample_time) ++ elif which == 2: ++ self.__print_rpc_op_stats('LOOKUP', sample_time) ++ self.__print_rpc_op_stats('READDIR', sample_time) ++ if self.__rpc_data.has_key('READDIRPLUS'): ++ self.__print_rpc_op_stats('READDIRPLUS', sample_time) ++ self.__print_dir_cache_stats(sample_time) ++ elif which == 3: ++ self.__print_rpc_op_stats('READ', sample_time) ++ self.__print_rpc_op_stats('WRITE', sample_time) ++ self.__print_page_stats(sample_time) ++ ++# ++# 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 ++ 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_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: ++ old_stats = DeviceData() ++ old_stats.parse_stats(old[device]) ++ diff_stats = stats.compare_iostats(old_stats) ++ diff_stats.display_iostats(time, ac) ++ ++def iostat_command(name): ++ """iostat-like command for NFS mount points ++ """ ++ mountstats = parse_stats_file('/proc/self/mountstats') ++ devices = [] ++ 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 ++ ++ 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 value' ++ return ++ elif not count_seen: ++ count = int(arg) ++ if count > 0: ++ count_seen = True ++ else: ++ print 'Illegal 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.0 ++ ++ if not interval_seen: ++ print_iostat_summary(old_mountstats, mountstats, devices, sample_time, which) ++ return ++ ++ if count_seen: ++ while count != 0: ++ print_iostat_summary(old_mountstats, mountstats, devices, sample_time, which) ++ 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, which) ++ 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: ++ iostat_command(prog) ++except KeyboardInterrupt: ++ print 'Caught ^C... exiting' ++ sys.exit(1) ++ ++sys.exit(0) diff --git a/nfs-utils-1.1.2-nfsstat-counters.patch b/nfs-utils-1.1.2-nfsstat-counters.patch new file mode 100644 index 0000000..70c0134 --- /dev/null +++ b/nfs-utils-1.1.2-nfsstat-counters.patch @@ -0,0 +1,60 @@ +commit 5be04020788598cb811e51c4b1342cf0796cbb65 +Author: Jeff Layton +Date: Mon Jun 23 07:21:52 2008 -0400 + + The nfsstat program reads /proc/net/rpc/* files to gets info about + calls. This info is output as unsigned numbers (at least on any + relatively recent kernel). When nfsstat prints these numbers, they are + printed as signed integers. When the call counters reach 2^31, things + start being printed as negative numbers. + + This patch changes nfsstat to read and print all counters as unsigned + integers. Tested by hacking up a kernel to initialize call counters to + 2^31+1. + + Thanks to Takafumi Miki for the initial version of this patch. + + Signed-off-by: Jeff Layton + Signed-off-by: Steve Dickson + +diff --git a/utils/nfsstat/nfsstat.c b/utils/nfsstat/nfsstat.c +index d2cca8d..1517414 100644 +--- a/utils/nfsstat/nfsstat.c ++++ b/utils/nfsstat/nfsstat.c +@@ -539,7 +539,7 @@ print_numbers(const char *hdr, unsigned int *info, unsigned int nr) + + fputs(hdr, stdout); + for (i = 0; i < nr; i++) +- printf("%s%-8d", i? " " : "", info[i]); ++ printf("%s%-8u", i? " " : "", info[i]); + printf("\n"); + } + +@@ -562,7 +562,7 @@ print_callstats(const char *hdr, const char **names, + printf("\n"); + for (j = 0; j < 6 && i + j < nr; j++) { + pct = ((unsigned long long) info[i+j]*100)/total; +- printf("%-8d%3llu%% ", info[i+j], pct); ++ printf("%-8u%3llu%% ", info[i+j], pct); + } + printf("\n"); + } +@@ -604,7 +604,7 @@ parse_raw_statfile(const char *name, struct statinfo *statp) + for (i = 0; i < cnt; i++) { + if (!(sp = strtok(NULL, " \t"))) + break; +- ip->valptr[i] = atoi(sp); ++ ip->valptr[i] = (unsigned int) strtoul(sp, NULL, 0); + total += ip->valptr[i]; + } + ip->valptr[cnt - 1] = total; +@@ -618,7 +618,8 @@ parse_raw_statfile(const char *name, struct statinfo *statp) + static int + parse_pretty_statfile(const char *filename, struct statinfo *info) + { +- int numvals, curindex, numconsumed, n, sum, err = 1; ++ int numvals, curindex, numconsumed, n, err = 1; ++ unsigned int sum; + char buf[4096], *bufp, *fmt, is_proc; + FILE *fp = NULL; + struct statinfo *ip; diff --git a/nfs-utils.spec b/nfs-utils.spec index 49e3226..130888d 100644 --- a/nfs-utils.spec +++ b/nfs-utils.spec @@ -47,8 +47,11 @@ Patch112: nfs-utils-1.1.2-mount-statd-chk.patch Patch113: nfs-utils-1.1.2-mount-cleanup.patch Patch114: nfs-utils-1.1.2-mount-error-reporting.patch Patch115: nfs-utils-1.1.2-nfsstat-m-arg.patch - - +Patch116: nfs-utils-1.1.2-nfsstat-counters.patch +Patch117: nfs-utils-1.1.2-mountstats.patch +Patch118: nfs-utils-1.1.2-mountstats-rdma.patch +Patch119: nfs-utils-1.1.2-nfs-iostat.patch +Patch120: nfs-utils-1.1.2-nfs-iostat-rdma.patch %if %{enablefscache} Patch90: nfs-utils-1.1.0-mount-fsc.patch @@ -124,6 +127,11 @@ This package also contains the mount.nfs and umount.nfs program. %patch113 -p1 %patch114 -p1 %patch115 -p1 +%patch116 -p1 +%patch117 -p1 +%patch118 -p1 +%patch119 -p1 +%patch120 -p1 %if %{enablefscache} %patch90 -p1 @@ -143,7 +151,7 @@ export PIE sh -x autogen.sh -CFLAGS="`echo $RPM_OPT_FLAGS $ARCH_OPT_FLAGS $PIE`" +CFLAGS="`echo $RPM_OPT_FLAGS $ARCH_OPT_FLAGS $PIE -D_FILE_OFFSET_BITS=64`" %configure \ CFLAGS="$CFLAGS" \ CPPFLAGS="$DEFINES" \ @@ -291,6 +299,12 @@ fi %attr(4755,root,root) /sbin/umount.nfs4 %changelog +* Mon Jun 23 2008 Steve Dickson 1.1.2-7 +- Added -D_FILE_OFFSET_BITS=64 to CFLAGS +- make nfsstat read and print stats as unsigned integers +- Added (but not installed) the mountstats and nfs-iostat + python scripts. + * Fri Jun 6 2008 Steve Dickson 1.1.2-6 - Added 5 (111 thru 115) upstream patches that fixed things mostly in the text mounting code.