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., 51 Franklin Street, Fifth Floor, Boston,
26 Mountstats_version = '0.2'
29 """Used for a map() function
34 """DeviceData objects provide methods for parsing and displaying
35 data for a single mount grabbed from /proc/self/mountstats
38 self.__nfs_data = dict()
39 self.__rpc_data = dict()
40 self.__rpc_data['ops'] = []
42 def __parse_nfs_line(self, words):
43 if words[0] == 'device':
44 self.__nfs_data['export'] = words[1]
45 self.__nfs_data['mountpoint'] = words[4]
46 self.__nfs_data['fstype'] = words[7]
47 if words[7].find('nfs') != -1:
48 self.__nfs_data['statvers'] = words[8]
49 elif 'nfs' in words or 'nfs4' in words:
50 self.__nfs_data['export'] = words[0]
51 self.__nfs_data['mountpoint'] = words[3]
52 self.__nfs_data['fstype'] = words[6]
53 if words[6].find('nfs') != -1:
54 self.__nfs_data['statvers'] = words[7]
55 elif words[0] == 'age:':
56 self.__nfs_data['age'] = long(words[1])
57 elif words[0] == 'opts:':
58 self.__nfs_data['mountoptions'] = ''.join(words[1:]).split(',')
59 elif words[0] == 'caps:':
60 self.__nfs_data['servercapabilities'] = ''.join(words[1:]).split(',')
61 elif words[0] == 'nfsv4:':
62 self.__nfs_data['nfsv4flags'] = ''.join(words[1:]).split(',')
63 elif words[0] == 'sec:':
64 keys = ''.join(words[1:]).split(',')
65 self.__nfs_data['flavor'] = int(keys[0].split('=')[1])
66 self.__nfs_data['pseudoflavor'] = 0
67 if self.__nfs_data['flavor'] == 6:
68 self.__nfs_data['pseudoflavor'] = int(keys[1].split('=')[1])
69 elif words[0] == 'events:':
70 self.__nfs_data['inoderevalidates'] = int(words[1])
71 self.__nfs_data['dentryrevalidates'] = int(words[2])
72 self.__nfs_data['datainvalidates'] = int(words[3])
73 self.__nfs_data['attrinvalidates'] = int(words[4])
74 self.__nfs_data['syncinodes'] = int(words[5])
75 self.__nfs_data['vfsopen'] = int(words[6])
76 self.__nfs_data['vfslookup'] = int(words[7])
77 self.__nfs_data['vfspermission'] = int(words[8])
78 self.__nfs_data['vfsreadpage'] = int(words[9])
79 self.__nfs_data['vfsreadpages'] = int(words[10])
80 self.__nfs_data['vfswritepage'] = int(words[11])
81 self.__nfs_data['vfswritepages'] = int(words[12])
82 self.__nfs_data['vfsreaddir'] = int(words[13])
83 self.__nfs_data['vfsflush'] = int(words[14])
84 self.__nfs_data['vfsfsync'] = int(words[15])
85 self.__nfs_data['vfslock'] = int(words[16])
86 self.__nfs_data['vfsrelease'] = int(words[17])
87 self.__nfs_data['setattrtrunc'] = int(words[18])
88 self.__nfs_data['extendwrite'] = int(words[19])
89 self.__nfs_data['sillyrenames'] = int(words[20])
90 self.__nfs_data['shortreads'] = int(words[21])
91 self.__nfs_data['shortwrites'] = int(words[22])
92 self.__nfs_data['delay'] = int(words[23])
93 elif words[0] == 'bytes:':
94 self.__nfs_data['normalreadbytes'] = long(words[1])
95 self.__nfs_data['normalwritebytes'] = long(words[2])
96 self.__nfs_data['directreadbytes'] = long(words[3])
97 self.__nfs_data['directwritebytes'] = long(words[4])
98 self.__nfs_data['serverreadbytes'] = long(words[5])
99 self.__nfs_data['serverwritebytes'] = long(words[6])
101 def __parse_rpc_line(self, words):
102 if words[0] == 'RPC':
103 self.__rpc_data['statsvers'] = float(words[3])
104 self.__rpc_data['programversion'] = words[5]
105 elif words[0] == 'xprt:':
106 self.__rpc_data['protocol'] = words[1]
107 if words[1] == 'udp':
108 self.__rpc_data['port'] = int(words[2])
109 self.__rpc_data['bind_count'] = int(words[3])
110 self.__rpc_data['rpcsends'] = int(words[4])
111 self.__rpc_data['rpcreceives'] = int(words[5])
112 self.__rpc_data['badxids'] = int(words[6])
113 self.__rpc_data['inflightsends'] = long(words[7])
114 self.__rpc_data['backlogutil'] = long(words[8])
115 elif words[1] == 'tcp':
116 self.__rpc_data['port'] = words[2]
117 self.__rpc_data['bind_count'] = int(words[3])
118 self.__rpc_data['connect_count'] = int(words[4])
119 self.__rpc_data['connect_time'] = int(words[5])
120 self.__rpc_data['idle_time'] = int(words[6])
121 self.__rpc_data['rpcsends'] = int(words[7])
122 self.__rpc_data['rpcreceives'] = int(words[8])
123 self.__rpc_data['badxids'] = int(words[9])
124 self.__rpc_data['inflightsends'] = long(words[10])
125 self.__rpc_data['backlogutil'] = int(words[11])
126 elif words[1] == 'rdma':
127 self.__rpc_data['port'] = words[2]
128 self.__rpc_data['bind_count'] = int(words[3])
129 self.__rpc_data['connect_count'] = int(words[4])
130 self.__rpc_data['connect_time'] = int(words[5])
131 self.__rpc_data['idle_time'] = int(words[6])
132 self.__rpc_data['rpcsends'] = int(words[7])
133 self.__rpc_data['rpcreceives'] = int(words[8])
134 self.__rpc_data['badxids'] = int(words[9])
135 self.__rpc_data['backlogutil'] = int(words[10])
136 self.__rpc_data['read_chunks'] = int(words[11])
137 self.__rpc_data['write_chunks'] = int(words[12])
138 self.__rpc_data['reply_chunks'] = int(words[13])
139 self.__rpc_data['total_rdma_req'] = int(words[14])
140 self.__rpc_data['total_rdma_rep'] = int(words[15])
141 self.__rpc_data['pullup'] = int(words[16])
142 self.__rpc_data['fixup'] = int(words[17])
143 self.__rpc_data['hardway'] = int(words[18])
144 self.__rpc_data['failed_marshal'] = int(words[19])
145 self.__rpc_data['bad_reply'] = int(words[20])
146 elif words[0] == 'per-op':
147 self.__rpc_data['per-op'] = words
150 self.__rpc_data['ops'] += [op]
151 self.__rpc_data[op] = [long(word) for word in words[1:]]
153 def parse_stats(self, lines):
154 """Turn a list of lines from a mount stat file into a
155 dictionary full of stats, keyed by name
162 if (not found and words[0] != 'RPC'):
163 self.__parse_nfs_line(words)
167 self.__parse_rpc_line(words)
169 def is_nfs_mountpoint(self):
170 """Return True if this is an NFS or NFSv4 mountpoint,
171 otherwise return False
173 if self.__nfs_data['fstype'] == 'nfs':
175 elif self.__nfs_data['fstype'] == 'nfs4':
179 def display_nfs_options(self):
180 """Pretty-print the NFS options
182 print 'Stats for %s mounted on %s:' % \
183 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
185 print ' NFS mount options: %s' % ','.join(self.__nfs_data['mountoptions'])
186 print ' NFS server capabilities: %s' % ','.join(self.__nfs_data['servercapabilities'])
187 if self.__nfs_data.has_key('nfsv4flags'):
188 print ' NFSv4 capability flags: %s' % ','.join(self.__nfs_data['nfsv4flags'])
189 if self.__nfs_data.has_key('pseudoflavor'):
190 print ' NFS security flavor: %d pseudoflavor: %d' % \
191 (self.__nfs_data['flavor'], self.__nfs_data['pseudoflavor'])
193 print ' NFS security flavor: %d' % self.__nfs_data['flavor']
195 def display_nfs_events(self):
196 """Pretty-print the NFS event counters
199 print 'Cache events:'
200 print ' data cache invalidated %d times' % self.__nfs_data['datainvalidates']
201 print ' attribute cache invalidated %d times' % self.__nfs_data['attrinvalidates']
202 print ' inodes synced %d times' % self.__nfs_data['syncinodes']
205 print ' VFS requested %d inode revalidations' % self.__nfs_data['inoderevalidates']
206 print ' VFS requested %d dentry revalidations' % self.__nfs_data['dentryrevalidates']
208 print ' VFS called nfs_readdir() %d times' % self.__nfs_data['vfsreaddir']
209 print ' VFS called nfs_lookup() %d times' % self.__nfs_data['vfslookup']
210 print ' VFS called nfs_permission() %d times' % self.__nfs_data['vfspermission']
211 print ' VFS called nfs_file_open() %d times' % self.__nfs_data['vfsopen']
212 print ' VFS called nfs_file_flush() %d times' % self.__nfs_data['vfsflush']
213 print ' VFS called nfs_lock() %d times' % self.__nfs_data['vfslock']
214 print ' VFS called nfs_fsync() %d times' % self.__nfs_data['vfsfsync']
215 print ' VFS called nfs_file_release() %d times' % self.__nfs_data['vfsrelease']
218 print ' VFS called nfs_readpage() %d times' % self.__nfs_data['vfsreadpage']
219 print ' VFS called nfs_readpages() %d times' % self.__nfs_data['vfsreadpages']
220 print ' VFS called nfs_writepage() %d times' % self.__nfs_data['vfswritepage']
221 print ' VFS called nfs_writepages() %d times' % self.__nfs_data['vfswritepages']
223 print 'Generic NFS counters:'
224 print ' File size changing operations:'
225 print ' truncating SETATTRs: %d extending WRITEs: %d' % \
226 (self.__nfs_data['setattrtrunc'], self.__nfs_data['extendwrite'])
227 print ' %d silly renames' % self.__nfs_data['sillyrenames']
228 print ' short reads: %d short writes: %d' % \
229 (self.__nfs_data['shortreads'], self.__nfs_data['shortwrites'])
230 print ' NFSERR_DELAYs from server: %d' % self.__nfs_data['delay']
232 def display_nfs_bytes(self):
233 """Pretty-print the NFS event counters
236 print 'NFS byte counts:'
237 print ' applications read %d bytes via read(2)' % self.__nfs_data['normalreadbytes']
238 print ' applications wrote %d bytes via write(2)' % self.__nfs_data['normalwritebytes']
239 print ' applications read %d bytes via O_DIRECT read(2)' % self.__nfs_data['directreadbytes']
240 print ' applications wrote %d bytes via O_DIRECT write(2)' % self.__nfs_data['directwritebytes']
241 print ' client read %d bytes via NFS READ' % self.__nfs_data['serverreadbytes']
242 print ' client wrote %d bytes via NFS WRITE' % self.__nfs_data['serverwritebytes']
244 def display_rpc_generic_stats(self):
245 """Pretty-print the generic RPC stats
247 sends = self.__rpc_data['rpcsends']
250 print 'RPC statistics:'
252 print ' %d RPC requests sent, %d RPC replies received (%d XIDs not found)' % \
253 (sends, self.__rpc_data['rpcreceives'], self.__rpc_data['badxids'])
255 print ' average backlog queue length: %d' % \
256 (float(self.__rpc_data['backlogutil']) / sends)
258 def display_rpc_op_stats(self):
259 """Pretty-print the per-op stats
261 sends = self.__rpc_data['rpcsends']
263 # XXX: these should be sorted by 'count'
265 for op in self.__rpc_data['ops']:
266 stats = self.__rpc_data[op]
268 retrans = stats[1] - count
271 print '\t%d ops (%d%%)' % \
272 (count, ((count * 100) / sends)),
273 print '\t%d retrans (%d%%)' % (retrans, ((retrans * 100) / count)),
274 print '\t%d major timeouts' % stats[2]
275 print '\tavg bytes sent per op: %d\tavg bytes received per op: %d' % \
276 (stats[3] / count, stats[4] / count)
277 print '\tbacklog wait: %f' % (float(stats[5]) / count),
278 print '\tRTT: %f' % (float(stats[6]) / count),
279 print '\ttotal execute time: %f (milliseconds)' % \
280 (float(stats[7]) / count)
282 def compare_iostats(self, old_stats):
283 """Return the difference between two sets of stats
285 result = DeviceData()
287 # copy self into result
288 for key, value in self.__nfs_data.iteritems():
289 result.__nfs_data[key] = value
290 for key, value in self.__rpc_data.iteritems():
291 result.__rpc_data[key] = value
293 # compute the difference of each item in the list
294 # note the copy loop above does not copy the lists, just
295 # the reference to them. so we build new lists here
296 # for the result object.
297 for op in result.__rpc_data['ops']:
298 result.__rpc_data[op] = map(difference, self.__rpc_data[op], old_stats.__rpc_data[op])
300 # update the remaining keys we care about
301 result.__rpc_data['rpcsends'] -= old_stats.__rpc_data['rpcsends']
302 result.__rpc_data['backlogutil'] -= old_stats.__rpc_data['backlogutil']
303 result.__nfs_data['serverreadbytes'] -= old_stats.__nfs_data['serverreadbytes']
304 result.__nfs_data['serverwritebytes'] -= old_stats.__nfs_data['serverwritebytes']
308 def display_iostats(self, sample_time):
309 """Display NFS and RPC stats in an iostat-like way
311 sends = float(self.__rpc_data['rpcsends'])
313 sample_time = float(self.__nfs_data['age'])
316 print '%s mounted on %s:' % \
317 (self.__nfs_data['export'], self.__nfs_data['mountpoint'])
319 print '\top/s\trpc bklog'
320 print '\t%.2f' % (sends / sample_time),
323 ((float(self.__rpc_data['backlogutil']) / sends) / sample_time)
327 # reads: ops/s, kB/s, avg rtt, and avg exe
328 # XXX: include avg xfer size and retransmits?
329 read_rpc_stats = self.__rpc_data['READ']
330 ops = float(read_rpc_stats[0])
331 kilobytes = float(self.__nfs_data['serverreadbytes']) / 1024
332 rtt = float(read_rpc_stats[6])
333 exe = float(read_rpc_stats[7])
335 print '\treads:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)'
336 print '\t\t%.2f' % (ops / sample_time),
337 print '\t\t%.2f' % (kilobytes / sample_time),
339 print '\t\t%.2f' % (rtt / ops),
340 print '\t\t%.2f' % (exe / ops)
345 # writes: ops/s, kB/s, avg rtt, and avg exe
346 # XXX: include avg xfer size and retransmits?
347 write_rpc_stats = self.__rpc_data['WRITE']
348 ops = float(write_rpc_stats[0])
349 kilobytes = float(self.__nfs_data['serverwritebytes']) / 1024
350 rtt = float(write_rpc_stats[6])
351 exe = float(write_rpc_stats[7])
353 print '\twrites:\tops/s\t\tkB/s\t\tavg RTT (ms)\tavg exe (ms)'
354 print '\t\t%.2f' % (ops / sample_time),
355 print '\t\t%.2f' % (kilobytes / sample_time),
357 print '\t\t%.2f' % (rtt / ops),
358 print '\t\t%.2f' % (exe / ops)
363 def parse_stats_file(filename):
364 """pop the contents of a mountstats file into a dictionary,
365 keyed by mount point. each value object is a list of the
366 lines in the mountstats file corresponding to the mount
367 point named in the key.
373 for line in f.readlines():
377 if words[0] == 'device':
379 new = [ line.strip() ]
380 elif 'nfs' in words or 'nfs4' in words:
382 new = [ line.strip() ]
384 new += [ line.strip() ]
390 def print_mountstats_help(name):
391 print 'usage: %s [ options ] <mount point>' % name
393 print ' Version %s' % Mountstats_version
395 print ' Display NFS client per-mount statistics.'
397 print ' --version display the version of this command'
398 print ' --nfs display only the NFS statistics'
399 print ' --rpc display only the RPC statistics'
400 print ' --start sample and save statistics'
401 print ' --end resample statistics and compare them with saved'
404 def mountstats_command():
405 """Mountstats command
412 if arg in ['-h', '--help', 'help', 'usage']:
413 print_mountstats_help(prog)
416 if arg in ['-v', '--version', 'version']:
417 print '%s version %s' % (sys.argv[0], Mountstats_version)
420 if arg in ['-n', '--nfs']:
424 if arg in ['-r', '--rpc']:
428 if arg in ['-s', '--start']:
429 raise Exception, 'Sampling is not yet implemented'
431 if arg in ['-e', '--end']:
432 raise Exception, 'Sampling is not yet implemented'
434 if arg == sys.argv[0]:
439 if mountpoints == []:
440 print_mountstats_help(prog)
443 if rpc_only == True and nfs_only == True:
444 print_mountstats_help(prog)
447 mountstats = parse_stats_file('/proc/self/mountstats')
449 for mp in mountpoints:
450 if mp not in mountstats:
451 print 'Statistics for mount point %s not found' % mp
455 stats.parse_stats(mountstats[mp])
457 if not stats.is_nfs_mountpoint():
458 print 'Mount point %s exists but is not an NFS mount' % mp
462 stats.display_nfs_options()
463 stats.display_nfs_events()
464 stats.display_nfs_bytes()
466 stats.display_rpc_generic_stats()
467 stats.display_rpc_op_stats()
469 stats.display_nfs_options()
470 stats.display_nfs_bytes()
471 stats.display_rpc_generic_stats()
472 stats.display_rpc_op_stats()
474 def print_nfsstat_help(name):
475 print 'usage: %s [ options ]' % name
477 print ' Version %s' % Mountstats_version
479 print ' nfsstat-like program that uses NFS client per-mount statistics.'
482 def nfsstat_command():
483 print_nfsstat_help(prog)
485 def print_iostat_help(name):
486 print 'usage: %s [ <interval> [ <count> ] ] [ <mount point> ] ' % name
488 print ' Version %s' % Mountstats_version
490 print ' iostat-like program to display NFS client per-mount statistics.'
492 print ' The <interval> parameter specifies the amount of time in seconds between'
493 print ' each report. The first report contains statistics for the time since each'
494 print ' file system was mounted. Each subsequent report contains statistics'
495 print ' collected during the interval since the previous report.'
497 print ' If the <count> parameter is specified, the value of <count> determines the'
498 print ' number of reports generated at <interval> seconds apart. If the interval'
499 print ' parameter is specified without the <count> parameter, the command generates'
500 print ' reports continuously.'
502 print ' If one or more <mount point> names are specified, statistics for only these'
503 print ' mount points will be displayed. Otherwise, all NFS mount points on the'
504 print ' client are listed.'
507 def print_iostat_summary(old, new, devices, time):
508 for device in devices:
510 stats.parse_stats(new[device])
512 stats.display_iostats(time)
514 old_stats = DeviceData()
515 old_stats.parse_stats(old[device])
516 diff_stats = stats.compare_iostats(old_stats)
517 diff_stats.display_iostats(time)
519 def iostat_command():
520 """iostat-like command for NFS mount points
522 mountstats = parse_stats_file('/proc/self/mountstats')
524 interval_seen = False
528 if arg in ['-h', '--help', 'help', 'usage']:
529 print_iostat_help(prog)
532 if arg in ['-v', '--version', 'version']:
533 print '%s version %s' % (sys.argv[0], Mountstats_version)
536 if arg == sys.argv[0]:
539 if arg in mountstats:
541 elif not interval_seen:
546 print 'Illegal <interval> value'
553 print 'Illegal <count> value'
556 # make certain devices contains only NFS mount points
559 for device in devices:
561 stats.parse_stats(mountstats[device])
562 if stats.is_nfs_mountpoint():
566 for device, descr in mountstats.iteritems():
568 stats.parse_stats(descr)
569 if stats.is_nfs_mountpoint():
571 if len(devices) == 0:
572 print 'No NFS mount points were found'
575 old_mountstats = None
578 if not interval_seen:
579 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
584 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
585 old_mountstats = mountstats
587 sample_time = interval
588 mountstats = parse_stats_file('/proc/self/mountstats')
592 print_iostat_summary(old_mountstats, mountstats, devices, sample_time)
593 old_mountstats = mountstats
595 sample_time = interval
596 mountstats = parse_stats_file('/proc/self/mountstats')
601 prog = os.path.basename(sys.argv[0])
604 if prog == 'mountstats':
606 elif prog == 'ms-nfsstat':
608 elif prog == 'ms-iostat':
610 except KeyboardInterrupt:
611 print 'Caught ^C... exiting'