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