]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3664: memory: use the process total instead of per thread totals to...
authorRuss Combs (rucombs) <rucombs@cisco.com>
Fri, 6 Jan 2023 16:43:50 +0000 (16:43 +0000)
committerRuss Combs (rucombs) <rucombs@cisco.com>
Fri, 6 Jan 2023 16:43:50 +0000 (16:43 +0000)
Merge in SNORT/snort3 from ~RUCOMBS/snort3:process_memory to master

Squashed commit of the following:

commit 3d3da0fd75a73eb43fd4aa6b7e8e252b9c6ea1ee
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Jan 4 08:31:40 2023 -0500

    memory: rename manager to overloads to better indicate purpose

commit e343738e2b178002b7e8f63f60cdbe7c512499db
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Jan 4 06:37:34 2023 -0500

    memory: update developer notes

commit 7f374a318e87662c1d7766ffd237d65eb605f60f
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Dec 28 09:01:20 2022 -0500

    memory: update stats regardless of state; add unit tests

commit 71822045d1ed62da660573d2c82a5566ba42967d
Author: Russ Combs <rucombs@cisco.com>
Date:   Tue Dec 27 09:33:34 2022 -0500

    memory: delete unnecessary includes

commit cc19d105f6b08a7071978de0681fdf840413967e
Author: Russ Combs <rucombs@cisco.com>
Date:   Thu Dec 22 16:02:14 2022 -0500

    memory: refactor jemalloc code and add relevant pegs

commit 7e30c6081c4fb0cac8c55658b50d5abfd14bc977
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Nov 23 13:51:30 2022 -0500

    build: exclude unused memory related sources

commit fc74bce73bd0db2b4fd67872615fd3f0dbf0a916
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Nov 23 12:21:38 2022 -0500

    build: error out if both jemalloc and tcmalloc are configured

commit 0663095ec3344f97cb80a9291bda6ed675edd469
Author: Russ Combs <rucombs@cisco.com>
Date:   Wed Nov 23 12:18:18 2022 -0500

    memory: incorporate overloads into profiler

commit 7824486ad5799116da4c825d991fc3d9d8e2738f
Author: Russ Combs <rucombs@cisco.com>
Date:   Thu Nov 10 14:03:23 2022 -0500

    memory: use the process total instead of per thread totals to enforce cap

    Since Snort doesn't always free memory in the thread that allocated it,
    switch to a process cap enforcement strategy when using jemalloc. To get
    updated stats.allocated it is necessary to bump the epoch, which can be
    expensive, so it is done by the main thread once per interval ms. If
    over limit, each packet thread will prune one flow per packet until the
    prune_target is reached.

44 files changed:
cmake/configure_options.cmake
cmake/create_options.cmake
cmake/create_pkg_config.cmake
configure_cmake.sh
src/flow/flow.cc
src/flow/flow_cache.cc
src/flow/flow_data.cc
src/flow/stash_item.h
src/main/analyzer.cc
src/main/snort.cc
src/main/test/distill_verdict_stubs.h
src/memory/CMakeLists.txt
src/memory/dev_notes.txt
src/memory/heap_interface.cc [new file with mode: 0644]
src/memory/heap_interface.h [moved from src/memory/prune_handler.cc with 63% similarity]
src/memory/memory_cap.cc
src/memory/memory_cap.h
src/memory/memory_config.h
src/memory/memory_module.cc
src/memory/memory_module.h
src/memory/memory_overloads.cc [moved from src/memory/memory_manager.cc with 81% similarity]
src/memory/prune_handler.h [deleted file]
src/memory/test/CMakeLists.txt [new file with mode: 0644]
src/memory/test/memory_cap_test.cc [new file with mode: 0644]
src/mime/file_mime_process.cc
src/network_inspectors/appid/appid_http_session.cc
src/network_inspectors/appid/test/appid_http_session_test.cc
src/service_inspectors/dce_rpc/dce_smb1.cc
src/service_inspectors/dce_rpc/dce_smb2.h
src/service_inspectors/dce_rpc/dce_smb_common.cc
src/service_inspectors/dce_rpc/smb_message.cc
src/service_inspectors/sip/sip.cc
src/service_inspectors/sip/sip_parser.cc
src/stream/file/file_session.cc
src/stream/icmp/icmp_session.cc
src/stream/ip/ip_defrag.cc
src/stream/ip/ip_session.cc
src/stream/tcp/tcp_reassembler.cc
src/stream/tcp/tcp_segment_node.cc
src/stream/tcp/tcp_session.cc
src/stream/tcp/tcp_stream_tracker.cc
src/stream/udp/udp_session.cc
src/stream/user/user_session.cc
src/utils/stats.cc

index e86cbf862dc91e3facc4098b986e691352942f27..f3673f399fb7202b6916dfd97d6934a5e9906f5d 100644 (file)
@@ -20,7 +20,6 @@ set ( USE_STDLOG ${ENABLE_STDLOG} )
 set ( USE_TSC_CLOCK ${ENABLE_TSC_CLOCK} )
 set ( NO_PROFILER ${DISABLE_SNORT_PROFILER} )
 set ( DEEP_PROFILING ${ENABLE_DEEP_PROFILING} )
-set ( ENABLE_MEMORY_OVERLOADS ${ENABLE_MEMORY_OVERLOADS} )
 set ( ENABLE_MEMORY_PROFILER ${ENABLE_MEMORY_PROFILER} )
 set ( ENABLE_RULE_PROFILER ${ENABLE_RULE_PROFILER} )
 
index 17a7cc3149522814aa25b220625bbe5e13105a36..a3921482ee24254ecdee49709364418f42044015 100644 (file)
@@ -42,7 +42,6 @@ option ( ENABLE_GDB "Enable gdb debugging information" ON )
 option ( ENABLE_PROFILE "Enable profiling options (developers only)" OFF )
 option ( DISABLE_SNORT_PROFILER "Disable snort Profiler (developers only)" OFF )
 option ( ENABLE_DEEP_PROFILING "Enable deep profiling of snort functions (developers only)" OFF )
-option ( ENABLE_MEMORY_OVERLOADS "Use new / delete overloads for profiling (developers only)" OFF )
 option ( ENABLE_MEMORY_PROFILER "Enable memory profiler (developers only)" OFF )
 option ( ENABLE_RULE_PROFILER "Enable rule keyword profiler (developers only)" OFF )
 option ( ENABLE_ADDRESS_SANITIZER "enable address sanitizer support" OFF )
index dd2b532f8d3f46756d3c516830eba147b705036e..d7a5667cd533e2604633491143698a6cd42d5f6f 100644 (file)
@@ -16,10 +16,6 @@ if(DAQ_INCLUDE_DIR)
     set(DAQ_CPPFLAGS "-I${DAQ_INCLUDE_DIR}")
 endif()
 
-if(ENABLE_MEMORY_OVERLOADS)
-    set(MEMORY_OVERLOADS_CPPFLAGS "-DENABLE_MEMORY_OVERLOADS")
-endif()
-
 if(ENABLE_MEMORY_PROFILER)
     set(MEMORY_PROFILER_CPPFLAGS "-DENABLE_MEMORY_PROFILER")
 endif()
index 6ca9bc9a838ae5fe917d712a05fd6529eb3c5eea..fae15e4184a5ef62c4a0cdf22305cd03ec17df39 100755 (executable)
@@ -54,8 +54,6 @@ Optional Features:
     --enable-gprof-profile  enable gprof profiling options (developers only)
     --disable-snort-profiler
                             disable snort performance profiling (cpu and memory) (developers only)
-    --enable-memory-overloads
-                            overload new and delete
     --enable-memory-profiler
                             enable memory profiler
     --enable-rule-profiler  enable rule keyword profiler (developers only)
@@ -260,9 +258,6 @@ while [ $# -ne 0 ]; do
         --disable-snort-profiler)
             append_cache_entry DISABLE_SNORT_PROFILER   BOOL false
             ;;
-        --enable-memory-overloads)
-            append_cache_entry ENABLE_MEMORY_OVERLOADS  BOOL true
-            ;;
         --enable-memory-profiler)
             append_cache_entry ENABLE_MEMORY_PROFILER   BOOL true
             ;;
@@ -325,6 +320,7 @@ while [ $# -ne 0 ]; do
             ;;
         --enable-tcmalloc)
             append_cache_entry ENABLE_TCMALLOC          BOOL true
+            tcm=1
             ;;
         --disable-tcmalloc)
             append_cache_entry ENABLE_TCMALLOC          BOOL false
@@ -332,6 +328,7 @@ while [ $# -ne 0 ]; do
         --enable-jemalloc)
             append_cache_entry ENABLE_JEMALLOC          BOOL true
             append_cache_entry STATIC_JEMALLOC          BOOL false
