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

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'netconsole-optimize-console-registration-and-improve-testing'

Breno Leitao says:

====================
netconsole: Optimize console registration and improve testing

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.

v2: https://lore.kernel.org/20250602-netcons_ext-v2-0-ef88d999326d@debian.org
v1: https://lore.kernel.org/20250528-netcons_ext-v1-1-69f71e404e00@debian.org
====================

Link: https://patch.msgid.link/20250609-netcons_ext-v3-0-5336fa670326@debian.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+111 -31
+58 -9
drivers/net/netconsole.c
··· 86 86 static DEFINE_MUTEX(target_cleanup_list_lock); 87 87 88 88 /* 89 - * Console driver for extended netconsoles. Registered on the first use to 90 - * avoid unnecessarily enabling ext message formatting. 89 + * Console driver for netconsoles. Register only consoles that have 90 + * an associated target of the same type. 91 91 */ 92 - static struct console netconsole_ext; 92 + static struct console netconsole_ext, netconsole; 93 93 94 94 struct netconsole_target_stats { 95 95 u64_stats_t xmit_drop_count; 96 96 u64_stats_t enomem_count; 97 97 struct u64_stats_sync syncp; 98 + }; 99 + 100 + enum console_type { 101 + CONS_BASIC = BIT(0), 102 + CONS_EXTENDED = BIT(1), 98 103 }; 99 104 100 105 /* Features enabled in sysdata. Contrary to userdata, this data is populated by ··· 460 455 return sysfs_emit(buf, "%d\n", release_enabled); 461 456 } 462 457 458 + /* Iterate in the list of target, and make sure we don't have any console 459 + * register without targets of the same type 460 + */ 461 + static void unregister_netcons_consoles(void) 462 + { 463 + struct netconsole_target *nt; 464 + u32 console_type_needed = 0; 465 + unsigned long flags; 466 + 467 + spin_lock_irqsave(&target_list_lock, flags); 468 + list_for_each_entry(nt, &target_list, list) { 469 + if (nt->extended) 470 + console_type_needed |= CONS_EXTENDED; 471 + else 472 + console_type_needed |= CONS_BASIC; 473 + } 474 + spin_unlock_irqrestore(&target_list_lock, flags); 475 + 476 + if (!(console_type_needed & CONS_EXTENDED) && 477 + console_is_registered(&netconsole_ext)) 478 + unregister_console(&netconsole_ext); 479 + 480 + if (!(console_type_needed & CONS_BASIC) && 481 + console_is_registered(&netconsole)) 482 + unregister_console(&netconsole); 483 + } 484 + 463 485 /* 464 486 * This one is special -- targets created through the configfs interface 465 487 * are not enabled (and the corresponding netpoll activated) by default. ··· 520 488 goto out_unlock; 521 489 } 522 490 523 - if (nt->extended && !console_is_registered(&netconsole_ext)) 491 + if (nt->extended && !console_is_registered(&netconsole_ext)) { 492 + netconsole_ext.flags |= CON_ENABLED; 524 493 register_console(&netconsole_ext); 494 + } 495 + 496 + /* User might be enabling the basic format target for the very 497 + * first time, make sure the console is registered. 498 + */ 499 + if (!nt->extended && !console_is_registered(&netconsole)) { 500 + netconsole.flags |= CON_ENABLED; 501 + register_console(&netconsole); 502 + } 525 503 526 504 /* 527 505 * Skip netpoll_parse_options() -- all the attributes are ··· 559 517 list_move(&nt->list, &target_cleanup_list); 560 518 spin_unlock_irqrestore(&target_list_lock, flags); 561 519 mutex_unlock(&target_cleanup_list_lock); 520 + /* Unregister consoles, whose the last target of that type got 521 + * disabled. 522 + */ 523 + unregister_netcons_consoles(); 562 524 } 563 525 564 526 ret = strnlen(buf, count); ··· 1737 1691 { 1738 1692 int err; 1739 1693 struct netconsole_target *nt, *tmp; 1694 + u32 console_type_needed = 0; 1740 1695 unsigned int count = 0; 1741 - bool extended = false; 1742 1696 unsigned long flags; 1743 1697 char *target_config; 1744 1698 char *input = config; ··· 1754 1708 } 1755 1709 /* Dump existing printks when we register */ 1756 1710 if (nt->extended) { 1757 - extended = true; 1711 + console_type_needed |= CONS_EXTENDED; 1758 1712 netconsole_ext.flags |= CON_PRINTBUFFER; 1759 1713 } else { 1714 + console_type_needed |= CONS_BASIC; 1760 1715 netconsole.flags |= CON_PRINTBUFFER; 1761 1716 } 1762 1717 ··· 1776 1729 if (err) 1777 1730 goto undonotifier; 1778 1731 1779 - if (extended) 1732 + if (console_type_needed & CONS_EXTENDED) 1780 1733 register_console(&netconsole_ext); 1781 - register_console(&netconsole); 1734 + if (console_type_needed & CONS_BASIC) 1735 + register_console(&netconsole); 1782 1736 pr_info("network logging started\n"); 1783 1737 1784 1738 return err; ··· 1809 1761 1810 1762 if (console_is_registered(&netconsole_ext)) 1811 1763 unregister_console(&netconsole_ext); 1812 - unregister_console(&netconsole); 1764 + if (console_is_registered(&netconsole)) 1765 + unregister_console(&netconsole); 1813 1766 dynamic_netconsole_exit(); 1814 1767 unregister_netdevice_notifier(&netconsole_netdev_notifier); 1815 1768
+22 -5
tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh
··· 95 95 } 96 96 97 97 function create_dynamic_target() { 98 + local FORMAT=${1:-"extended"} 99 + 98 100 DSTMAC=$(ip netns exec "${NAMESPACE}" \ 99 101 ip link show "${DSTIF}" | awk '/ether/ {print $2}') 100 102 ··· 107 105 echo "${SRCIP}" > "${NETCONS_PATH}"/local_ip 108 106 echo "${DSTMAC}" > "${NETCONS_PATH}"/remote_mac 109 107 echo "${SRCIF}" > "${NETCONS_PATH}"/dev_name 108 + 109 + if [ "${FORMAT}" == "basic" ] 110 + then 111 + # Basic target does not support release 112 + echo 0 > "${NETCONS_PATH}"/release 113 + echo 0 > "${NETCONS_PATH}"/extended 114 + elif [ "${FORMAT}" == "extended" ] 115 + then 116 + echo 1 > "${NETCONS_PATH}"/extended 117 + fi 110 118 111 119 echo 1 > "${NETCONS_PATH}"/enabled 112 120 } ··· 171 159 172 160 function validate_result() { 173 161 local TMPFILENAME="$1" 162 + local FORMAT=${2:-"extended"} 174 163 175 164 # TMPFILENAME will contain something like: 176 165 # 6.11.1-0_fbk0_rc13_509_g30d75cea12f7,13,1822,115075213798,-;netconsole selftest: netcons_gtJHM ··· 189 176 exit "${ksft_fail}" 190 177 fi 191 178 192 - if ! grep -q "${USERDATA_KEY}=${USERDATA_VALUE}" "${TMPFILENAME}"; then 193 - echo "FAIL: ${USERDATA_KEY}=${USERDATA_VALUE} not found in ${TMPFILENAME}" >&2 194 - cat "${TMPFILENAME}" >&2 195 - exit "${ksft_fail}" 179 + # userdata is not supported on basic format target, 180 + # thus, do not validate it. 181 + if [ "${FORMAT}" != "basic" ]; 182 + then 183 + if ! grep -q "${USERDATA_KEY}=${USERDATA_VALUE}" "${TMPFILENAME}"; then 184 + echo "FAIL: ${USERDATA_KEY}=${USERDATA_VALUE} not found in ${TMPFILENAME}" >&2 185 + cat "${TMPFILENAME}" >&2 186 + exit "${ksft_fail}" 187 + fi 196 188 fi 197 189 198 190 # Delete the file once it is validated, otherwise keep it 199 191 # for debugging purposes 200 192 rm "${TMPFILENAME}" 201 - exit "${ksft_pass}" 202 193 } 203 194 204 195 function check_for_dependencies() {
+31 -17
tools/testing/selftests/drivers/net/netcons_basic.sh
··· 32 32 echo "6 5" > /proc/sys/kernel/printk 33 33 # Remove the namespace, interfaces and netconsole target on exit 34 34 trap cleanup EXIT 35 - # Create one namespace and two interfaces 36 - set_network 37 - # Create a dynamic target for netconsole 38 - create_dynamic_target 39 - # Set userdata "key" with the "value" value 40 - set_user_data 41 - # Listed for netconsole port inside the namespace and destination interface 42 - listen_port_and_save_to "${OUTPUT_FILE}" & 43 - # Wait for socat to start and listen to the port. 44 - wait_local_port_listen "${NAMESPACE}" "${PORT}" udp 45 - # Send the message 46 - echo "${MSG}: ${TARGET}" > /dev/kmsg 47 - # Wait until socat saves the file to disk 48 - busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" 49 35 50 - # Make sure the message was received in the dst part 51 - # and exit 52 - validate_result "${OUTPUT_FILE}" 36 + # Run the test twice, with different format modes 37 + for FORMAT in "basic" "extended" 38 + do 39 + echo "Running with target mode: ${FORMAT}" 40 + # Create one namespace and two interfaces 41 + set_network 42 + # Create a dynamic target for netconsole 43 + create_dynamic_target "${FORMAT}" 44 + # Only set userdata for extended format 45 + if [ "$FORMAT" == "extended" ] 46 + then 47 + # Set userdata "key" with the "value" value 48 + set_user_data 49 + fi 50 + # Listed for netconsole port inside the namespace and destination interface 51 + listen_port_and_save_to "${OUTPUT_FILE}" & 52 + # Wait for socat to start and listen to the port. 53 + wait_local_port_listen "${NAMESPACE}" "${PORT}" udp 54 + # Send the message 55 + echo "${MSG}: ${TARGET}" > /dev/kmsg 56 + # Wait until socat saves the file to disk 57 + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" 58 + 59 + # Make sure the message was received in the dst part 60 + # and exit 61 + validate_result "${OUTPUT_FILE}" "${FORMAT}" 62 + cleanup 63 + done 64 + 65 + trap - EXIT 66 + exit "${ksft_pass}"