From: Denys Zikratyi -X (dzikraty - SOFTSERVE INC at Cisco) Date: Wed, 19 Feb 2025 19:04:10 +0000 (+0000) Subject: Pull request #4612: thread_config: add option for setting NUMA memory policy X-Git-Tag: 3.7.1.0~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=79ad998471e1d672cd6b6fa4ae59ac337dfb408d;p=thirdparty%2Fsnort3.git Pull request #4612: thread_config: add option for setting NUMA memory policy Merge in SNORT/snort3 from ~DZIKRATY/snort3:add_option_for_numa_mpol to master Squashed commit of the following: commit 44a1028f45a1e0f5a93fba57b9f6a43fd0d77d26 Author: Denys Zikratyi -X (dzikraty - SOFTSERVE INC at Cisco) Date: Mon Feb 10 11:35:48 2025 -0500 thread_config: add option for setting NUMA memory policy --- diff --git a/doc/reference/snort_reference.text b/doc/reference/snort_reference.text index 99aa9b574..e232e06f1 100644 --- a/doc/reference/snort_reference.text +++ b/doc/reference/snort_reference.text @@ -1344,6 +1344,8 @@ Configuration: (seconds, 0 to disable) { 0:60 } * int process.watchdog_min_thread_count = 1: minimum unresponsive threads for watchdog to trigger { 1:65535 } + * string process.numa_memory_policy = "preferred": set + default|preferred|bind|local memory policy for NUMA 2.27. profiler @@ -10763,6 +10765,8 @@ libraries see the Getting Started section of the manual. threads for watchdog to trigger { 1:65535 } * int process.watchdog_timer = 0: watchdog timer for packet threads (seconds, 0 to disable) { 0:60 } + * string process.numa_memory_policy = "preferred": set + default|preferred|bind|local memory policy for NUMA * int profiler.memory.count = 0: limit results to count items per level (0 = no limit) { 0:max32 } * int profiler.memory.dump_file_size = 1073741824: files will be diff --git a/src/main.cc b/src/main.cc index c3ca748c5..2913e47c4 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1244,7 +1244,7 @@ static void snort_main() ControlMgmt::socket_init(SnortConfig::get_conf()); #endif - SnortConfig::get_conf()->thread_config->implement_thread_affinity( + SnortConfig::get_conf()->thread_config->apply_thread_policy( STHREAD_TYPE_MAIN, get_instance_id()); max_pigs = ThreadConfig::get_instance_max(); diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 46829581f..00458609e 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -33,6 +33,7 @@ add_library (main OBJECT network_module.cc network_module.h numa.h + numa.cc oops_handler.cc oops_handler.h policy.cc diff --git a/src/main/modules.cc b/src/main/modules.cc index e35c8ee5b..f0591d39a 100644 --- a/src/main/modules.cc +++ b/src/main/modules.cc @@ -1191,6 +1191,9 @@ static const Parameter process_params[] = { "watchdog_min_thread_count", Parameter::PT_INT, "1:65535", "1", "minimum unresponsive threads for watchdog to trigger" }, + { "numa_memory_policy", Parameter::PT_STRING, nullptr, "preferred", + "set default|preferred|bind|local memory policy for NUMA" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; @@ -1261,6 +1264,11 @@ bool ProcessModule::set(const char*, Value& v, SnortConfig* sc) else if ( v.is("watchdog_min_thread_count") ) sc->set_watchdog_min_thread_count(v.get_uint16()); +#ifdef HAVE_NUMA + else if ( v.is("numa_memory_policy") ) + sc->thread_config->set_numa_mempolicy(v.get_string()); +#endif + return true; } diff --git a/src/main/numa.cc b/src/main/numa.cc new file mode 100644 index 000000000..19938eb70 --- /dev/null +++ b/src/main/numa.cc @@ -0,0 +1,89 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2025-2025 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. +//-------------------------------------------------------------------------- +// numa.cc author Denys Zikratyi + +#include "numa.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef UNIT_TEST +#include "catch/snort_catch.h" +#endif + +NumaMemPolicy convert_string_to_numa_mempolicy(const std::string& policy) +{ + static const std::unordered_map string_to_numa_mempolicy = + { + {"default", NumaMemPolicy::DEFAULT}, + {"preferred", NumaMemPolicy::PREFERRED}, + {"bind", NumaMemPolicy::BIND}, + {"local", NumaMemPolicy::LOCAL} + }; + + auto it = string_to_numa_mempolicy.find(policy); + if (it != string_to_numa_mempolicy.end()) + return it->second; + + return NumaMemPolicy::UNKNOWN; +} + +std::string stringify_numa_mempolicy(const NumaMemPolicy& policy) +{ + switch (policy) + { + case NumaMemPolicy::DEFAULT: return "default"; + case NumaMemPolicy::PREFERRED: return "preferred"; + case NumaMemPolicy::BIND: return "bind"; + case NumaMemPolicy::LOCAL: return "local"; + default: return "unknown"; + } +} + +// ----------------------------------------------------------------------------- +// unit tests +// ----------------------------------------------------------------------------- +#ifdef UNIT_TEST + +TEST_CASE("Parse string to NumaMemPolicy positive test") +{ + CHECK(NumaMemPolicy::DEFAULT == convert_string_to_numa_mempolicy("default")); + CHECK(NumaMemPolicy::PREFERRED == convert_string_to_numa_mempolicy("preferred")); + CHECK(NumaMemPolicy::BIND == convert_string_to_numa_mempolicy("bind")); + CHECK(NumaMemPolicy::LOCAL == convert_string_to_numa_mempolicy("local")); +} + +TEST_CASE("Parse string to NumaMemPolicy negative test") +{ + CHECK(NumaMemPolicy::UNKNOWN == convert_string_to_numa_mempolicy("preferred_many")); + CHECK(NumaMemPolicy::UNKNOWN == convert_string_to_numa_mempolicy("interleave")); + CHECK(NumaMemPolicy::UNKNOWN == convert_string_to_numa_mempolicy("fake_policy")); +} + +TEST_CASE("Parse NumaMemPolicy to string") +{ + CHECK("default" == stringify_numa_mempolicy(NumaMemPolicy::DEFAULT)); + CHECK("preferred" == stringify_numa_mempolicy(NumaMemPolicy::PREFERRED)); + CHECK("bind" == stringify_numa_mempolicy(NumaMemPolicy::BIND)); + CHECK("local" == stringify_numa_mempolicy(NumaMemPolicy::LOCAL)); + + CHECK("unknown" == stringify_numa_mempolicy(NumaMemPolicy::UNKNOWN)); +} + +#endif diff --git a/src/main/numa.h b/src/main/numa.h index 5d7506655..df79017fd 100644 --- a/src/main/numa.h +++ b/src/main/numa.h @@ -20,11 +20,27 @@ #ifndef NUMA_H #define NUMA_H +#include +#include + #include #include #include #include +enum NumaMemPolicy : uint8_t +{ + DEFAULT = MPOL_DEFAULT, + PREFERRED = MPOL_PREFERRED, + BIND = MPOL_BIND, + LOCAL = MPOL_LOCAL, + UNKNOWN = 255 +}; + +NumaMemPolicy convert_string_to_numa_mempolicy(const std::string& policy); + +std::string stringify_numa_mempolicy(const NumaMemPolicy& policy); + class NumaWrapper { public: @@ -37,10 +53,6 @@ public: { return numa_max_node(); } - virtual int preferred() - { - return numa_preferred(); - } virtual int set_mem_policy(int mode, const unsigned long *nodemask, unsigned long maxnode) { @@ -64,9 +76,13 @@ public: { return hwloc_get_type_depth(topology, type); } - virtual int bitmap_intersects(hwloc_const_cpuset_t set1, hwloc_const_cpuset_t set2) + virtual int bitmap_isincluded(hwloc_const_cpuset_t sub_set, hwloc_const_cpuset_t super_set) + { + return hwloc_bitmap_isincluded(sub_set, super_set); + } + virtual int bitmap_iszero(hwloc_const_cpuset_t set) { - return hwloc_bitmap_intersects(set1, set2); + return hwloc_bitmap_iszero(set); } }; #endif diff --git a/src/main/thread_config.cc b/src/main/thread_config.cc index c57cdf147..87f676e34 100644 --- a/src/main/thread_config.cc +++ b/src/main/thread_config.cc @@ -35,10 +35,6 @@ #include "catch/snort_catch.h" #endif -#ifdef HAVE_NUMA -#include "numa.h" -#endif - using namespace snort; using namespace std; @@ -50,10 +46,8 @@ 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 @@ -233,8 +227,19 @@ void ThreadConfig::apply_thread_policy(SThreadType type, unsigned id) #ifdef HAVE_NUMA +void snort::ThreadConfig::set_numa_mempolicy(const std::string& policy) +{ + numa_mempolicy = convert_string_to_numa_mempolicy(policy); + if (numa_mempolicy == NumaMemPolicy::UNKNOWN) + LogMessage( "memory policy '%s' is unknown or not supported, defaulting to 'preferred'\n", + policy.c_str()); +} + int ThreadConfig::get_numa_node(hwloc_topology_t topology, hwloc_cpuset_t cpuset) { + if (hwloc->bitmap_iszero(cpuset)) + return -1; + int depth = hwloc->get_type_depth(topology, HWLOC_OBJ_NUMANODE); if (depth == HWLOC_TYPE_DEPTH_UNKNOWN) return -1; @@ -242,23 +247,19 @@ int ThreadConfig::get_numa_node(hwloc_topology_t topology, hwloc_cpuset_t cpuset 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)) + if (node and hwloc->bitmap_isincluded(cpuset, node->cpuset)) return node->os_index; } return -1; } -bool ThreadConfig::set_preferred_mempolicy(int node) +bool ThreadConfig::apply_numa_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) + if (numa->set_mem_policy(numa_mempolicy, &nodemask, sizeof(nodemask)*8) != 0) return false; return true; @@ -277,11 +278,13 @@ bool ThreadConfig::implement_thread_mempolicy(SThreadType type, unsigned id) 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); + if(apply_numa_mempolicy(node_index)) + LogMessage( "%s memory policy set to %s for node %d\n", + stringify_numa_mempolicy(numa_mempolicy).c_str(), + stringify_thread(type, id).c_str(), node_index); else return false; - } + } else { return false; @@ -606,12 +609,10 @@ 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; } @@ -622,7 +623,7 @@ class HwlocWrapperMock : public HwlocWrapper public: int nbobjs_by_depth = 1; int type_depth = 2; - int intersects = 1; + int is_included = 1; struct hwloc_obj node; unsigned get_nbobjs_by_depth(hwloc_topology_t , int ) override @@ -631,8 +632,10 @@ public: { 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; } + int bitmap_isincluded(hwloc_const_cpuset_t, hwloc_const_cpuset_t ) override + { return is_included; } + int bitmap_iszero(hwloc_const_cpuset_t ) override + { return 0; } }; TEST_CASE("set node for thread", "[ThreadConfig]") @@ -655,7 +658,6 @@ TEST_CASE("set node for thread", "[ThreadConfig]") CHECK(true == tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); hwloc_mock->node.os_index = 1; - numa_mock->pref = 1; CHECK(true == tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 1)); } @@ -674,21 +676,6 @@ TEST_CASE("numa_available negative test", "[ThreadConfig]") CHECK(false == 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(false == tc.implement_thread_mempolicy(STHREAD_TYPE_PACKET, 0)); -} - TEST_CASE("depth unknown negative test", "[ThreadConfig]") { CpuSet* cpuset = new CpuSet(hwloc_bitmap_dup(process_cpuset)); diff --git a/src/main/thread_config.h b/src/main/thread_config.h index 7dc8dfadf..5300e0dad 100644 --- a/src/main/thread_config.h +++ b/src/main/thread_config.h @@ -25,6 +25,10 @@ #include "main/thread.h" +#ifdef HAVE_NUMA +#include "numa.h" +#endif + struct CpuSet; namespace snort @@ -50,7 +54,11 @@ public: 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); + +#ifdef HAVE_NUMA bool implement_thread_mempolicy(SThreadType type, unsigned id); + void set_numa_mempolicy(const std::string& policy); +#endif static constexpr unsigned int DEFAULT_THREAD_ID = 0; @@ -74,8 +82,12 @@ private: std::map thread_affinity; std::map named_thread_affinity; - bool set_preferred_mempolicy(int node); +#ifdef HAVE_NUMA + NumaMemPolicy numa_mempolicy = NumaMemPolicy::PREFERRED; + + bool apply_numa_mempolicy(int node); int get_numa_node(hwloc_topology_t, hwloc_cpuset_t); +#endif }; } #endif