From: Stanislav Fomichev <stfomichev@gmail.com>
To: Jakub Kicinski <kuba@kernel.org>
Cc: davem@davemloft.net, netdev@vger.kernel.org, edumazet@google.com,
pabeni@redhat.com, andrew+netdev@lunn.ch, horms@kernel.org,
donald.hunter@gmail.com, sdf@fomichev.me, almasrymina@google.com,
dw@davidwei.uk, asml.silence@gmail.com, ap420073@gmail.com,
jdamato@fastly.com, dtatulea@nvidia.com,
michael.chan@broadcom.com
Subject: Re: [RFC net-next 22/22] selftests: drv-net: add test for rx-buf-len
Date: Tue, 22 Apr 2025 10:06:48 -0700 [thread overview]
Message-ID: <aAfMqE_m6QFTph_k@mini-arch> (raw)
In-Reply-To: <20250421222827.283737-23-kuba@kernel.org>
On 04/21, Jakub Kicinski wrote:
> Add a test for rx-buf-len. Check both nic-wide and per-queue settings.
>
> # ./drivers/net/hw/rx_buf_len.py
> TAP version 13
> 1..6
> ok 1 rx_buf_len.nic_wide
> ok 2 rx_buf_len.nic_wide_check_packets
> ok 3 rx_buf_len.per_queue
> ok 4 rx_buf_len.per_queue_check_packets
> ok 5 rx_buf_len.queue_check_ring_count
> ok 6 rx_buf_len.queue_check_ring_count_check_packets
> # Totals: pass:6 fail:0 xfail:0 xpass:0 skip:0 error:0
>
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> .../testing/selftests/drivers/net/hw/Makefile | 1 +
> .../selftests/drivers/net/hw/rx_buf_len.py | 299 ++++++++++++++++++
> 2 files changed, 300 insertions(+)
> create mode 100755 tools/testing/selftests/drivers/net/hw/rx_buf_len.py
>
> diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
> index 07cddb19ba35..88625c0e86c8 100644
> --- a/tools/testing/selftests/drivers/net/hw/Makefile
> +++ b/tools/testing/selftests/drivers/net/hw/Makefile
> @@ -20,6 +20,7 @@ TEST_PROGS = \
> pp_alloc_fail.py \
> rss_ctx.py \
> rss_input_xfrm.py \
> + rx_buf_len.py \
> tso.py \
> #
>
> diff --git a/tools/testing/selftests/drivers/net/hw/rx_buf_len.py b/tools/testing/selftests/drivers/net/hw/rx_buf_len.py
> new file mode 100755
> index 000000000000..d8a6d07fac5e
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/net/hw/rx_buf_len.py
> @@ -0,0 +1,299 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +
> +import errno, time
> +from typing import Tuple
> +from lib.py import ksft_run, ksft_exit, KsftSkipEx, KsftFailEx
> +from lib.py import ksft_eq, ksft_ge, ksft_in, ksft_not_in
> +from lib.py import EthtoolFamily, NetdevFamily, NlError
> +from lib.py import NetDrvEpEnv, GenerateTraffic
> +from lib.py import cmd, defer, bpftrace, ethtool, rand_port
> +
> +
> +def _do_bpftrace(cfg, mul, base_size, tgt_queue=None):
> + queue_filter = ''
> + if tgt_queue is not None:
> + queue_filter = 'if ($skb->queue_mapping != %d) {return;}' % (tgt_queue + 1, )
if tgt_queue: should work as well?
> +
> + t = ('tracepoint:net:netif_receive_skb { ' +
> + '$skb = (struct sk_buff *)args->skbaddr; '+
> + '$sh = (struct skb_shared_info *)($skb->head + $skb->end); ' +
> + 'if ($skb->dev->ifindex != ' + str(cfg.ifindex) + ') {return;} ' +
> + queue_filter +
> + '@[$skb->len - $skb->data_len] = count(); ' +
> + '@h[$skb->len - $skb->data_len] = count(); ' +
> + 'if ($sh->nr_frags > 0) { @[$sh->frags[0].len] = count(); @d[$sh->frags[0].len] = count();} }'
> + )
Why do we have @h and @d? We seem to check only the 'sizes'/@?
> + maps = bpftrace(t, json=True, timeout=2)
> + # We expect one-dim array with something like:
> + # {"type": "map", "data": {"@": {"1500": 1, "719": 1,
> + sizes = maps["@"]
> + h = maps["@h"]
> + d = maps["@d"]
> + good = 0
> + bad = 0
[..]
> + for k, v in sizes.items():
> + k = int(k)
> + if mul == 1 and k > base_size:
> + bad += v
> + elif mul > 1 and k > base_size:
> + good += v
> + elif mul < 1 and k >= base_size:
> + bad += v
I haven't fully processed what's going on here, but will it be
easier if we go from mul*base_size to old_size and new_size? Or maybe
the comments can help?
if old_size == new_size and frag > old_size:
# unchanged buf len, unexpected large frag
elif new_size < old_size and frag >= old_size:
# shrank buf len, but got old (big) frag
elif new_size > old_size and frag > old_size:
# good
> + ksft_eq(bad, 0, "buffer was decreased but large buffers seen")
> + if mul > 1:
> + ksft_ge(good, 100, "buffer was increased but no large buffers seen")
> +
> +
> +def _ethtool_create(cfg, act, opts):
> + output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
> + # Output will be something like: "New RSS context is 1" or
> + # "Added rule with ID 7", we want the integer from the end
> + return int(output.split()[-1])
> +
> +
> +def nic_wide(cfg, check_geometry=False) -> None:
> + """
> + Apply NIC wide rx-buf-len change. Run some traffic to make sure there
> + are no crashes. Test that setting 0 restores driver default.
> + Assume we start with the default.
> + """
> + try:
> + rings = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
> + except NlError as e:
> + rings = {}
> + if "rx-buf-len" not in rings:
> + raise KsftSkipEx('rx-buf-len configuration not supported by device')
> +
> + if rings['rx-buf-len'] * 2 <= rings['rx-buf-len-max']:
> + mul = 2
> + else:
> + mul = 1/2
And similarly here? (and elsewhere)
def pick_buf_size(rings):
""" pick new rx-buf-len depending on current and max settings """
buf_len = rings['rx-buf-len']
if buf_len * 2 <= <= rings['rx-buf-len-max']:
# if can grow, try to grow
return buf_len, buf_len * 2
else:
# otherwise shrink
return buf_len, buf_len / 2
old_buf_len, new_buf_len = pick_buf_size(ring)
...
(or maybe its just me, idk, easier to think in old>new comparisons vs
doing mul*base_size math)
> + cfg.ethnl.rings_set({'header': {'dev-index': cfg.ifindex},
> + 'rx-buf-len': rings['rx-buf-len'] * mul})
> +
> + # Use zero to restore default, per uAPI, we assume we started with default
> + reset = defer(cfg.ethnl.rings_set, {'header': {'dev-index': cfg.ifindex},
> + 'rx-buf-len': 0})
> +
> + new = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
> + ksft_eq(new['rx-buf-len'], rings['rx-buf-len'] * mul, "config after change")
> +
> + # Runs some traffic thru them buffers, to make things implode if they do
> + traf = GenerateTraffic(cfg)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, mul, rings['rx-buf-len'])
> + finally:
> + traf.wait_pkts_and_stop(20000)
> +
> + reset.exec()
> + new = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
> + ksft_eq(new['rx-buf-len'], rings['rx-buf-len'], "config reset to default")
> +
> +
> +def nic_wide_check_packets(cfg) -> None:
> + nic_wide(cfg, check_geometry=True)
> +
> +
> +def _check_queues_with_config(cfg, buf_len, qset):
> + cnt = 0
> + queues = cfg.netnl.queue_get({'ifindex': cfg.ifindex}, dump=True)
> + for q in queues:
> + if 'rx-buf-len' in q:
> + cnt += 1
> + ksft_eq(q['type'], "rx")
> + ksft_in(q['id'], qset)
> + ksft_eq(q['rx-buf-len'], buf_len, "buf size")
> + if cnt != len(qset):
> + raise KsftFailEx('queue rx-buf-len config invalid')
> +
> +
> +def _per_queue_configure(cfg) -> Tuple[dict, int, defer]:
> + """
> + Prep for per queue test. Set the config on one queue and return
> + the original ring settings, the multiplier and reset defer.
> + """
> + # Validate / get initial settings
> + try:
> + rings = cfg.ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
> + except NlError as e:
> + rings = {}
> + if "rx-buf-len" not in rings:
> + raise KsftSkipEx('rx-buf-len configuration not supported by device')
> +
> + try:
> + queues = cfg.netnl.queue_get({'ifindex': cfg.ifindex}, dump=True)
> + except NlError as e:
> + raise KsftSkipEx('queue configuration not supported by device')
> +
> + if len(queues) < 2:
> + raise KsftSkipEx('not enough queues: ' + str(len(queues)))
> + for q in queues:
> + if 'rx-buf-len' in q:
> + raise KsftFailEx('queue rx-buf-len already configured')
> +
> + # Apply a change, we'll target queue 1
> + if rings['rx-buf-len'] * 2 <= rings['rx-buf-len-max']:
> + mul = 2
> + else:
> + mul = 1/2
> + try:
> + cfg.netnl.queue_set({'ifindex': cfg.ifindex, "type": "rx", "id": 1,
> + 'rx-buf-len': rings['rx-buf-len'] * mul })
> + except NlError as e:
> + if e.error == errno.EOPNOTSUPP:
> + raise KsftSkipEx('per-queue rx-buf-len configuration not supported')
> + raise
> +
> + reset = defer(cfg.netnl.queue_set, {'ifindex': cfg.ifindex,
> + "type": "rx", "id": 1,
> + 'rx-buf-len': 0})
> + # Make sure config stuck
> + _check_queues_with_config(cfg, rings['rx-buf-len'] * mul, {1})
> +
> + return rings, mul, reset
> +
> +
> +def per_queue(cfg, check_geometry=False) -> None:
> + """
> + Similar test to nic_wide, but done a single queue (queue 1).
> + Flow filter is used to direct traffic to that queue.
> + """
> +
> + rings, mul, reset = _per_queue_configure(cfg)
> + _check_queues_with_config(cfg, rings['rx-buf-len'] * mul, {1})
> +
> + # Check with traffic, we need to direct the traffic to the expected queue
> + port = rand_port()
> + flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} action 1"
> + nid = _ethtool_create(cfg, "-N", flow)
> + ntuple = defer(ethtool, f"-N {cfg.ifname} delete {nid}")
> +
> + traf = GenerateTraffic(cfg, port=port)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, mul, rings['rx-buf-len'], tgt_queue=1)
> + finally:
> + traf.wait_pkts_and_stop(20000)
> + ntuple.exec()
> +
> + # And now direct to another queue
> + flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port} action 0"
> + nid = _ethtool_create(cfg, "-N", flow)
> + ntuple = defer(ethtool, f"-N {cfg.ifname} delete {nid}")
> +
> + traf = GenerateTraffic(cfg, port=port)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, 1, rings['rx-buf-len'], tgt_queue=0)
> + finally:
> + traf.wait_pkts_and_stop(20000)
> +
> + # Back to default
> + reset.exec()
> + queues = cfg.netnl.queue_get({'ifindex': cfg.ifindex}, dump=True)
> + for q in queues:
> + ksft_not_in('rx-buf-len', q)
> +
> +
> +def per_queue_check_packets(cfg) -> None:
> + per_queue(cfg, check_geometry=True)
> +
> +
> +def queue_check_ring_count(cfg, check_geometry=False) -> None:
> + """
> + Make sure the change of ring count is handled correctly.
> + """
> + rings, mul, reset = _per_queue_configure(cfg)
> +
> + channels = cfg.ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
> + if channels.get('combined-count', 0) < 4:
> + raise KsftSkipEx('need at least 4 rings, have',
> + channels.get('combined-count'))
> +
> + # Move the channel count up and down, should make no difference
> + moves = [1, 0]
> + if channels['combined-count'] == channels['combined-max']:
> + moves = [-1, 0]
> + for move in moves:
> + target = channels['combined-count'] + move
> + cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex},
> + 'combined-count': target})
> +
> + _check_queues_with_config(cfg, rings['rx-buf-len'] * mul, {1})
> +
> + # Check with traffic, we need to direct the traffic to the expected queue
> + port1 = rand_port()
> + flow1 = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port1} action 1"
> + nid = _ethtool_create(cfg, "-N", flow1)
> + ntuple = defer(ethtool, f"-N {cfg.ifname} delete {nid}")
> +
> + traf = GenerateTraffic(cfg, port=port1)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, mul, rings['rx-buf-len'], tgt_queue=1)
> + finally:
> + traf.wait_pkts_and_stop(20000)
> +
> + # And now direct to another queue
> + port0 = rand_port()
> + flow = f"flow-type tcp{cfg.addr_ipver} dst-ip {cfg.addr} dst-port {port0} action 0"
> + nid = _ethtool_create(cfg, "-N", flow)
> + defer(ethtool, f"-N {cfg.ifname} delete {nid}")
> +
> + traf = GenerateTraffic(cfg, port=port0)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, 1, rings['rx-buf-len'], tgt_queue=0)
> + finally:
> + traf.wait_pkts_and_stop(20000)
> +
> + # Go to a single queue, should reset
> + ntuple.exec()
> + cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex},
> + 'combined-count': 1})
> + cfg.ethnl.channels_set({'header': {'dev-index': cfg.ifindex},
> + 'combined-count': channels['combined-count']})
> +
> + nid = _ethtool_create(cfg, "-N", flow1)
> + defer(ethtool, f"-N {cfg.ifname} delete {nid}")
> +
> + queues = cfg.netnl.queue_get({'ifindex': cfg.ifindex}, dump=True)
> + for q in queues:
> + ksft_not_in('rx-buf-len', q)
> +
> + # Check with traffic that queue is now getting normal buffers
> + traf = GenerateTraffic(cfg, port=port1)
> + try:
> + if check_geometry:
> + _do_bpftrace(cfg, 1, rings['rx-buf-len'], tgt_queue=1)
> + finally:
> + traf.wait_pkts_and_stop(20000)
> +
> +
> +def queue_check_ring_count_check_packets(cfg):
> + queue_check_ring_count(cfg, True)
> +
> +
> +def main() -> None:
> + with NetDrvEpEnv(__file__) as cfg:
> + cfg.netnl = NetdevFamily()
> + cfg.ethnl = EthtoolFamily()
> +
> + o = [nic_wide,
> + per_queue,
> + nic_wide_check_packets]
o?
next prev parent reply other threads:[~2025-04-22 17:06 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-04-21 22:28 [RFC net-next 00/22] net: per-queue rx-buf-len configuration Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 01/22] docs: ethtool: document that rx_buf_len must control payload lengths Jakub Kicinski
2025-04-22 16:19 ` David Wei
2025-04-22 19:48 ` Joe Damato
2025-04-23 20:08 ` Mina Almasry
2025-04-25 22:50 ` Jakub Kicinski
2025-04-25 23:20 ` Joe Damato
2025-04-21 22:28 ` [RFC net-next 02/22] net: ethtool: report max value for rx-buf-len Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 03/22] net: use zero value to restore rx_buf_len to default Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 04/22] net: clarify the meaning of netdev_config members Jakub Kicinski
2025-04-22 19:57 ` Joe Damato
2025-04-21 22:28 ` [RFC net-next 05/22] net: add rx_buf_len to netdev config Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 06/22] eth: bnxt: read the page size from the adapter struct Jakub Kicinski
2025-04-23 20:35 ` Mina Almasry
2025-04-25 22:51 ` Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 07/22] eth: bnxt: set page pool page order based on rx_page_size Jakub Kicinski
2025-04-22 15:32 ` Stanislav Fomichev
2025-04-22 15:52 ` Jakub Kicinski
2025-04-22 17:27 ` Stanislav Fomichev
2025-04-21 22:28 ` [RFC net-next 08/22] eth: bnxt: support setting size of agg buffers via ethtool Jakub Kicinski
2025-04-23 21:00 ` Mina Almasry
2025-04-25 22:58 ` Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 09/22] net: move netdev_config manipulation to dedicated helpers Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 10/22] net: reduce indent of struct netdev_queue_mgmt_ops members Jakub Kicinski
2025-04-23 21:04 ` Mina Almasry
2025-04-21 22:28 ` [RFC net-next 11/22] net: allocate per-queue config structs and pass them thru the queue API Jakub Kicinski
2025-04-23 21:17 ` Mina Almasry
2025-04-25 23:24 ` Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 12/22] net: pass extack to netdev_rx_queue_restart() Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 13/22] net: add queue config validation callback Jakub Kicinski
2025-04-22 15:49 ` Stanislav Fomichev
2025-04-22 20:16 ` Joe Damato
2025-04-21 22:28 ` [RFC net-next 14/22] eth: bnxt: always set the queue mgmt ops Jakub Kicinski
2025-04-22 15:50 ` Stanislav Fomichev
2025-04-22 20:18 ` Joe Damato
2025-04-21 22:28 ` [RFC net-next 15/22] eth: bnxt: store the rx buf size per queue Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 16/22] eth: bnxt: adjust the fill level of agg queues with larger buffers Jakub Kicinski
2025-04-22 16:13 ` Stanislav Fomichev
2025-04-21 22:28 ` [RFC net-next 17/22] netdev: add support for setting rx-buf-len per queue Jakub Kicinski
2025-04-22 16:15 ` Stanislav Fomichev
2025-04-25 23:41 ` Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 18/22] net: wipe the setting of deactived queues Jakub Kicinski
2025-04-22 16:21 ` Stanislav Fomichev
2025-04-25 23:42 ` Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 19/22] eth: bnxt: use queue op config validate Jakub Kicinski
2025-04-23 10:00 ` Dragos Tatulea
2025-04-23 13:46 ` Jakub Kicinski
2025-04-23 14:24 ` Dragos Tatulea
2025-04-23 15:33 ` Jakub Kicinski
2025-06-12 11:56 ` Dragos Tatulea
2025-06-12 14:10 ` Jakub Kicinski
2025-06-12 15:52 ` Dragos Tatulea
2025-06-12 22:30 ` Jakub Kicinski
2025-06-13 19:02 ` Dragos Tatulea
2025-06-13 23:16 ` Jakub Kicinski
2025-06-17 12:36 ` Dragos Tatulea
2025-04-21 22:28 ` [RFC net-next 20/22] eth: bnxt: support per queue configuration of rx-buf-len Jakub Kicinski
2025-04-21 22:28 ` [RFC net-next 21/22] selftests: drv-net: add helper/wrapper for bpftrace Jakub Kicinski
2025-04-22 16:36 ` Stanislav Fomichev
2025-04-22 16:39 ` Stanislav Fomichev
2025-06-25 12:23 ` Breno Leitao
2025-04-21 22:28 ` [RFC net-next 22/22] selftests: drv-net: add test for rx-buf-len Jakub Kicinski
2025-04-22 17:06 ` Stanislav Fomichev [this message]
2025-04-25 23:52 ` Jakub Kicinski
2025-04-23 20:02 ` [RFC net-next 00/22] net: per-queue rx-buf-len configuration Mina Almasry
2025-04-25 23:55 ` Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aAfMqE_m6QFTph_k@mini-arch \
--to=stfomichev@gmail.com \
--cc=almasrymina@google.com \
--cc=andrew+netdev@lunn.ch \
--cc=ap420073@gmail.com \
--cc=asml.silence@gmail.com \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=dtatulea@nvidia.com \
--cc=dw@davidwei.uk \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=jdamato@fastly.com \
--cc=kuba@kernel.org \
--cc=michael.chan@broadcom.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sdf@fomichev.me \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox