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 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
98def 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
171def 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
193def __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
202def __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
208def 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
231def 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
278def 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
288if __name__ == "__main__":
289 main()