+            jem=1
             ;;
         --disable-jemalloc)
             append_cache_entry ENABLE_JEMALLOC          BOOL false
@@ -486,6 +483,11 @@ while [ $# -ne 0 ]; do
     shift
 done
 
+if [ $tcm -eq 1 -a $jem -eq 1 ] ; then
+    echo "--enable-tcmalloc and --enable-tcmalloc are mutually exclusive; enable at most one"
+    exit 2
+fi
+
 if [ -d $builddir ]; then
     # If build directory exists, check if it has a CMake cache
     if [ -f $builddir/CMakeCache.txt ]; then
index 33871e18a1b8ad120eb5bf53405ecc00a056347c..f5e7a070bc8c4ea0d3c33ebd6729d3cb667611df 100644 (file)
@@ -32,7 +32,6 @@
 #include "framework/data_bus.h"
 #include "helpers/bitop.h"
 #include "main/analyzer.h"
-#include "memory/memory_cap.h"
 #include "protocols/packet.h"
 #include "protocols/tcp.h"
 #include "pub_sub/intrinsic_event_ids.h"
index 75677b3788b06a03694e537022fca3a10c82f486..beee69ddbdcfd5ebcda1e4261fa21abb88c91ea0 100644 (file)
@@ -28,7 +28,6 @@
 #include "hash/hash_defs.h"
 #include "hash/zhash.h"
 #include "helpers/flag_context.h"
-#include "memory/memory_cap.h"
 #include "packet_io/active.h"
 #include "packet_tracer/packet_tracer.h"
 #include "stream/base/stream_module.h"
index a1334a4c38584edde1ca62c9d1b7cd4745b67723..02584888b46f57f18a713018afbe16ba9822dca8 100644 (file)
@@ -28,7 +28,6 @@
 #include "framework/inspector.h"
 #include "main/snort_config.h"
 #include "managers/so_manager.h"
-#include "memory/memory_cap.h"
 
 using namespace snort;
 
index 879b091fd416915377820a01c969e326ada12d4c..aeecc01e5b8ca6e6c56ea76f1ee9493a6e55f8bf 100644 (file)
@@ -24,8 +24,6 @@
 #include <cstdint>
 #include <string>
 
-#include "memory/memory_cap.h"
-
 #define STASH_APPID_DATA "appid_data"
 
 #define STASH_GENERIC_OBJECT_APPID 1
index 42ba696f466cd8aac5008d01c1752937b40b0c79..eece6f9f7d6eb8937a0313c773e767a2ef803f12 100644 (file)
@@ -619,6 +619,7 @@ void Analyzer::init_unprivileged()
     InitTag();
     EventTrace_Init();
 
+    memory::MemoryCap::thread_init();
     EventManager::open_outputs();
     IpsManager::setup_options(sc);
     ActionManager::thread_init(sc);
index 5e774bfbe51bb76a5d18d202a9139a9213abf721..3201e98d82de414b49799c91c28df3678f4b7f91 100644 (file)
@@ -68,6 +68,7 @@
 #include "service_inspectors/service_inspectors.h"
 #include "side_channel/side_channel.h"
 #include "stream/stream_inspectors.h"
+#include "stream/stream.h"
 #include "target_based/host_attributes.h"
 #include "time/periodic.h"
 #include "trace/trace_api.h"
@@ -398,8 +399,8 @@ void Snort::setup(int argc, char* argv[])
 
     set_quick_exit(false);
 
-    memory::MemoryCap::setup(*sc->memory, sc->thread_config->get_instance_max());
-    memory::MemoryCap::print(SnortConfig::log_verbose());
+    memory::MemoryCap::setup(*sc->memory, sc->thread_config->get_instance_max(), Stream::prune_flows);
+    memory::MemoryCap::print(SnortConfig::log_verbose(), true);
 
     host_cache.print_config();
 
index 4440c90477ebddfc05f9b3236fd5765a7087af8b..5fc19fc851b17d5793de7edc8a2a0c39e93a8113 100644 (file)
@@ -45,6 +45,7 @@
 #include "main/snort_config.h"
 #include "main/swapper.h"
 #include "main/thread_config.h"
+#include "memory/memory_cap.h"
 #include "network_inspectors/packet_tracer/packet_tracer.h"
 #include "packet_io/active.h"
 #include "packet_io/sfdaq.h"
@@ -226,7 +227,6 @@ void ThreadConfig::implement_thread_affinity(SThreadType, unsigned) { }
 void ThreadConfig::set_instance_tid(const int, const int) { }
 }
 
-namespace memory
-{
-void MemoryCap::free_space() { }
-}
+void memory::MemoryCap::thread_init() { }
+void memory::MemoryCap::free_space() { }
+
index 4d2c563c5f6446e0c033401553a2467cee6e4dc0..2b0f2751c207ea6a5d65ea4d41034f900d4eda97 100644 (file)
@@ -1,20 +1,27 @@
 set (MEMCAP_INCLUDES
+    heap_interface.h
     memory_cap.h
 )
 
 set ( MEMORY_SOURCES
     ${MEMCAP_INCLUDES}
-    memory_allocator.cc
-    memory_allocator.h
+    heap_interface.cc
     memory_cap.cc
     memory_config.h
-    memory_manager.cc
     memory_module.cc
     memory_module.h
-    prune_handler.cc
-    prune_handler.h
+    memory_overloads.cc
 )
 
+set ( ALLOC_SOURCES
+    memory_allocator.cc
+    memory_allocator.h
+)
+
+if ( ENABLE_MEMORY_PROFILER )
+    list ( APPEND MEMORY_SOURCES ${ALLOC_SOURCES} )
+endif ()
+
 add_library ( memory OBJECT
     ${MEMORY_SOURCES}
 )
