1
1
#!/usr/bin/env python3
2
2
#
3
- # Copyright (c) 2020 by Delphix. All rights reserved.
3
+ # Copyright (c) 2020-2021 by Delphix. All rights reserved.
4
4
#
5
5
# SPDX-License-Identifier: GPL-2.0-or-later
6
6
#
9
9
Display NFS thread usage info along with NFS I/O context.
10
10
11
11
Output 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
21
19
'''
22
20
21
+ import os
23
22
import psutil
24
23
from signal import signal , SIGINT
25
24
import sys
26
25
from time import sleep
26
+ import datetime
27
+ import argparse
27
28
29
+ PROCFS_NFSD = "/proc/fs/nfsd"
28
30
POOL_STATS = "/proc/fs/nfsd/pool_stats"
29
31
NFSD_STATS = "/proc/net/rpc/nfsd"
30
32
33
35
H2 = ['arrived' , 'enqueued' , 'woken' , 'used' , 'calls' , 'iops' , 'thruput' ,
34
36
'iops' , 'thruput' ]
35
37
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 ()
37
47
38
48
39
49
def server_stopped (message = '' ):
40
- print ("NFS Server Stopped {}" .format (message ))
41
- sys .exit ()
50
+ print ("*NFS Server Stopped {}" .format (message ))
42
51
43
52
44
53
def print_header (header ):
54
+ print (' ' * 19 , end = '' )
45
55
for col in header :
46
56
print ('{0:>10}' .format (col ), end = '' )
47
- print ()
57
+ print (flush = True )
48
58
49
59
50
60
def pool_stats ():
@@ -56,10 +66,10 @@ def pool_stats():
56
66
packets = int (fields [1 ])
57
67
enqueued = int (fields [2 ])
58
68
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 :
62
71
server_stopped ()
72
+ return 0 , 0 , 0 , e
63
73
64
74
65
75
def nfs_stats ():
@@ -93,21 +103,22 @@ def nfs_stats():
93
103
metadata += int (fields [11 ])
94
104
metadata += int (fields [17 ])
95
105
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 :
98
108
server_stopped ()
109
+ return 0 , 0 , 0 , 0 , 0 , e
99
110
100
111
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"
103
114
ls = []
104
115
for pid in pids :
105
116
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 :
109
119
server_stopped ()
110
- return ls
120
+ return None , e
121
+ return ls , None
111
122
112
123
113
124
def nfsd_processes ():
@@ -132,35 +143,44 @@ def print_thruput(value):
132
143
print ('{0:>8}KB' .format (int (value / 1024 )), end = '' )
133
144
134
145
135
- def print_line ():
146
+ def print_line (interval ):
147
+ lines = 0
136
148
pids = nfsd_processes ()
137
149
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
141
155
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
146
162
163
+ #
164
+ # Count threads that used cpu time in this interval
165
+ #
147
166
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 ]:
150
169
threads += 1
151
- threads -= nextTimedout - prevTimedout
152
- prevSwitches = nextSwitches .copy ()
170
+ prevCpuTime = nextCpuTime .copy ()
153
171
154
172
#
155
173
# The published 'sockets-enqueued' value needs adjustment
156
174
#
157
175
enqueued = (nextEnqueued - prevEnqueued ) - (nextWoken - prevWoken )
176
+ if enqueued < 0 :
177
+ enqueued = 0
158
178
159
179
#
160
180
# For IOPS values less than 10 display with decimal
161
181
#
162
- readOps = (nextRO - prevRO ) / INTERVAL
163
- writeOps = (nextWO - prevWO ) / INTERVAL
182
+ readOps = (nextRO - prevRO ) / interval
183
+ writeOps = (nextWO - prevWO ) / interval
164
184
readOps = int (readOps ) if readOps > 9 else round (readOps , 1 )
165
185
writeOps = int (writeOps ) if writeOps > 9 else round (writeOps , 1 )
166
186
@@ -173,35 +193,43 @@ def print_line():
173
193
if nextWB < prevWB :
174
194
prevWB = 0
175
195
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 = '' )
176
201
print_value (nextPackets - prevPackets )
177
202
print_value (enqueued )
178
203
print_value (nextWoken - prevWoken )
179
204
print_value (threads )
180
205
print_value (nextMeta - prevMeta )
181
206
print_value (readOps )
182
- print_thruput ((nextRB - prevRB ) / INTERVAL )
207
+ print_thruput ((nextRB - prevRB ) / interval )
183
208
print_value (writeOps )
184
- print_thruput ((nextWB - prevWB ) / INTERVAL )
185
- print ()
209
+ print_thruput ((nextWB - prevWB ) / interval )
210
+ print (flush = True )
186
211
187
212
prevPackets = nextPackets
188
213
prevEnqueued = nextEnqueued
189
214
prevWoken = nextWoken
190
- prevTimedout = nextTimedout
191
215
prevMeta = nextMeta
192
216
prevRB = nextRB
193
217
prevWB = nextWB
194
218
prevRO = nextRO
195
219
prevWO = nextWO
220
+ lines += 1
196
221
197
222
198
223
def handler (signal_received , frame ):
199
- print ()
224
+ print (flush = True )
200
225
sys .exit (0 )
201
226
202
227
203
228
signal (SIGINT , handler )
204
229
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