Skip to content

Commit eb4ad93

Browse files
author
Don Brady
authored
Add nfs_threads command (#25)
1 parent 20a83af commit eb4ad93

File tree

3 files changed

+209
-1
lines changed

3 files changed

+209
-1
lines changed

cmd/nfs_threads.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2020 by Delphix. All rights reserved.
4+
#
5+
# SPDX-License-Identifier: GPL-2.0-or-later
6+
#
7+
8+
'''
9+
Display NFS thread usage info along with NFS I/O context.
10+
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
21+
'''
22+
23+
import psutil
24+
from signal import signal, SIGINT
25+
import sys
26+
from time import sleep
27+
28+
POOL_STATS = "/proc/fs/nfsd/pool_stats"
29+
NFSD_STATS = "/proc/net/rpc/nfsd"
30+
31+
H1 = ['packets', 'sockets', 'threads', 'threads', 'metadata', 'read',
32+
'read', 'write', 'write']
33+
H2 = ['arrived', 'enqueued', 'woken', 'used', 'calls', 'iops', 'thruput',
34+
'iops', 'thruput']
35+
36+
INTERVAL = 5
37+
38+
39+
def server_stopped(message=''):
40+
print("NFS Server Stopped {}".format(message))
41+
sys.exit()
42+
43+
44+
def print_header(header):
45+
for col in header:
46+
print('{0:>10}'.format(col), end='')
47+
print()
48+
49+
50+
def pool_stats():
51+
try:
52+
with open(POOL_STATS, "r") as file:
53+
for line in file:
54+
if not line.startswith("#"):
55+
fields = line.split(" ")
56+
packets = int(fields[1])
57+
enqueued = int(fields[2])
58+
woken = int(fields[3])
59+
timedout = int(fields[4])
60+
return packets, enqueued, woken, timedout
61+
except OSError:
62+
server_stopped()
63+
64+
65+
def nfs_stats():
66+
try:
67+
metadata = 0
68+
readops = 0
69+
writeops = 0
70+
with open(NFSD_STATS, "r") as file:
71+
for line in file:
72+
if line.startswith("io"):
73+
fields = line.split(" ")
74+
readbytes = int(fields[1])
75+
writebytes = int(fields[2])
76+
if line.startswith("proc3"):
77+
fields = line.split(" ")
78+
readops += int(fields[8])
79+
writeops += int(fields[9])
80+
metadata += int(fields[3])
81+
metadata += int(fields[4])
82+
metadata += int(fields[5])
83+
metadata += int(fields[6])
84+
metadata += int(fields[20])
85+
metadata += int(fields[21])
86+
metadata += int(fields[22])
87+
if line.startswith("proc4ops"):
88+
fields = line.split(" ")
89+
readops += int(fields[27])
90+
writeops += int(fields[40])
91+
metadata += int(fields[5])
92+
metadata += int(fields[8])
93+
metadata += int(fields[11])
94+
metadata += int(fields[17])
95+
metadata += int(fields[36])
96+
return readbytes, writebytes, readops, writeops, metadata
97+
except OSError:
98+
server_stopped()
99+
100+
101+
def context_switches(pids):
102+
"Return a list of context switches per process in pids"
103+
ls = []
104+
for pid in pids:
105+
try:
106+
pctxsw = psutil.Process(pid).num_ctx_switches()
107+
ls.append(pctxsw.voluntary + pctxsw.involuntary)
108+
except psutil.NoSuchProcess:
109+
server_stopped()
110+
return ls
111+
112+
113+
def nfsd_processes():
114+
"Return a list of nfsd proceses"
115+
ls = []
116+
for p in psutil.process_iter(attrs=['name', 'pid', 'uids']):
117+
if p.info['name'] == "nfsd" and p.info['uids'].real == 0:
118+
ls.append(p.info['pid'])
119+
return ls
120+
121+
122+
def print_value(value):
123+
print('{0:>10}'.format(value), end='')
124+
125+
126+
def print_thruput(value):
127+
if value > 1073741824:
128+
print('{0:>8}GB'.format(round(value / 1073741824, 1)), end='')
129+
elif value > 1048576:
130+
print('{0:>8}MB'.format(round(value / 1048576, 1)), end='')
131+
else:
132+
print('{0:>8}KB'.format(int(value / 1024)), end='')
133+
134+
135+
def print_line():
136+
pids = nfsd_processes()
137+
138+
prevSwitches = context_switches(pids)
139+
prevPackets, prevEnqueued, prevWoken, prevTimedout = pool_stats()
140+
prevRB, prevWB, prevRO, prevWO, prevMeta = nfs_stats()
141+
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()
146+
147+
threads = 0
148+
for i in range(0, len(prevSwitches)):
149+
if not prevSwitches[i] == nextSwitches[i]:
150+
threads += 1
151+
threads -= nextTimedout - prevTimedout
152+
prevSwitches = nextSwitches.copy()
153+
154+
#
155+
# The published 'sockets-enqueued' value needs adjustment
156+
#
157+
enqueued = (nextEnqueued - prevEnqueued) - (nextWoken - prevWoken)
158+
159+
#
160+
# For IOPS values less than 10 display with decimal
161+
#
162+
readOps = (nextRO - prevRO) / INTERVAL
163+
writeOps = (nextWO - prevWO) / INTERVAL
164+
readOps = int(readOps) if readOps > 9 else round(readOps, 1)
165+
writeOps = int(writeOps) if writeOps > 9 else round(writeOps, 1)
166+
167+
#
168+
# The read/write values are published as a 32-bit
169+
# value so account for it to wrap in the interval
170+
#
171+
if nextRB < prevRB:
172+
prevRB = 0
173+
if nextWB < prevWB:
174+
prevWB = 0
175+
176+
print_value(nextPackets - prevPackets)
177+
print_value(enqueued)
178+
print_value(nextWoken - prevWoken)
179+
print_value(threads)
180+
print_value(nextMeta - prevMeta)
181+
print_value(readOps)
182+
print_thruput((nextRB - prevRB) / INTERVAL)
183+
print_value(writeOps)
184+
print_thruput((nextWB - prevWB) / INTERVAL)
185+
print()
186+
187+
prevPackets = nextPackets
188+
prevEnqueued = nextEnqueued
189+
prevWoken = nextWoken
190+
prevTimedout = nextTimedout
191+
prevMeta = nextMeta
192+
prevRB = nextRB
193+
prevWB = nextWB
194+
prevRO = nextRO
195+
prevWO = nextWO
196+
197+
198+
def handler(signal_received, frame):
199+
print()
200+
sys.exit(0)
201+
202+
203+
signal(SIGINT, handler)
204+
205+
print_header(H1)
206+
print_header(H2)
207+
print_line()

debian/control

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ Standards-Version: 4.1.2
1313

1414
Package: performance-diagnostics
1515
Architecture: any
16-
Depends: python3-bcc, python-minimal
16+
Depends: python3-bcc, python-minimal, python3-psutil
1717
Description: eBPF-based Performance Diagnostic Tools
1818
A collection of eBPF-based tools for diagnosing performance issues.

debian/rules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ override_dh_auto_build:
1616
mkdir -p build/cmd/
1717
cp cmd/estat.py build/cmd/estat
1818
cp cmd/stbtrace.py build/cmd/stbtrace
19+
cp cmd/nfs_threads.py build/cmd/nfs_threads
1920

2021
override_dh_auto_install:
2122
dh_install build/cmd/* /usr/bin

0 commit comments

Comments
 (0)