@@ -22,3 +29,6 @@ add_library ( memory OBJECT
 install(FILES ${MEMCAP_INCLUDES}
     DESTINATION "${INCLUDE_INSTALL_PATH}/memory/"
 )
+
+add_subdirectory(test)
+
index 7bb872eff5bd1e2b40e69c7fca390d637feaa5a1..066992fdc7052a9f431cfc2e3666ad38393c709b 100644 (file)
@@ -1,41 +1,64 @@
-Snort memory management monitors memory usage and prunes flows as needed to keep the total process
-usage below the configured limit, if any. There are two ways to build memory management: build with
-jemalloc (--enable-jemalloc) or enable the new / delete overloads (--enable-memory-overloads). The
-latter option is required to support memory profiling (--enable-memory-profiler). Profiling is not
-enabled by default due to performance concerns and is viewed as a developer tool: apart from cache
-memcaps, users should not have to care about how Snort allocates memory, only that it doesn't
-exceed the configured limit if any.
+Snort provides two memory related features: management and profiling. Memory management ensures that
+Snort uses no more than a configured cap and is built with --enable-jemalloc (jemalloc required).
+Profiling displays memory usage by component and is enabled with --enable-memory-profiling.  These
+features are independent and may be used together. jemalloc is required for management because it
+supports the necessary introspection in an efficient manner. Profiling is not enabled by default
+because of the runtime overhead incurred by overloading new and delete.
+
+Memory management is implemented by periodically comparing the total process usage reported by
+jemalloc ("stats.allocated") against the configured cap. If over limit, packet threads commence a
+reap cycle and prune LRU flows up to the configured prune target. This is achieved by capturing the
+deallocated total (via "thread.deallocatedp") at the start of the reap cycle and checking the
+current difference and pruning a single flow if the target has not been reached. At most one flow is
+pruned per packet. You can learn more about the jemalloc api at http://jemalloc.net/jemalloc.3.html.
+
+Snort will go over the configured cap by a (relatively) small amount so the configured cap should be
+below the hard limit, eg the limit enforced by cgroups. The default values for memory.interval and
+memory.prune_target generally work well so you should only have to set memory.cap and
+memory.threshold. If cap is set to the hard limit, set threshold to 99% or so (or just set cap lower
+and threshold to 100%). memory.threshold is provided for convenience; the actual effective limit is
+the product of cap * threshold..
+
+Some things to keep in mind:
+
+* jemalloc updates summary stats like process total once per epoch. Snort will bump the epoch once
+  per memory.interval ms. Performance suffers if interval is too low.
+
+* Snort generally frees memory in the same thread that allocated it, however this is not always
+  true. Shared caches may delete LRU entries when updated leading to freeing by different packet
+  threads. File capture will free memory allocated by a packet thread in a capture thread. This means
+  that the memory.allocated and memory.deallocated pegs can be confusing and should be considered
+  independently.
+
+* Since only packet threads take action to enforce the cap, packet threads will be forced to
+  compensate for non-packet threads. This could lead to an apparent out of memory condition if
+  non-packet threads allocate and hold significant memory. Therefore, non-packet threads should be
+  configured with some cap and that amount should be factored into the fixed startup cost.
 
 tcmalloc builds (--enable-tcmalloc) do not support memory management.  A process total is available
-from the tcmalloc extensions but it is too expensive to call per packet. Checking every N packets
-would also mean potentially freeing K > 1 flows after each exceeded event. Also, a process total
-does not allow pruning only the threads that are over limit. tcmalloc does provide a performance
-advantage over glibc so that may be preferred for deployments that don't need memory management.
+from the tcmalloc extensions but it lacks a thread deallocated number. A scheme could be implemented
+that released prune_target flows but that is not planned.  tcmalloc does provide a performance
+advantage over glibc so that may be preferred for deployments that don't need memory management,
+however internal tests show jemalloc performs better than tcmalloc for more than about 8 threads /
+cores.
 
-jemalloc is preferred because it is quicker and uses less memory since the implementation does not
-require memory tracking. jemalloc provides access to the current thread allocated total (which is
-between the number that Snort requests and what the system footprint is).
+Files pertaining to management (all under src/memory/):
 
-memory_module.* - provides parameters and peg counts. The key parameters are the process_cap,
-thread_cap, and threshold. The caps are in bytes, and the threshold is a percentage of the caps
-specified.
+* heap_interface: implements a jemalloc interface if enabled or a nerfed interface if disabled. A
+  custom HeapInterface (and pruner) can be provided by a plugin for testing. MemoryCap provides
+  methods to install them.
 
-memory_manager.cc - when enabled with --enable-memory-overloads, overloads new and delete operators
-to provide support memory tracking. Metadata is allocated in front of the requested memory to store
-the sized allocated so the deallocation can be tracked. Due to the drag on performance, this is
-disabled by default.
+* memory_cap: implementation of reap cycles using heap interface.
 
-memory_allocator.* - implements the malloc and free calls used by the operator new and delete
-overloads.
+* memory_config: defines config struct.
 
-memory_config.h - provides MemoryConfig used by MemoryCap and stored in SnortConfig.
+* memory_module: glue between Snort and memory features.
 
-memory_cap.* - provides the logic to enforce the thread cap by calling the prune handler. Tracks
-thread usage in pegs and updates the memory profiler if built. To avoid confusion, thread_cap
-refers to the configured maximums, while thread_limit refer to the configured percentage of the
-caps (memory.cap * memory.threshold / 100). The jemalloc specific code is here.
+Files pertaining to profiling (all under src/memory/):
 
-prune_handler.* - implements the call to stream to prune.
+* memory_allocator: overloads call the allocator to actually malloc and free memory.
+
+* memory_overloads: implements new and delete overloads using allocator.
 
 The current iteration of the memory manager is exclusively preemptive.  MemoryCap::free_space is
 called by the analyzer before each DAQ message is processed. If thread_usage > thread_limit, a
@@ -45,11 +68,9 @@ more allocations).
 
 This implementation has the following limitations:
 
-* If the overload manager is built, it only tracks memory allocated with C++ new.  Specifically,
+* If the profiler is built, it only tracks memory allocated with C++ new.  Specifically,
   direct calls to malloc or calloc which may be made by libraries are not tracked.
 
-* Packet thread tracking does not include heap overhead, which can be substantial.
-
 * Non-packet threads are assumed to have bounded memory usage, eg via a cache.
 
 * Heap managers tend to acquire memory from the system quickly and release back much more slowly,
@@ -63,17 +84,3 @@ well enough below the actual hard limit, for example the limit enforced by cgrou
 processing of a single packet will not push us over. It must also allow for additional heap
 overhead.
 
-Future work:
-
-* Support simplified configuration of a process cap instead of a thread cap.  Implement a MemoryCap
-  method that can be called to inform the memory module of various cache related memcaps.  Deduct
-  the startup ru_maxrss and the sum of memcaps from the configured process cap and then divide by
-  --max-packet-threads to get the effective thread cap.
-
-* Compensate for heap fragmentation and other overhead by using the current process footprint
-  (process_total below) as feedback to adjust the current packet thread limits:
-
-  thread_limit = [(cap - (process_total - sum_thread_usage)) / num_threads] * threshold
-
-* Recognize when a memory leak drives excessive pruning.
-
diff --git a/src/memory/heap_interface.cc b/src/memory/heap_interface.cc
new file mode 100644 (file)
index 0000000..8fd5cb2
--- /dev/null
@@ -0,0 +1,122 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2023-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.
+//--------------------------------------------------------------------------
+
+// heap_interface.cc author Russ Combs <rucombs@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "heap_interface.h"
+
+#include <cassert>
+
+#ifdef HAVE_JEMALLOC
+#include <jemalloc/jemalloc.h>
+#endif
+
+#include "main/thread.h"
+
+namespace memory
+{
+
+// -----------------------------------------------------------------------------
+#ifdef HAVE_JEMALLOC
+// -----------------------------------------------------------------------------
+
+class JemallocInterface : public HeapInterface
+{
+    void main_init() override;
+    void thread_init() override;
+
+    void get_process_total(uint64_t&, uint64_t&) override;
+    void get_thread_allocs(uint64_t&, uint64_t&) override;
+};
+
+static size_t stats_mib[2], mib_len = 2;
+
+static THREAD_LOCAL uint64_t* alloc_ptr = nullptr;
+static THREAD_LOCAL uint64_t* dealloc_ptr = nullptr;
+
+void JemallocInterface::main_init()
+{
+    mallctlnametomib("stats.allocated", stats_mib, &mib_len);
+}
+
+void JemallocInterface::thread_init()
+{
+    size_t sz = sizeof(alloc_ptr);
+
+    // __STRDUMP_DISABLE__
+    mallctl("thread.allocatedp", (void*)&alloc_ptr, &sz, nullptr, 0);
+    mallctl("thread.deallocatedp", (void*)&dealloc_ptr, &sz, nullptr, 0);
+    // __STRDUMP_ENABLE__
+}
+
+void JemallocInterface::get_process_total(uint64_t& epoch, uint64_t& utotal)
+{
+    uint64_t cycle = 13;
+    size_t sz = sizeof(epoch);
+    mallctl("epoch", (void*)&epoch, &sz, (void*)&cycle, sizeof(cycle));
+
+    size_t total;
+    sz = sizeof(total);
+    mallctlbymib(stats_mib, mib_len, (void*)&total, &sz, NULL, 0);
+
+    utotal = total;
+}
+
+void JemallocInterface::get_thread_allocs(uint64_t& alloc, uint64_t& dealloc)
+{
+    assert(alloc_ptr);
+    assert(dealloc_ptr);
+
+    alloc = *alloc_ptr;
+    dealloc = *dealloc_ptr;
+}
+
+//--------------------------------------------------------------------------
+#else  // disabled interface
+//--------------------------------------------------------------------------
+
+class NerfedInterface : public HeapInterface
+{
+public:
+    void main_init() override { }
+    void thread_init() override { }
+
+    void get_process_total(uint64_t& e, uint64_t& t) override
+    { e = t = 0; }
+
+    void get_thread_allocs(uint64_t& a, uint64_t& d) override
+    { a = d = 0; }
+};
+
+#endif
+
+HeapInterface* HeapInterface::get_instance()
+{
+#ifdef HAVE_JEMALLOC
+    return new JemallocInterface;
+#else
+    return new NerfedInterface;
+#endif
+}
+
+}  // memory
+
similarity index 63%
rename from src/memory/prune_handler.cc
rename to src/memory/heap_interface.h
index 4c454a4ff48b7b93b8c09fe029b3d8617a2ce9f5..9665202481262aea46c563f38604ca8a37339542 100644 (file)
@@ -1,5 +1,5 @@
 //--------------------------------------------------------------------------
-// Copyright (C) 2016-2022 Cisco and/or its affiliates. All rights reserved.
+// Copyright (C) 2023-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
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-// prune_handler.cc author Joel Cornett <jocornet@cisco.com>
+// heap_interface.cc author Russ Combs <rucombs@cisco.com>
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include "prune_handler.h"
-
-#include "stream/stream.h"
+#ifndef HEAP_INTERFACE_H
+#define HEAP_INTERFACE_H
 
-using namespace snort;
+#include <cstdint>
 
 namespace memory
 {
 
-bool prune_handler()
+class HeapInterface
 {
-    return Stream::prune_flows();
+public:
+    virtual ~HeapInterface() { }
+
+    virtual void main_init() = 0;
+    virtual void thread_init() = 0;
+
+    virtual void get_process_total(uint64_t& epoch, uint64_t& total) = 0;
+    virtual void get_thread_allocs(uint64_t& alloc, uint64_t& dealloc) = 0;
+
+    static HeapInterface* get_instance();
+
+protected:
+    HeapInterface() { }
+};
+
 }
 
