3 """Parse /proc/self/mountstats and display it in human readable form
7 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 Mountstats_version = '0.2'
28 """Used for a map() function
33 """DeviceData objects provide methods for parsing and displaying
34 data for a single mount grabbed from /proc/self/mountstats
37 self.__nfs_data = dict()
38 self.__rpc_data = dict()
39 self.__rpc_data['ops'] = []
41 def __parse_nfs_line(self, words):
42 if words[0] == 'device':
43 self.__nfs_data['export'] = words[1]
44 self.__nfs_data['mountpoint'] = words[4]
45 self.__nfs_data['fstype'] = words[7]
46 if words[7].find('nfs') != -1:
47 self.__nfs_data['statvers'] = words[8]
48 elif words[0] == 'age:':
49 self.__nfs_data['age'] = long(words[1])
50 elif words[0] == 'opts:':
51 self.__nfs_data['mountoptions'] = ''.join(words[1:]).split(',')
52 elif words[0] == 'caps:':
53 self.__nfs_data['servercapabilities'] = ''.join(words[1:]).split(',')
54 elif words[0] == 'nfsv4:':
55 self.__nfs_data['nfsv4flags'] = ''.join(words[1:]).split(',')
56 elif words[0] == 'sec:':
57 keys = ''.join(words[1:]).split(',')
58 self.__nfs_data['flavor'] = int(keys[0].split('=')[1])
59 self.__nfs_data['pseudoflavor'] = 0
60 if self.__nfs_data['flavor'] == 6:
61 self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1])
62 elif words[0] == 'events:':
63 self.__nfs_data['inoderevalidates'] = int(words[1])
64 self.__nfs_data['dentryrevalidates'] = int(words[2])
65 self.__nfs_data['datainvalidates'] = int(words[3])
66 self.__nfs_data['attrinvalidates'] = int(words[4])
67 self.__nfs_data['syncinodes'] = int(words[5])
68 self.__nfs_data['vfsopen'] = int(words[6])
69 self.__nfs_data['vfslookup'] = int(words[7])
70 self.__nfs_data['vfspermission'] = int(words[8])
71 self.__nfs_data['vfsreadpage'] = int(words[9])
72 self.__nfs_data['vfsreadpages'] = int(words[10])
73 self.__nfs_data['vfswritepage'] = int(words[11])
74 self.__nfs_data['vfswritepages'] = int(words[12])
75 self.__nfs_data['vfsreaddir'] = int(words[13])
76 self.__nfs_data['vfsflush'] = int(words[14])
77 self.__nfs_data['vfsfsync'] = int(words[15])
78 self.__nfs_data['vfslock'] = int(words[16])
79 self.__nfs_data['vfsrelease'] = int(words[17])
80 self.__nfs_data['setattrtrunc'] = int(words[18])
81 self.__nfs_data['extendwrite'] = int(words[19])
82 self.__nfs_data['sillyrenames'] = int(words[20])
83 self.__nfs_data['shortreads'] = int(words[21])
84 self.__nfs_data['shortwrites'] = int(words[22])
85 self.__nfs_data['delay'] = int(words[23])
86 elif words[0] == 'bytes:':
87 self.__nfs_data['normalreadbytes'] = long(words[1])
88 self.__nfs_data['normalwritebytes'] = long(words[2])
89 self.__nfs_data['directreadbytes'] = long(words[3])
90 self.__nfs_data['directwritebytes'] = long(words[4])
91 self.__nfs_data['serverreadbytes'] = long(words[5])
92 self.__nfs_data['serverwritebytes'] = long(words[6])
94 def __parse_rpc_line(self, words):
96 self.__rpc_data['statsvers'] = float(words[3])
97 self.__rpc_data['programversion'] = words[5]
98 elif words[0] == 'xprt:':
99 self.__rpc_data['protocol'] = words[1]
100 if words[1] == 'udp':
101 self.__rpc_data['port'] = int(words[2])
102 self.__rpc_data['bind_count'] = int(words[3])
103 self.__rpc_data['rpcsends'] = int(words[4])
104 self.__rpc_data['rpcreceives'] = int(words[5])
105 self.__rpc_data['badxids'] = int(words[6])
106 self.__rpc_data['inflightsends'] = long(words[7])
107 self.__rpc_data['backlogutil'] = long(words[8])
108 elif words[1] == 'tcp':
109 self.__rpc_data['port'] = words[2]
110 self.__rpc_data['bind_count'] = int(words[3])
111 self.__rpc_data['connect_count'] = int(words[4])
112 self.__rpc_data['connect_time'] = int(words[5])
113 self.__rpc_data['idle_time'] = int(words[6])
114 self.__rpc_data['rpcsends'] = int(words[7])
115 self.__rpc_data['rpcreceives'] = int(words[8])
116 self.__rpc_data['badxids'] = int(words[9])
117 self.__rpc_data['inflightsends'] = long(words[10])
118 self.__rpc_data['backlogutil'] = int(words[11])
119 elif words[0] == 'per-op':
120 self.__rpc_data['per-op'] = words
123 self.__rpc_data['ops'] += [op]
124 self.__rpc_data[op] = [long(word) for word in words[1:]]
126 def parse_stats(self, lines):
127 """Turn a list of lines from a mount stat file into a
128 dictionary full of stats, keyed by name
135 if (not found and words[0] != 'RPC'):
136 self.__parse_nfs_line(words)
140 self.__parse_rpc_line(words)
142 def is_nfs_mountpoint(self):
143 """Return True if this is an NFS or NFSv4 mountpoint,
144 otherwise return False
146 if self.__nfs_data['fstype'] == 'nfs':
148 elif self.__nfs_data['fstype'] == 'nfs4':
152 def display_nfs_options(self):
153 """Pretty-print the NFS options
155 print 'Stats for %s mounted on %s:' % \
156 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
158 print ' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])
159 print ' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])
160 if self.__nfs_data.has_key('nfsv4flags'):
161 print ' NFSv4 capability flags: %s' % ','.join(self.__nfs_data['nfsv4flags'])
162 if self.__nfs_data.has_key('pseudoflavor'):
163 print ' NFS security flavor: %d pseudoflavor: %d' % \
164 (self.__nfs_data['flavor'], self.__nfs_data['pseudoflavor'])
166 print ' NFS security flavor: %d' % self.__nfs_data['flavor']
168 def display_nfs_events(self):
169 """Pretty-print the NFS event counters
172 print 'Cache events:'
173 print ' data cache invalidated %d times' % self.__nfs_data['datainvalidates']
174 print ' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates']
175 print ' inodes synced %d times' % self.__nfs_data['syncinodes']
178 print ' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates']
179 print ' VFS requested %d dentry revalidations' % self.__nfs_data['dentryrevalidates']
181 print ' VFS called nfs_readdir() %d times' % self.__nfs_data['vfsreaddir']
182 print ' VFS called nfs_lookup() %d times' % self.__nfs_data['vfslookup']
183 print ' VFS called nfs_permission() %d times' % self.__nfs_data['vfspermission']
184 print ' VFS called nfs_file_open() %d times' % self.__nfs_data['vfsopen']
185 print ' VFS called nfs_file_flush() %d times' % self.__nfs_data['vfsflush']
186 print ' VFS called nfs_lock() %d times' % self.__nfs_data['vfslock']
187 print ' VFS called nfs_fsync() %d times' % self.__nfs_data['vfsfsync']
188 print ' VFS called nfs_file_release() %d times' % self.__nfs_data['vfsrelease']
191 print ' VFS called nfs_readpage() %d times' % self.__nfs_data['vfsreadpage']
192 print ' VFS called nfs_readpages() %d times' % self.__nfs_data['vfsreadpages']
193 print ' VFS called nfs_writepage() %d times' % self.__nfs_data['vfswritepage']
194 print ' VFS called nfs_writepages() %d times' % self.__nfs_data['vfswritepages']
196 print 'Generic NFS counters:'
197 print ' File size changing operations:'
198 print ' truncating SETATTRs: %d extending WRITEs: %d' % \
199 (self.__nfs_data['setattrtrunc'], self.__nfs_data['extendwrite'])
200 print ' %d silly renames' % self.__nfs_data['sillyrenames']
201 print ' short reads: %d short writes: %d' % \
202 (self.__nfs_data['shortreads'], self.__nfs_data['shortwrites'])
203 print ' NFSERR_DELAYs from server: %d' % self.__nfs_data['delay']
205 def display_nfs_bytes(self):
206 """Pretty-print the NFS event counters
209 print 'NFS byte counts:'
210 print ' applications read %d bytes via read(2)' % self.__nfs_data['normalreadbytes']
211 print ' applications wrote %d bytes via write(2)' % self.__nfs_data['normalwritebytes']
212 print ' applications read %d bytes via O_DIRECT read(2)' % self.__nfs_data['directreadbytes']
213 print ' applications wrote %d bytes via O_DIRECT write(2)' % self.__nfs_data['directwritebytes']
214 print ' client read %d bytes via NFS READ' % self.__nfs_data['serverreadbytes']
215 print ' client wrote %d bytes via NFS WRITE' % self.__nfs_data['serverwritebytes']
217 def display_rpc_generic_stats(self):
218 """Pretty-print the generic RPC stats
220 sends = self.__rpc_data['rpcsends']
223 print 'RPC statistics:'
225 print ' %d RPC requests sent, %d RPC replies received (%d XIDs not found)' % \
226 (sends, self.__rpc_data['rpcreceives'], self.__rpc_data['badxids'])
228 print ' average backlog queue length: %d' % \
229 (float(self.__rpc_data['backlogutil']) / sends)
231 def display_rpc_op_stats(self):
232 """Pretty-print the per-op stats
234 sends = self.__rpc_data['rpcsends']
236 # XXX: these should be sorted by 'count'
238 for op in self.__rpc_data['ops']:
239 stats = self.__rpc_data[op]
241 retrans = stats[1] - count
244 print '\t%d ops (%d%%)' % \
245 (count, ((count * 100) / sends)),
246 print '\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)),
247 print '\t%d major timeouts' % stats[2]
248 print '\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \
249 (stats[3] / count, stats[4] / count)
250 print '\tbacklog wait: %f' % (float(stats[5]) / count),
251 print '\tRTT: %f' % (float(stats[6]) / count),
252 print '\ttotal execute time: %f (milliseconds)' % \
253 (float(stats[7]) / count)
255 def compare_iostats(self, old_stats):
256 """Return the difference between two sets of stats
258 result = DeviceData()
260 # copy self into result
261 for key, value in self.__nfs_data.iteritems():
262 result.__nfs_data[key] = value
263 for key, value in self.__rpc_data.iteritems():
264 result.__rpc_data[key] = value
266 # compute the difference of each item in the list
267 # note the copy loop above does not copy the lists, just
268 # the reference to them. so we build new lists here
269 # for the result object.
270 for op in result.__rpc_data['ops']:
271 result.__rpc_data[op] = map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])
273 # update the remaining keys we care about
274 result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
275 result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil']
276 result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes']
277 result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes']
281 def display_iostats(self, sample_time):
282 """Display NFS and RPC stats in an iostat-like way
284 sends = float(self.__rpc_data['rpcsends'])
286 sample_time = float(self.__nfs_data['age'])
289 print '%s mounted on %s:' % \
290 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
292 print '\top/s\trpc bklog'
293 print '\t%.2f' % (sends / sample_time),
296 ((float(self.__rpc_data['backlogutil']) / sends) / sample_time)
300 # reads: ops/s, Kb/s, avg rtt, and avg exe
301 # XXX: include avg xfer size and retransmits?
302 read_rpc_stats = self.__rpc_data['READ']
303 ops = float(read_rpc_stats[0])
304 kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024
305 rtt = float(read_rpc_stats[6])
306 exe = float(read_rpc_stats[7])
308 print '\treads:\tops/s\t\tKb/s\t\tavg RTT (ms)\tavg exe (ms)'
309 print '\t\t%.2f' % (ops / sample_time),
310 print '\t\t%.2f' % (kilobytes / sample_time),
312 print '\t\t%.2f' % (rtt / ops),
313 print '\t\t%.2f' % (exe / ops)
318 # writes: ops/s, Kb/s, avg rtt, and avg exe
319 # XXX: include avg xfer size and retransmits?
320 write_rpc_stats = self.__rpc_data['WRITE']
321 ops = float(write_rpc_stats[0])
322 kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024
323 rtt = float(write_rpc_stats[6])
324 exe = float(write_rpc_stats[7])
326 print '\twrites:\tops/s\t\tKb/s\t\tavg RTT (ms)\tavg exe (ms)'
327 print '\t\t%.2f' % (ops / sample_time),
328 print '\t\t%.2f' % (kilobytes / sample_time),
330 print '\t\t%.2f' % (rtt / ops),
331 print '\t\t%.2f' % (exe / ops)
336 def parse_stats_file(filename):
337 """pop the contents of a mountstats file into a dictionary,
338 keyed by mount point. each value object is a list of the
339 lines in the mountstats file corresponding to the mount
340 point named in the key.
346 for line in f.readlines():
350 if words[0] == 'device':
352 new = [ line.strip() ]
354 new += [ line.strip() ]
360 def print_mountstats_help(name):
361 print 'usage: %s [ options ] <mount point>' % name
363 print ' Version %s' % Mountstats_version
365 print ' Display NFS client per-mount statistics.'
367 print ' --version display the version of this command'
368 print ' --nfs display only the NFS statistics'
369 print ' --rpc display only the RPC statistics'
370 print ' --start sample and save statistics'
371 print ' --end resample statistics and compare them with saved'
374 def mountstats_command():
375 """Mountstats command
382 if arg in ['-h', '--help', 'help', 'usage']:
383 print_mountstats_help(prog)
386 if arg in ['-v', '--version', 'version']:
387 print '%s version %s' % (sys.argv[0], Mountstats_version)
390 if arg in ['-n', '--nfs']:
394 if arg in ['-r', '--rpc']:
398 if arg in ['-s', '--start']:
399 raise Exception, 'Sampling is not yet implemented'
401 if arg in ['-e', '--end']:
402 raise Exception, 'Sampling is not yet implemented'
404 if arg == sys.argv[0]:
409 if mountpoints == []:
410 print_mountstats_help(prog)
413 if rpc_only == True and nfs_only == True:
414 print_mountstats_help(prog)
417 mountstats = parse_stats_file('/proc/self/mountstats')
419 for mp in mountpoints:
420 if mp not in mountstats:
421 print 'Statistics for mount point %s not found' % mp
425 stats.parse_stats(mountstats[mp])
427 if not stats.is_nfs_mountpoint():
428 print 'Mount point %s exists but is not an NFS mount' % mp
432 stats.display_nfs_options()
433 stats.display_nfs_events()
434 stats.display_nfs_bytes()
436 stats.display_rpc_generic_stats()
437 stats.display_rpc_op_stats()
439 stats.display_nfs_options()
440 stats.display_nfs_bytes()
441 stats.display_rpc_generic_stats()
442 stats.display_rpc_op_stats()
444 def print_nfsstat_help(name):
445 print 'usage: %s [ options ]' % name
447 print ' Version %s' % Mountstats_version
449 print ' nfsstat-like program that uses NFS client per-mount statistics.'
452 def nfsstat_command():
453 print_nfsstat_help(prog)
455 def print_iostat_help(name):
456 print 'usage: %s [ <interval> [ <count> ] ] [ <mount point> ] ' % name
458 print ' Version %s' % Mountstats_version
460 print ' iostat-like program to display NFS client per-mount statistics.'
462 print ' The <interval> parameter specifies the amount of time in seconds between'
463 print ' each report. The first report contains statistics for the time since each'
464 print ' file system was mounted. Each subsequent report contains statistics'
465 print ' collected during the interval since the previous report.'
467 print ' If the <count> parameter is specified, the value of <count> determines the'
468 print ' number of reports generated at <interval> seconds apart. If the interval'
469 print ' parameter is specified without the <count> parameter, the command generates'
470 print ' reports continuously.'
472 print ' If one or more <mount point> names are specified, statistics for only these'
473 print ' mount points will be displayed. Otherwise, all NFS mount points on the'
474 print ' client are listed.'
477 def print_iostat_summary(old, new, devices, time):
478 for device in devices:
480 stats.parse_stats(new[device])
482 stats.display_iostats(time)
484 old_stats = DeviceData()
485 old_stats.parse_stats(old[device])
486 diff_stats = stats.compare_iostats(old_stats)
487 diff_stats.display_iostats(time)
489 def iostat_command():
490 """iostat-like command for NFS mount points
492 mountstats = parse_stats_file('/proc/self/mountstats')
494 interval_seen = False
498 if arg in ['-h', '--help', 'help', 'usage']:
499 print_iostat_help(prog)
502 if arg in ['-v', '--version', 'version']:
503 print '%s version %s' % (sys.argv[0], Mountstats_version)
506 if arg == sys.argv[0]:
509 if arg in mountstats:
511 elif not interval_seen:
516 print 'Illegal <interval> value'
523 print 'Illegal <count> value'
526 # make certain devices contains only NFS mount points
529 for device in devices:
531 stats.parse_stats(mountstats[device])
532 if stats.is_nfs_mountpoint():
536 for device, descr in mountstats.iteritems():
538 stats.parse_stats(descr)
539 if stats.is_nfs_mountpoint():
541 if len(devices) == 0:
542 print 'No NFS mount points were found'
545 old_mountstats = None
548 if not interval_seen:
549 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
554 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
555 old_mountstats = mountstats
557 sample_time = interval
558 mountstats = parse_stats_file('/proc/self/mountstats')
562 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
563 old_mountstats = mountstats
565 sample_time = interval
566 mountstats = parse_stats_file('/proc/self/mountstats')
571 prog = os.path.basename(sys.argv[0])
574 if prog == 'mountstats':
576 elif prog == 'ms-nfsstat':
578 elif prog == 'ms-iostat':
580 except KeyboardInterrupt:
581 print 'Caught ^C... exiting'