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 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 | 61 +++++++++++++++++++--- .../selftests/drivers/net/lib/sh/lib_netcons.sh | 27 ++++++++-- .../testing/selftests/drivers/net/netcons_basic.sh | 50 +++++++++++------- 3 files changed, 107 insertions(+), 31 deletions(-) --- base-commit: 914873bc7df913db988284876c16257e6ab772c6 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 | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 01baa45221b4b..524e717a71e8e 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. @@ -528,6 +555,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 3c96b022954db..1b508131a6461 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -186,7 +186,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..ada6b899c5282 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 | 52 +++++++++++++--------- 2 files changed, 54 insertions(+), 24 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 1b508131a6461..e0bc5927e83d5 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -96,6 +96,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}')
@@ -107,6 +109,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 }
@@ -160,6 +172,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 @@ -177,10 +190,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 ada6b899c5282..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}" - -exit "${ksft_pass} + +# 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}"
On Mon, 02 Jun 2025 03:34:40 -0700 Breno Leitao 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:
- Implementing dynamic console registration/unregistration based on target availability, ensuring console handlers are only active when needed
- Adding automatic cleanup of unused console registrations when targets are disabled or removed
- 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.
Ah, I wasn't very clear, full form letter at the end.
But also the tests seem to have failed now: https://netdev.bots.linux.dev/contest.html?branch=net-next-2025-06-02--12-00...
## Form letter - net-next-closed
Linus has already merged our PR with features for v6.16. net-next is closed for new drivers, features, code refactoring and optimizations for the remained for the merge window.
Please repost when net-next reopens after June 9th.
RFC patches sent for review only are obviously welcome at any time.
See: https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#developm...
linux-kselftest-mirror@lists.linaro.org