Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4"""
5Tests related to standard netdevice statistics.
6"""
7
8import errno
9import subprocess
10import time
11from lib.py import ksft_run, ksft_exit, ksft_pr
12from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises
13from lib.py import KsftSkipEx, KsftFailEx
14from lib.py import ksft_disruptive
15from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
16from lib.py import NetDrvEnv
17from lib.py import cmd, ip, defer
18
19ethnl = EthtoolFamily()
20netfam = NetdevFamily()
21rtnl = RtnlFamily()
22
23
24def 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
42def 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
60def 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
90def 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
128def 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
201def 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
223def __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
232def __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
238def 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
261def 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
308def 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
319if __name__ == "__main__":
320 main()