]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4612: thread_config: add option for setting NUMA memory policy
authorDenys Zikratyi -X (dzikraty - SOFTSERVE INC at Cisco) <dzikraty@cisco.com>
Wed, 19 Feb 2025 19:04:10 +0000 (19:04 +0000)
committerSteven Baigal (sbaigal) <sbaigal@cisco.com>
Wed, 19 Feb 2025 19:04:10 +0000 (19:04 +0000)
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) <dzikraty@cisco.com>
Date:   Mon Feb 10 11:35:48 2025 -0500

    thread_config: add option for setting NUMA memory policy

doc/reference/snort_reference.text
src/main.cc
src/main/CMakeLists.txt
src/main/modules.cc
src/main/numa.cc [new file with mode: 0644]
src/main/numa.h
src/main/thread_config.cc
src/main/thread_config.h

index 99aa9b574250a8eca4903fa11ba0ad39c241443c..e232e06f1fc273aaa51d3e37214d37f3bd08f388 100644 (file)
@@ -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
index c3ca748c5e87a1d067cdc308adeedfa1d6268ba7..2913e47c4e3641c5870e0b2b4ebb9218cea6749a 100644 (file)
@@ -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();
index 46829581ffd86d703c3fd83d4dacc3cb1503da2e..00458609e36ba5a80326f0050e28b7143965e954 100644 (file)
@@ -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
index e35c8ee5be6e2724583abe1651095006ae257b88..f0591d39adab0ae56d4e78b38bc79ea57759c245 100644 (file)
@@ -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 (file)
index 0000000..19938eb
--- /dev/null
@@ -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 <dzikraty@cisco.com>
+
+#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<std::string, NumaMemPolicy> 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
index 5d7506655b77ff5b549b8165749ec76fa6236cab..df79017fd96ceccf367f23d43fee738dbb9e1107 100644 (file)
 #ifndef NUMA_H
 #define NUMA_H
 
+#include <unordered_map>
+#include <string>
+
 #include <numa.h>
 #include <numaif.h>
 #include <sched.h>
 #include <hwloc.h>
 
+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
index c57cdf147fdbf2313d8948276b788ea47ca9034b..87f676e3403dc99818ce4146c5b53026cfac130a 100644 (file)
 #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<int, int> instance_id_to_tid;
 
 #ifdef HAVE_NUMA
-
 std::shared_ptr<NumaWrapper> numa;
 std::shared_ptr<HwlocWrapper> 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<NumaWrapperMock> numa_mock = std::make_shared<NumaWrapperMock>();
-    std::shared_ptr<HwlocWrapperMock> hwloc_mock = std::make_shared<HwlocWrapperMock>();
-    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));
index 7dc8dfadf2586770931b7676d496ffd87931f3e0..5300e0dad70291f11889fdc67ba92ac304a3b413 100644 (file)
 
 #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<TypeIdPair, CpuSet*, TypeIdPairComparer> thread_affinity;
     std::map<std::string, CpuSet*> 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