-} // namespace memory
+#endif
+
index fd803321ca2849761d70103ef0d8fe7af9ea1866..bc2f5dfb40af17b40e01399f2938ca36d3656d9e 100644 (file)
 #include "config.h"
 #endif
 
+#include "memory_cap.h"
+
 #include <malloc.h>
 #include <sys/resource.h>
 
+#include <atomic>
 #include <cassert>
 #include <vector>
 
-#ifdef HAVE_JEMALLOC
-#include <jemalloc/jemalloc.h>
-#endif
-
-#include "memory_cap.h"
-
 #include "log/messages.h"
 #include "main/snort_config.h"
 #include "main/snort_types.h"
 #include "main/thread.h"
-#include "profiler/memory_profiler_active_context.h"
+#include "time/periodic.h"
+#include "trace/trace_api.h"
 #include "utils/stats.h"
 
+#include "heap_interface.h"
 #include "memory_config.h"
 #include "memory_module.h"
-#include "prune_handler.h"
 
 using namespace snort;
 
 namespace memory
 {
 
-static MemoryCounts ctl_mem_stats;
-static std::vector<MemoryCounts> pkt_mem_stats;
-
-namespace
-{
-
 // -----------------------------------------------------------------------------
-// helpers
+// private
 // -----------------------------------------------------------------------------
 
-#ifdef HAVE_JEMALLOC
-static size_t get_usage(MemoryCounts& mc)
-{
-    static THREAD_LOCAL uint64_t* alloc_ptr = nullptr, * dealloc_ptr = nullptr;
-
-    if ( !alloc_ptr )
-    {
-        size_t sz = sizeof(alloc_ptr);
-        // __STRDUMP_DISABLE__
-        mallctl("thread.allocatedp", (void*)&alloc_ptr, &sz, nullptr, 0);
-        mallctl("thread.deallocatedp", (void*)&dealloc_ptr, &sz, nullptr, 0);
-        // __STRDUMP_ENABLE__
-    }
-    mc.allocated = *alloc_ptr;
-    mc.deallocated = *dealloc_ptr;
+static std::vector<MemoryCounts> pkt_mem_stats;
 
-    if ( mc.allocated > mc.deallocated )
-    {
-        size_t usage =  mc.allocated - mc.deallocated;
+static MemoryConfig config;
+static size_t limit = 0;
 
-        if ( usage > mc.max_in_use )
-            mc.max_in_use = usage;
+static std::atomic<bool> over_limit { false };
+static std::atomic<uint64_t> current_epoch { 0 };
 
-        return usage;
-    }
-    return 0;
-}
-#else
-static size_t get_usage(const MemoryCounts& mc)
-{
-#ifdef ENABLE_MEMORY_OVERLOADS
-    assert(mc.allocated >= mc.deallocated);
-    return mc.allocated - mc.deallocated;
+static THREAD_LOCAL uint64_t last_dealloc = 0;
+static THREAD_LOCAL uint64_t start_dealloc = 0;
+static THREAD_LOCAL uint64_t start_epoch = 0;
 
-#else
-    UNUSED(mc);
-    return 0;
-#endif
-}
-#endif
+static HeapInterface* heap = nullptr;
+static PruneHandler pruner;
 
-template<typename Handler>
-inline void free_space(size_t cap, Handler& handler)
+static void epoch_check(void*)
 {
-    MemoryCounts& mc = memory::MemoryCap::get_mem_stats();
-    size_t usage = get_usage(mc);
+    uint64_t epoch, total;
+    heap->get_process_total(epoch, total);
 
-    if ( usage < cap )
-        return;
+    current_epoch = epoch;
 
-    ++mc.reap_attempts;
+    bool prior = over_limit;
+    over_limit = limit and total > limit;
 
-    if ( handler() )
-        return;
+    if ( prior != over_limit )
+        trace_logf(memory_trace, nullptr, "Epoch=%lu, memory=%lu (%s)\n", epoch, total, over_limit?"over":"under");
 
-    ++mc.reap_failures;
-}
+    MemoryCounts& mc = memory::MemoryCap::get_mem_stats();
 
-inline size_t calculate_threshold(size_t cap, size_t threshold)
-{ return cap * threshold / 100; }
+    if ( total > mc.max_in_use )
+        mc.max_in_use = total;
 
-} // namespace
+    mc.cur_in_use = total;
+    mc.epochs++;
+}
 
 // -----------------------------------------------------------------------------
-// per-thread configuration
+// public
 // -----------------------------------------------------------------------------
 
-size_t MemoryCap::limit = 0;
+void MemoryCap::set_heap_interface(HeapInterface* h)
+{ heap = h; }
 
-// -----------------------------------------------------------------------------
-// public interface
-// -----------------------------------------------------------------------------
+void MemoryCap::set_pruner(PruneHandler p)
+{ pruner = p; }
 
-void MemoryCap::setup(const MemoryConfig& config, unsigned n)
+void MemoryCap::setup(const MemoryConfig& c, unsigned n, PruneHandler ph)
 {
     assert(!is_packet_thread());
-    limit = memory::calculate_threshold(config.cap, config.threshold);
+
     pkt_mem_stats.resize(n);
+    config = c;
+
+    if ( !heap )
+        heap = HeapInterface::get_instance();
+
+    if ( !config.enabled )
+        return;
+
+    if ( !pruner )
+        pruner = ph;
+
+    limit = config.cap * config.threshold / 100;
+    over_limit = false;
+    current_epoch = 0;
+
+    Periodic::register_handler(epoch_check, nullptr, 0, config.interval);
+    heap->main_init();
+
+    MemoryCounts& mc = memory::MemoryCap::get_mem_stats();
+#ifdef UNIT_TEST
+    mc = { };
+#endif
+
+    epoch_check(nullptr);
+    mc.start_up_use = mc.cur_in_use;
 }
 
 void MemoryCap::cleanup()
 {
     pkt_mem_stats.resize(0);
+    delete heap;
+    heap = nullptr;
+}
+
+void MemoryCap::thread_init()
+{
+    if ( config.enabled )
+        heap->thread_init();
+
+    start_dealloc = 0;
+    start_epoch = 0;
 }
 
 MemoryCounts& MemoryCap::get_mem_stats()
 {
+    // main thread stats do not overlap with packet threads
     if ( !is_packet_thread() )
-        return ctl_mem_stats;
+        return pkt_mem_stats[0];
 
     auto id = get_instance_id();
     return pkt_mem_stats[id];
@@ -158,78 +161,62 @@ void MemoryCap::free_space()
 {
     assert(is_packet_thread());
 
-    if ( !limit )
-        return;
-
-    memory::free_space(limit, prune_handler);
-}
-
-#ifdef ENABLE_MEMORY_OVERLOADS
-void MemoryCap::allocate(size_t n)
-{
     MemoryCounts& mc = memory::MemoryCap::get_mem_stats();
+    heap->get_thread_allocs(mc.allocated, mc.deallocated);
 
-    mc.allocated += n;
-    ++mc.allocations;
-
-    assert(mc.allocated >= mc.deallocated);
-    auto in_use = mc.allocated - mc.deallocated;
+    if ( !over_limit and !start_dealloc )
+        return;
 
-    if ( in_use > mc.max_in_use )
-        mc.max_in_use = in_use;
+    if ( !start_dealloc )
+    {
+        if ( current_epoch == start_epoch )
+            return;
 
-#ifdef ENABLE_MEMORY_PROFILER
-    mp_active_context.update_allocs(n);
-#endif
-}
+        start_dealloc = last_dealloc = mc.deallocated;
+        start_epoch = current_epoch;
+        mc.reap_cycles++;
+    }
 
-void MemoryCap::deallocate(size_t n)
-{
-    MemoryCounts& mc = memory::MemoryCap::get_mem_stats();
+    mc.pruned += (mc.deallocated - last_dealloc);
+    last_dealloc = mc.deallocated;
 
-    // std::thread causes an extra deallocation in packet
-    // threads so the below asserts don't hold
-    if ( mc.allocated >= mc.deallocated + n )
+    if ( mc.deallocated - start_dealloc  >= config.prune_target )
     {
-        mc.deallocated += n;
-        ++mc.deallocations;
+        start_dealloc = 0;
+        return;
     }
 
-#if 0
-    assert(mc.deallocated <= mc.allocated);
-    assert(mc.deallocations <= mc.allocations);
-    assert(mc.allocated or !mc.allocations);
-#endif
+    ++mc.reap_attempts;
 
-#ifdef ENABLE_MEMORY_PROFILER
-    mp_active_context.update_deallocs(n);
-#endif
+    if ( pruner() )
+        return;
+
+    ++mc.reap_failures;
 }
-#endif
 
-void MemoryCap::print(bool verbose, bool print_all)
+// called at startup and shutdown
+void MemoryCap::print(bool verbose, bool init)
 {
-    if ( !MemoryModule::is_active() )
+    if ( !config.enabled )
         return;
 
     MemoryCounts& mc = get_mem_stats();
-    uint64_t usage = get_usage(mc);
-
-    if ( verbose or usage )
-        LogLabel("memory (heap)");
 
-    if ( verbose and print_all )
+    if ( init and (verbose or mc.start_up_use) )
+    {
+        LogLabel("memory");
         LogCount("pruning threshold", limit);
+        LogCount("start up use", mc.start_up_use);
+    }
 
-    LogCount("main thread usage", usage);
-    LogCount("allocations", mc.allocations);
-    LogCount("deallocations", mc.deallocations);
+    if ( limit and (mc.max_in_use > limit) )
+        LogCount("process over limit", mc.max_in_use - limit);
 
     if ( verbose )
     {
         struct rusage ru;
         getrusage(RUSAGE_SELF, &ru);
-        LogCount("max_rss", ru.ru_maxrss * 1024);
+        LogCount("max rss", ru.ru_maxrss * 1024);
     }
 }
 
index 53fa5f4dfd788174f6c33664f32fdd93573f636e..396988fd3ddb07f232dae261e48f3a25617b893c 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef MEMORY_CAP_H
 #define MEMORY_CAP_H
 
+#include "memory/heap_interface.h"
+
 #include <cstddef>
 
 #include "framework/counts.h"
@@ -33,37 +35,39 @@ namespace memory
 
 struct MemoryCounts
 {
-    PegCount allocations;
-    PegCount deallocations;
+    PegCount start_up_use;
+    PegCount cur_in_use;
+    PegCount max_in_use;
+    PegCount epochs;
     PegCount allocated;
     PegCount deallocated;
+    PegCount reap_cycles;
     PegCount reap_attempts;
     PegCount reap_failures;
-    PegCount max_in_use;
+    PegCount pruned;
 };
 
+typedef bool (*PruneHandler)();
+
 class SO_PUBLIC MemoryCap
 {
 public:
-    static void setup(const MemoryConfig&, unsigned);
+    // main thread - in configure
+    static void set_heap_interface(HeapInterface*);
+    static void set_pruner(PruneHandler);
+
+    // main thread - after configure
+    static void setup(const MemoryConfig&, unsigned num_threads, PruneHandler);
     static void cleanup();
+    static void print(bool verbose, bool init = false);
 
+    // packet threads
+    static void thread_init();
     static void free_space();
 
-    // call from main thread
-    static void print(bool verbose, bool print_all = true);
-
     static MemoryCounts& get_mem_stats();
-
-#ifdef ENABLE_MEMORY_OVERLOADS
-    static void allocate(size_t);
-    static void deallocate(size_t);
-#endif
-
-private:
-    static size_t limit;
 };
 
-} // namespace memory
+}
 
 #endif
