Skip to content

Commit 31eae6d

Browse files
committed
selftests: drv-net: test drivers sleeping in ndo_get_stats64
Most of our tests use rtnetlink to read device stats, so they don't expose the drivers much to paths in which device stats are read under RCU. Add tests which hammer profcs reads to make sure drivers: - don't sleep while reporting stats, - can handle parallel reads, - can handle device going down while reading. Set ifname on the env class in NetDrvEnv, we already do that in NetDrvEpEnv. KTAP version 1 1..7 ok 1 stats.check_pause ok 2 stats.check_fec ok 3 stats.pkt_byte_sum ok 4 stats.qstat_by_ifindex ok 5 stats.check_down ok 6 stats.procfs_hammer # completed up/down cycles: 6 ok 7 stats.procfs_downup_hammer # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:0 error:0 Reviewed-by: Petr Machata <petrm@nvidia.com> Reviewed-by: Willem de Bruijn <willemb@google.com> Link: https://patch.msgid.link/20250107022932.2087744-1-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent 7bf1659 commit 31eae6d

File tree

3 files changed

+97
-3
lines changed

3 files changed

+97
-3
lines changed

tools/testing/selftests/drivers/net/lib/py/env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def __init__(self, src_path, **kwargs):
4848
else:
4949
self._ns = NetdevSimDev(**kwargs)
5050
self.dev = self._ns.nsims[0].dev
51+
self.ifname = self.dev['ifname']
5152
self.ifindex = self.dev['ifindex']
5253

5354
def __enter__(self):

tools/testing/selftests/drivers/net/stats.py

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
# SPDX-License-Identifier: GPL-2.0
33

44
import errno
5+
import subprocess
6+
import time
57
from lib.py import ksft_run, ksft_exit, ksft_pr
6-
from lib.py import ksft_ge, ksft_eq, ksft_in, ksft_true, ksft_raises, KsftSkipEx, KsftXfailEx
8+
from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises
9+
from lib.py import KsftSkipEx, KsftXfailEx
710
from lib.py import ksft_disruptive
811
from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
912
from lib.py import NetDrvEnv
10-
from lib.py import ip, defer
13+
from lib.py import cmd, ip, defer
1114

1215
ethnl = EthtoolFamily()
1316
netfam = NetdevFamily()
@@ -174,10 +177,95 @@ def check_down(cfg) -> None:
174177
netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)
175178

176179

180+
def __run_inf_loop(body):
181+
body = body.strip()
182+
if body[-1] != ';':
183+
body += ';'
184+
185+
return subprocess.Popen(f"while true; do {body} done", shell=True,
186+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
187+
188+
189+
def __stats_increase_sanely(old, new) -> None:
190+
for k in old.keys():
191+
ksft_ge(new[k], old[k])
192+
ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error")
193+
194+
195+
def procfs_hammer(cfg) -> None:
196+
"""
197+
Reading stats via procfs only holds the RCU lock, which is not an exclusive
198+
lock, make sure drivers can handle parallel reads of stats.
199+
"""
200+
one = __run_inf_loop("cat /proc/net/dev")
201+
defer(one.kill)
202+
two = __run_inf_loop("cat /proc/net/dev")
203+
defer(two.kill)
204+
205+
time.sleep(1)
206+
# Make sure the processes are running
207+
ksft_is(one.poll(), None)
208+
ksft_is(two.poll(), None)
209+
210+
rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
211+
time.sleep(2)
212+
rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
213+
__stats_increase_sanely(rtstat1, rtstat2)
214+
# defers will kill the loops
215+
216+
217+
@ksft_disruptive
218+
def procfs_downup_hammer(cfg) -> None:
219+
"""
220+
Reading stats via procfs only holds the RCU lock, drivers often try
221+
to sleep when reading the stats, or don't protect against races.
222+
"""
223+
# Max out the queues, we'll flip between max and 1
224+
channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
225+
if channels['combined-count'] == 0:
226+
rx_type = 'rx'
227+
else:
228+
rx_type = 'combined'
229+
cur_queue_cnt = channels[f'{rx_type}-count']
230+
max_queue_cnt = channels[f'{rx_type}-max']
231+
232+
cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}")
233+
defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}")
234+
235+
# Real test stats
236+
stats = __run_inf_loop("cat /proc/net/dev")
237+
defer(stats.kill)
238+
239+
ipset = f"ip link set dev {cfg.ifname}"
240+
defer(ip, f"link set dev {cfg.ifname} up")
241+
# The "echo -n 1" lets us count iterations below
242+
updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \
243+
f"ethtool -L {cfg.ifname} {rx_type} 1; " + \
244+
f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \
245+
"echo -n 1"
246+
updown = __run_inf_loop(updown)
247+
kill_updown = defer(updown.kill)
248+
249+
time.sleep(1)
250+
# Make sure the processes are running
251+
ksft_is(stats.poll(), None)
252+
ksft_is(updown.poll(), None)
253+
254+
rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
255+
# We're looking for crashes, give it extra time
256+
time.sleep(9)
257+
rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
258+
__stats_increase_sanely(rtstat1, rtstat2)
259+
260+
kill_updown.exec()
261+
stdout, _ = updown.communicate(timeout=5)
262+
ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8')))
263+
264+
177265
def main() -> None:
178266
with NetDrvEnv(__file__, queue_count=100) as cfg:
179267
ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex,
180-
check_down],
268+
check_down, procfs_hammer, procfs_downup_hammer],
181269
args=(cfg, ))
182270
ksft_exit()
183271

tools/testing/selftests/net/lib/py/ksft.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def ksft_in(a, b, comment=""):
7171
_fail("Check failed", a, "not in", b, comment)
7272

7373

74+
def ksft_is(a, b, comment=""):
75+
if a is not b:
76+
_fail("Check failed", a, "is not", b, comment)
77+
78+
7479
def ksft_ge(a, b, comment=""):
7580
if a < b:
7681
_fail("Check failed", a, "<", b, comment)

0 commit comments

Comments
 (0)