Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'selftests-drv-net-rss_ctx-more-tests'

Jakub Kicinski says:

====================
selftests: drv-net: rss_ctx: more tests

Add a few more tests for RSS.

v1: https://lore.kernel.org/all/20240705015725.680275-1-kuba@kernel.org/
====================

Link: https://patch.msgid.link/20240708213627.226025-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+189 -25
+189 -25
tools/testing/selftests/drivers/net/hw/rss_ctx.py
··· 5 5 import random 6 6 from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ge, ksft_lt 7 7 from lib.py import NetDrvEpEnv 8 - from lib.py import NetdevFamily 8 + from lib.py import EthtoolFamily, NetdevFamily 9 9 from lib.py import KsftSkipEx 10 10 from lib.py import rand_port 11 11 from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure ··· 63 63 return queue_stats 64 64 65 65 66 + def _send_traffic_check(cfg, port, name, params): 67 + # params is a dict with 3 possible keys: 68 + # - "target": required, which queues we expect to get iperf traffic 69 + # - "empty": optional, which queues should see no traffic at all 70 + # - "noise": optional, which queues we expect to see low traffic; 71 + # used for queues of the main context, since some background 72 + # OS activity may use those queues while we're testing 73 + # the value for each is a list, or some other iterable containing queue ids. 74 + 75 + cnts = _get_rx_cnts(cfg) 76 + GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 77 + cnts = _get_rx_cnts(cfg, prev=cnts) 78 + 79 + directed = sum(cnts[i] for i in params['target']) 80 + 81 + ksft_ge(directed, 20000, f"traffic on {name}: " + str(cnts)) 82 + if params.get('noise'): 83 + ksft_lt(sum(cnts[i] for i in params['noise']), directed / 2, 84 + "traffic on other queues:" + str(cnts)) 85 + if params.get('empty'): 86 + ksft_eq(sum(cnts[i] for i in params['empty']), 0, 87 + "traffic on inactive queues: " + str(cnts)) 88 + 89 + 66 90 def test_rss_key_indir(cfg): 67 - """ 68 - Test basics like updating the main RSS key and indirection table. 69 - """ 91 + """Test basics like updating the main RSS key and indirection table.""" 92 + 70 93 if len(_get_rx_cnts(cfg)) < 2: 71 94 KsftSkipEx("Device has only one queue (or doesn't support queue stats)") 72 95 ··· 112 89 113 90 # Set the indirection table 114 91 ethtool(f"-X {cfg.ifname} equal 2") 92 + reset_indir = defer(ethtool, f"-X {cfg.ifname} default") 115 93 data = get_rss(cfg) 116 94 ksft_eq(0, min(data['rss-indirection-table'])) 117 95 ksft_eq(1, max(data['rss-indirection-table'])) ··· 128 104 ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts)) 129 105 130 106 # Restore, and check traffic gets spread again 131 - ethtool(f"-X {cfg.ifname} default") 107 + reset_indir.exec() 132 108 133 109 cnts = _get_rx_cnts(cfg) 134 110 GenerateTraffic(cfg).wait_pkts_and_stop(20000) 135 111 cnts = _get_rx_cnts(cfg, prev=cnts) 136 112 # First two queues get less traffic than all the rest 137 113 ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts)) 114 + 115 + 116 + def test_rss_queue_reconfigure(cfg, main_ctx=True): 117 + """Make sure queue changes can't override requested RSS config. 118 + 119 + By default main RSS table should change to include all queues. 120 + When user sets a specific RSS config the driver should preserve it, 121 + even when queue count changes. Driver should refuse to deactivate 122 + queues used in the user-set RSS config. 123 + """ 124 + 125 + if not main_ctx: 126 + require_ntuple(cfg) 127 + 128 + # Start with 4 queues, an arbitrary known number. 129 + try: 130 + qcnt = len(_get_rx_cnts(cfg)) 131 + ethtool(f"-L {cfg.ifname} combined 4") 132 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 133 + except: 134 + raise KsftSkipEx("Not enough queues for the test or qstat not supported") 135 + 136 + if main_ctx: 137 + ctx_id = 0 138 + ctx_ref = "" 139 + else: 140 + ctx_id = ethtool_create(cfg, "-X", "context new") 141 + ctx_ref = f"context {ctx_id}" 142 + defer(ethtool, f"-X {cfg.ifname} {ctx_ref} delete") 143 + 144 + # Indirection table should be distributing to all queues. 145 + data = get_rss(cfg, context=ctx_id) 146 + ksft_eq(0, min(data['rss-indirection-table'])) 147 + ksft_eq(3, max(data['rss-indirection-table'])) 148 + 149 + # Increase queues, indirection table should be distributing to all queues. 150 + # It's unclear whether tables of additional contexts should be reset, too. 151 + if main_ctx: 152 + ethtool(f"-L {cfg.ifname} combined 5") 153 + data = get_rss(cfg) 154 + ksft_eq(0, min(data['rss-indirection-table'])) 155 + ksft_eq(4, max(data['rss-indirection-table'])) 156 + ethtool(f"-L {cfg.ifname} combined 4") 157 + 158 + # Configure the table explicitly 159 + port = rand_port() 160 + ethtool(f"-X {cfg.ifname} {ctx_ref} weight 1 0 0 1") 161 + if main_ctx: 162 + other_key = 'empty' 163 + defer(ethtool, f"-X {cfg.ifname} default") 164 + else: 165 + other_key = 'noise' 166 + flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}" 167 + ntuple = ethtool_create(cfg, "-N", flow) 168 + defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") 169 + 170 + _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3), 171 + other_key: (1, 2) }) 172 + 173 + # We should be able to increase queues, but table should be left untouched 174 + ethtool(f"-L {cfg.ifname} combined 5") 175 + data = get_rss(cfg, context=ctx_id) 176 + ksft_eq({0, 3}, set(data['rss-indirection-table'])) 177 + 178 + _send_traffic_check(cfg, port, ctx_ref, { 'target': (0, 3), 179 + other_key: (1, 2, 4) }) 180 + 181 + # Setting queue count to 3 should fail, queue 3 is used 182 + try: 183 + ethtool(f"-L {cfg.ifname} combined 3") 184 + except CmdExitFailure: 185 + pass 186 + else: 187 + raise Exception(f"Driver didn't prevent us from deactivating a used queue (context {ctx_id})") 188 + 189 + 190 + def test_rss_resize(cfg): 191 + """Test resizing of the RSS table. 192 + 193 + Some devices dynamically increase and decrease the size of the RSS 194 + indirection table based on the number of enabled queues. 195 + When that happens driver must maintain the balance of entries 196 + (preferably duplicating the smaller table). 197 + """ 198 + 199 + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}}) 200 + ch_max = channels['combined-max'] 201 + qcnt = channels['combined-count'] 202 + 203 + if ch_max < 2: 204 + raise KsftSkipEx(f"Not enough queues for the test: {ch_max}") 205 + 206 + ethtool(f"-L {cfg.ifname} combined 2") 207 + defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 208 + 209 + ethtool(f"-X {cfg.ifname} weight 1 7") 210 + defer(ethtool, f"-X {cfg.ifname} default") 211 + 212 + ethtool(f"-L {cfg.ifname} combined {ch_max}") 213 + data = get_rss(cfg) 214 + ksft_eq(0, min(data['rss-indirection-table'])) 215 + ksft_eq(1, max(data['rss-indirection-table'])) 216 + 217 + ksft_eq(7, 218 + data['rss-indirection-table'].count(1) / 219 + data['rss-indirection-table'].count(0), 220 + f"Table imbalance after resize: {data['rss-indirection-table']}") 221 + 222 + 223 + def test_hitless_key_update(cfg): 224 + """Test that flows may be rehashed without impacting traffic. 225 + 226 + Some workloads may want to rehash the flows in response to an imbalance. 227 + Most effective way to do that is changing the RSS key. Check that changing 228 + the key does not cause link flaps or traffic disruption. 229 + 230 + Disrupting traffic for key update is not a bug, but makes the key 231 + update unusable for rehashing under load. 232 + """ 233 + data = get_rss(cfg) 234 + key_len = len(data['rss-hash-key']) 235 + 236 + key = _rss_key_rand(key_len) 237 + 238 + tgen = GenerateTraffic(cfg) 239 + try: 240 + errors0, carrier0 = get_drop_err_sum(cfg) 241 + t0 = datetime.datetime.now() 242 + ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key)) 243 + t1 = datetime.datetime.now() 244 + errors1, carrier1 = get_drop_err_sum(cfg) 245 + finally: 246 + tgen.wait_pkts_and_stop(5000) 247 + 248 + ksft_lt((t1 - t0).total_seconds(), 0.2) 249 + ksft_eq(errors1 - errors1, 0) 250 + ksft_eq(carrier1 - carrier0, 0) 138 251 139 252 140 253 def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None): ··· 331 170 defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") 332 171 333 172 for i in range(ctx_cnt): 334 - cnts = _get_rx_cnts(cfg) 335 - GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 336 - cnts = _get_rx_cnts(cfg, prev=cnts) 337 - 338 - directed = sum(cnts[2+i*2:4+i*2]) 339 - 340 - ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context:" + str(cnts)) 341 - ksft_ge(directed, 20000, f"traffic on context {i}: " + str(cnts)) 342 - ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 173 + _send_traffic_check(cfg, ports[i], f"context {i}", 174 + { 'target': (2+i*2, 3+i*2), 175 + 'noise': (0, 1), 176 + 'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) }) 343 177 344 178 if requested_ctx_cnt != ctx_cnt: 345 179 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") ··· 350 194 351 195 def test_rss_context4_create_with_cfg(cfg): 352 196 test_rss_context(cfg, 4, create_with_cfg=True) 197 + 198 + 199 + def test_rss_context_queue_reconfigure(cfg): 200 + test_rss_queue_reconfigure(cfg, main_ctx=False) 353 201 354 202 355 203 def test_rss_context_out_of_order(cfg, ctx_cnt=4): ··· 390 230 391 231 def check_traffic(): 392 232 for i in range(ctx_cnt): 393 - cnts = _get_rx_cnts(cfg) 394 - GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 395 - cnts = _get_rx_cnts(cfg, prev=cnts) 396 - 397 233 if ctx[i]: 398 - directed = sum(cnts[2+i*2:4+i*2]) 399 - ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context:" + str(cnts)) 400 - ksft_ge(directed, 20000, f"traffic on context {i}: " + str(cnts)) 401 - ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 234 + expected = { 235 + 'target': (2+i*2, 3+i*2), 236 + 'noise': (0, 1), 237 + 'empty': list(range(2, 2+i*2)) + list(range(4+i*2, 2+2*ctx_cnt)) 238 + } 402 239 else: 403 - ksft_ge(sum(cnts[ :2]), 20000, "traffic on main context:" + str(cnts)) 404 - ksft_eq(sum(cnts[2: ]), 0, "traffic on other contexts: " + str(cnts)) 240 + expected = { 241 + 'target': (0, 1), 242 + 'empty': range(2, 2+2*ctx_cnt) 243 + } 244 + 245 + _send_traffic_check(cfg, ports[i], f"context {i}", expected) 405 246 406 247 # Use queues 0 and 1 for normal traffic 407 248 ethtool(f"-X {cfg.ifname} equal 2") ··· 505 344 506 345 def main() -> None: 507 346 with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 347 + cfg.ethnl = EthtoolFamily() 508 348 cfg.netdevnl = NetdevFamily() 509 349 510 - ksft_run([test_rss_key_indir, 350 + ksft_run([test_rss_key_indir, test_rss_queue_reconfigure, 351 + test_rss_resize, test_hitless_key_update, 511 352 test_rss_context, test_rss_context4, test_rss_context32, 353 + test_rss_context_queue_reconfigure, 512 354 test_rss_context_overlap, test_rss_context_overlap2, 513 355 test_rss_context_out_of_order, test_rss_context4_create_with_cfg], 514 356 args=(cfg, ))