Skip to content

Commit 2a60ce9

Browse files
cjubrankuba-moo
authored andcommitted
selftests: drv-net: introduce Iperf3Runner for measurement use cases
GenerateTraffic was added to spin up long-running iperf3 load, mainly to drive high PPS background traffic. It was never meant to provide stable throughput numbers, and trying to repurpose it for measurement does not make sense. Introduce Iperf3Runner to allow tests to split out server/client configuration, control start/stop, and collect JSON output for analysis. This makes it possible to measure bandwidth directly when validating egress shaping. GenerateTraffic stays as the background load generator, reusing the common iperf3 helpers under the hood. Signed-off-by: Carolina Jubran <cjubran@nvidia.com> Reviewed-by: Cosmin Ratiu <cratiu@nvidia.com> Reviewed-by: Nimrod Oren <noren@nvidia.com> Link: https://patch.msgid.link/20251130091938.4109055-3-cjubran@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1 parent a8658f7 commit 2a60ce9

3 files changed

Lines changed: 82 additions & 12 deletions

File tree

tools/testing/selftests/drivers/net/hw/lib/py/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
ksft_setup, ksft_variants, KsftNamedVariant
2929
from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \
3030
ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none
31-
from drivers.net.lib.py import GenerateTraffic, Remote
31+
from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner
3232
from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv
3333

3434
__all__ = ["NetNS", "NetNSEnter", "NetdevSimDev",
@@ -44,7 +44,8 @@
4444
"ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt",
4545
"ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt",
4646
"ksft_not_none", "ksft_not_none",
47-
"NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote"]
47+
"NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
48+
"Iperf3Runner"]
4849
except ModuleNotFoundError as e:
4950
print("Failed importing `net` library from kernel sources")
5051
print(str(e))

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@
4444
"ksft_not_none", "ksft_not_none"]
4545

4646
from .env import NetDrvEnv, NetDrvEpEnv
47-
from .load import GenerateTraffic
47+
from .load import GenerateTraffic, Iperf3Runner
4848
from .remote import Remote
4949

50-
__all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote"]
50+
__all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote",
51+
"Iperf3Runner"]
5152
except ModuleNotFoundError as e:
5253
print("Failed importing `net` library from kernel sources")
5354
print(str(e))

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

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,89 @@
22

33
import re
44
import time
5+
import json
56

67
from lib.py import ksft_pr, cmd, ip, rand_port, wait_port_listen
78

8-
class GenerateTraffic:
9-
def __init__(self, env, port=None):
10-
env.require_cmd("iperf3", local=True, remote=True)
119

10+
class Iperf3Runner:
11+
"""
12+
Sets up and runs iperf3 traffic.
13+
"""
14+
def __init__(self, env, port=None, server_ip=None, client_ip=None):
15+
env.require_cmd("iperf3", local=True, remote=True)
1216
self.env = env
13-
1417
self.port = rand_port() if port is None else port
15-
self._iperf_server = cmd(f"iperf3 -s -1 -p {self.port}", background=True)
18+
self.server_ip = server_ip
19+
self.client_ip = client_ip
20+
21+
def _build_server(self):
22+
cmdline = f"iperf3 -s -1 -p {self.port}"
23+
if self.server_ip:
24+
cmdline += f" -B {self.server_ip}"
25+
return cmdline
26+
27+
def _build_client(self, streams, duration, reverse):
28+
host = self.env.addr if self.server_ip is None else self.server_ip
29+
cmdline = f"iperf3 -c {host} -p {self.port} -P {streams} -t {duration} -J"
30+
if self.client_ip:
31+
cmdline += f" -B {self.client_ip}"
32+
if reverse:
33+
cmdline += " --reverse"
34+
return cmdline
35+
36+
def start_server(self):
37+
"""
38+
Starts an iperf3 server with optional bind IP.
39+
"""
40+
cmdline = self._build_server()
41+
proc = cmd(cmdline, background=True)
1642
wait_port_listen(self.port)
1743
time.sleep(0.1)
18-
self._iperf_client = cmd(f"iperf3 -c {env.addr} -P 16 -p {self.port} -t 86400",
19-
background=True, host=env.remote)
44+
return proc
45+
46+
def start_client(self, background=False, streams=1, duration=10, reverse=False):
47+
"""
48+
Starts the iperf3 client with the configured options.
49+
"""
50+
cmdline = self._build_client(streams, duration, reverse)
51+
return cmd(cmdline, background=background, host=self.env.remote)
52+
53+
def measure_bandwidth(self, reverse=False):
54+
"""
55+
Runs an iperf3 measurement and returns the average bandwidth (Gbps).
56+
Discards the first and last few reporting intervals and uses only the
57+
middle part of the run where throughput is typically stable.
58+
"""
59+
self.start_server()
60+
result = self.start_client(duration=10, reverse=reverse)
61+
62+
if result.ret != 0:
63+
raise RuntimeError("iperf3 failed to run successfully")
64+
try:
65+
out = json.loads(result.stdout)
66+
except json.JSONDecodeError as exc:
67+
raise ValueError("Failed to parse iperf3 JSON output") from exc
68+
69+
intervals = out.get("intervals", [])
70+
samples = [i["sum"]["bits_per_second"] / 1e9 for i in intervals]
71+
if len(samples) < 10:
72+
raise ValueError(f"iperf3 returned too few intervals: {len(samples)}")
73+
# Discard potentially unstable first and last 3 seconds.
74+
stable = samples[3:-3]
75+
76+
avg = sum(stable) / len(stable)
77+
78+
return avg
79+
80+
81+
class GenerateTraffic:
82+
def __init__(self, env, port=None):
83+
self.env = env
84+
self.runner = Iperf3Runner(env, port)
85+
86+
self._iperf_server = self.runner.start_server()
87+
self._iperf_client = self.runner.start_client(background=True, streams=16, duration=86400)
2088

2189
# Wait for traffic to ramp up
2290
if not self._wait_pkts(pps=1000):
@@ -61,7 +129,7 @@ def stop(self, verbose=None):
61129
def _wait_client_stopped(self, sleep=0.005, timeout=5):
62130
end = time.monotonic() + timeout
63131

64-
live_port_pattern = re.compile(fr":{self.port:04X} 0[^6] ")
132+
live_port_pattern = re.compile(fr":{self.runner.port:04X} 0[^6] ")
65133

66134
while time.monotonic() < end:
67135
data = cmd("cat /proc/net/tcp*", host=self.env.remote).stdout

0 commit comments

Comments
 (0)