(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
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
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();
network_module.cc
network_module.h
numa.h
+ numa.cc
oops_handler.cc
oops_handler.h
policy.cc
{ "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 }
};
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;
}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
#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:
{
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 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
#include "catch/snort_catch.h"
#endif
-#ifdef HAVE_NUMA
-#include "numa.h"
-#endif
-
using namespace snort;
using namespace std;
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
#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;
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;
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;
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; }
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
{ 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]")
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));
}
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));
#include "main/thread.h"
+#ifdef HAVE_NUMA
+#include "numa.h"
+#endif
+
struct CpuSet;
namespace snort
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;
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