index 3951784012f776d66af3dbf8fd4cef794e1465f4..da9f8f47c448dc0d176525b561c731257aa6c019 100644 (file)
@@ -27,6 +27,9 @@ struct MemoryConfig
 {
     size_t cap = 0;
     unsigned threshold = 0;
+    unsigned interval = 1000;
+    unsigned prune_target = 1048576;
+    bool enabled = false;
 
     constexpr MemoryConfig() = default;
 };
index cedabc5f416b8f7cd69dbeb3b5d588dbc8c03597..76d5c689bf68e94b1539c597aa21bee2735c6739 100644 (file)
 #include "memory_module.h"
 
 #include "main/snort_config.h"
+#include "trace/trace.h"
 
 #include "memory_cap.h"
 #include "memory_config.h"
 
 using namespace snort;
 
+THREAD_LOCAL const Trace* memory_trace = nullptr;
+
 // -----------------------------------------------------------------------------
 // memory attributes
 // -----------------------------------------------------------------------------
@@ -42,7 +45,13 @@ using namespace snort;
 static const Parameter s_params[] =
 {
     { "cap", Parameter::PT_INT, "0:maxSZ", "0",
-        "set the per-packet-thread cap on memory (bytes, 0 to disable)" },
+        "set the process cap on memory in bytes (0 to disable)" },
+
+    { "interval", Parameter::PT_INT, "1:max32", "50",
+        "approximate ms between memory epochs" },
+
+    { "prune_target", Parameter::PT_INT, "1:max32", "1048576",
+        "bytes to prune per packet thread prune cycle" },
 
     { "threshold", Parameter::PT_INT, "1:100", "100",
         "scale cap to account for heap overhead" },
@@ -50,17 +59,18 @@ static const Parameter s_params[] =
     { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
 };
 
-static memory::MemoryCounts zero_stats = { };
-
 const PegInfo mem_pegs[] =
 {
-    { CountType::NOW, "allocations", "total number of allocations" },
-    { CountType::NOW, "deallocations", "total number of deallocations" },
-    { CountType::NOW, "allocated", "total amount of memory allocated" },
-    { CountType::NOW, "deallocated", "total amount of memory deallocated" },
+    { CountType::NOW, "start_up_use", "memory used before packet processing" },
+    { CountType::NOW, "cur_in_use", "current memory used" },
+    { CountType::MAX, "max_in_use", "maximum memory used" },
+    { CountType::NOW, "epochs", "number of memory updates" },
+    { CountType::NOW, "allocated", "total amount of memory allocated by packet threads" },
+    { CountType::NOW, "deallocated", "total amount of memory deallocated by packet threads" },
+    { CountType::NOW, "reap_cycles", "number of actionable over-limit conditions" },
     { CountType::NOW, "reap_attempts", "attempts to reclaim memory" },
     { CountType::NOW, "reap_failures", "failures to reclaim memory" },
-    { CountType::MAX, "max_in_use", "maximum memory used" },
+    { CountType::NOW, "pruned", "total amount of memory pruned" },
     { CountType::END, nullptr, nullptr }
 };
 
@@ -68,8 +78,6 @@ const PegInfo mem_pegs[] =
 // memory module
 // -----------------------------------------------------------------------------
 
-bool MemoryModule::configured = false;
-
 MemoryModule::MemoryModule() :
     Module(s_name, s_help, s_params)
 { }
@@ -79,29 +87,37 @@ bool MemoryModule::set(const char*, Value& v, SnortConfig* sc)
     if ( v.is("cap") )
         sc->memory->cap = v.get_size();
 
+    else if ( v.is("interval") )
+        sc->memory->interval = v.get_uint32();
+
+    else if ( v.is("prune_target") )
+        sc->memory->prune_target = v.get_uint32();
+
     else if ( v.is("threshold") )
         sc->memory->threshold = v.get_uint8();
 
     return true;
 }
 
-bool MemoryModule::end(const char*, int, SnortConfig*)
+bool MemoryModule::end(const char*, int, SnortConfig* sc)
 {
-    configured = true;
+    sc->memory->enabled = true;
     return true;
 }
 
-bool MemoryModule::is_active()
-{ return configured; }
-
 const PegInfo* MemoryModule::get_pegs() const
 { return mem_pegs; }
 
 PegCount* MemoryModule::get_counts() const
+{ return (PegCount*)&memory::MemoryCap::get_mem_stats(); }
+
+void MemoryModule::set_trace(const Trace* trace) const
+{ memory_trace = trace; }
+
+const TraceOption* MemoryModule::get_trace_options() const
 {
-    if ( !is_active() )
-        return (PegCount*)&zero_stats;
+    static const TraceOption memory_trace_options(nullptr, 0, nullptr);
 
-    return (PegCount*)&memory::MemoryCap::get_mem_stats();
+    return &memory_trace_options;
 }
 
index b3116649f49a55fe1c48dfa504cdf10e9f8f9eaa..3dc087f03de571872412310ee16e5a803fca0b47 100644 (file)
@@ -37,11 +37,11 @@ public:
     Usage get_usage() const override
     { return GLOBAL; }
 
-    static bool is_active();
-
-private:
-    static bool configured;
+    void set_trace(const snort::Trace*) const override;
+    const snort::TraceOption* get_trace_options() const override;
 };
 
+extern THREAD_LOCAL const snort::Trace* memory_trace;
+
 #endif
 
