2121----------
22221. 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
29292. 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
3535Bandwidth 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
6464from lib .py import NetDrvEpEnv , DevlinkFamily
6565from lib .py import NlError
6666from lib .py import cmd , defer , ethtool , ip
67+ from lib .py import Iperf3Runner
6768
6869
6970class 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
106103def 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
245241def 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+
327312def 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-
399372def 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
414388def 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
431405def 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
442416def 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
0 commit comments