Skip to content

Commit cbc19b3

Browse files
committed
Merge branch 'selftests-drv-net-fix-issues-in-devlink_rate_tc_bw-py'
Carolina Jubran says: ==================== selftests: drv-net: Fix issues in devlink_rate_tc_bw.py This series fixes issues in the devlink_rate_tc_bw.py selftest and introduces a new Iperf3Runner that helps with measurement handling. ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
2 parents 4a18b6c + 5cc1bdd commit cbc19b3

File tree

5 files changed

+157
-112
lines changed

5 files changed

+157
-112
lines changed

tools/testing/selftests/drivers/net/hw/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ TEST_GEN_FILES := \
2020
TEST_PROGS = \
2121
csum.py \
2222
devlink_port_split.py \
23+
devlink_rate_tc_bw.py \
2324
devmem.py \
2425
ethtool.sh \
2526
ethtool_extended_state.sh \

tools/testing/selftests/drivers/net/hw/devlink_rate_tc_bw.py

Lines changed: 74 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@
2121
----------
2222
1. test_no_tc_mapping_bandwidth:
2323
- Verifies that without TC mapping, bandwidth is NOT distributed according to
24-
the configured 80/20 split between TC4 and TC3
25-
- This test should fail if bandwidth matches the 80/20 split without TC
24+
the configured 20/80 split between TC3 and TC4
25+
- This test should fail if bandwidth matches the 20/80 split without TC
2626
mapping
27-
- Expected: Bandwidth should NOT be distributed as 80/20
27+
- Expected: Bandwidth should NOT be distributed as 20/80
2828
2929
2. test_tc_mapping_bandwidth:
3030
- Configures TC mapping using mqprio qdisc
3131
- Verifies that with TC mapping, bandwidth IS distributed according to the
32-
configured 80/20 split between TC3 and TC4
33-
- Expected: Bandwidth should be distributed as 80/20
32+
configured 20/80 split between TC3 and TC4
33+
- Expected: Bandwidth should be distributed as 20/80
3434
3535
Bandwidth Distribution:
3636
----------------------
37-
- TC3 (VLAN 101): Configured for 80% of total bandwidth
38-
- TC4 (VLAN 102): Configured for 20% of total bandwidth
37+
- TC3 (VLAN 101): Configured for 20% of total bandwidth
38+
- TC4 (VLAN 102): Configured for 80% of total bandwidth
3939
- Total bandwidth: 1Gbps
4040
- Tolerance: +-12%
4141
@@ -64,43 +64,40 @@
6464
from lib.py import NetDrvEpEnv, DevlinkFamily
6565
from lib.py import NlError
6666
from lib.py import cmd, defer, ethtool, ip
67+
from lib.py import Iperf3Runner
6768

6869

6970
class BandwidthValidator:
7071
"""
71-
Validates bandwidth totals and per-TC shares against expected values
72-
with a tolerance.
72+
Validates total bandwidth and individual shares with tolerance
73+
relative to the overall total.
7374
"""
7475

75-
def __init__(self):
76+
def __init__(self, shares):
7677
self.tolerance_percent = 12
77-
self.expected_total_gbps = 1.0
78-
self.total_min_expected = self.min_expected(self.expected_total_gbps)
79-
self.total_max_expected = self.max_expected(self.expected_total_gbps)
80-
self.tc_expected_percent = {
81-
3: 20.0,
82-
4: 80.0,
83-
}
78+
self.expected_total = sum(shares.values())
79+
self.bounds = {}
80+
81+
for name, exp in shares.items():
82+
self.bounds[name] = (self.min_expected(exp), self.max_expected(exp))
8483

8584
def min_expected(self, value):
8685
"""Calculates the minimum acceptable value based on tolerance."""
87-
return value - (value * self.tolerance_percent / 100)
86+
return value - (self.expected_total * self.tolerance_percent / 100)
8887

8988
def max_expected(self, value):
9089
"""Calculates the maximum acceptable value based on tolerance."""
91-
return value + (value * self.tolerance_percent / 100)
92-
93-
def bound(self, expected, value):
94-
"""Returns True if value is within expected tolerance."""
95-
return self.min_expected(expected) <= value <= self.max_expected(expected)
90+
return value + (self.expected_total * self.tolerance_percent / 100)
9691

