From: Russ Combs (rucombs) Date: Fri, 6 Jan 2023 16:43:50 +0000 (+0000) Subject: Pull request #3664: memory: use the process total instead of per thread totals to... X-Git-Tag: 3.1.51.0~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9ac428d8574f4ab8049668783f478ddb453db52f;p=thirdparty%2Fsnort3.git Pull request #3664: memory: use the process total instead of per thread totals to enforce cap Merge in SNORT/snort3 from ~RUCOMBS/snort3:process_memory to master Squashed commit of the following: commit 3d3da0fd75a73eb43fd4aa6b7e8e252b9c6ea1ee Author: Russ Combs Date: Wed Jan 4 08:31:40 2023 -0500 memory: rename manager to overloads to better indicate purpose commit e343738e2b178002b7e8f63f60cdbe7c512499db Author: Russ Combs Date: Wed Jan 4 06:37:34 2023 -0500 memory: update developer notes commit 7f374a318e87662c1d7766ffd237d65eb605f60f Author: Russ Combs Date: Wed Dec 28 09:01:20 2022 -0500 memory: update stats regardless of state; add unit tests commit 71822045d1ed62da660573d2c82a5566ba42967d Author: Russ Combs Date: Tue Dec 27 09:33:34 2022 -0500 memory: delete unnecessary includes commit cc19d105f6b08a7071978de0681fdf840413967e Author: Russ Combs Date: Thu Dec 22 16:02:14 2022 -0500 memory: refactor jemalloc code and add relevant pegs commit 7e30c6081c4fb0cac8c55658b50d5abfd14bc977 Author: Russ Combs Date: Wed Nov 23 13:51:30 2022 -0500 build: exclude unused memory related sources commit fc74bce73bd0db2b4fd67872615fd3f0dbf0a916 Author: Russ Combs Date: Wed Nov 23 12:21:38 2022 -0500 build: error out if both jemalloc and tcmalloc are configured commit 0663095ec3344f97cb80a9291bda6ed675edd469 Author: Russ Combs Date: Wed Nov 23 12:18:18 2022 -0500 memory: incorporate overloads into profiler commit 7824486ad5799116da4c825d991fc3d9d8e2738f Author: Russ Combs 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. --- diff --git a/cmake/configure_options.cmake b/cmake/configure_options.cmake index e86cbf862..f3673f399 100644 --- a/cmake/configure_options.cmake +++ b/cmake/configure_options.cmake @@ -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} ) diff --git a/cmake/create_options.cmake b/cmake/create_options.cmake index 17a7cc314..a3921482e 100644 --- a/cmake/create_options.cmake +++ b/cmake/create_options.cmake @@ -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 ) diff --git a/cmake/create_pkg_config.cmake b/cmake/create_pkg_config.cmake index dd2b532f8..d7a5667cd 100644 --- a/cmake/create_pkg_config.cmake +++ b/cmake/create_pkg_config.cmake @@ -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() diff --git a/configure_cmake.sh b/configure_cmake.sh index 6ca9bc9a8..fae15e418 100755 --- a/configure_cmake.sh +++ b/configure_cmake.sh @@ -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 diff --git a/src/flow/flow.cc b/src/flow/flow.cc index 33871e18a..f5e7a070b 100644 --- a/src/flow/flow.cc +++ b/src/flow/flow.cc @@ -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" diff --git a/src/flow/flow_cache.cc b/src/flow/flow_cache.cc index 75677b378..beee69ddb 100644 --- a/src/flow/flow_cache.cc +++ b/src/flow/flow_cache.cc @@ -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" diff --git a/src/flow/flow_data.cc b/src/flow/flow_data.cc index a1334a4c3..02584888b 100644 --- a/src/flow/flow_data.cc +++ b/src/flow/flow_data.cc @@ -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; diff --git a/src/flow/stash_item.h b/src/flow/stash_item.h index 879b091fd..aeecc01e5 100644 --- a/src/flow/stash_item.h +++ b/src/flow/stash_item.h @@ -24,8 +24,6 @@ #include #include -#include "memory/memory_cap.h" - #define STASH_APPID_DATA "appid_data" #define STASH_GENERIC_OBJECT_APPID 1 diff --git a/src/main/analyzer.cc b/src/main/analyzer.cc index 42ba696f4..eece6f9f7 100644 --- a/src/main/analyzer.cc +++ b/src/main/analyzer.cc @@ -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); diff --git a/src/main/snort.cc b/src/main/snort.cc index 5e774bfbe..3201e98d8 100644 --- a/src/main/snort.cc +++ b/src/main/snort.cc @@ -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(); diff --git a/src/main/test/distill_verdict_stubs.h b/src/main/test/distill_verdict_stubs.h index 4440c9047..5fc19fc85 100644 --- a/src/main/test/distill_verdict_stubs.h +++ b/src/main/test/distill_verdict_stubs.h @@ -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() { } + diff --git a/src/memory/CMakeLists.txt b/src/memory/CMakeLists.txt index 4d2c563c5..2b0f2751c 100644 --- a/src/memory/CMakeLists.txt +++ b/src/memory/CMakeLists.txt @@ -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) + diff --git a/src/memory/dev_notes.txt b/src/memory/dev_notes.txt index 7bb872eff..066992fdc 100644 --- a/src/memory/dev_notes.txt +++ b/src/memory/dev_notes.txt @@ -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 index 000000000..8fd5cb2c9 --- /dev/null +++ b/src/memory/heap_interface.cc @@ -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 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "heap_interface.h" + +#include + +#ifdef HAVE_JEMALLOC +#include +#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 + diff --git a/src/memory/prune_handler.cc b/src/memory/heap_interface.h similarity index 63% rename from src/memory/prune_handler.cc rename to src/memory/heap_interface.h index 4c454a4ff..966520248 100644 --- a/src/memory/prune_handler.cc +++ b/src/memory/heap_interface.h @@ -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 @@ -16,24 +16,34 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// prune_handler.cc author Joel Cornett +// heap_interface.cc author Russ Combs -#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 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 + diff --git a/src/memory/memory_cap.cc b/src/memory/memory_cap.cc index fd803321c..bc2f5dfb4 100644 --- a/src/memory/memory_cap.cc +++ b/src/memory/memory_cap.cc @@ -22,133 +22,136 @@ #include "config.h" #endif +#include "memory_cap.h" + #include #include +#include #include #include -#ifdef HAVE_JEMALLOC -#include -#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 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 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 over_limit { false }; +static std::atomic 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 -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); } } diff --git a/src/memory/memory_cap.h b/src/memory/memory_cap.h index 53fa5f4df..396988fd3 100644 --- a/src/memory/memory_cap.h +++ b/src/memory/memory_cap.h @@ -21,6 +21,8 @@ #ifndef MEMORY_CAP_H #define MEMORY_CAP_H +#include "memory/heap_interface.h" + #include #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 diff --git a/src/memory/memory_config.h b/src/memory/memory_config.h index 395178401..da9f8f47c 100644 --- a/src/memory/memory_config.h +++ b/src/memory/memory_config.h @@ -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; }; diff --git a/src/memory/memory_module.cc b/src/memory/memory_module.cc index cedabc5f4..76d5c689b 100644 --- a/src/memory/memory_module.cc +++ b/src/memory/memory_module.cc @@ -25,12 +25,15 @@ #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; } diff --git a/src/memory/memory_module.h b/src/memory/memory_module.h index b3116649f..3dc087f03 100644 --- a/src/memory/memory_module.h +++ b/src/memory/memory_module.h @@ -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 diff --git a/src/memory/memory_manager.cc b/src/memory/memory_overloads.cc similarity index 81% rename from src/memory/memory_manager.cc rename to src/memory/memory_overloads.cc index 81b357193..a240caa5b 100644 --- a/src/memory/memory_manager.cc +++ b/src/memory/memory_overloads.cc @@ -16,7 +16,7 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// memory_manager.cc author Joel Cornett +// memory_overloads.cc author Joel Cornett #ifdef HAVE_CONFIG_H #include "config.h" @@ -26,9 +26,9 @@ #include #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 +template struct Interface { static void* allocate(size_t); @@ -151,8 +151,8 @@ struct Interface static THREAD_LOCAL bool in_allocation_call; }; -template -void* Interface::allocate(size_t n) +template +void* Interface::allocate(size_t n) { // prevent allocation reentry ReentryContext reentry_context(in_allocation_call); @@ -163,12 +163,15 @@ void* Interface::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 -void Interface::deallocate(void* p) +template +void Interface::deallocate(void* p) { if ( !p ) return; @@ -176,12 +179,15 @@ void Interface::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 -THREAD_LOCAL bool Interface::in_allocation_call = false; +template +THREAD_LOCAL bool Interface::in_allocation_call = false; } //namespace memory @@ -191,7 +197,7 @@ THREAD_LOCAL bool Interface::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; + using Interface = memory::Interface; 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 index cb1343d66..000000000 --- a/src/memory/prune_handler.h +++ /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 - -#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 index 000000000..b1b2baf19 --- /dev/null +++ b/src/memory/test/CMakeLists.txt @@ -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 index 000000000..a1c1fc4b8 --- /dev/null +++ b/src/memory/test/memory_cap_test.cc @@ -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 + +#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 +#include + +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); +} + diff --git a/src/mime/file_mime_process.cc b/src/mime/file_mime_process.cc index 141b671fc..6832ef077 100644 --- a/src/mime/file_mime_process.cc +++ b/src/mime/file_mime_process.cc @@ -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" diff --git a/src/network_inspectors/appid/appid_http_session.cc b/src/network_inspectors/appid/appid_http_session.cc index a609f1f68..f18473ef4 100644 --- a/src/network_inspectors/appid/appid_http_session.cc +++ b/src/network_inspectors/appid/appid_http_session.cc @@ -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" diff --git a/src/network_inspectors/appid/test/appid_http_session_test.cc b/src/network_inspectors/appid/test/appid_http_session_test.cc index 0dbd4c34b..b9f8d28aa 100644 --- a/src/network_inspectors/appid/test/appid_http_session_test.cc +++ b/src/network_inspectors/appid/test/appid_http_session_test.cc @@ -27,7 +27,6 @@ #include #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" diff --git a/src/service_inspectors/dce_rpc/dce_smb1.cc b/src/service_inspectors/dce_rpc/dce_smb1.cc index 39855b518..eb22ee4c3 100644 --- a/src/service_inspectors/dce_rpc/dce_smb1.cc +++ b/src/service_inspectors/dce_rpc/dce_smb1.cc @@ -24,8 +24,6 @@ #include "dce_smb1.h" -#include "memory/memory_cap.h" - #include "dce_smb_utils.h" using namespace snort; diff --git a/src/service_inspectors/dce_rpc/dce_smb2.h b/src/service_inspectors/dce_rpc/dce_smb2.h index 231aba48a..6d2e77538 100644 --- a/src/service_inspectors/dce_rpc/dce_smb2.h +++ b/src/service_inspectors/dce_rpc/dce_smb2.h @@ -25,10 +25,10 @@ // This implements smb session data for SMBv2 // Also provides SMBv2 related header structures +#include + #include "main/thread_config.h" -#include "memory/memory_cap.h" #include "utils/util.h" -#include #include "dce_smb_common.h" diff --git a/src/service_inspectors/dce_rpc/dce_smb_common.cc b/src/service_inspectors/dce_rpc/dce_smb_common.cc index 3b9d08272..035b32e02 100644 --- a/src/service_inspectors/dce_rpc/dce_smb_common.cc +++ b/src/service_inspectors/dce_rpc/dce_smb_common.cc @@ -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" diff --git a/src/service_inspectors/dce_rpc/smb_message.cc b/src/service_inspectors/dce_rpc/smb_message.cc index 07f872a3f..686cf7798 100644 --- a/src/service_inspectors/dce_rpc/smb_message.cc +++ b/src/service_inspectors/dce_rpc/smb_message.cc @@ -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" diff --git a/src/service_inspectors/sip/sip.cc b/src/service_inspectors/sip/sip.cc index 47f0f0c9a..a3032f1c8 100644 --- a/src/service_inspectors/sip/sip.cc +++ b/src/service_inspectors/sip/sip.cc @@ -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" diff --git a/src/service_inspectors/sip/sip_parser.cc b/src/service_inspectors/sip/sip_parser.cc index a2d1475c0..f579771df 100644 --- a/src/service_inspectors/sip/sip_parser.cc +++ b/src/service_inspectors/sip/sip_parser.cc @@ -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" diff --git a/src/stream/file/file_session.cc b/src/stream/file/file_session.cc index a3651f2fd..6ccfdda85 100644 --- a/src/stream/file/file_session.cc +++ b/src/stream/file/file_session.cc @@ -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" diff --git a/src/stream/icmp/icmp_session.cc b/src/stream/icmp/icmp_session.cc index ca53a6628..7d29aa3d4 100644 --- a/src/stream/icmp/icmp_session.cc +++ b/src/stream/icmp/icmp_session.cc @@ -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" diff --git a/src/stream/ip/ip_defrag.cc b/src/stream/ip/ip_defrag.cc index c725e9d25..4f017311b 100644 --- a/src/stream/ip/ip_defrag.cc +++ b/src/stream/ip/ip_defrag.cc @@ -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" diff --git a/src/stream/ip/ip_session.cc b/src/stream/ip/ip_session.cc index e51534491..d5d1fa684 100644 --- a/src/stream/ip/ip_session.cc +++ b/src/stream/ip/ip_session.cc @@ -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" diff --git a/src/stream/tcp/tcp_reassembler.cc b/src/stream/tcp/tcp_reassembler.cc index 6b865d57e..86637734c 100644 --- a/src/stream/tcp/tcp_reassembler.cc +++ b/src/stream/tcp/tcp_reassembler.cc @@ -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" diff --git a/src/stream/tcp/tcp_segment_node.cc b/src/stream/tcp/tcp_segment_node.cc index 759de12ff..7ad51e369 100644 --- a/src/stream/tcp/tcp_segment_node.cc +++ b/src/stream/tcp/tcp_segment_node.cc @@ -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" diff --git a/src/stream/tcp/tcp_session.cc b/src/stream/tcp/tcp_session.cc index 2fb4f2699..564874526 100644 --- a/src/stream/tcp/tcp_session.cc +++ b/src/stream/tcp/tcp_session.cc @@ -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" diff --git a/src/stream/tcp/tcp_stream_tracker.cc b/src/stream/tcp/tcp_stream_tracker.cc index e37435b13..9d8fb205c 100644 --- a/src/stream/tcp/tcp_stream_tracker.cc +++ b/src/stream/tcp/tcp_stream_tracker.cc @@ -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" diff --git a/src/stream/udp/udp_session.cc b/src/stream/udp/udp_session.cc index c42af4c77..144823cd0 100644 --- a/src/stream/udp/udp_session.cc +++ b/src/stream/udp/udp_session.cc @@ -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" diff --git a/src/stream/user/user_session.cc b/src/stream/user/user_session.cc index dcaa5b25c..57f4d7cfb 100644 --- a/src/stream/user/user_session.cc +++ b/src/stream/user/user_session.cc @@ -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" diff --git a/src/utils/stats.cc b/src/utils/stats.cc index 0f3147f96..29a951208 100644 --- a/src/utils/stats.cc +++ b/src/utils/stats.cc @@ -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)