From: Jakub Kicinski <kuba@kernel.org>
To: Breno Leitao <leitao@debian.org>
Cc: Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>, Shuah Khan <shuah@kernel.org>,
Simon Horman <horms@kernel.org>,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
linux-kselftest@vger.kernel.org,
Willem de Bruijn <willemdebruijn.kernel@gmail.com>,
bpf@vger.kernel.org, kernel-team@meta.com,
Willem de Bruijn <willemb@google.com>
Subject: Re: [PATCH net-next v5 3/3] selftests: net: add netpoll basic functionality test
Date: Thu, 10 Jul 2025 18:45:35 -0700 [thread overview]
Message-ID: <20250710184535.374a0643@kernel.org> (raw)
In-Reply-To: <20250709-netpoll_test-v5-3-b3737895affe@debian.org>
On Wed, 09 Jul 2025 02:08:17 -0700 Breno Leitao wrote:
> Add a basic selftest for the netpoll polling mechanism, specifically
> targeting the netpoll poll() side.
>
> The test creates a scenario where network transmission is running at
> maximum speed, and netpoll needs to poll the NIC. This is achieved by:
>
> 1. Configuring a single RX/TX queue to create contention
> 2. Generating background traffic to saturate the interface
> 3. Sending netconsole messages to trigger netpoll polling
> 4. Using dynamic netconsole targets via configfs
> 5. Delete and create new netconsole targets after some messages
> 6. Start a bpftrace in parallel to make sure netpoll_poll_dev() is
> called
> 7. If bpftrace exists and netpoll_poll_dev() was called, stop.
>
> The test validates a critical netpoll code path by monitoring traffic
> flow and ensuring netpoll_poll_dev() is called when the normal TX path
> is blocked.
> +# Max number of netcons messages to send. Each iteration will setup
> +# netconsole and send MAX_WRITES messages
> +ITERATIONS: int = 20
> +# Number of writes to /dev/kmsg per iteration
> +MAX_WRITES: int = 40
FWIW the test takes 25sec on our debug-heavy VMs right now.
I think we can crank the writes quite a bit.. ?
> +def ethtool_read_rx_tx_queue(interface_name: str) -> tuple[int, int]:
> + """
> + Read the number of RX and TX queues using ethtool. This will be used
> + to restore it after the test
> + """
> + rx_queue = 0
> + tx_queue = 0
> +
> + try:
> + ethtool_result = ethtool(f"-g {interface_name}").stdout
json=True please and you'll get a dict, on CLI you can try:
ethtool --json -g eth0
> + for line in ethtool_result.splitlines():
> + if line.startswith("RX:"):
> + rx_queue = int(line.split()[1])
> + if line.startswith("TX:"):
> + tx_queue = int(line.split()[1])
> + except IndexError as exception:
> + raise KsftSkipEx(
> + f"Failed to read RX/TX queues numbers: {exception}. Not going to mess with them."
> + ) from exception
> +
> + if not rx_queue or not tx_queue:
> + raise KsftSkipEx(
> + "Failed to read RX/TX queues numbers. Not going to mess with them."
> + )
> + return rx_queue, tx_queue
> +
> +
> +def ethtool_set_rx_tx_queue(interface_name: str, rx_val: int, tx_val: int) -> None:
> + """Set the number of RX and TX queues to 1 using ethtool"""
> + try:
> + # This don't need to be reverted, since interfaces will be deleted after test
Well. But that's easily fixed;
defer(ethtool, f"-G {interface_name} rx {prev_rx} tx {prev_tx}")
> + ethtool(f"-G {interface_name} rx {rx_val} tx {tx_val}")
This is setting _ring size_ not queue count.
I suppose we want both, this and queue count to 1 (with ethtool -l / -L)
The ring size of 1 is unlikely to work on real devices.
I'd try setting it to 128 and 256 and if neither sticks just carry on
with whatever was there.
> + except Exception as exception:
> + raise KsftSkipEx(
> + f"Failed to configure RX/TX queues: {exception}. Ethtool not available?"
> + ) from exception
> +
> +
> +def netcons_generate_random_target_name() -> str:
> + """Generate a random target name starting with 'netcons'"""
> + random_suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
> + return f"netcons_{random_suffix}"
> +
> +
> +def netcons_create_target(
> + config_data: dict[str, str],
> + target_name: str,
> +) -> None:
> + """Create a netconsole dynamic target against the interfaces"""
> + logging.debug("Using netconsole name: %s", target_name)
> + try:
> + os.makedirs(f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}", exist_ok=True)
> + logging.debug(
> + "Created target directory: %s/%s", NETCONSOLE_CONFIGFS_PATH, target_name
> + )
> + except OSError as exception:
> + if exception.errno != errno.EEXIST:
> + raise KsftFailEx(
> + f"Failed to create netconsole target directory: {exception}"
> + ) from exception
> +
> + try:
> + for key, value in config_data.items():
> + path = f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}/{key}"
> + logging.debug("Writing %s to %s", key, path)
> + with open(path, "w", encoding="utf-8") as file:
> + # Always convert to string to write to file
> + file.write(str(value))
> +
> + # Read all configuration values for debugging purposes
> + for debug_key in config_data.keys():
> + with open(
> + f"{NETCONSOLE_CONFIGFS_PATH}/{target_name}/{debug_key}",
> + "r",
> + encoding="utf-8",
> + ) as file:
> + content = file.read()
> + logging.debug(
> + "%s/%s/%s : %s",
> + NETCONSOLE_CONFIGFS_PATH,
> + target_name,
> + debug_key,
> + content.strip(),
> + )
> +
> + except Exception as exception:
> + raise KsftFailEx(
> + f"Failed to configure netconsole target: {exception}"
> + ) from exception
> +
> +
> +def netcons_configure_target(
> + cfg: NetDrvEpEnv, interface_name: str, target_name: str
> +) -> None:
> + """Configure netconsole on the interface with the given target name"""
> + config_data = {
> + "extended": "1",
> + "dev_name": interface_name,
> + "local_port": NETCONS_LOCAL_PORT,
> + "remote_port": NETCONS_REMOTE_PORT,
> + "local_ip": cfg.addr_v["4"] if cfg.addr_ipver == "4" else cfg.addr_v["6"],
> + "remote_ip": (
> + cfg.remote_addr_v["4"] if cfg.addr_ipver == "4" else cfg.remote_addr_v["6"]
> + ),
this is already done for you
cfg.addr is either v4 or v6 depending on what was provided in the env
> + "remote_mac": "00:00:00:00:00:00", # Not important for this test
> + "enabled": "1",
> + }
> +
> + netcons_create_target(config_data, target_name)
> + logging.debug(
> + "Created netconsole target: %s on interface %s", target_name, interface_name
> + )
> +
> +
> +def netcons_delete_target(name: str) -> None:
> + """Delete a netconsole dynamic target"""
> + target_path = f"{NETCONSOLE_CONFIGFS_PATH}/{name}"
> + try:
> + if os.path.exists(target_path):
> + os.rmdir(target_path)
> + except OSError as exception:
> + raise KsftFailEx(
> + f"Failed to delete netconsole target: {exception}"
> + ) from exception
> +# toggle the interface up and down, to cause some congestion
Let's not do this, you're missing disruptive annotation and for many
drivers NAPI is stopped before queues
https://github.com/linux-netdev/nipa/wiki/Guidance-for-test-authors#ksft_disruptive
> +def toggle_interface(ifname: str) -> None:
> + """Toggle the interface up and down"""
> + logging.debug("Toggling interface %s", ifname)
> + try:
> + ip(f"link set dev {ifname} down")
> + # Send a message while the interface is down, just to
> + # cause more test scenarios. Netconsole should be
> + # going down here as well, giving the link was lost
> + with open("/dev/kmsg", "w", encoding="utf-8") as kmsg:
> + kmsg.write("netcons test while interface down\n")
> +
> + ip(f"link set dev {ifname} up")
> + except Exception as exception:
> + raise KsftFailEx(f"Failed to toggle interface: {exception}") from exception
> +
> +def test_netpoll(cfg: NetDrvEpEnv) -> None:
> + """
> + Test netpoll by sending traffic to the interface and then sending
> + netconsole messages to trigger a poll
> + """
> +
> + target_name = netcons_generate_random_target_name()
> + ifname = cfg.dev["ifname"]
cfg.ifname
> + traffic = None
> + original_queues = ethtool_read_rx_tx_queue(ifname)
> +
> + try:
> + # Set RX/TX queues to 1 to force congestion
> + ethtool_set_rx_tx_queue(ifname, 1, 1)
> +
> + traffic = GenerateTraffic(cfg)
> + do_netpoll_flush_monitored(cfg, ifname, target_name)
> + finally:
> + if traffic:
> + traffic.stop()
> +
> + # Revert RX/TX queues
> + ethtool_set_rx_tx_queue(ifname, original_queues[0], original_queues[1])
> + netcons_delete_target(target_name)
> +def main() -> None:
> + """Main function to run the test"""
> + netcons_load_module()
> + test_check_dependencies()
> + with NetDrvEpEnv(__file__, nsim_test=True) as cfg:
I think nsim_test=True will make the test run _only_ on netdevsim.
But there's nothing netdevsim specific here right?
You can remove the argument and let's have this run against real
drivers, too?
--
pw-bot: cr
next prev parent reply other threads:[~2025-07-11 1:45 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-09 9:08 [PATCH net-next v5 0/3] selftest: net: Add selftest for netpoll Breno Leitao
2025-07-09 9:08 ` [PATCH net-next v5 1/3] selftests: drv-net: add helper/wrapper for bpftrace Breno Leitao
2025-07-09 9:08 ` [PATCH net-next v5 2/3] selftests: drv-net: Strip '@' prefix from bpftrace map keys Breno Leitao
2025-07-09 9:08 ` [PATCH net-next v5 3/3] selftests: net: add netpoll basic functionality test Breno Leitao
2025-07-11 1:45 ` Jakub Kicinski [this message]
2025-07-11 15:51 ` Breno Leitao
2025-07-11 22:41 ` 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=20250710184535.374a0643@kernel.org \
--to=kuba@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=bpf@vger.kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kernel-team@meta.com \
--cc=leitao@debian.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
--cc=willemb@google.com \
--cc=willemdebruijn.kernel@gmail.com \
/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;
as well as URLs for NNTP newsgroup(s).