97-
def tc_bandwidth_bound(self, value, tc_ix):
92+
def bound(self, values):
9893
"""
99-
Returns True if the given bandwidth value is within tolerance
100-
for the TC's expected bandwidth.
94+
Return True if all given values fall within tolerance.
10195
"""
102-
expected = self.tc_expected_percent[tc_ix]
103-
return self.bound(expected, value)
96+
for name, value in values.items():
97+
low, high = self.bounds[name]
98+
if not low <= value <= high:
99+
return False
100+
return True
104101

105102

106103
def setup_vf(cfg, set_tc_mapping=True):
@@ -116,8 +113,8 @@ def setup_vf(cfg, set_tc_mapping=True):
116113
except Exception as exc:
117114
raise KsftSkipEx(f"Failed to enable switchdev mode on {cfg.pci}") from exc
118115
try:
119-
cmd(f"echo 1 > /sys/class/net/{cfg.ifname}/device/sriov_numvfs")
120-
defer(cmd, f"echo 0 > /sys/class/net/{cfg.ifname}/device/sriov_numvfs")
116+
cmd(f"echo 1 > /sys/class/net/{cfg.ifname}/device/sriov_numvfs", shell=True)
117+
defer(cmd, f"echo 0 > /sys/class/net/{cfg.ifname}/device/sriov_numvfs", shell=True)
121118
except Exception as exc:
122119
raise KsftSkipEx(f"Failed to enable SR-IOV on {cfg.ifname}") from exc
123120

