diff --git a/configure.ac b/configure.ac index 3e6d03c809..df0e85c317 100644 --- a/configure.ac +++ b/configure.ac @@ -1415,13 +1415,22 @@ AC_CHECK_FUNCS([bpf_xdp_query_id]) ]) + # Optional libnuma probe (used for memory affinity binding, also + # mandatory for DPDK and Napatech below). Sets HAVE_LIBNUMA and links + # -lnuma when available. + AC_CHECK_LIB(numa, numa_available, [ + have_libnuma=yes + AC_DEFINE([HAVE_LIBNUMA], [1], [Define to 1 if you have libnuma.]) + LIBS="${LIBS} -lnuma" + ], [have_libnuma=no]) + # DPDK support enable_dpdk_bond_pmd="no" AC_ARG_ENABLE(dpdk, AS_HELP_STRING([--enable-dpdk], [Enable DPDK support [default=no]]), [enable_dpdk=$enableval],[enable_dpdk=no]) AS_IF([test "x$enable_dpdk" = "xyes"], [ - AC_CHECK_LIB(numa, numa_available,, [numa_found="no"]) + if test "$have_libnuma" = "no"; then numa_found="no"; fi if test "$numa_found" = "no"; then echo echo " ERROR! libnuma not found by pkg-config, go get it" diff --git a/doc/userguide/configuration/suricata-yaml.rst b/doc/userguide/configuration/suricata-yaml.rst index c0f6e493da..4b33a64fd8 100644 --- a/doc/userguide/configuration/suricata-yaml.rst +++ b/doc/userguide/configuration/suricata-yaml.rst @@ -889,6 +889,17 @@ packets to process. The detect threads running on other cores will process more packets. This is only the case after setting the option to 'yes'. +On NUMA systems where ``set-cpu-affinity`` is enabled you can also opt +into binding each thread's memory to its local NUMA node so per-flow, +per-thread and packet pool allocations stay on the same node as the +worker CPU:: + + set-memory-affinity: yes + +This requires Suricata to be built with libnuma (already pulled in by +``--enable-dpdk`` and the Napatech build). On a single NUMA node system +the option has no effect. + *Example 7 Balancing workload* .. image:: suricata-yaml/balancing_workload.png diff --git a/src/runmodes.c b/src/runmodes.c index 6a004a33f5..85f4de1a32 100644 --- a/src/runmodes.c +++ b/src/runmodes.c @@ -59,6 +59,7 @@ int debuglog_enabled = 0; bool threading_set_cpu_affinity = false; +bool threading_set_memory_affinity = false; uint64_t threading_set_stack_size = 0; /* Runmode Global Thread Names */ @@ -968,6 +969,25 @@ void RunModeInitializeThreadSettings(void) if (threading_set_cpu_affinity) { AffinitySetupLoadFromConfig(); } + + int memory_affinity = 0; + if ((SCConfGetBool("threading.set-memory-affinity", &memory_affinity)) == 0) { + threading_set_memory_affinity = false; + } else { + threading_set_memory_affinity = memory_affinity == 1; + } + if (threading_set_memory_affinity && !threading_set_cpu_affinity) { + SCLogWarning("threading.set-memory-affinity has no effect without " + "threading.set-cpu-affinity, ignoring"); + threading_set_memory_affinity = false; + } +#ifndef HAVE_LIBNUMA + if (threading_set_memory_affinity) { + SCLogWarning("threading.set-memory-affinity requested but Suricata " + "was built without libnuma, ignoring"); + threading_set_memory_affinity = false; + } +#endif if ((SCConfGetFloat("threading.detect-thread-ratio", &threading_detect_ratio)) != 1) { if (SCConfGetNode("threading.detect-thread-ratio") != NULL) WarnInvalidConfEntry("threading.detect-thread-ratio", "%s", "1"); diff --git a/src/runmodes.h b/src/runmodes.h index 4d11413550..ed05fea082 100644 --- a/src/runmodes.h +++ b/src/runmodes.h @@ -104,6 +104,7 @@ void RunModeEnablesBypassManager(void); int RunModeNeedsBypassManager(void); extern bool threading_set_cpu_affinity; +extern bool threading_set_memory_affinity; extern float threading_detect_ratio; extern uint64_t threading_set_stack_size; diff --git a/src/tm-threads.c b/src/tm-threads.c index 8897d07601..6ed3fafa93 100644 --- a/src/tm-threads.c +++ b/src/tm-threads.c @@ -48,6 +48,10 @@ #include "util-validate.h" #include "source-pcap-file-helper.h" +#ifdef HAVE_LIBNUMA +#include +#endif + #ifdef PROFILE_LOCKING thread_local uint64_t mutex_lock_contention; thread_local uint64_t mutex_lock_wait_ticks; @@ -864,6 +868,33 @@ int TmThreadGetNbThreads(uint8_t type) return thread_affinity[type].nb_threads; } +/** + * \brief Bind subsequent allocations on this thread to the local NUMA node. + * + * Must be called after the thread's CPU affinity has been pinned. Uses + * MPOL_LOCAL (via numa_set_localalloc) so allocations prefer the node of + * the running CPU but can fall back to other nodes when local memory is + * exhausted. No-op when libnuma is not compiled in or the system has a + * single NUMA node. + */ +static void SetMemoryAffinityLocal(const char *name) +{ +#ifdef HAVE_LIBNUMA + if (!threading_set_memory_affinity) + return; + if (numa_available() == -1) + return; + if (numa_max_node() < 1) + return; + numa_set_localalloc(); + SCLogPerf("Setting memory affinity for thread \"%s\" to local NUMA node, " + "thread id %lu", + name, SCGetThreadIdLong()); +#else + (void)name; +#endif +} + /** * \brief Set the thread options (cpu affinitythread). * Priority should be already set by pthread_create. @@ -877,6 +908,7 @@ TmEcode TmThreadSetupOptions(ThreadVars *tv) "%"PRIu16", thread id %lu", tv->name, tv->cpu_affinity, SCGetThreadIdLong()); SetCPUAffinity(tv->cpu_affinity); + SetMemoryAffinityLocal(tv->name); } #if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined __sun @@ -923,6 +955,7 @@ TmEcode TmThreadSetupOptions(ThreadVars *tv) "thread id %lu", tv->thread_priority, tv->name, SCGetThreadIdLong()); } + SetMemoryAffinityLocal(tv->name); TmThreadSetPrio(tv); } #endif diff --git a/suricata.yaml.in b/suricata.yaml.in index 49c1df696c..92e6ad598d 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -1939,6 +1939,12 @@ spm-algo: auto # Suricata is multi-threaded. Here the threading can be influenced. threading: set-cpu-affinity: no + # When set-cpu-affinity is enabled, also bind thread heap allocations + # to the local NUMA node so per-flow, per-thread and packet pool memory + # stays on the same NUMA as the worker CPU. Requires libnuma at build + # time (already present when DPDK or Napatech is enabled). Has no effect + # on single-NUMA systems. + set-memory-affinity: no autopin: no # Tune cpu affinity of threads. Each family of threads can be bound # to specific CPUs.