11#!/usr/bin/env python3
22#
3- # Copyright (c) 2020 by Delphix. All rights reserved.
3+ # Copyright (c) 2020-2021 by Delphix. All rights reserved.
44#
55# SPDX-License-Identifier: GPL-2.0-or-later
66#
99Display NFS thread usage info along with NFS I/O context.
1010
1111Output Sample:
12-
13- packets sockets threads threads metadata read read write write
14- arrived enqueued woken used calls iops thruput iops thruput
15- 4589 0 4589 25 16 273 3.6MB 212 2.6MB
16- 4735 0 4735 8 1 287 3.8MB 212 2.7MB
17- 4693 0 4693 10 0 280 3.7MB 216 2.7MB
18- 4625 0 4625 15 0 278 3.7MB 212 2.6MB
19- 4687 0 4687 7 1 285 3.8MB 210 2.6MB
20- 4701 0 4701 12 0 285 3.8MB 215 2.7MB
12+ packets sockets threads threads metadata read read write write
13+ arrived enqueued woken used calls iops thruput iops thruput
14+ 78683 538 78145 57 209 3390 142.5MB 9014 107.0MB
15+ 106114 4527 101587 63 50 4211 166.8MB 13294 133.0MB
16+ 110220 1511 108709 61 10 4347 10.7MB 13767 137.5MB
17+ 80630 4741 75889 62 50 4218 179.4MB 8743 107.9MB
18+ 115463 11400 104063 62 21 4231 179.4MB 15404 150.5MB
2119'''
2220
21+ import os
2322import psutil
2423from signal import signal , SIGINT
2524import sys
2625from time import sleep
26+ import datetime
27+ import argparse
2728
29+ PROCFS_NFSD = "/proc/fs/nfsd"
2830POOL_STATS = "/proc/fs/nfsd/pool_stats"
2931NFSD_STATS = "/proc/net/rpc/nfsd"
3032
3335H2 = ['arrived' , 'enqueued' , 'woken' , 'used' , 'calls' , 'iops' , 'thruput' ,
3436 'iops' , 'thruput' ]
3537
36- INTERVAL = 5
38+
39+ def parse_cmdline ():
40+ parser = argparse .ArgumentParser (
41+ description = 'Display nfsd thread usage info along with NFS I/O '
42+ 'context' )
43+ parser .add_argument (
44+ '--interval' , type = int , choices = range (1 , 31 ),
45+ default = 5 , help = 'sampling interval in seconds (defaults to 5)' )
46+ return parser .parse_args ()
3747
3848
3949def server_stopped (message = '' ):
40- print ("NFS Server Stopped {}" .format (message ))
41- sys .exit ()
50+ print ("*NFS Server Stopped {}" .format (message ))
4251
4352
4453def print_header (header ):
54+ print (' ' * 19 , end = '' )
4555 for col in header :
4656 print ('{0:>10}' .format (col ), end = '' )
47- print ()
57+ print (flush = True )
4858
4959
5060def pool_stats ():
@@ -56,10 +66,10 @@ def pool_stats():
5666 packets = int (fields [1 ])
5767 enqueued = int (fields [2 ])
5868 woken = int (fields [3 ])
59- timedout = int (fields [4 ])
60- return packets , enqueued , woken , timedout
61- except OSError :
69+ return packets , enqueued , woken , None
70+ except OSError as e :
6271 server_stopped ()
72+ return 0 , 0 , 0 , e
6373
6474
6575def nfs_stats ():
@@ -93,21 +103,22 @@ def nfs_stats():
93103 metadata += int (fields [11 ])
94104 metadata += int (fields [17 ])
95105 metadata += int (fields [36 ])
96- return readbytes , writebytes , readops , writeops , metadata
97- except OSError :
106+ return readbytes , writebytes , readops , writeops , metadata , None
107+ except OSError as e :
98108 server_stopped ()
109+ return 0 , 0 , 0 , 0 , 0 , e
99110
100111
101- def context_switches (pids ):
102- "Return a list of context switches per process in pids"
112+ def cpu_time (pids ):
113+ "Return a list of time spent on cpu per process in pids"
103114 ls = []
104115 for pid in pids :
105116 try :
106- pctxsw = psutil .Process (pid ).num_ctx_switches ()
107- ls .append (pctxsw .voluntary + pctxsw .involuntary )
108- except psutil .NoSuchProcess :
117+ ls .append (psutil .Process (pid ).cpu_times ().system )
118+ except psutil .NoSuchProcess as e :
109119 server_stopped ()
110- return ls
120+ return None , e
121+ return ls , None
111122
112123
113124def nfsd_processes ():
@@ -132,35 +143,44 @@ def print_thruput(value):
132143 print ('{0:>8}KB' .format (int (value / 1024 )), end = '' )
133144
134145
135- def print_line ():
146+ def print_line (interval ):
147+ lines = 0
136148 pids = nfsd_processes ()
137149
138- prevSwitches = context_switches (pids )
139- prevPackets , prevEnqueued , prevWoken , prevTimedout = pool_stats ()
140- prevRB , prevWB , prevRO , prevWO , prevMeta = nfs_stats ()
150+ prevCpuTime , e1 = cpu_time (pids )
151+ prevPackets , prevEnqueued , prevWoken , e2 = pool_stats ()
152+ prevRB , prevWB , prevRO , prevWO , prevMeta , e3 = nfs_stats ()
153+ if e1 or e2 or e3 :
154+ return
141155
142- while (not sleep (INTERVAL )):
143- nextSwitches = context_switches (pids )
144- nextPackets , nextEnqueued , nextWoken , nextTimedout = pool_stats ()
145- nextRB , nextWB , nextRO , nextWO , nextMeta = nfs_stats ()
156+ while (not sleep (interval )):
157+ nextCpuTime , e1 = cpu_time (pids )
158+ nextPackets , nextEnqueued , nextWoken , e2 = pool_stats ()
159+ nextRB , nextWB , nextRO , nextWO , nextMeta , e3 = nfs_stats ()
160+ if e1 or e2 or e3 :
161+ return
146162
163+ #
164+ # Count threads that used cpu time in this interval
165+ #
147166 threads = 0
148- for i in range (0 , len (prevSwitches )):
149- if not prevSwitches [i ] == nextSwitches [i ]:
167+ for i in range (0 , len (prevCpuTime )):
168+ if not prevCpuTime [i ] == nextCpuTime [i ]:
150169 threads += 1
151- threads -= nextTimedout - prevTimedout
152- prevSwitches = nextSwitches .copy ()
170+ prevCpuTime = nextCpuTime .copy ()
153171
154172 #
155173 # The published 'sockets-enqueued' value needs adjustment
156174 #
157175 enqueued = (nextEnqueued - prevEnqueued ) - (nextWoken - prevWoken )
176+ if enqueued < 0 :
177+ enqueued = 0
158178
159179 #
160180 # For IOPS values less than 10 display with decimal
161181 #
162- readOps = (nextRO - prevRO ) / INTERVAL
163- writeOps = (nextWO - prevWO ) / INTERVAL
182+ readOps = (nextRO - prevRO ) / interval
183+ writeOps = (nextWO - prevWO ) / interval
164184 readOps = int (readOps ) if readOps > 9 else round (readOps , 1 )
165185 writeOps = int (writeOps ) if writeOps > 9 else round (writeOps , 1 )
166186
@@ -173,35 +193,43 @@ def print_line():
173193 if nextWB < prevWB :
174194 prevWB = 0
175195
196+ if lines % 48 == 0 :
197+ print_header (H1 )
198+ print_header (H2 )
199+
200+ print ('{:%Y-%m-%d %H:%M:%S}' .format (datetime .datetime .now ()), end = '' )
176201 print_value (nextPackets - prevPackets )
177202 print_value (enqueued )
178203 print_value (nextWoken - prevWoken )
179204 print_value (threads )
180205 print_value (nextMeta - prevMeta )
181206 print_value (readOps )
182- print_thruput ((nextRB - prevRB ) / INTERVAL )
207+ print_thruput ((nextRB - prevRB ) / interval )
183208 print_value (writeOps )
184- print_thruput ((nextWB - prevWB ) / INTERVAL )
185- print ()
209+ print_thruput ((nextWB - prevWB ) / interval )
210+ print (flush = True )
186211
187212 prevPackets = nextPackets
188213 prevEnqueued = nextEnqueued
189214 prevWoken = nextWoken
190- prevTimedout = nextTimedout
191215 prevMeta = nextMeta
192216 prevRB = nextRB
193217 prevWB = nextWB
194218 prevRO = nextRO
195219 prevWO = nextWO
220+ lines += 1
196221
197222
198223def handler (signal_received , frame ):
199- print ()
224+ print (flush = True )
200225 sys .exit (0 )
201226
202227
203228signal (SIGINT , handler )
204229
205- print_header (H1 )
206- print_header (H2 )
207- print_line ()
230+ arguments = parse_cmdline ()
231+
232+ while True :
233+ if os .path .exists (PROCFS_NFSD ):
234+ print_line (arguments .interval )
235+ sleep (2 )
0 commit comments