@@ -139,8 +136,8 @@ def setup_vlans_on_vf(vf_ifc):
139136
Sets up two VLAN interfaces on the given VF, each mapped to a different TC.
140137
"""
141138
vlan_configs = [
142-
{"vlan_id": 101, "tc": 3, "ip": "198.51.100.2"},
143-
{"vlan_id": 102, "tc": 4, "ip": "198.51.100.10"},
139+
{"vlan_id": 101, "tc": 3, "ip": "198.51.100.1"},
140+
{"vlan_id": 102, "tc": 4, "ip": "198.51.100.9"},
144141
]
145142

146143
for config in vlan_configs:
@@ -224,28 +221,27 @@ def setup_devlink_rate(cfg):
224221
raise KsftFailEx(f"rate_set failed on VF port {port_index}") from exc
225222

226223

227-
def setup_remote_server(cfg):
224+
def setup_remote_vlans(cfg):
228225
"""
229-
Sets up VLAN interfaces and starts iperf3 servers on the remote side.
226+
Sets up VLAN interfaces on the remote side.
230227
"""
231228
remote_dev = cfg.remote_ifname
232229
vlan_ids = [101, 102]
233-
remote_ips = ["198.51.100.1", "198.51.100.9"]
230+
remote_ips = ["198.51.100.2", "198.51.100.10"]
234231

235232
for vlan_id, ip_addr in zip(vlan_ids, remote_ips):
236233
vlan_dev = f"{remote_dev}.{vlan_id}"
237234
cmd(f"ip link add link {remote_dev} name {vlan_dev} "
238235
f"type vlan id {vlan_id}", host=cfg.remote)
239236
cmd(f"ip addr add {ip_addr}/29 dev {vlan_dev}", host=cfg.remote)
240237
cmd(f"ip link set dev {vlan_dev} up", host=cfg.remote)
241-
cmd(f"iperf3 -s -1 -B {ip_addr}",background=True, host=cfg.remote)
242238
defer(cmd, f"ip link del {vlan_dev}", host=cfg.remote)
243239

244240

245241
def setup_test_environment(cfg, set_tc_mapping=True):
246242
"""
247243
Sets up the complete test environment including VF creation, VLANs,
248-
bridge configuration, devlink rate setup, and the remote server.
244+
bridge configuration and devlink rate setup.
249245
"""
250246
vf_ifc = setup_vf(cfg, set_tc_mapping)
251247
ksft_pr(f"Created VF interface: {vf_ifc}")
@@ -256,51 +252,39 @@ def setup_test_environment(cfg, set_tc_mapping=True):
256252
setup_bridge(cfg)
257253

258254
setup_devlink_rate(cfg)
259-
setup_remote_server(cfg)
260-
time.sleep(2)
255+
setup_remote_vlans(cfg)
261256

262257

263-
def run_iperf_client(server_ip, local_ip, barrier, min_expected_gbps=0.1):
258+
def measure_bandwidth(cfg, server_ip, client_ip, barrier):
264259
"""
265-
Runs a single iperf3 client instance, binding to the given local IP.
266-
Waits on a barrier to synchronize with other threads.
260+
Synchronizes with peers and runs an iperf3-based bandwidth measurement
261+
between the given endpoints. Returns average Gbps.
267262
"""
263+
runner = Iperf3Runner(cfg, server_ip=server_ip, client_ip=client_ip)
268264
try:
269265
barrier.wait(timeout=10)
270266
except Exception as exc:
271267
raise KsftFailEx("iperf3 barrier wait timed") from exc
272268

273-
iperf_cmd = ["iperf3", "-c", server_ip, "-B", local_ip, "-J"]
274-
result = subprocess.run(iperf_cmd, capture_output=True, text=True,
275-
check=True)
276-
277269
try:
278-
output = json.loads(result.stdout)
279-
bits_per_second = output["end"]["sum_received"]["bits_per_second"]
280-
gbps = bits_per_second / 1e9
281-
if gbps < min_expected_gbps:
282-
ksft_pr(
283-
f"iperf3 bandwidth too low: {gbps:.2f} Gbps "
284-
f"(expected ≥ {min_expected_gbps} Gbps)"
285-
)
286-
return None
287-
return gbps
288-
except json.JSONDecodeError as exc:
289-
ksft_pr(f"Failed to parse iperf3 JSON output: {exc}")
290-
return None
270+
bw_gbps = runner.measure_bandwidth(reverse=True)
271+
except Exception as exc:
272+
raise KsftFailEx("iperf3 bandwidth measurement failed") from exc
273+
274+
return bw_gbps
291275

292276

293-
def run_bandwidth_test():
277+
def run_bandwidth_test(cfg):
294278
"""
295-
Launches iperf3 client threads for each VLAN/TC pair and collects results.
279+
Runs parallel bandwidth measurements for each VLAN/TC pair and collects results.
296280
"""
297-
def _run_iperf_client_thread(server_ip, local_ip, results, barrier, tc_ix):
298-
results[tc_ix] = run_iperf_client(server_ip, local_ip, barrier)
281+
def _run_measure_bandwidth_thread(local_ip, remote_ip, results, barrier, tc_ix):
282+
results[tc_ix] = measure_bandwidth(cfg, local_ip, remote_ip, barrier)
299283

300284
vf_vlan_data = [
301285
# (local_ip, remote_ip, TC)
302-
("198.51.100.2", "198.51.100.1", 3),
303-
("198.51.100.10", "198.51.100.9", 4),
286+
("198.51.100.1", "198.51.100.2", 3),
287+
("198.51.100.9", "198.51.100.10", 4),
304288
]
305289

306290
results = {}
@@ -309,8 +293,8 @@ def _run_iperf_client_thread(server_ip, local_ip, results, barrier, tc_ix):
309293

310294
for local_ip, remote_ip, tc_ix in vf_vlan_data:
311295
thread = threading.Thread(
312-
target=_run_iperf_client_thread,
313-
args=(remote_ip, local_ip, results, start_barrier, tc_ix)
296+
target=_run_measure_bandwidth_thread,
297+
args=(local_ip, remote_ip, results, start_barrier, tc_ix)
314298
)
315299
thread.start()
316300
threads.append(thread)
@@ -320,10 +304,11 @@ def _run_iperf_client_thread(server_ip, local_ip, results, barrier, tc_ix):
320304

321305
for tc_ix, tc_bw in results.items():
322306
if tc_bw is None:
323-
raise KsftFailEx("iperf3 client failed; cannot evaluate bandwidth")
307+
raise KsftFailEx("iperf3 failed; cannot evaluate bandwidth")
324308

325309
return results
326310

311+
327312
def calculate_bandwidth_percentages(results):
328313
"""
329314
Calculates the percentage of total bandwidth received by TC3 and TC4.
@@ -364,59 +349,48 @@ def verify_total_bandwidth(bw_data, validator):
364349
"""
365350
total = bw_data['total_bw']
366351

367-
if validator.bound(validator.expected_total_gbps, total):
352+
if validator.bound({"total": total}):
368353
return
369354

370-
if total < validator.total_min_expected:
355+
low, high = validator.bounds["total"]
356+
357+
if total < low:
371358
raise KsftSkipEx(
372359
f"Total bandwidth {total:.2f} Gbps < minimum "
373-
f"{validator.total_min_expected:.2f} Gbps; "
374-
f"parent tx_max ({validator.expected_total_gbps:.1f} G) "
360+
f"{low:.2f} Gbps; "
361+
f"parent tx_max ({validator.expected_total:.1f} G) "
375362
f"not reached, cannot validate share"
376363
)
377364

378365
raise KsftFailEx(
379366
f"Total bandwidth {total:.2f} Gbps exceeds allowed ceiling "
380-
f"{validator.total_max_expected:.2f} Gbps "
381-
f"(VF tx_max set to {validator.expected_total_gbps:.1f} G)"
367+
f"{high:.2f} Gbps "
368+
f"(VF tx_max set to {validator.expected_total:.1f} G)"
382369
)
383370

384371

385-
def check_bandwidth_distribution(bw_data, validator):
386-
"""
387-
Checks whether the measured TC3 and TC4 bandwidth percentages
388-
fall within their expected tolerance ranges.
389-
390-
Returns:
391-
bool: True if both TC3 and TC4 percentages are within bounds.
392-
"""
393-
tc3_valid = validator.tc_bandwidth_bound(bw_data['tc3_percentage'], 3)
394-
tc4_valid = validator.tc_bandwidth_bound(bw_data['tc4_percentage'], 4)
395-
396-
return tc3_valid and tc4_valid
397-
398-
399372
def run_bandwidth_distribution_test(cfg, set_tc_mapping):
400373
"""
401-
Runs parallel iperf3 tests for both TCs and collects results.
374+
Runs parallel bandwidth measurements for both TCs and collects results.
402375
"""
403376
setup_test_environment(cfg, set_tc_mapping)
404-
bandwidths = run_bandwidth_test()
377+
bandwidths = run_bandwidth_test(cfg)
405378
bw_data = calculate_bandwidth_percentages(bandwidths)
406379
test_name = "with TC mapping" if set_tc_mapping else "without TC mapping"
407380
print_bandwidth_results(bw_data, test_name)
408381

409-
verify_total_bandwidth(bw_data, cfg.bw_validator)
382+
verify_total_bandwidth(bw_data, cfg.traffic_bw_validator)
410383

411-
return check_bandwidth_distribution(bw_data, cfg.bw_validator)
384+
return cfg.tc_bw_validator.bound({"tc3": bw_data['tc3_percentage'],
385+
"tc4": bw_data['tc4_percentage']})
412386

413387

414388
def test_no_tc_mapping_bandwidth(cfg):
415389
"""
416-
Verifies that bandwidth is not split 80/20 without traffic class mapping.
390+
Verifies that bandwidth is not split 20/80 without traffic class mapping.
417391
"""
418-
pass_bw_msg = "Bandwidth is NOT distributed as 80/20 without TC mapping"
419-
fail_bw_msg = "Bandwidth matched 80/20 split without TC mapping"
392+
pass_bw_msg = "Bandwidth is NOT distributed as 20/80 without TC mapping"
393+
fail_bw_msg = "Bandwidth matched 20/80 split without TC mapping"
420394
is_mlx5 = "driver: mlx5" in ethtool(f"-i {cfg.ifname}").stdout
421395

422396
if run_bandwidth_distribution_test(cfg, set_tc_mapping=False):
@@ -430,13 +404,13 @@ def test_no_tc_mapping_bandwidth(cfg):
430404

431405
def test_tc_mapping_bandwidth(cfg):
432406
"""
433-
Verifies that bandwidth is correctly split 80/20 between TC3 and TC4
407+
Verifies that bandwidth is correctly split 20/80 between TC3 and TC4
434408
when traffic class mapping is set.
435409
"""
436410
if run_bandwidth_distribution_test(cfg, set_tc_mapping=True):
437-
ksft_pr("Bandwidth is distributed as 80/20 with TC mapping")
411+
ksft_pr("Bandwidth is distributed as 20/80 with TC mapping")
438412
else:
439-
raise KsftFailEx("Bandwidth did not match 80/20 split with TC mapping")
413+
raise KsftFailEx("Bandwidth did not match 20/80 split with TC mapping")
440414

441415

442416
def main() -> None:
@@ -451,9 +425,9 @@ def main() -> None:
451425
)
452426
if not cfg.pci:
453427
raise KsftSkipEx("Could not get PCI address of the interface")
454-
cfg.require_cmd("iperf3", local=True, remote=True)
455428

456-
cfg.bw_validator = BandwidthValidator()
429+
cfg.traffic_bw_validator = BandwidthValidator({"total": 1})
430+
cfg.tc_bw_validator = BandwidthValidator({"tc3": 20, "tc4": 80})
457431

458432
cases = [test_no_tc_mapping_bandwidth, test_tc_mapping_bandwidth]
459433

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))

0 commit comments

Comments
 (0)