Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/drivers/net/stats.py
29270 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
4
"""
5
Tests related to standard netdevice statistics.
6
"""
7
8
import errno
9
import subprocess
10
import time
11
from lib.py import ksft_run, ksft_exit, ksft_pr
12
from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises
13
from lib.py import KsftSkipEx, KsftFailEx
14
from lib.py import ksft_disruptive
15
from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
16
from lib.py import NetDrvEnv
17
from lib.py import cmd, ip, defer
18
19
ethnl = EthtoolFamily()
20
netfam = NetdevFamily()
21
rtnl = RtnlFamily()
22
23
24
def check_pause(cfg) -> None:
25
"""
26
Check that drivers which support Pause config also report standard
27
pause stats.
28
"""
29
30
try:
31
ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
32
except NlError as e:
33
if e.error == errno.EOPNOTSUPP:
34
raise KsftSkipEx("pause not supported by the device") from e
35
raise
36
37
data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
38
"flags": {'stats'}}})
39
ksft_true(data['stats'], "driver does not report stats")
40
41
42
def check_fec(cfg) -> None:
43
"""
44
Check that drivers which support FEC config also report standard
45
FEC stats.
46
"""
47
48
try:
49
ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
50
except NlError as e:
51
if e.error == errno.EOPNOTSUPP:
52
raise KsftSkipEx("FEC not supported by the device") from e
53
raise
54
55
data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
56
"flags": {'stats'}}})
57
ksft_true(data['stats'], "driver does not report stats")
58
59
60
def check_fec_hist(cfg) -> None:
61
"""
62
Check that drivers which support FEC histogram statistics report
63
reasonable values.
64
"""
65
66
try:
67
data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
68
"flags": {'stats'}}})
69
except NlError as e:
70
if e.error == errno.EOPNOTSUPP:
71
raise KsftSkipEx("FEC not supported by the device") from e
72
raise
73
if 'stats' not in data:
74
raise KsftSkipEx("FEC stats not supported by the device")
75
if 'hist' not in data['stats']:
76
raise KsftSkipEx("FEC histogram not supported by the device")
77
78
hist = data['stats']['hist']
79
for fec_bin in hist:
80
for key in ['bin-low', 'bin-high', 'bin-val']:
81
ksft_in(key, fec_bin,
82
"Drivers should always report FEC bin range and value")
83
ksft_ge(fec_bin['bin-high'], fec_bin['bin-low'],
84
"FEC bin range should be valid")
85
if 'bin-val-per-lane' in fec_bin:
86
ksft_eq(sum(fec_bin['bin-val-per-lane']), fec_bin['bin-val'],
87
"FEC bin value should be equal to sum of per-plane values")
88
89
90
def pkt_byte_sum(cfg) -> None:
91
"""
92
Check that qstat and interface stats match in value.
93
"""
94
95
def get_qstat(test):
96
stats = netfam.qstats_get({}, dump=True)
97
if stats:
98
for qs in stats:
99
if qs["ifindex"]== test.ifindex:
100
return qs
101
return None
102
103
qstat = get_qstat(cfg)
104
if qstat is None:
105
raise KsftSkipEx("qstats not supported by the device")
106
107
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
108
ksft_in(key, qstat, "Drivers should always report basic keys")
109
110
# Compare stats, rtnl stats and qstats must match,
111
# but the interface may be up, so do a series of dumps
112
# each time the more "recent" stats must be higher or same.
113
def stat_cmp(rstat, qstat):
114
for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
115
if rstat[key] != qstat[key]:
116
return rstat[key] - qstat[key]
117
return 0
118
119
for _ in range(10):
120
rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
121
if stat_cmp(rtstat, qstat) < 0:
122
raise KsftFailEx("RTNL stats are lower, fetched later")
123
qstat = get_qstat(cfg)
124
if stat_cmp(rtstat, qstat) > 0:
125
raise KsftFailEx("Qstats are lower, fetched later")
126
127
128
def qstat_by_ifindex(cfg) -> None:
129
""" Qstats Netlink API tests - querying by ifindex. """
130
131
# Construct a map ifindex -> [dump, by-index, dump]
132
ifindexes = {}
133
stats = netfam.qstats_get({}, dump=True)
134
for entry in stats:
135
ifindexes[entry['ifindex']] = [entry, None, None]
136
137
for ifindex in ifindexes:
138
entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)
139
ksft_eq(len(entry), 1)
140
ifindexes[entry[0]['ifindex']][1] = entry[0]
141
142
stats = netfam.qstats_get({}, dump=True)
143
for entry in stats:
144
ifindexes[entry['ifindex']][2] = entry
145
146
if len(ifindexes) == 0:
147
raise KsftSkipEx("No ifindex supports qstats")
148
149
# Now make sure the stats match/make sense
150
for ifindex, triple in ifindexes.items():
151
all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()
152
153
for key in all_keys:
154
ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)
155
ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)
156
157
# Sanity check the dumps
158
queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)
159
# Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}
160
parsed = {}
161
for entry in queues:
162
ifindex = entry["ifindex"]
163
if ifindex not in parsed:
164
parsed[ifindex] = {"rx":[], "tx": []}
165
parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])
166
# Now, validate
167
for ifindex, queues in parsed.items():
168
for qtype in ['rx', 'tx']:
169
ksft_eq(len(queues[qtype]), len(set(queues[qtype])),
170
comment="repeated queue keys")
171
ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,
172
comment="missing queue keys")
173
174
# Test invalid dumps
175
# 0 is invalid
176
with ksft_raises(NlError) as cm:
177
netfam.qstats_get({"ifindex": 0}, dump=True)
178
ksft_eq(cm.exception.nl_msg.error, -34)
179
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
180
181
# loopback has no stats
182
with ksft_raises(NlError) as cm:
183
netfam.qstats_get({"ifindex": 1}, dump=True)
184
ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
185
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
186
187
# Try to get stats for lowest unused ifindex but not 0
188
devs = rtnl.getlink({}, dump=True)
189
all_ifindexes = set(dev["ifi-index"] for dev in devs)
190
lowest = 2
191
while lowest in all_ifindexes:
192
lowest += 1
193
194
with ksft_raises(NlError) as cm:
195
netfam.qstats_get({"ifindex": lowest}, dump=True)
196
ksft_eq(cm.exception.nl_msg.error, -19)
197
ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
198
199
200
@ksft_disruptive
201
def check_down(cfg) -> None:
202
""" Test statistics (interface and qstat) are not impacted by ifdown """
203
204
try:
205
qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
206
except NlError as e:
207
if e.error == errno.EOPNOTSUPP:
208
raise KsftSkipEx("qstats not supported by the device") from e
209
raise
210
211
ip(f"link set dev {cfg.dev['ifname']} down")
212
defer(ip, f"link set dev {cfg.dev['ifname']} up")
213
214
qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
215
for k in qstat:
216
ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")
217
218
# exercise per-queue API to make sure that "device down" state
219
# is handled correctly and doesn't crash
220
netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)
221
222
223
def __run_inf_loop(body):
224
body = body.strip()
225
if body[-1] != ';':
226
body += ';'
227
228
return subprocess.Popen(f"while true; do {body} done", shell=True,
229
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
230
231
232
def __stats_increase_sanely(old, new) -> None:
233
for k in old.keys():
234
ksft_ge(new[k], old[k])
235
ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error")
236
237
238
def procfs_hammer(cfg) -> None:
239
"""
240
Reading stats via procfs only holds the RCU lock, which is not an exclusive
241
lock, make sure drivers can handle parallel reads of stats.
242
"""
243
one = __run_inf_loop("cat /proc/net/dev")
244
defer(one.kill)
245
two = __run_inf_loop("cat /proc/net/dev")
246
defer(two.kill)
247
248
time.sleep(1)
249
# Make sure the processes are running
250
ksft_is(one.poll(), None)
251
ksft_is(two.poll(), None)
252
253
rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
254
time.sleep(2)
255
rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
256
__stats_increase_sanely(rtstat1, rtstat2)
257
# defers will kill the loops
258
259
260
@ksft_disruptive
261
def procfs_downup_hammer(cfg) -> None:
262
"""
263
Reading stats via procfs only holds the RCU lock, drivers often try
264
to sleep when reading the stats, or don't protect against races.
265
"""
266
# Max out the queues, we'll flip between max and 1
267
channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
268
if channels['combined-count'] == 0:
269
rx_type = 'rx'
270
else:
271
rx_type = 'combined'
272
cur_queue_cnt = channels[f'{rx_type}-count']
273
max_queue_cnt = channels[f'{rx_type}-max']
274
275
cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}")
276
defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}")
277
278
# Real test stats
279
stats = __run_inf_loop("cat /proc/net/dev")
280
defer(stats.kill)
281
282
ipset = f"ip link set dev {cfg.ifname}"
283
defer(ip, f"link set dev {cfg.ifname} up")
284
# The "echo -n 1" lets us count iterations below
285
updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \
286
f"ethtool -L {cfg.ifname} {rx_type} 1; " + \
287
f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \
288
"echo -n 1"
289
updown = __run_inf_loop(updown)
290
kill_updown = defer(updown.kill)
291
292
time.sleep(1)
293
# Make sure the processes are running
294
ksft_is(stats.poll(), None)
295
ksft_is(updown.poll(), None)
296
297
rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
298
# We're looking for crashes, give it extra time
299
time.sleep(9)
300
rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
301
__stats_increase_sanely(rtstat1, rtstat2)
302
303
kill_updown.exec()
304
stdout, _ = updown.communicate(timeout=5)
305
ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8')))
306
307
308
def main() -> None:
309
""" Ksft boiler plate main """
310
311
with NetDrvEnv(__file__, queue_count=100) as cfg:
312
ksft_run([check_pause, check_fec, check_fec_hist, pkt_byte_sum,
313
qstat_by_ifindex, check_down, procfs_hammer,
314
procfs_downup_hammer],
315
args=(cfg, ))
316
ksft_exit()
317
318
319
if __name__ == "__main__":
320
main()
321
322