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[1] == 'rdma':
120 self.__rpc_data['port'] = words[2]
121 self.__rpc_data['bind_count'] = int(words[3])
122 self.__rpc_data['connect_count'] = int(words[4])
123 self.__rpc_data['connect_time'] = int(words[5])
124 self.__rpc_data['idle_time'] = int(words[6])
125 self.__rpc_data['rpcsends'] = int(words[7])
126 self.__rpc_data['rpcreceives'] = int(words[8])
127 self.__rpc_data['badxids'] = int(words[9])
128 self.__rpc_data['backlogutil'] = int(words[10])
129 self.__rpc_data['read_chunks'] = int(words[11])
130 self.__rpc_data['write_chunks'] = int(words[12])
131 self.__rpc_data['reply_chunks'] = int(words[13])
132 self.__rpc_data['total_rdma_req'] = int(words[14])
133 self.__rpc_data['total_rdma_rep'] = int(words[15])
134 self.__rpc_data['pullup'] = int(words[16])
135 self.__rpc_data['fixup'] = int(words[17])
136 self.__rpc_data['hardway'] = int(words[18])
137 self.__rpc_data['failed_marshal'] = int(words[19])
138 self.__rpc_data['bad_reply'] = int(words[20])
139 elif words[0] == 'per-op':
140 self.__rpc_data['per-op'] = words
143 self.__rpc_data['ops'] += [op]
144 self.__rpc_data[op] = [long(word) for word in words[1:]]
146 def parse_stats(self, lines):
147 """Turn a list of lines from a mount stat file into a
148 dictionary full of stats, keyed by name
155 if (not found and words[0] != 'RPC'):
156 self.__parse_nfs_line(words)
160 self.__parse_rpc_line(words)
162 def is_nfs_mountpoint(self):
163 """Return True if this is an NFS or NFSv4 mountpoint,
164 otherwise return False
166 if self.__nfs_data['fstype'] == 'nfs':
168 elif self.__nfs_data['fstype'] == 'nfs4':
172 def display_nfs_options(self):
173 """Pretty-print the NFS options
175 print 'Stats for %s mounted on %s:' % \
176 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
178 print ' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])
179 print ' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])
180 if self.__nfs_data.has_key('nfsv4flags'):
181 print ' NFSv4 capability flags: %s' % ','.join(self.__nfs_data['nfsv4flags'])
182 if self.__nfs_data.has_key('pseudoflavor'):
183 print ' NFS security flavor: %d pseudoflavor: %d' % \
184 (self.__nfs_data['flavor'], self.__nfs_data['pseudoflavor'])
186 print ' NFS security flavor: %d' % self.__nfs_data['flavor']
188 def display_nfs_events(self):
189 """Pretty-print the NFS event counters
192 print 'Cache events:'
193 print ' data cache invalidated %d times' % self.__nfs_data['datainvalidates']
194 print ' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates']
195 print ' inodes synced %d times' % self.__nfs_data['syncinodes']
198 print ' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates']
199 print ' VFS requested %d dentry revalidations' % self.__nfs_data['dentryrevalidates']
201 print ' VFS called nfs_readdir() %d times' % self.__nfs_data['vfsreaddir']
202 print ' VFS called nfs_lookup() %d times' % self.__nfs_data['vfslookup']
203 print ' VFS called nfs_permission() %d times' % self.__nfs_data['vfspermission']
204 print ' VFS called nfs_file_open() %d times' % self.__nfs_data['vfsopen']
205 print ' VFS called nfs_file_flush() %d times' % self.__nfs_data['vfsflush']
206 print ' VFS called nfs_lock() %d times' % self.__nfs_data['vfslock']
207 print ' VFS called nfs_fsync() %d times' % self.__nfs_data['vfsfsync']
208 print ' VFS called nfs_file_release() %d times' % self.__nfs_data['vfsrelease']
211 print ' VFS called nfs_readpage() %d times' % self.__nfs_data['vfsreadpage']
212 print ' VFS called nfs_readpages() %d times' % self.__nfs_data['vfsreadpages']
213 print ' VFS called nfs_writepage() %d times' % self.__nfs_data['vfswritepage']
214 print ' VFS called nfs_writepages() %d times' % self.__nfs_data['vfswritepages']
216 print 'Generic NFS counters:'
217 print ' File size changing operations:'
218 print ' truncating SETATTRs: %d extending WRITEs: %d' % \
219 (self.__nfs_data['setattrtrunc'], self.__nfs_data['extendwrite'])
220 print ' %d silly renames' % self.__nfs_data['sillyrenames']
221 print ' short reads: %d short writes: %d' % \
222 (self.__nfs_data['shortreads'], self.__nfs_data['shortwrites'])
223 print ' NFSERR_DELAYs from server: %d' % self.__nfs_data['delay']
225 def display_nfs_bytes(self):
226 """Pretty-print the NFS event counters
229 print 'NFS byte counts:'
230 print ' applications read %d bytes via read(2)' % self.__nfs_data['normalreadbytes']
231 print ' applications wrote %d bytes via write(2)' % self.__nfs_data['normalwritebytes']
232 print ' applications read %d bytes via O_DIRECT read(2)' % self.__nfs_data['directreadbytes']
233 print ' applications wrote %d bytes via O_DIRECT write(2)' % self.__nfs_data['directwritebytes']
234 print ' client read %d bytes via NFS READ' % self.__nfs_data['serverreadbytes']
235 print ' client wrote %d bytes via NFS WRITE' % self.__nfs_data['serverwritebytes']
237 def display_rpc_generic_stats(self):
238 """Pretty-print the generic RPC stats
240 sends = self.__rpc_data['rpcsends']
243 print 'RPC statistics:'
245 print ' %d RPC requests sent, %d RPC replies received (%d XIDs not found)' % \
246 (sends, self.__rpc_data['rpcreceives'], self.__rpc_data['badxids'])
248 print ' average backlog queue length: %d' % \
249 (float(self.__rpc_data['backlogutil']) / sends)
251 def display_rpc_op_stats(self):
252 """Pretty-print the per-op stats
254 sends = self.__rpc_data['rpcsends']
256 # XXX: these should be sorted by 'count'
258 for op in self.__rpc_data['ops']:
259 stats = self.__rpc_data[op]
261 retrans = stats[1] - count
264 print '\t%d ops (%d%%)' % \
265 (count, ((count * 100) / sends)),
266 print '\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)),
267 print '\t%d major timeouts' % stats[2]
268 print '\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \
269 (stats[3] / count, stats[4] / count)
270 print '\tbacklog wait: %f' % (float(stats[5]) / count),
271 print '\tRTT: %f' % (float(stats[6]) / count),
272 print '\ttotal execute time: %f (milliseconds)' % \
273 (float(stats[7]) / count)
275 def compare_iostats(self, old_stats):
276 """Return the difference between two sets of stats
278 result = DeviceData()
280 # copy self into result
281 for key, value in self.__nfs_data.iteritems():
282 result.__nfs_data[key] = value
283 for key, value in self.__rpc_data.iteritems():
284 result.__rpc_data[key] = value
286 # compute the difference of each item in the list
287 # note the copy loop above does not copy the lists, just
288 # the reference to them. so we build new lists here
289 # for the result object.
290 for op in result.__rpc_data['ops']:
291 result.__rpc_data[op] = map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])
293 # update the remaining keys we care about
294 result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
295 result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil']
296 result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes']
297 result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes']
301 def display_iostats(self, sample_time):
302 """Display NFS and RPC stats in an iostat-like way
304 sends = float(self.__rpc_data['rpcsends'])
306 sample_time = float(self.__nfs_data['age'])
309 print '%s mounted on %s:' % \
310 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
312 print '\top/s\trpc bklog'
313 print '\t%.2f' % (sends / sample_time),
316 ((float(self.__rpc_data['backlogutil']) / sends) / sample_time)
320 # reads: ops/s, kB/s, avg rtt, and avg exe
321 # XXX: include avg xfer size and retransmits?
322 read_rpc_stats = self.__rpc_data['READ']
323 ops = float(read_rpc_stats[0])
324 kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024
325 rtt = float(read_rpc_stats[6])
326 exe = float(read_rpc_stats[7])
328 print '\treads:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)'
329 print '\t\t%.2f' % (ops / sample_time),
330 print '\t\t%.2f' % (kilobytes / sample_time),
332 print '\t\t%.2f' % (rtt / ops),
333 print '\t\t%.2f' % (exe / ops)
338 # writes: ops/s, kB/s, avg rtt, and avg exe
339 # XXX: include avg xfer size and retransmits?
340 write_rpc_stats = self.__rpc_data['WRITE']
341 ops = float(write_rpc_stats[0])
342 kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024
343 rtt = float(write_rpc_stats[6])
344 exe = float(write_rpc_stats[7])
346 print '\twrites:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)'
347 print '\t\t%.2f' % (ops / sample_time),
348 print '\t\t%.2f' % (kilobytes / sample_time),
350 print '\t\t%.2f' % (rtt / ops),
351 print '\t\t%.2f' % (exe / ops)
356 def parse_stats_file(filename):
357 """pop the contents of a mountstats file into a dictionary,
358 keyed by mount point. each value object is a list of the
359 lines in the mountstats file corresponding to the mount
360 point named in the key.
366 for line in f.readlines():
370 if words[0] == 'device':
372 new = [ line.strip() ]
374 new += [ line.strip() ]
380 def print_mountstats_help(name):
381 print 'usage: %s [ options ] <mount point>' % name
383 print ' Version %s' % Mountstats_version
385 print ' Display NFS client per-mount statistics.'
387 print ' --version display the version of this command'
388 print ' --nfs display only the NFS statistics'
389 print ' --rpc display only the RPC statistics'
390 print ' --start sample and save statistics'
391 print ' --end resample statistics and compare them with saved'
394 def mountstats_command():
395 """Mountstats command
402 if arg in ['-h', '--help', 'help', 'usage']:
403 print_mountstats_help(prog)
406 if arg in ['-v', '--version', 'version']:
407 print '%s version %s' % (sys.argv[0], Mountstats_version)
410 if arg in ['-n', '--nfs']:
414 if arg in ['-r', '--rpc']:
418 if arg in ['-s', '--start']:
419 raise Exception, 'Sampling is not yet implemented'
421 if arg in ['-e', '--end']:
422 raise Exception, 'Sampling is not yet implemented'
424 if arg == sys.argv[0]:
429 if mountpoints == []:
430 print_mountstats_help(prog)
433 if rpc_only == True and nfs_only == True:
434 print_mountstats_help(prog)
437 mountstats = parse_stats_file('/proc/self/mountstats')
439 for mp in mountpoints:
440 if mp not in mountstats:
441 print 'Statistics for mount point %s not found' % mp
445 stats.parse_stats(mountstats[mp])
447 if not stats.is_nfs_mountpoint():
448 print 'Mount point %s exists but is not an NFS mount' % mp
452 stats.display_nfs_options()
453 stats.display_nfs_events()
454 stats.display_nfs_bytes()
456 stats.display_rpc_generic_stats()
457 stats.display_rpc_op_stats()
459 stats.display_nfs_options()
460 stats.display_nfs_bytes()
461 stats.display_rpc_generic_stats()
462 stats.display_rpc_op_stats()
464 def print_nfsstat_help(name):
465 print 'usage: %s [ options ]' % name
467 print ' Version %s' % Mountstats_version
469 print ' nfsstat-like program that uses NFS client per-mount statistics.'
472 def nfsstat_command():
473 print_nfsstat_help(prog)
475 def print_iostat_help(name):
476 print 'usage: %s [ <interval> [ <count> ] ] [ <mount point> ] ' % name
478 print ' Version %s' % Mountstats_version
480 print ' iostat-like program to display NFS client per-mount statistics.'
482 print ' The <interval> parameter specifies the amount of time in seconds between'
483 print ' each report. The first report contains statistics for the time since each'
484 print ' file system was mounted. Each subsequent report contains statistics'
485 print ' collected during the interval since the previous report.'
487 print ' If the <count> parameter is specified, the value of <count> determines the'
488 print ' number of reports generated at <interval> seconds apart. If the interval'
489 print ' parameter is specified without the <count> parameter, the command generates'
490 print ' reports continuously.'
492 print ' If one or more <mount point> names are specified, statistics for only these'
493 print ' mount points will be displayed. Otherwise, all NFS mount points on the'
494 print ' client are listed.'
497 def print_iostat_summary(old, new, devices, time):
498 for device in devices:
500 stats.parse_stats(new[device])
502 stats.display_iostats(time)
504 old_stats = DeviceData()
505 old_stats.parse_stats(old[device])
506 diff_stats = stats.compare_iostats(old_stats)
507 diff_stats.display_iostats(time)
509 def iostat_command():
510 """iostat-like command for NFS mount points
512 mountstats = parse_stats_file('/proc/self/mountstats')
514 interval_seen = False
518 if arg in ['-h', '--help', 'help', 'usage']:
519 print_iostat_help(prog)
522 if arg in ['-v', '--version', 'version']:
523 print '%s version %s' % (sys.argv[0], Mountstats_version)
526 if arg == sys.argv[0]:
529 if arg in mountstats:
531 elif not interval_seen:
536 print 'Illegal <interval> value'
543 print 'Illegal <count> value'
546 # make certain devices contains only NFS mount points
549 for device in devices:
551 stats.parse_stats(mountstats[device])
552 if stats.is_nfs_mountpoint():
556 for device, descr in mountstats.iteritems():
558 stats.parse_stats(descr)
559 if stats.is_nfs_mountpoint():
561 if len(devices) == 0:
562 print 'No NFS mount points were found'
565 old_mountstats = None
568 if not interval_seen:
569 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
574 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
575 old_mountstats = mountstats
577 sample_time = interval
578 mountstats = parse_stats_file('/proc/self/mountstats')
582 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
583 old_mountstats = mountstats
585 sample_time = interval
586 mountstats = parse_stats_file('/proc/self/mountstats')
591 prog = os.path.basename(sys.argv[0])
594 if prog == 'mountstats':
596 elif prog == 'ms-nfsstat':
598 elif prog == 'ms-iostat':
600 except KeyboardInterrupt:
601 print 'Caught ^C... exiting'