During performance analysis of console subsystem latency, I discovered that netconsole registers console handlers even when no active targets exist. These orphaned console handlers are invoked on every printk() call, get the lock, iterate through empty target lists, and consume CPU cycles without performing any useful work.
This patch series addresses the inefficiency by:
1. Implementing dynamic console registration/unregistration based on target availability, ensuring console handlers are only active when needed 2. Adding automatic cleanup of unused console registrations when targets are disabled or removed 3. Extending the selftest suite to cover non-extended console format, which was previously untested
The optimization reduces printk() overhead by eliminating unnecessary function calls and list traversals when netconsole targets are not configured, improving overall system performance during heavy logging scenarios.
--- Changes in v3: - Set CON_ENABLED before re-enabling the console, to fix a selftest that was failing, as reported by Jakub on v2. - Link to v2: https://lore.kernel.org/r/20250602-netcons_ext-v2-0-ef88d999326d@debian.org
Changes in v2: - Added selftests to test the new mechanism - Unregister the console if the last target got disabled - Sending to net-next instead of net (Jakub) - Link to v1: https://lore.kernel.org/r/20250528-netcons_ext-v1-1-69f71e404e00@debian.org
--- Breno Leitao (4): netconsole: Only register console drivers when targets are configured netconsole: Add automatic console unregistration on target removal selftests: netconsole: Do not exit from inside the validation function selftests: netconsole: Add support for basic netconsole target format
drivers/net/netconsole.c | 67 +++++++++++++++++++--- .../selftests/drivers/net/lib/sh/lib_netcons.sh | 27 +++++++-- .../testing/selftests/drivers/net/netcons_basic.sh | 50 ++++++++++------ 3 files changed, 112 insertions(+), 32 deletions(-) --- base-commit: 2c7e4a2663a1ab5a740c59c31991579b6b865a26 change-id: 20250528-netcons_ext-572982619bea
Best regards,
The netconsole driver currently registers the basic console driver unconditionally during initialization, even when only extended targets are configured. This results in unnecessary console registration and performance overhead, as the write_msg() callback is invoked for every log message only to return early when no matching targets are found.
Optimize the driver by conditionally registering console drivers based on the actual target configuration. The basic console driver is now registered only when non-extended targets exist, same as the extended console. The implementation also handles dynamic target creation through the configfs interface.
This change eliminates unnecessary console driver registrations, redundant write_msg() callbacks for unused console types, and associated lock contention and target list iterations. The optimization is particularly beneficial for systems using only the most common extended console type.
Fixes: e2f15f9a79201 ("netconsole: implement extended console support") Signed-off-by: Breno Leitao leitao@debian.org --- drivers/net/netconsole.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 4289ccd3e41bf..01baa45221b4b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -86,10 +86,10 @@ static DEFINE_SPINLOCK(target_list_lock); static DEFINE_MUTEX(target_cleanup_list_lock);
/* - * Console driver for extended netconsoles. Registered on the first use to - * avoid unnecessarily enabling ext message formatting. + * Console driver for netconsoles. Register only consoles that have + * an associated target of the same type. */ -static struct console netconsole_ext; +static struct console netconsole_ext, netconsole;
struct netconsole_target_stats { u64_stats_t xmit_drop_count; @@ -97,6 +97,11 @@ struct netconsole_target_stats { struct u64_stats_sync syncp; };
+enum console_type { + CONS_BASIC = BIT(0), + CONS_EXTENDED = BIT(1), +}; + /* Features enabled in sysdata. Contrary to userdata, this data is populated by * the kernel. The fields are designed as bitwise flags, allowing multiple * features to be set in sysdata_fields. @@ -491,6 +496,12 @@ static ssize_t enabled_store(struct config_item *item, if (nt->extended && !console_is_registered(&netconsole_ext)) register_console(&netconsole_ext);
+ /* User might be enabling the basic format target for the very + * first time, make sure the console is registered. + */ + if (!nt->extended && !console_is_registered(&netconsole)) + register_console(&netconsole); + /* * Skip netpoll_parse_options() -- all the attributes are * already configured via configfs. Just print them out. @@ -1691,8 +1702,8 @@ static int __init init_netconsole(void) { int err; struct netconsole_target *nt, *tmp; + u32 console_type_needed = 0; unsigned int count = 0; - bool extended = false; unsigned long flags; char *target_config; char *input = config; @@ -1708,9 +1719,10 @@ static int __init init_netconsole(void) } /* Dump existing printks when we register */ if (nt->extended) { - extended = true; + console_type_needed |= CONS_EXTENDED; netconsole_ext.flags |= CON_PRINTBUFFER; } else { + console_type_needed |= CONS_BASIC; netconsole.flags |= CON_PRINTBUFFER; }
@@ -1729,9 +1741,10 @@ static int __init init_netconsole(void) if (err) goto undonotifier;
- if (extended) + if (console_type_needed & CONS_EXTENDED) register_console(&netconsole_ext); - register_console(&netconsole); + if (console_type_needed & CONS_BASIC) + register_console(&netconsole); pr_info("network logging started\n");
return err; @@ -1761,7 +1774,8 @@ static void __exit cleanup_netconsole(void)
if (console_is_registered(&netconsole_ext)) unregister_console(&netconsole_ext); - unregister_console(&netconsole); + if (console_is_registered(&netconsole)) + unregister_console(&netconsole); dynamic_netconsole_exit(); unregister_netdevice_notifier(&netconsole_netdev_notifier);
Add unregister_netcons_consoles() function to automatically unregister console handlers when no targets of the corresponding type remain active.
The function iterates through the target list to determine which console types (basic vs extended) are still needed, and unregisters any console handlers that are no longer required. This prevents having registered console handlers without corresponding active targets.
The function is called when a target is disabled and moved to the cleanup list, ensuring proper cleanup of unused console registrations.
Signed-off-by: Breno Leitao leitao@debian.org --- drivers/net/netconsole.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 01baa45221b4b..21077aff061c5 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -460,6 +460,33 @@ static ssize_t sysdata_release_enabled_show(struct config_item *item, return sysfs_emit(buf, "%d\n", release_enabled); }
+/* Iterate in the list of target, and make sure we don't have any console + * register without targets of the same type + */ +static void unregister_netcons_consoles(void) +{ + struct netconsole_target *nt; + u32 console_type_needed = 0; + unsigned long flags; + + spin_lock_irqsave(&target_list_lock, flags); + list_for_each_entry(nt, &target_list, list) { + if (nt->extended) + console_type_needed |= CONS_EXTENDED; + else + console_type_needed |= CONS_BASIC; + } + spin_unlock_irqrestore(&target_list_lock, flags); + + if (!(console_type_needed & CONS_EXTENDED) && + console_is_registered(&netconsole_ext)) + unregister_console(&netconsole_ext); + + if (!(console_type_needed & CONS_BASIC) && + console_is_registered(&netconsole)) + unregister_console(&netconsole); +} + /* * This one is special -- targets created through the configfs interface * are not enabled (and the corresponding netpoll activated) by default. @@ -493,14 +520,18 @@ static ssize_t enabled_store(struct config_item *item, goto out_unlock; }
- if (nt->extended && !console_is_registered(&netconsole_ext)) + if (nt->extended && !console_is_registered(&netconsole_ext)) { + netconsole_ext.flags |= CON_ENABLED; register_console(&netconsole_ext); + }
/* User might be enabling the basic format target for the very * first time, make sure the console is registered. */ - if (!nt->extended && !console_is_registered(&netconsole)) + if (!nt->extended && !console_is_registered(&netconsole)) { + netconsole.flags |= CON_ENABLED; register_console(&netconsole); + }
/* * Skip netpoll_parse_options() -- all the attributes are @@ -528,6 +559,10 @@ static ssize_t enabled_store(struct config_item *item, list_move(&nt->list, &target_cleanup_list); spin_unlock_irqrestore(&target_list_lock, flags); mutex_unlock(&target_cleanup_list_lock); + /* Unregister consoles, whose the last target of that type got + * disabled. + */ + unregister_netcons_consoles(); }
ret = strnlen(buf, count);
Remove the exit call from validate_result() function and move the test exit logic to the main script. This allows the function to be reused in scenarios where the test needs to continue execution after validation, rather than terminating immediately.
The validate_result() function should focus on validation logic only, while the calling script maintains control over program flow and exit conditions. This change improves code modularity and prepares for potential future enhancements where multiple validations might be needed in a single test run.
Signed-off-by: Breno Leitao leitao@debian.org --- tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh | 1 - tools/testing/selftests/drivers/net/netcons_basic.sh | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh index 29b01b8e2215c..2d5dd3297693c 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -185,7 +185,6 @@ function validate_result() { # Delete the file once it is validated, otherwise keep it # for debugging purposes rm "${TMPFILENAME}" - exit "${ksft_pass}" }
function check_for_dependencies() { diff --git a/tools/testing/selftests/drivers/net/netcons_basic.sh b/tools/testing/selftests/drivers/net/netcons_basic.sh index fe765da498e84..d2f0685d24ba3 100755 --- a/tools/testing/selftests/drivers/net/netcons_basic.sh +++ b/tools/testing/selftests/drivers/net/netcons_basic.sh @@ -50,3 +50,5 @@ busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" # Make sure the message was received in the dst part # and exit validate_result "${OUTPUT_FILE}" + +exit "${ksft_pass}"
Extend the netconsole selftest to validate both basic and extended target formats. The basic format is a simpler variant that doesn't support userdata or release functionality.
The test now validates that netconsole works correctly in both configurations, improving test coverage for different netconsole deployment scenarios.
Signed-off-by: Breno Leitao leitao@debian.org --- .../selftests/drivers/net/lib/sh/lib_netcons.sh | 26 ++++++++++-- .../testing/selftests/drivers/net/netcons_basic.sh | 48 ++++++++++++++-------- 2 files changed, 52 insertions(+), 22 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh index 2d5dd3297693c..71a5a8b1712c0 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -95,6 +95,8 @@ function set_network() { }
function create_dynamic_target() { + local FORMAT=${1:-"extended"} + DSTMAC=$(ip netns exec "${NAMESPACE}" \ ip link show "${DSTIF}" | awk '/ether/ {print $2}')
@@ -106,6 +108,16 @@ function create_dynamic_target() { echo "${DSTMAC}" > "${NETCONS_PATH}"/remote_mac echo "${SRCIF}" > "${NETCONS_PATH}"/dev_name
+ if [ "${FORMAT}" == "basic" ] + then + # Basic target does not support release + echo 0 > "${NETCONS_PATH}"/release + echo 0 > "${NETCONS_PATH}"/extended + elif [ "${FORMAT}" == "extended" ] + then + echo 1 > "${NETCONS_PATH}"/extended + fi + echo 1 > "${NETCONS_PATH}"/enabled }
@@ -159,6 +171,7 @@ function listen_port_and_save_to() {
function validate_result() { local TMPFILENAME="$1" + local FORMAT=${2:-"extended"}
# TMPFILENAME will contain something like: # 6.11.1-0_fbk0_rc13_509_g30d75cea12f7,13,1822,115075213798,-;netconsole selftest: netcons_gtJHM @@ -176,10 +189,15 @@ function validate_result() { exit "${ksft_fail}" fi
- if ! grep -q "${USERDATA_KEY}=${USERDATA_VALUE}" "${TMPFILENAME}"; then - echo "FAIL: ${USERDATA_KEY}=${USERDATA_VALUE} not found in ${TMPFILENAME}" >&2 - cat "${TMPFILENAME}" >&2 - exit "${ksft_fail}" + # userdata is not supported on basic format target, + # thus, do not validate it. + if [ "${FORMAT}" != "basic" ]; + then + if ! grep -q "${USERDATA_KEY}=${USERDATA_VALUE}" "${TMPFILENAME}"; then + echo "FAIL: ${USERDATA_KEY}=${USERDATA_VALUE} not found in ${TMPFILENAME}" >&2 + cat "${TMPFILENAME}" >&2 + exit "${ksft_fail}" + fi fi
# Delete the file once it is validated, otherwise keep it diff --git a/tools/testing/selftests/drivers/net/netcons_basic.sh b/tools/testing/selftests/drivers/net/netcons_basic.sh index d2f0685d24ba3..40a6ac6191b8b 100755 --- a/tools/testing/selftests/drivers/net/netcons_basic.sh +++ b/tools/testing/selftests/drivers/net/netcons_basic.sh @@ -32,23 +32,35 @@ check_for_dependencies echo "6 5" > /proc/sys/kernel/printk # Remove the namespace, interfaces and netconsole target on exit trap cleanup EXIT -# Create one namespace and two interfaces -set_network -# Create a dynamic target for netconsole -create_dynamic_target -# Set userdata "key" with the "value" value -set_user_data -# Listed for netconsole port inside the namespace and destination interface -listen_port_and_save_to "${OUTPUT_FILE}" & -# Wait for socat to start and listen to the port. -wait_local_port_listen "${NAMESPACE}" "${PORT}" udp -# Send the message -echo "${MSG}: ${TARGET}" > /dev/kmsg -# Wait until socat saves the file to disk -busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" - -# Make sure the message was received in the dst part -# and exit -validate_result "${OUTPUT_FILE}"
+# Run the test twice, with different format modes +for FORMAT in "basic" "extended" +do + echo "Running with target mode: ${FORMAT}" + # Create one namespace and two interfaces + set_network + # Create a dynamic target for netconsole + create_dynamic_target "${FORMAT}" + # Only set userdata for extended format + if [ "$FORMAT" == "extended" ] + then + # Set userdata "key" with the "value" value + set_user_data + fi + # Listed for netconsole port inside the namespace and destination interface + listen_port_and_save_to "${OUTPUT_FILE}" & + # Wait for socat to start and listen to the port. + wait_local_port_listen "${NAMESPACE}" "${PORT}" udp + # Send the message + echo "${MSG}: ${TARGET}" > /dev/kmsg + # Wait until socat saves the file to disk + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + + # Make sure the message was received in the dst part + # and exit + validate_result "${OUTPUT_FILE}" "${FORMAT}" + cleanup +done + +trap - EXIT exit "${ksft_pass}"
Hello:
This series was applied to netdev/net-next.git (main) by Jakub Kicinski kuba@kernel.org:
On Mon, 09 Jun 2025 02:46:25 -0700 you wrote:
During performance analysis of console subsystem latency, I discovered that netconsole registers console handlers even when no active targets exist. These orphaned console handlers are invoked on every printk() call, get the lock, iterate through empty target lists, and consume CPU cycles without performing any useful work.
This patch series addresses the inefficiency by:
[...]
Here is the summary with links: - [net-next,v3,1/4] netconsole: Only register console drivers when targets are configured https://git.kernel.org/netdev/net-next/c/bc0cb64db1c7 - [net-next,v3,2/4] netconsole: Add automatic console unregistration on target removal https://git.kernel.org/netdev/net-next/c/e99d938f8671 - [net-next,v3,3/4] selftests: netconsole: Do not exit from inside the validation function https://git.kernel.org/netdev/net-next/c/69b25dd20c83 - [net-next,v3,4/4] selftests: netconsole: Add support for basic netconsole target format https://git.kernel.org/netdev/net-next/c/224a6e602fb3
You are awesome, thank you!
linux-kselftest-mirror@lists.linaro.org