similarity index 81%
rename from src/memory/memory_manager.cc
rename to src/memory/memory_overloads.cc
index 81b35719350ec8f2eb9a6c9e02ee049663aeffc2..a240caa5b7c11f7aeb75a6b89645a97cc79c295c 100644 (file)
@@ -16,7 +16,7 @@
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 //--------------------------------------------------------------------------
 
-// memory_manager.cc author Joel Cornett <jocornet@cisco.com>
+// memory_overloads.cc author Joel Cornett <jocornet@cisco.com>
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -26,9 +26,9 @@
 #include <new>
 
 #include "main/thread.h"
+#include "profiler/memory_profiler_active_context.h"
 
 #include "memory_allocator.h"
-#include "memory_cap.h"
 
 #ifdef UNIT_TEST
 #include "catch/snort_catch.h"
@@ -142,7 +142,7 @@ private:
     bool& flag;
 };
 
-template<typename Allocator = MemoryAllocator, typename Cap = MemoryCap>
+template<typename Allocator = MemoryAllocator>
 struct Interface
 {
     static void* allocate(size_t);
@@ -151,8 +151,8 @@ struct Interface
     static THREAD_LOCAL bool in_allocation_call;
 };
 
-template<typename Allocator, typename Cap>
-void* Interface<Allocator, Cap>::allocate(size_t n)
+template<typename Allocator>
+void* Interface<Allocator>::allocate(size_t n)
 {
     // prevent allocation reentry
     ReentryContext reentry_context(in_allocation_call);
@@ -163,12 +163,15 @@ void* Interface<Allocator, Cap>::allocate(size_t n)
     if ( !meta )
         return nullptr;
 
-    Cap::allocate(meta->total_size());
+#ifdef ENABLE_MEMORY_PROFILER
+    mp_active_context.update_allocs(meta->total_size());
+#endif
+
     return meta->payload_offset();
 }
 
-template<typename Allocator, typename Cap>
-void Interface<Allocator, Cap>::deallocate(void* p)
+template<typename Allocator>
+void Interface<Allocator>::deallocate(void* p)
 {
     if ( !p )
         return;
@@ -176,12 +179,15 @@ void Interface<Allocator, Cap>::deallocate(void* p)
     auto meta = Metadata::extract(p);
     assert(meta);
 
-    Cap::deallocate(meta->total_size());
+#ifdef ENABLE_MEMORY_PROFILER
+    mp_active_context.update_deallocs(meta->total_size());
+#endif
+
     Allocator::deallocate(meta);
 }
 
-template<typename Allocator, typename Cap>
-THREAD_LOCAL bool Interface<Allocator, Cap>::in_allocation_call = false;
+template<typename Allocator>
+THREAD_LOCAL bool Interface<Allocator>::in_allocation_call = false;
 
 } //namespace memory
 
@@ -191,7 +197,7 @@ THREAD_LOCAL bool Interface<Allocator, Cap>::in_allocation_call = false;
 
 // these don't have to be visible to operate as replacements
 
-#ifdef ENABLE_MEMORY_OVERLOADS
+#ifdef ENABLE_MEMORY_PROFILER
 void* operator new(size_t n)
 {
     auto p = memory::Interface<>::allocate(n);
@@ -268,42 +274,6 @@ size_t AllocatorSpy::allocate_arg = 0;
 bool AllocatorSpy::deallocate_called = false;
 void* AllocatorSpy::deallocate_arg = nullptr;
 
-struct CapSpy
-{
-    static void allocate(size_t n)
-    {
-        update_allocations_called = true;
-        update_allocations_arg = n;
-    }
-
-    static void deallocate(size_t n)
-    {
-        update_deallocations_called = true;
-        update_deallocations_arg = n;
-    }
-
-    static void reset()
-    {
-        update_allocations_called = false;
-        update_allocations_arg = 0;
-
-        update_deallocations_called = false;
-        update_deallocations_arg = 0;
-    }
-
-    static bool update_allocations_called;
-    static size_t update_allocations_arg;
-
-    static bool update_deallocations_called;
-    static size_t update_deallocations_arg;
-};
-
-bool CapSpy::update_allocations_called = false;
-size_t CapSpy::update_allocations_arg = 0;
-
-bool CapSpy::update_deallocations_called = false;
-size_t CapSpy::update_deallocations_arg = 0;
-
 } // namespace t_memory
 
 TEST_CASE( "memory metadata", "[memory]" )
@@ -339,17 +309,16 @@ TEST_CASE( "memory metadata", "[memory]" )
     }
 }
 
-TEST_CASE( "memory manager interface", "[memory]" )
+TEST_CASE( "memory overloads", "[memory]" )
 {
     using namespace t_memory;
 
     AllocatorSpy::reset();
-    CapSpy::reset();
 
     constexpr size_t n = 1;
     char pool[sizeof(memory::Metadata) + n];
 
-    using Interface = memory::Interface<AllocatorSpy, CapSpy>;
+    using Interface = memory::Interface<AllocatorSpy>;
 
     SECTION( "allocation" )
     {
@@ -361,8 +330,6 @@ TEST_CASE( "memory manager interface", "[memory]" )
 
             CHECK( AllocatorSpy::allocate_called );
             CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
-
-            CHECK_FALSE( CapSpy::update_allocations_called );
         }
 
         SECTION( "success" )
@@ -375,9 +342,6 @@ TEST_CASE( "memory manager interface", "[memory]" )
 
             CHECK( AllocatorSpy::allocate_called );
             CHECK( AllocatorSpy::allocate_arg == memory::Metadata::calculate_total_size(n) );
-
-            CHECK( CapSpy::update_allocations_called );
-            CHECK( CapSpy::update_allocations_arg == memory::Metadata::calculate_total_size(n) );
         }
     }
 
@@ -386,9 +350,7 @@ TEST_CASE( "memory manager interface", "[memory]" )
         SECTION( "nullptr" )
         {
             Interface::deallocate(nullptr);
-
             CHECK_FALSE( AllocatorSpy::deallocate_called );
-            CHECK_FALSE( CapSpy::update_deallocations_called );
         }
 
         SECTION( "success" )
@@ -402,8 +364,6 @@ TEST_CASE( "memory manager interface", "[memory]" )
 
             CHECK( AllocatorSpy::deallocate_called );
             CHECK( AllocatorSpy::deallocate_arg == (void*)pool );
-            CHECK( CapSpy::update_deallocations_called );
-            CHECK( CapSpy::update_deallocations_arg == memory::Metadata::calculate_total_size(n) );
         }
     }
     AllocatorSpy::pool = nullptr;
