From: Raza Shafiq (rshafiq) Date: Wed, 21 Jun 2023 01:42:26 +0000 (+0000) Subject: Pull request #3883: NUMA memory policy X-Git-Tag: 3.1.65.0~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=83e9a2dda250a0ced9a105454a5a4fe59c304601;p=thirdparty%2Fsnort3.git Pull request #3883: NUMA memory policy Merge in SNORT/snort3 from ~RSHAFIQ/snort3:numa_memory_policy to master Squashed commit of the following: commit 9078d21f8c3e62519dadb794bd72abcf502b033b Author: rshafiq Date: Wed Jun 7 15:18:57 2023 -0400 thread_config: added thread level mempolicy --- diff --git a/CMakeLists.txt b/CMakeLists.txt index d3fbf66e7..c96bdab3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,4 +196,12 @@ else () UUID: OFF") endif () +if (HAVE_NUMA) + message("\ + NUMA: ON") +else () + message("\ + NUMA: OFF") +endif () + message("-------------------------------------------------------\n") diff --git a/cmake/FindNUMA.cmake b/cmake/FindNUMA.cmake new file mode 100644 index 000000000..f51836396 --- /dev/null +++ b/cmake/FindNUMA.cmake @@ -0,0 +1,28 @@ +find_path(NUMA_INCLUDE_DIRS numa.h) +find_library(NUMA_LIBRARIES NAMES numa) + +if(NUMA_INCLUDE_DIRS AND NUMA_LIBRARIES) + set(NUMA_FOUND TRUE) + set(NUMA_LIBRARIES ${NUMA_LIBRARIES}) + set(NUMA_INCLUDE_DIRS ${NUMA_INCLUDE_DIRS}) +endif() + +if (NOT NUMA_FOUND) + find_package(PkgConfig) + pkg_check_modules(PC_NUMA libnuma) + + if(PC_NUMA_FOUND) + set(NUMA_FOUND TRUE) + set(NUMA_INCLUDE_DIRS ${PC_NUMA_INCLUDEDIR} ${PC_NUMA_INCLUDE_DIRS}) + set(NUMA_LIBRARIES ${PC_NUMA_LIBDIR} ${PC_NUMA_LIBRARY_DIRS}) + endif() +endif() + +if(NUMA_FOUND) + set(HAVE_NUMA "1") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NUMA DEFAULT_MSG NUMA_LIBRARIES NUMA_INCLUDE_DIRS) + +mark_as_advanced(NUMA_INCLUDE_DIRS NUMA_LIBRARIES) \ No newline at end of file diff --git a/cmake/create_pkg_config.cmake b/cmake/create_pkg_config.cmake index d7a5667cd..0d12b026f 100644 --- a/cmake/create_pkg_config.cmake +++ b/cmake/create_pkg_config.cmake @@ -80,6 +80,9 @@ if(UUID_INCLUDE_DIR) set(UUID_CPPFLAGS "-I${UUID_INCLUDE_DIR}") endif() +if(NUMA_INCLUDE_DIR) + set(NUMA_CPPFLAGS "-I${NUMA_INCLUDE_DIR}") +endif() # create & install pkgconfig file configure_file( diff --git a/cmake/include_libraries.cmake b/cmake/include_libraries.cmake index b3b4fd457..1a0b197f8 100644 --- a/cmake/include_libraries.cmake +++ b/cmake/include_libraries.cmake @@ -27,3 +27,4 @@ endif (ENABLE_SAFEC) find_package(ICONV QUIET) find_package(UUID QUIET) find_package(Libunwind) +find_package(NUMA QUIET) diff --git a/cmake/sanity_checks.cmake b/cmake/sanity_checks.cmake index 78ea3879e..898782a5f 100644 --- a/cmake/sanity_checks.cmake +++ b/cmake/sanity_checks.cmake @@ -150,6 +150,10 @@ if (ICONV_FOUND) set (HAVE_ICONV "1") endif() +if (NUMA_FOUND) + check_library_exists (${NUMA_LIBRARIES} numa_num_possible_cpus "" HAVE_NUMA) +endif() + if (LIBUNWIND_FOUND) # We don't actually use backtrace from libunwind, but it's basically the # only symbol guaranteed to be present. diff --git a/config.cmake.h.in b/config.cmake.h.in index 867f9996f..78205f5cf 100644 --- a/config.cmake.h.in +++ b/config.cmake.h.in @@ -147,6 +147,8 @@ /* tirpc should be used for RPC database lookups */ #cmakedefine USE_TIRPC 1 +/* numa available */ +#cmakedefine HAVE_NUMA 1 /* Availability of specific library functions */ diff --git a/doc/user/snort_user.text b/doc/user/snort_user.text index badd04460..d57724dd8 100644 --- a/doc/user/snort_user.text +++ b/doc/user/snort_user.text @@ -1063,13 +1063,14 @@ Optional: received * lzma >= 5.1.2 from http://tukaani.org/xz/ for decompression of SWF and PDF files + * numa from https://github.com/numactl/numactl for NUMA memory management * safec >= 3.5 from https://github.com/rurban/safeclib/ for runtime bounds checks on certain legacy C-library calls * source-highlight from http://www.gnu.org/software/src-highlite/ to generate the dev guide * w3m from http://sourceforge.net/projects/w3m/ to build the plain text manual - * uuid from uuid-dev package for unique identifiers + * uuid from uuid-dev package for unique identifiers 3.2. Building diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71ad64cae..4cf2456cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,11 @@ if ( HAVE_LZMA ) LIST(APPEND EXTERNAL_LIBRARIES ${LIBLZMA_LIBRARIES}) endif() +if ( HAVE_NUMA ) + LIST(APPEND EXTERNAL_INCLUDES ${NUMA_INCLUDE_DIRS}) + LIST(APPEND EXTERNAL_LIBRARIES ${NUMA_LIBRARIES}) +endif() + if ( HAVE_SAFEC ) LIST(APPEND EXTERNAL_LIBRARIES ${SAFEC_LIBRARIES}) LIST(APPEND EXTERNAL_INCLUDES ${SAFEC_INCLUDE_DIR}) diff --git a/src/main/analyzer.cc b/src/main/analyzer.cc index 634d71b64..68496cb15 100644 --- a/src/main/analyzer.cc +++ b/src/main/analyzer.cc @@ -746,7 +746,7 @@ void Analyzer::operator()(Swapper* ps, uint16_t run_num) SnortConfig::get_conf()->thread_config->set_instance_tid(id); // Perform all packet thread initialization actions that need to be taken with escalated // privileges prior to starting the DAQ module. - SnortConfig::get_conf()->thread_config->implement_thread_affinity( + SnortConfig::get_conf()->thread_config->apply_thread_policy( STHREAD_TYPE_PACKET, get_instance_id()); SFDAQ::set_local_instance(daq_instance); diff --git a/src/main/dev_notes.txt b/src/main/dev_notes.txt index c4a535fd2..f4dd2df01 100644 --- a/src/main/dev_notes.txt +++ b/src/main/dev_notes.txt @@ -63,3 +63,16 @@ information and management. Currently it is being used as a cross-platform mechanism for managing CPU affinity of threads, but it will be used in the future for NUMA (non-uniform memory access) awareness among other things. +Use of libnuma in thread_config.cc: + +The libnuma library offers a straightforward programming interface to +the NUMA (Non Uniform Memory Access) policy that is supported by +the Linux kernel. Preferred memory mode support for NUMA systems +has been added. set_mempolicy() is used to establish the memory policy +for packet threads operating on different nodes within a NUMA system. +The libnuma-dev library must be installed to enable this support. +In systems without NUMA architecture, this feature will not affect system +performance or behavior. This, alongside with libhwloc, presents an efficient +cross-platform mechanism for thread configuration and managing CPU affinity +of threads, not only considering CPU architecture but also memory access policies, +providing a more balanced and optimized execution environment. \ No newline at end of file diff --git a/src/main/test/distill_verdict_stubs.h b/src/main/test/distill_verdict_stubs.h index 73931fb4c..649af12bc 100644 --- a/src/main/test/distill_verdict_stubs.h +++ b/src/main/test/distill_verdict_stubs.h @@ -225,6 +225,7 @@ NetworkPolicy* get_network_policy() { return nullptr; } InspectionPolicy* get_inspection_policy() { return nullptr; } Flow::~Flow() = default; void ThreadConfig::implement_thread_affinity(SThreadType, unsigned) { } +void ThreadConfig::apply_thread_policy(SThreadType , unsigned ) { } void ThreadConfig::set_instance_tid(int) { } } diff --git a/src/main/thread_config.cc b/src/main/thread_config.cc index 1119c8a99..87975c2ae 100644 --- a/src/main/thread_config.cc +++ b/src/main/thread_config.cc @@ -24,7 +24,6 @@ #include "thread_config.h" #include -#include #include "analyzer_command.h" #include "log/messages.h" @@ -32,6 +31,10 @@ #include "time/periodic.h" #include "utils/util.h" +#ifdef HAVE_NUMA +#include "utils/util_numa.h" +#endif + #ifdef UNIT_TEST #include "catch/snort_catch.h" #endif @@ -46,6 +49,13 @@ static unsigned instance_max = 1; static std::mutex instance_mutex; static std::map instance_id_to_tid; +#ifdef HAVE_NUMA + +std::shared_ptr numa; +std::shared_ptr hwloc; + +#endif + struct CpuSet { CpuSet(hwloc_cpuset_t set) : cpuset(set) { } @@ -60,6 +70,13 @@ struct CpuSet bool ThreadConfig::init() { +#ifdef HAVE_NUMA + + numa = std::make_shared(); + hwloc = std::make_shared(); + +#endif + if (hwloc_topology_init(&topology)) return false; if (hwloc_topology_load(topology)) @@ -125,6 +142,13 @@ void ThreadConfig::term() process_cpuset = nullptr; } topology_support = nullptr; + +#ifdef HAVE_NUMA + + numa.reset(); + hwloc.reset(); + +#endif } ThreadConfig::~ThreadConfig() @@ -148,7 +172,10 @@ void ThreadConfig::set_thread_affinity(SThreadType type, unsigned id, CpuSet* cp thread_affinity[key] = cpuset; } else + { + delete cpuset; ParseWarning(WARN_CONF, "This platform does not support setting thread affinity.\n"); + } } void ThreadConfig::set_named_thread_affinity(const string& name, CpuSet* cpuset) @@ -193,6 +220,78 @@ static inline string stringify_thread(const SThreadType& type, const unsigned& i return info; } +void ThreadConfig::apply_thread_policy(SThreadType type, unsigned id) +{ + implement_thread_affinity( type, id ); + +#ifdef HAVE_NUMA + + implement_thread_mempolicy( type, id ); + +#endif +} + +#ifdef HAVE_NUMA + +int ThreadConfig::get_numa_node(hwloc_topology_t topology, hwloc_cpuset_t cpuset) +{ + int depth = hwloc->get_type_depth(topology, HWLOC_OBJ_NODE); + if (depth == HWLOC_TYPE_DEPTH_UNKNOWN) + return -1; + + for (unsigned i = 0; i < hwloc->get_nbobjs_by_depth(topology, depth); ++i) + { + hwloc_obj_t node = hwloc->get_obj_by_depth(topology, depth, i); + if (node and hwloc->bitmap_intersects(cpuset, node->cpuset)) + return node->os_index; + } + return -1; +} + +bool ThreadConfig::set_preferred_mempolicy(int node) +{ + if (node < 0) + return false; + + unsigned long nodemask = 1UL << (unsigned long)node; + int result = numa->set_mem_policy(MPOL_PREFERRED, &nodemask, sizeof(nodemask)*8); + if (result != 0) + return false; + + if(numa->preferred() != node) + return false; + + return true; +} + +bool ThreadConfig::implement_thread_mempolicy(SThreadType type, unsigned id) +{ + if (!topology_support->cpubind->set_thisthread_cpubind or + numa->available() < 0 or numa->max_node() <= 0) + { + return false; + } + + TypeIdPair key { type, id }; + auto iter = thread_affinity.find(key); + if (iter != thread_affinity.end()) + { + int node_index = get_numa_node(topology, iter->second->cpuset); + if(set_preferred_mempolicy(node_index)) + LogMessage( "Preferred memory policy set for %s to node %d\n",stringify_thread(type, id).c_str(), node_index); + else + return false; + } + else + { + return false; + } + + return true; +} + +#endif + void ThreadConfig::implement_thread_affinity(SThreadType type, unsigned id) { if (!topology_support->cpubind->set_thisthread_cpubind) @@ -472,4 +571,143 @@ TEST_CASE("Named thread affinity with type configured", "[ThreadConfig]") } } +#ifdef HAVE_NUMA + +class NumaWrapperMock : public NumaWrapper +{ +public: + int numa_avail = 1; + int max_n = 1; + int pref = 0; + int mem_policy = 0; + + int available() override { return numa_avail; } + int max_node() override { return max_n; } + int preferred() override { return pref; } + int set_mem_policy(int , const unsigned long *, + unsigned long ) override + { return mem_policy; } +}; + +class HwlocWrapperMock : public HwlocWrapper +{ +public: + int nbobjs_by_depth = 1; + int type_depth = 2; + int intersects = 1; + struct hwloc_obj node; + + unsigned get_nbobjs_by_depth(hwloc_topology_t , int ) override + { return nbobjs_by_depth; } + hwloc_obj_t get_obj_by_depth(hwloc_topology_t, int, unsigned ) override + { return &node; } + int get_type_depth(hwloc_topology_t, hwloc_obj_type_t ) override + { return type_depth; } + int bitmap_intersects(hwloc_const_cpuset_t, hwloc_const_cpuset_t ) override + { return intersects; } +}; + +TEST_CASE("set node for thread", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + CpuSet* cpuset2 = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + ThreadConfig tc; + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + + hwloc_mock->node.os_index = 0; + + numa = numa_mock; + hwloc = hwloc_mock; + + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 0, cpuset2); + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 1, cpuset); + + CHECK(tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); + + hwloc_mock->node.os_index = 1; + numa_mock->pref = 1; + CHECK(tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 1)); +} + +TEST_CASE("numa_available negative test", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + ThreadConfig tc; + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 1, cpuset); + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + + numa_mock->numa_avail = -1; + numa = numa_mock; + hwloc = hwloc_mock; + CHECK(!tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); +} + +TEST_CASE("set node failure negative test", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + ThreadConfig tc; + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 0, cpuset); + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + hwloc_mock->node.os_index = 0; + numa_mock->pref = -1; + numa = numa_mock; + hwloc = hwloc_mock; + CHECK(!tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); +} + +TEST_CASE("depth unknown negative test", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + + ThreadConfig tc; + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 0, cpuset); + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + + hwloc_mock->type_depth = HWLOC_TYPE_DEPTH_UNKNOWN; + hwloc = hwloc_mock; + numa = numa_mock; + CHECK(!tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); +} + +TEST_CASE("set memory policy failure negative test", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + ThreadConfig tc; + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 0, cpuset); + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + + hwloc_mock->node.os_index = 0; + numa_mock->mem_policy = -1; + numa = numa_mock; + hwloc = hwloc_mock; + CHECK(!tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); +} + +TEST_CASE("get_nbobjs_by_depth failure negative test", "[ThreadConfig]") +{ + CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); + ThreadConfig tc; + tc.set_thread_affinity(STHREAD_TYPE_PACKET, 0, cpuset); + + std::shared_ptr numa_mock = std::make_shared(); + std::shared_ptr hwloc_mock = std::make_shared(); + + hwloc_mock->nbobjs_by_depth = 0; + hwloc = hwloc_mock; + numa = numa_mock; + CHECK(!tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); +} + +#endif + #endif diff --git a/src/main/thread_config.h b/src/main/thread_config.h index ba4096941..fb8ec2f25 100644 --- a/src/main/thread_config.h +++ b/src/main/thread_config.h @@ -19,6 +19,7 @@ #ifndef THREAD_CONFIG_H #define THREAD_CONFIG_H +#include #include #include @@ -44,11 +45,13 @@ public: static int get_instance_tid(int); ~ThreadConfig(); + void apply_thread_policy(SThreadType type, unsigned id); void set_thread_affinity(SThreadType, unsigned id, CpuSet*); void set_named_thread_affinity(const std::string&, CpuSet*); void implement_thread_affinity(SThreadType, unsigned id); void implement_named_thread_affinity(const std::string& name); - + bool implement_thread_mempolicy(SThreadType type, unsigned id); + static constexpr unsigned int DEFAULT_THREAD_ID = 0; private: @@ -70,7 +73,9 @@ private: }; std::map thread_affinity; std::map named_thread_affinity; + + bool set_preferred_mempolicy(int node); + int get_numa_node(hwloc_topology_t, hwloc_cpuset_t); }; } - #endif diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 19372c3af..9973a56c2 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -16,6 +16,7 @@ set( UTIL_INCLUDES util_cstring.h util_unfold.h util_utf.h + util_numa.h ) add_library ( utils OBJECT @@ -40,6 +41,7 @@ add_library ( utils OBJECT util_net.h util_unfold.cc util_utf.cc + util_numa.h ${TEST_FILES} ) diff --git a/src/utils/util_numa.h b/src/utils/util_numa.h new file mode 100644 index 000000000..53010bac6 --- /dev/null +++ b/src/utils/util_numa.h @@ -0,0 +1,74 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2020-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// util_numa.h author Raza Shafiq + +#ifndef NUMA_UTILS_H +#define NUMA_UTILS_H + +#ifdef HAVE_NUMA + +#include +#include +#include +#include + +class NumaWrapper +{ +public: + virtual ~NumaWrapper() {} + virtual int available() + { + return numa_available(); + } + virtual int max_node() + { + return numa_max_node(); + } + virtual int preferred() + { + return numa_preferred(); + } + virtual int set_mem_policy(int mode, const unsigned long *nodemask, + unsigned long maxnode) + { + return set_mempolicy(mode, nodemask, maxnode); + } +}; +class HwlocWrapper +{ +public: + virtual ~HwlocWrapper() {} + virtual unsigned get_nbobjs_by_depth(hwloc_topology_t topology, int depth) + { + return hwloc_get_nbobjs_by_depth(topology, depth); + } + virtual hwloc_obj_t get_obj_by_depth(hwloc_topology_t topology, int depth, unsigned idx) + { + return hwloc_get_obj_by_depth(topology, depth, idx); + } + virtual int get_type_depth(hwloc_topology_t topology, hwloc_obj_type_t type) + { + return hwloc_get_type_depth(topology, type); + } + virtual int bitmap_intersects(hwloc_const_cpuset_t set1, hwloc_const_cpuset_t set2) + { + return hwloc_bitmap_intersects(set1, set2); + } +}; +#endif // HAVE_NUMA +#endif // NUMA_UTILS_H \ No newline at end of file