diff --git a/src/memory/prune_handler.h b/src/memory/prune_handler.h
deleted file mode 100644 (file)
index cb1343d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-//--------------------------------------------------------------------------
-// Copyright (C) 2016-2022 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.
-//--------------------------------------------------------------------------
-
-// prune_handler.h author Joel Cornett <jocornet@cisco.com>
-
-#ifndef PRUNE_HANDLER_H
-#define PRUNE_HANDLER_H
-
-namespace memory
-{
-
-bool prune_handler();
-
-}
-
-
-#endif
-
diff --git a/src/memory/test/CMakeLists.txt b/src/memory/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b1b2baf
--- /dev/null
@@ -0,0 +1,6 @@
+
+add_cpputest( memory_cap_test
+    SOURCES
+    ../heap_interface.cc
+    ../memory_cap.cc
+)
diff --git a/src/memory/test/memory_cap_test.cc b/src/memory/test/memory_cap_test.cc
new file mode 100644 (file)
index 0000000..a1c1fc4
--- /dev/null
@@ -0,0 +1,397 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2023-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.
+//--------------------------------------------------------------------------
+
+// memory_cap_test.cc author Russ Combs <rucombs@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "memory/memory_cap.h"
+
+#include "log/messages.h"
+#include "main/thread.h"
+#include "time/periodic.h"
+#include "trace/trace_api.h"
+
+#include "memory/heap_interface.h"
+#include "memory/memory_config.h"
+
+using namespace snort;
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+using namespace memory;
+
+//--------------------------------------------------------------------------
+// stubs
+//--------------------------------------------------------------------------
+
+namespace snort
+{
+// LCOV_EXCL_START
+void LogCount(char const*, uint64_t, FILE*) { }
+void LogLabel(const char*, FILE*) { }
+
+void TraceApi::filter(snort::Packet const&) { }
+void trace_vprintf(const char*, TraceLevel, const char*, const Packet*, const char*, va_list) { }
+
+uint8_t snort::TraceApi::get_constraints_generation() { return 0; }
+// LCOV_EXCL_STOP
+
+unsigned get_instance_id()
+{ return 0; }
+}
+
+THREAD_LOCAL const Trace* memory_trace = nullptr;
+
+//--------------------------------------------------------------------------
+// mocks
+//--------------------------------------------------------------------------
+
+class MockHeap : public HeapInterface
+{
+public:
+    void main_init() override
+    { main_init_calls++; }
+
+    void thread_init() override
+    { thread_init_calls++; }
+
+    void get_process_total(uint64_t& e, uint64_t& t) override
+    { e = ++epoch; t = total; }
+
+    void get_thread_allocs(uint64_t& a, uint64_t& d) override
+    { a = alloc; d = dealloc; }
+
+    uint64_t alloc = 2, dealloc = 1;
+    uint64_t epoch = 0, total = 0;
+
+    unsigned main_init_calls = 0;
+    unsigned thread_init_calls = 0;
+};
+
+static PeriodicHook phook = nullptr;
+static void* parg = nullptr;
+
+void Periodic::register_handler(PeriodicHook f, void* v, uint16_t, uint32_t)
+{ phook = f; parg = v; }
+
+static void periodic_check()
+{
+    if ( phook )
+        phook(parg);
+}
+
+static int flows;
+
+static bool pruner()
+{ return --flows >= 0; }
+
+static bool pkt_thread = false;
+
+namespace snort
+{
+
+SThreadType get_thread_type()
+{ return pkt_thread ? STHREAD_TYPE_PACKET : STHREAD_TYPE_MAIN; }
+
+}
+
+static void free_space()
+{
+    pkt_thread = true;
+    MemoryCap::free_space();
+    pkt_thread = false;
+}
+
+//--------------------------------------------------------------------------
+// tests
+//--------------------------------------------------------------------------
+
+TEST_GROUP(memory_off)
+{
+    void setup() override
+    { MemoryCap::set_pruner(pruner); }
+
+    void teardown() override
+    { flows = 0; }
+};
+
+TEST(memory_off, disabled)
+{
+    MemoryConfig config { 0, 100, 0, 1, false };
+    MemoryCap::setup(config, 1, pruner);
+
+    free_space();
+
+    CHECK(phook == nullptr);
+    CHECK(flows == 0);
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.start_up_use == 0);
+    CHECK(mc.epochs == 0);
+    CHECK(mc.reap_cycles == 0);
+
+    MemoryCap::cleanup();
+}
+
+TEST(memory_off, nerfed)
+{
+    MemoryConfig config { 100, 100, 0, 1, false };
+    MemoryCap::setup(config, 1, pruner);
+
+    free_space();
+
+    CHECK(phook == nullptr);
+    CHECK(flows == 0);
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.start_up_use == 0);
+    CHECK(mc.epochs == 0);
+    CHECK(mc.reap_cycles == 0);
+
+    MemoryCap::cleanup();
+}
+
+//--------------------------------------------------------------------------
+// tests
+//--------------------------------------------------------------------------
+
+TEST_GROUP(memory)
+{
+    MockHeap* heap = nullptr;
+
+    void setup() override
+    {
+        heap = new MockHeap;
+        MemoryCap::set_heap_interface(heap);
+    }
+
+    void teardown() override
+    {
+        heap = nullptr;
+        phook = nullptr;
+        parg = nullptr;
+        flows = 0;
+    }
+};
+
+TEST(memory, default_enabled)
+{
+    MemoryConfig config { 0, 100, 0, 1, true };
+    MemoryCap::setup(config, 1, pruner);
+
+    CHECK(phook != nullptr);
+    CHECK(heap->epoch == 1);
+    CHECK(heap->main_init_calls == 1);
+
+    CHECK(heap->thread_init_calls == 0);
+    MemoryCap::thread_init();
+    CHECK(heap->thread_init_calls == 1);
+    CHECK(heap->main_init_calls == 1);
+
+    periodic_check();
+    CHECK(heap->epoch == 2);
+    CHECK(flows == 0);
+
+    MemoryCap::cleanup();
+}
+
+TEST(memory, prune1)
+{
+    const uint64_t cap = 100;
+    const uint64_t start = 50;
+    heap->total = start;
+
+    MemoryConfig config { cap, 100, 0, 1, true };
+    MemoryCap::setup(config, 1, pruner);
+    MemoryCap::thread_init();
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.start_up_use == start);
+
+    flows = 1;
+    free_space();
+    CHECK(flows == 1);
+
+    heap->total = cap + 1;
+    periodic_check();
+    CHECK(heap->epoch == 2);
+
+    CHECK(flows == 1);
+    free_space();
+    CHECK(flows == 0);
+
+    heap->total = cap;
+    periodic_check();
+    CHECK(heap->epoch == 3);
+
+    heap->alloc++;
+    heap->dealloc++;
+
+    free_space();
+    CHECK(flows == 0);
+
+    CHECK(mc.start_up_use == start);
+    CHECK(mc.max_in_use == cap + 1);
+    CHECK(mc.cur_in_use == cap);
+
+    CHECK(mc.epochs == heap->epoch);
+    CHECK(mc.allocated == heap->alloc);
+    CHECK(mc.deallocated == heap->dealloc);
+
+    CHECK(mc.reap_cycles == 1);
+    CHECK(mc.reap_attempts == 1);
+    CHECK(mc.reap_failures == 0);
+    CHECK(mc.pruned == 1);
+
+    MemoryCap::cleanup();
+}
+
+TEST(memory, prune3)
+{
+    uint64_t cap = 100;
+    MemoryConfig config { cap, 100, 0, 1, true };
+    MemoryCap::setup(config, 1, pruner);
+    MemoryCap::thread_init();
+
+    flows = 3;
+    heap->total = cap + 1;
+
+    periodic_check();
+    CHECK(heap->epoch == 2);
+
+    CHECK(flows == 3);
+    free_space();
+    CHECK(flows == 2);
+
+    free_space();
+    CHECK(flows == 1);
+
+    free_space();
+    CHECK(flows == 0);
+
+    heap->total = cap;
+    periodic_check();
+    CHECK(heap->epoch == 3);
+
+    heap->dealloc++;
+    free_space();
+    CHECK(flows == 0);
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.reap_cycles == 1);
+    CHECK(mc.reap_attempts == 3);
+    CHECK(mc.reap_failures == 0);
+    CHECK(mc.pruned == 1);
+
+    MemoryCap::cleanup();
+}
+
+TEST(memory, two_cycles)
+{
+    uint64_t cap = 100;
+    MemoryConfig config { cap, 100, 0, 1, true };
+    MemoryCap::setup(config, 1, pruner);
+    MemoryCap::thread_init();
+
+    flows = 3;
+    heap->total = cap + 1;
+    periodic_check();
+
+    CHECK(flows == 3);
+    free_space();  // prune 1 flow
+    CHECK(flows == 2);
+
+    heap->dealloc++;
+    free_space();  // reset state
+    CHECK(flows == 2);
+
+    free_space();  // at most 1 reap cycle per epoch
+    CHECK(flows == 2);
+
+    heap->total = cap;
+    periodic_check();
+    free_space();
+    CHECK(flows == 2);
+
+    heap->total = cap + 10;
+    periodic_check();
+    free_space();
+    CHECK(flows == 1);
+
+    heap->total = cap;
+    heap->dealloc += 10;
+    periodic_check();
+
+    free_space();
+    CHECK(flows == 1);
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.reap_cycles == 2);
+    CHECK(mc.reap_attempts == 2);
+    CHECK(mc.reap_failures == 0);
+    CHECK(mc.pruned == 11);
+
+    MemoryCap::cleanup();
+}
+
+TEST(memory, reap_failure)
+{
+    const uint64_t cap = 100;
+    const uint64_t start = 50;
+    heap->total = start;
+
+    MemoryConfig config { cap, 100, 0, 2, true };
+    MemoryCap::setup(config, 1, pruner);
+    MemoryCap::thread_init();
+
+    flows = 1;
+    heap->total = cap + 1;
+
+    periodic_check();
+
+    CHECK(flows == 1);
+    free_space();
+    CHECK(flows == 0);
+
+    heap->dealloc++;
+    free_space();
+    CHECK(flows == -1);
+
+    const MemoryCounts& mc = MemoryCap::get_mem_stats();
+    CHECK(mc.reap_cycles == 1);
+    CHECK(mc.reap_attempts == 2);
+    CHECK(mc.reap_failures == 1);
+    CHECK(mc.pruned == 1);
+
+    MemoryCap::cleanup();
+}
+
+//-------------------------------------------------------------------------
+// main
+//-------------------------------------------------------------------------
+
+int main(int argc, char** argv)
+{
+    MemoryLeakWarningPlugin::turnOffNewDeleteOverloads();
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+
index 141b671fc133221368f05483102a6f7634e5bad5..6832ef077f10a26b359d2634684820b1fccc44a3 100644 (file)
@@ -30,7 +30,6 @@
 #include "file_api/file_flows.h"
 #include "hash/hash_key_operations.h"
 #include "log/messages.h"
-#include "memory/memory_cap.h"
 #include "search_engines/search_tool.h"
 #include "utils/util_cstring.h"
 
index a609f1f689aec53d40ddc297e868c273a67293e0..f18473ef44198e320279452b56ccf9674f1445ee 100644 (file)
@@ -26,7 +26,6 @@
 #include "appid_http_session.h"
 
 #include "flow/ha.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler.h"
 
 #include "app_info_table.h"
index 0dbd4c34b0cd7c4a0c80b71e4521f200850f19e8..b9f8d28aad3b5da73129fedd28406eba1b632df0 100644 (file)
@@ -27,7 +27,6 @@
 #include <string>
 
 #include "framework/data_bus.h"
-#include "memory/memory_cap.h"
 #include "protocols/protocol_ids.h"
 #include "service_inspectors/http_inspect/http_msg_header.h"
 #include "tp_appid_module_api.h"
index 39855b518540ec8083c69582bc5e038915695e05..eb22ee4c338fd9ea1b9f45337073ffe2a07b94ad 100644 (file)
@@ -24,8 +24,6 @@
 
 #include "dce_smb1.h"
 
-#include "memory/memory_cap.h"
-
 #include "dce_smb_utils.h"
 
 using namespace snort;
index 231aba48aef44dd7457e39fa93a384f88c0927ae..6d2e775388f22642fb7a4ec0f3eba4c189815c4a 100644 (file)
 // This implements smb session data for SMBv2
 // Also provides SMBv2 related header structures
 
+#include <mutex>
+
 #include "main/thread_config.h"
-#include "memory/memory_cap.h"
 #include "utils/util.h"
-#include <mutex>
 
 #include "dce_smb_common.h"
 
index 3b9d082722d30334d3e948ba569173b03f3eeac3..035b32e02e778399c468993dda832245e20ac69c 100644 (file)
@@ -26,7 +26,6 @@
 
 #include "file_api/file_flows.h"
 #include "file_api/file_service.h"
-#include "memory/memory_cap.h"
 
 #include "dce_smb1.h"
 #include "dce_smb2.h"
index 07f872a3fc4624ab352340847be358a95980d457..686cf77980e0238f3f54606b17078d76936ba905 100644 (file)
@@ -30,7 +30,6 @@
 #include "dce_smb_transaction.h"
 #include "detection/detect.h"
 #include "file_api/file_service.h"
-#include "memory/memory_cap.h"
 #include "packet_io/active.h"
 #include "protocols/packet.h"
 #include "trace/trace_api.h"
index 47f0f0c9af8a7b59800d93a51e2405f517a28b0e..a3032f1c812e9a8a09a1abf07c7f13877bf7588a 100644 (file)
@@ -27,7 +27,6 @@
 #include "events/event_queue.h"
 #include "log/messages.h"
 #include "managers/inspector_manager.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler.h"
 #include "protocols/packet.h"
 #include "pub_sub/sip_events.h"
index a2d1475c0c23e43ca4985c241df39e9e9494c107..f579771df561a649e6dc88a8ec1d647e8f0ca10f 100644 (file)
@@ -27,7 +27,6 @@
 
 #include "detection/detection_engine.h"
 #include "events/event_queue.h"
-#include "memory/memory_cap.h"
 #include "utils/util.h"
 #include "utils/util_cstring.h"
 
index a3651f2fd5050a677ea540c23f81c80f0bc5c4ec..6ccfdda8518799468505f4c9510a5a92c592c8fd 100644 (file)
@@ -25,7 +25,6 @@
 
 #include "detection/detection_engine.h"
 #include "file_api/file_flows.h"
-#include "memory/memory_cap.h"
 #include "packet_io/sfdaq.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/packet.h"
index ca53a66282541dcf4e639364b12b51f0ea719d67..7d29aa3d419ad51a8181b2c70730904620232a5c 100644 (file)
@@ -25,7 +25,6 @@
 
 #include "detection/ips_context.h"
 #include "flow/flow_key.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/icmp4.h"
 #include "protocols/packet.h"
index c725e9d258f2283850e1c0776f77e8c530ded54c..4f017311bc3a990f5541cc76ac3b9296f5a5aabb 100644 (file)
@@ -75,7 +75,6 @@
 #include "log/messages.h"
 #include "main/analyzer.h"
 #include "main/snort_config.h"
-#include "memory/memory_cap.h"
 #include "packet_io/active.h"
 #include "packet_io/sfdaq_config.h"
 #include "profiler/profiler_defs.h"
index e515344913e1348437cba2c36409578a3e0e38a9..d5d1fa684b8b902127d0afd74d1fe004a90a73db 100644 (file)
@@ -24,7 +24,6 @@
 #include "ip_session.h"
 
 #include "framework/data_bus.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/packet.h"
 #include "pub_sub/stream_event_ids.h"
index 6b865d57e417ddd05b4457dfb00a923321ab3e7a..86637734c7e3892204ae1ec4f0b30cdfd79fe9fa 100644 (file)
@@ -30,7 +30,6 @@
 #include "detection/detection_engine.h"
 #include "log/log.h"
 #include "main/analyzer.h"
-#include "memory/memory_cap.h"
 #include "packet_io/active.h"
 #include "profiler/profiler.h"
 #include "protocols/packet_manager.h"
index 759de12ffdd9d7d8807ef5ba5849d60e984e8ab0..7ad51e369694c4e1142c52e99096b43fc0387972 100644 (file)
@@ -26,7 +26,6 @@
 #include "tcp_segment_node.h"
 
 #include "main/thread.h"
-#include "memory/memory_cap.h"
 #include "utils/util.h"
 
 #include "segment_overlap_editor.h"
index 2fb4f2699d81b823abe223be1bb7d4a43e97d13f..564874526f21e41b2442e277a25b6185dd6053c4 100644 (file)
@@ -51,7 +51,6 @@
 #include "detection/detection_engine.h"
 #include "detection/rules.h"
 #include "log/log.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler.h"
 #include "protocols/eth.h"
 #include "pub_sub/intrinsic_event_ids.h"
index e37435b131d77c2b48c067e3780990d4680458bd..9d8fb205cc0ec4981225a5ee3d533a2cef72d4fd 100644 (file)
@@ -30,7 +30,6 @@
 #include "log/messages.h"
 #include "main/analyzer.h"
 #include "main/snort.h"
-#include "memory/memory_cap.h"
 #include "packet_io/active.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/eth.h"
index c42af4c776ac491b2c8f0d399a619d615497900c..144823cd061279e7460e7b75dae02342b8af728d 100644 (file)
@@ -26,7 +26,6 @@
 #include "flow/session.h"
 #include "framework/data_bus.h"
 #include "hash/xhash.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/packet.h"
 #include "pub_sub/intrinsic_event_ids.h"
index dcaa5b25caab9f67c8890716dc6793598ef7b7f2..57f4d7cfbb524d5e63556cf4cea993f324c6ab77 100644 (file)
@@ -26,7 +26,6 @@
 #include "detection/detection_engine.h"
 #include "detection/rules.h"
 #include "main/analyzer.h"
-#include "memory/memory_cap.h"
 #include "profiler/profiler_defs.h"
 #include "protocols/packet.h"
 #include "trace/trace_api.h"
index 0f3147f965cd8d58bb5c59279bf99ba051b476c3..29a951208891e8021172edf44752d60ee7548d10 100644 (file)
@@ -246,16 +246,18 @@ void DropStats(ControlConn* ctrlcon)
     s_ctrlcon = ctrlcon;
     LogLabel("Packet Statistics");
     ModuleManager::get_module("daq")->show_stats();
-
     PacketManager::dump_stats();
 
     LogLabel("Module Statistics");
-    const char* exclude = "daq snort";
+    const char* exclude = "daq snort memory";
     ModuleManager::dump_stats(exclude, false);
     ModuleManager::dump_stats(exclude, true);
 
     LogLabel("Summary Statistics");
     show_stats((PegCount*)&proc_stats, proc_names, array_size(proc_names)-1, "process");
+    ModuleManager::get_module("memory")->show_stats();
+    memory::MemoryCap::print(SnortConfig::log_verbose());
+
     s_ctrlcon = nullptr;
 }
 
@@ -264,7 +266,6 @@ void DropStats(ControlConn* ctrlcon)
 void PrintStatistics()
 {
     DropStats();
-    memory::MemoryCap::print(SnortConfig::log_verbose(), false);
     timing_stats();
 
     // FIXIT-L can do flag saving with RAII (much cleaner)