From: Akhilesh MY (amuttuva) Date: Wed, 20 Dec 2023 08:33:26 +0000 (+0000) Subject: Pull request #4112: profiler: dump memory profiler stats at frequent interval X-Git-Tag: 3.1.77.0~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc9b3a9cfdc3eabb6eda459f121ecfc154319b76;p=thirdparty%2Fsnort3.git Pull request #4112: profiler: dump memory profiler stats at frequent interval Merge in SNORT/snort3 from ~AMUTTUVA/snort3:mem_prof_master to master Squashed commit of the following: commit 8f5b8f6f3fcbfe60a28429ec41266cd88a2bf2c9 Author: sunimukh Date: Wed Jun 28 06:45:54 2023 +0000 profiler: dump memory profiler stats at frequent interval --- diff --git a/cmake/create_options.cmake b/cmake/create_options.cmake index a3921482e..f3c64b5cb 100644 --- a/cmake/create_options.cmake +++ b/cmake/create_options.cmake @@ -42,7 +42,7 @@ 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_PROFILER "Enable memory profiler (developers only)" OFF ) +option ( ENABLE_MEMORY_PROFILER "Enable memory profiler" OFF ) option ( ENABLE_RULE_PROFILER "Enable rule keyword profiler (developers only)" OFF ) option ( ENABLE_ADDRESS_SANITIZER "enable address sanitizer support" OFF ) option ( ENABLE_THREAD_SANITIZER "enable thread sanitizer support" OFF ) diff --git a/src/main/snort.cc b/src/main/snort.cc index 1874debd9..70f1c94ed 100644 --- a/src/main/snort.cc +++ b/src/main/snort.cc @@ -306,7 +306,6 @@ void Snort::term() * double-freeing any memory. Not guaranteed to be * thread-safe, but it will prevent the simple cases. */ - static bool already_exiting = false; if ( already_exiting ) return; already_exiting = true; @@ -376,6 +375,7 @@ void Snort::clean_exit(int) bool Snort::reloading = false; bool Snort::privileges_dropped = false; +bool Snort::already_exiting = false; bool Snort::is_reloading() { return reloading; } diff --git a/src/main/snort.h b/src/main/snort.h index 9390eb1c8..5cb19a403 100644 --- a/src/main/snort.h +++ b/src/main/snort.h @@ -48,6 +48,7 @@ public: static bool has_dropped_privileges(); SO_PUBLIC static bool is_reloading(); + inline SO_PUBLIC static bool is_exiting() { return already_exiting; } private: static void init(int, char**); @@ -59,6 +60,7 @@ private: static bool initializing; static bool reloading; static bool privileges_dropped; + static bool already_exiting; }; // RAII-style mechanism for removal and reinstallation of Snort's crash handler diff --git a/src/main/thread.cc b/src/main/thread.cc index db1f00b3b..735fb3295 100644 --- a/src/main/thread.cc +++ b/src/main/thread.cc @@ -35,7 +35,7 @@ //------------------------------------------------------------------------- static THREAD_LOCAL uint16_t run_num = 0; -static THREAD_LOCAL unsigned instance_id = 0; +THREAD_LOCAL unsigned instance_id = 0; static THREAD_LOCAL SThreadType thread_type = STHREAD_TYPE_OTHER; void set_run_num(uint16_t num) diff --git a/src/memory/CMakeLists.txt b/src/memory/CMakeLists.txt index 2b0f2751c..13b75023a 100644 --- a/src/memory/CMakeLists.txt +++ b/src/memory/CMakeLists.txt @@ -11,10 +11,10 @@ set ( MEMORY_SOURCES memory_module.cc memory_module.h memory_overloads.cc + memory_overloads.h ) set ( ALLOC_SOURCES - memory_allocator.cc memory_allocator.h ) diff --git a/src/memory/memory_allocator.h b/src/memory/memory_allocator.h index e73d5fd44..fc07e8359 100644 --- a/src/memory/memory_allocator.h +++ b/src/memory/memory_allocator.h @@ -32,6 +32,12 @@ struct MemoryAllocator static void deallocate(void*); }; +inline void* MemoryAllocator::allocate(size_t n) +{ return malloc(n); } + +inline void MemoryAllocator::deallocate(void* p) +{ free(p); } + } // namespace memory #endif diff --git a/src/memory/memory_overloads.cc b/src/memory/memory_overloads.cc index 620c9b70e..213dae2f2 100644 --- a/src/memory/memory_overloads.cc +++ b/src/memory/memory_overloads.cc @@ -18,13 +18,15 @@ // memory_overloads.cc author Joel Cornett +#include "memory_overloads.h" + #ifdef HAVE_CONFIG_H #include "config.h" #endif #include -#include +#include "main/snort.h" #include "main/thread.h" #include "profiler/memory_profiler_active_context.h" @@ -34,6 +36,8 @@ #include "catch/snort_catch.h" #endif +extern THREAD_LOCAL unsigned instance_id; + namespace memory { @@ -52,6 +56,9 @@ struct alignas(max_align_t) Metadata // number of requested bytes size_t payload_size; + uint32_t thread_id; + // stat used to keep track of allocation/deallocation + MemoryTracker* mp_inspector_stats = nullptr; // total number of bytes allocated, including Metadata header size_t total_size() const; @@ -82,7 +89,8 @@ inline Metadata::Metadata(size_t n) : #if defined(REG_TEST) || defined(UNIT_TEST) sanity(SANITY_CHECK_VALUE), #endif - payload_size(n) + payload_size(n), thread_id(instance_id), + mp_inspector_stats(&mp_active_context.get_default()) { } inline size_t Metadata::calculate_total_size(size_t n) @@ -109,8 +117,6 @@ Metadata* Metadata::create(size_t n) Metadata* Metadata::extract(void* p) { - assert(p); - auto meta = static_cast(p) - 1; #if defined(REG_TEST) || defined(UNIT_TEST) @@ -124,6 +130,7 @@ Metadata* Metadata::extract(void* p) // the meat // ----------------------------------------------------------------------------- +#ifdef REG_TEST class ReentryContext { public: @@ -141,6 +148,7 @@ private: const bool already_entered; bool& flag; }; +#endif template struct Interface @@ -154,10 +162,11 @@ struct Interface template void* Interface::allocate(size_t n) { +#ifdef REG_TEST // prevent allocation reentry ReentryContext reentry_context(in_allocation_call); assert(!reentry_context.is_reentry()); - +#endif auto meta = Metadata::create(n); if ( !meta ) @@ -180,7 +189,13 @@ void Interface::deallocate(void* p) assert(meta); #ifdef ENABLE_MEMORY_PROFILER - mp_active_context.update_deallocs(meta->total_size()); + if (!snort::Snort::is_exiting()) + { + if (meta->mp_inspector_stats and meta->thread_id == instance_id) + meta->mp_inspector_stats->update_deallocs(meta->total_size()); + else + mp_active_context.update_deallocs(meta->total_size()); + } #endif Allocator::deallocate(meta); @@ -233,6 +248,13 @@ void operator delete(void* p, size_t) noexcept void operator delete[](void* p, size_t) noexcept { ::operator delete[](p); } + +void operator delete[](void* p, size_t, const std::nothrow_t&) noexcept +{ ::operator delete[](p); } + +void operator delete(void* p, size_t, const std::nothrow_t&) noexcept +{ ::operator delete(p); } + #endif // ----------------------------------------------------------------------------- diff --git a/src/memory/memory_overloads.h b/src/memory/memory_overloads.h new file mode 100644 index 000000000..acd3e5ff9 --- /dev/null +++ b/src/memory/memory_overloads.h @@ -0,0 +1,46 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-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_overloads.h author Sunirmal Mukherjee + +#ifndef MEMORY_OVERLOADS_H +#define MEMORY_OVERLOADS_H + +#include + +// ----------------------------------------------------------------------------- +// new /delete replacements +// ----------------------------------------------------------------------------- + +// these don't have to be visible to operate as replacements + + +void* operator new(std::size_t); +void* operator new[](std::size_t); +void* operator new(std::size_t, const std::nothrow_t&) noexcept; +void* operator new[](std::size_t, const std::nothrow_t&) noexcept; +void operator delete(void*) noexcept; +void operator delete[](void*) noexcept; +void operator delete(void*, const std::nothrow_t&) noexcept; +void operator delete[](void*, const std::nothrow_t&) noexcept; +void operator delete(void*, std::size_t) noexcept; +void operator delete[](void*, std::size_t) noexcept; +void operator delete[](void*, std::size_t, const std::nothrow_t&) noexcept; +void operator delete(void*, std::size_t, const std::nothrow_t&) noexcept; + +#endif diff --git a/src/memory/test/memory_cap_test.cc b/src/memory/test/memory_cap_test.cc index d89274689..afacfef3f 100644 --- a/src/memory/test/memory_cap_test.cc +++ b/src/memory/test/memory_cap_test.cc @@ -26,6 +26,7 @@ #include "log/messages.h" #include "main/thread.h" +#include "profiler/profiler.h" #include "time/periodic.h" #include "trace/trace_api.h" @@ -64,6 +65,8 @@ THREAD_LOCAL const Trace* memory_trace = nullptr; void Periodic::register_handler(PeriodicHook, void*, uint16_t, uint32_t) { } +void Profiler::register_module(const char*, const char*, snort::Module*) { } + //-------------------------------------------------------------------------- // mocks //-------------------------------------------------------------------------- diff --git a/src/network_inspectors/perf_monitor/perf_monitor.cc b/src/network_inspectors/perf_monitor/perf_monitor.cc index ec66a3ca7..9a48d33e9 100644 --- a/src/network_inspectors/perf_monitor/perf_monitor.cc +++ b/src/network_inspectors/perf_monitor/perf_monitor.cc @@ -350,6 +350,9 @@ void PerfMonitor::eval(Packet* p) { if (ready_to_process(p)) { +#ifdef ENABLE_MEMORY_PROFILER + Profiler::show_runtime_memory_stats(); +#endif for (unsigned i = 0; i < trackers->size(); i++) { (*trackers)[i]->process(false); diff --git a/src/profiler/memory_context.cc b/src/profiler/memory_context.cc index 29aa15bc1..612861f5c 100644 --- a/src/profiler/memory_context.cc +++ b/src/profiler/memory_context.cc @@ -38,7 +38,7 @@ using namespace snort; // ----------------------------------------------------------------------------- THREAD_LOCAL MemoryActiveContext mp_active_context; -static CombinedMemoryStats s_fallthrough_stats; +static MemoryStats s_fallthrough_stats; // ----------------------------------------------------------------------------- @@ -46,7 +46,7 @@ static CombinedMemoryStats s_fallthrough_stats; // ----------------------------------------------------------------------------- // get thread local default stats -const CombinedMemoryStats& MemoryProfiler::get_fallthrough_stats() +const MemoryStats& MemoryProfiler::get_fallthrough_stats() { return s_fallthrough_stats; } // thread local call diff --git a/src/profiler/memory_context.h b/src/profiler/memory_context.h index 18e4e177c..98458fdd2 100644 --- a/src/profiler/memory_context.h +++ b/src/profiler/memory_context.h @@ -21,13 +21,13 @@ #ifndef MEMORY_CONTEXT_H #define MEMORY_CONTEXT_H -struct CombinedMemoryStats; +struct MemoryStats; class MemoryProfiler { public: // global accumulated stats - static const CombinedMemoryStats& get_fallthrough_stats(); + static const MemoryStats& get_fallthrough_stats(); // thread local call static void consolidate_fallthrough_stats(); diff --git a/src/profiler/memory_defs.h b/src/profiler/memory_defs.h index d844aebe7..93a5dca08 100644 --- a/src/profiler/memory_defs.h +++ b/src/profiler/memory_defs.h @@ -91,62 +91,9 @@ inline MemoryStats& MemoryStats::operator+=(const MemoryStats& rhs) return *this; } -struct CombinedMemoryStats -{ - MemoryStats startup; - MemoryStats runtime; - - void update_allocs(size_t); - void update_deallocs(size_t); - - void reset(); - - operator bool() const - { return startup || runtime; } - - bool operator==(const CombinedMemoryStats&) const; - bool operator!=(const CombinedMemoryStats& rhs) const - { return !(*this == rhs); } - - CombinedMemoryStats& operator+=(const CombinedMemoryStats&); -}; - -inline void CombinedMemoryStats::update_allocs(size_t n) -{ - if ( snort::is_packet_thread() ) - runtime.update_allocs(n); - else - startup.update_allocs(n); -} - -inline void CombinedMemoryStats::update_deallocs(size_t n) -{ - if ( snort::is_packet_thread() ) - runtime.update_deallocs(n); - else - startup.update_deallocs(n); -} - -inline void CombinedMemoryStats::reset() -{ - startup.reset(); - runtime.reset(); -} - -inline bool CombinedMemoryStats::operator==(const CombinedMemoryStats& rhs) const -{ return startup == rhs.startup && runtime == rhs.runtime; } - -inline CombinedMemoryStats& CombinedMemoryStats::operator+=(const CombinedMemoryStats& rhs) -{ - startup += rhs.startup; - runtime += rhs.runtime; - - return *this; -} - struct MemoryTracker { - CombinedMemoryStats stats; + MemoryStats stats; void reset() { stats.reset(); } @@ -158,7 +105,7 @@ struct MemoryTracker { stats.update_deallocs(n); } constexpr MemoryTracker() = default; - constexpr MemoryTracker(const CombinedMemoryStats &stats) : stats(stats) { } + constexpr MemoryTracker(const MemoryStats &stats) : stats(stats) { } }; #endif diff --git a/src/profiler/memory_profiler.cc b/src/profiler/memory_profiler.cc index 91203669f..1b140bade 100644 --- a/src/profiler/memory_profiler.cc +++ b/src/profiler/memory_profiler.cc @@ -104,7 +104,7 @@ struct View { return !(*this == rhs); } View(const ProfilerNode& node, View* parent = nullptr) : - name(node.name), stats(new MemoryStats(node.get_stats().memory.stats.runtime)) + name(node.name), stats(new MemoryStats(node.get_stats().memory.stats)) { if ( parent ) { @@ -135,7 +135,7 @@ static const ProfilerSorter sorters[] = }; static bool include_fn(const ProfilerNode& node) -{ return node.get_stats().memory.stats.runtime; } +{ return node.get_stats().memory.stats; } static void print_fn(StatsTable& t, const View& v) { @@ -277,7 +277,7 @@ TEST_CASE( "memory profiler view", "[profiler][memory_profiler]" ) ProfileStats the_stats; ProfilerNode node("foo"); - the_stats.memory.stats.runtime = { 1, 2, 100, 4 }; + the_stats.memory.stats = { 1, 2, 100, 4 }; node.set_stats(the_stats); SECTION( "no parent" ) @@ -286,7 +286,7 @@ TEST_CASE( "memory profiler view", "[profiler][memory_profiler]" ) SECTION( "ctor sets members" ) { - CHECK( *view.stats == the_stats.memory.stats.runtime ); + CHECK( *view.stats == the_stats.memory.stats ); CHECK( view.caller_stats == nullptr ); } diff --git a/src/profiler/memory_profiler_defs.h b/src/profiler/memory_profiler_defs.h index 121025254..57561c653 100644 --- a/src/profiler/memory_profiler_defs.h +++ b/src/profiler/memory_profiler_defs.h @@ -38,6 +38,7 @@ struct MemoryProfilerConfig bool show = false; unsigned count = 0; int max_depth = -1; + uint64_t dump_file_size = 0; }; namespace snort diff --git a/src/profiler/profiler.cc b/src/profiler/profiler.cc index e43cc6438..46cbcaa62 100644 --- a/src/profiler/profiler.cc +++ b/src/profiler/profiler.cc @@ -169,6 +169,11 @@ void Profiler::show_stats() show_rule_profiler_stats(config->rule); } +void Profiler::show_runtime_memory_stats() +{ + s_profiler_nodes.print_runtime_memory_stats(); +} + #ifdef UNIT_TEST TEST_CASE( "profile stats", "[profiler]" ) @@ -186,11 +191,7 @@ TEST_CASE( "profile stats", "[profiler]" ) SECTION( "il" ) { TimeProfilerStats time_stats = { 12_ticks, 2 }; - MemoryTracker memory_stats = - {{ - { 1, 2, 3, 4 }, - { 5, 6, 7, 8 } - }}; + MemoryTracker memory_stats = {{ 1, 2, 3, 4 }}; ProfileStats stats(time_stats, memory_stats); @@ -203,10 +204,7 @@ TEST_CASE( "profile stats", "[profiler]" ) { ProfileStats stats { { 1_ticks, 2 }, - {{ - { 1, 2, 3, 4 }, - { 5, 6, 7, 8 }, - }} + {{ 1, 2, 3, 4 }} }; SECTION( "reset" ) @@ -219,10 +217,7 @@ TEST_CASE( "profile stats", "[profiler]" ) ProfileStats other_stats { { 12_ticks, 12 }, - {{ - { 5, 6, 7, 8 }, - { 9, 10, 11, 12 } - }} + {{ 5, 6, 7, 8 }} }; SECTION( "==/!=" ) @@ -236,10 +231,7 @@ TEST_CASE( "profile stats", "[profiler]" ) { stats += other_stats; CHECK( stats.time == TimeProfilerStats(13_ticks, 14) ); - CombinedMemoryStats memory_result = { - { 6, 8, 10, 12 }, - { 14, 16, 18, 20 } - }; + MemoryStats memory_result = { 6, 8, 10, 12 }; CHECK( stats.memory.stats == memory_result ); } diff --git a/src/profiler/profiler.h b/src/profiler/profiler.h index 5a9fc2f15..eb0cdc004 100644 --- a/src/profiler/profiler.h +++ b/src/profiler/profiler.h @@ -45,6 +45,7 @@ public: static void prepare_stats(); static void show_stats(); static ProfilerNodeMap& get_profiler_nodes(); + SO_PUBLIC static void show_runtime_memory_stats(); }; extern THREAD_LOCAL snort::ProfileStats totalPerfStats; diff --git a/src/profiler/profiler_module.cc b/src/profiler/profiler_module.cc index ec34c1281..abe525fad 100644 --- a/src/profiler/profiler_module.cc +++ b/src/profiler/profiler_module.cc @@ -417,6 +417,9 @@ static const Parameter profiler_memory_params[] = { "max_depth", Parameter::PT_INT, "-1:255", "-1", "limit depth to max_depth (-1 = no limit)" }, + { "dump_file_size", Parameter::PT_INT, "4096:max53", "1073741824", + "files will be rolled over if they exceed this size" }, + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } }; @@ -489,6 +492,14 @@ static bool s_profiler_module_set_max_depth(T& config, Value& v) static bool s_profiler_module_set_max_depth(RuleProfilerConfig&, Value&) { return false; } +template +static bool s_profiler_module_set_dump_file_size(T&, Value&) +{ return false; } + +// cppcheck-suppress constParameter +static bool s_profiler_module_set_dump_file_size(MemoryProfilerConfig& config, Value& v) +{ config.dump_file_size = v.get_int64(); return true; } + template static bool s_profiler_module_set(T& config, Value& v) { @@ -504,6 +515,9 @@ static bool s_profiler_module_set(T& config, Value& v) else if ( v.is("max_depth") ) return s_profiler_module_set_max_depth(config, v); + else if ( v.is("dump_file_size") ) + return s_profiler_module_set_dump_file_size(config, v); + else return false; diff --git a/src/profiler/profiler_nodes.cc b/src/profiler/profiler_nodes.cc index f1392fa9c..1fca935d5 100644 --- a/src/profiler/profiler_nodes.cc +++ b/src/profiler/profiler_nodes.cc @@ -25,11 +25,21 @@ #include "profiler_nodes.h" #include +#include #include +#include +#include +#include #include "framework/module.h" +#include "main/snort_config.h" +#include "memory_profiler_active_context.h" #include "profiler_defs.h" +#include "log/messages.h" +#include "time/packet_time.h" +#include "utils/util.h" +#include "utils/util_cstring.h" #ifdef UNIT_TEST #include "catch/snort_catch.h" @@ -37,6 +47,9 @@ using namespace snort; +static std::string tracker_fname = "mem_profile_stats.csv"; +static THREAD_LOCAL FILE* tracker_fd = nullptr; + // ----------------------------------------------------------------------------- // types // ----------------------------------------------------------------------------- @@ -150,6 +163,419 @@ void ProfilerNode::reset(ProfilerType type) } } +void ProfilerNodeMap::write_header() +{ + fprintf(tracker_fd, "#timestamp,"); + + for ( auto it = nodes.begin(); it != nodes.end(); ++it ) + { + const char* ins_name = it->second.name.c_str(); + fprintf(tracker_fd, "%s.bytes_allocated,%s.bytes_deallocated," + "%s.allocation_count,%s.deallocation_count,", ins_name, + ins_name, ins_name, ins_name); + } + + fprintf(tracker_fd, "global.bytes_allocated,global.bytes_deallocated," + "global.allocation_count,global.deallocation_count\n"); + + fflush(tracker_fd); +} + +static void inline write_memtracking_info(const MemoryStats& stats, FILE* fd) +{ + fprintf(fd, "%" PRIu64 ",", (uint64_t)(stats.allocated)); + fprintf(fd, "%" PRIu64 ",", (uint64_t)(stats.deallocated)); + fprintf(fd, "%" PRIu64 ",", (uint64_t)(stats.allocs)); + fprintf(fd, "%" PRIu64 ",", (uint64_t)(stats.deallocs)); +} + +void ProfilerNode::get_local_memory_stats(FILE* fd) +{ + if (is_set()) + { + const auto* local_stats = (*getter)(); + + if (!local_stats) + return; + + write_memtracking_info(local_stats->memory.stats, fd); + } +} + +bool ProfilerNodeMap::open(std::string& fname, uint64_t max_file_size, bool append) +{ + if (fname.length()) + { + struct stat pt; + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + const char* file_name = fname.c_str(); + bool existed = false; + + tracker_fd = fopen(file_name, "r"); + if (tracker_fd) + { + // Check file before change permission + if (fstat(fileno(tracker_fd), &pt) == 0) + + { + existed = true; + + // Only change permission for file owned by root + if ((0 == pt.st_uid) || (0 == pt.st_gid)) + { + if (fchmod(fileno(tracker_fd), mode) != 0) + { + WarningMessage("Profiler: Unable to change mode of " + "stats file '%s' to mode:%u: %s.\n", + file_name, mode, get_error(errno)); + } + + const SnortConfig* sc = SnortConfig::get_conf(); + + if (fchown(fileno(tracker_fd), sc->get_uid(), sc->get_gid()) != 0) + { + WarningMessage("Profiler: Unable to change permissions of " + "stats file '%s' to user:%d and group:%d: %s.\n", + file_name, sc->get_uid(), sc->get_gid(), get_error(errno)); + } + } + } + + fclose(tracker_fd); + tracker_fd = nullptr; + } + + // This file needs to be readable by everyone + mode_t old_umask = umask(022); + // Append to the existing file if just starting up, otherwise we've + // rotated so start a new one. + tracker_fd = fopen(file_name, append ? "a" : "w"); + umask(old_umask); + + if (!tracker_fd) + { + ErrorMessage("Profiler: Cannot open stats file '%s'.\n", file_name); + return false; + } + + // FIXIT-L refactor rotation so it doesn't require an open file handle + if (existed and append) + return rotate(fname, max_file_size); + } + + return true; +} + +// FIXIT-M combine with fileRotate +// FIXIT-M refactor file naming foo to use std::string +static bool rotate_file(const char* old_file, FILE* old_fd, + uint32_t max_file_size) +{ + time_t ts; + char rotate_file[PATH_MAX]; + struct stat fstats; + FILE* rotate_fh; + + if (!old_file) + return false; + + if (!old_fd) + { + ErrorMessage("Profiler: Memtracker stats file \"%s\" " + "isn't open.\n", old_file); + return false; + } + + // Mostly file mode is "a", so can't use rewind() or fseek(). Had to close and reopen. + fclose(old_fd); + old_fd = fopen(old_file, "r"); + + // Fetching the first timestamp from the file and renaming it by appending the time + int line = 0; + int holder; + + while((holder=fgetc(old_fd)) != EOF) + { + if (holder == '\n') + line++; + if (line == 1) + break; + } + + // Use the current time if 2nd line is not there + if (holder == EOF) + ts = time(nullptr); + else + { + char line_str[15]; + if (!fgets(line_str, 15, old_fd)) + ts = time(nullptr); + else + { + char* p = strtok(line_str, ","); + ts = atoll(p); + } + } + + fclose(old_fd); + old_fd = nullptr; + + // Create rotate file name based on path, optional prefix and date + // Need to be mindful that we get 64-bit times on OSX + SnortSnprintf(rotate_file, PATH_MAX, "%s_" STDu64, old_file, (uint64_t)ts); + + // If the rotate file doesn't exist, just rename the old one to the new one + rotate_fh = fopen(rotate_file, "r"); + if (rotate_fh == NULL) + { + if (rename(old_file, rotate_file) != 0) + { + ErrorMessage("Profiler: Could not rename Memtracker stats " + "file from \"%s\" to \"%s\": %s.\n", + old_file, rotate_file, get_error(errno)); + } + } + else // Otherwise, if it does exist, append data from current stats file to it + { + char read_buf[4096]; + size_t num_read, num_wrote; + int rotate_index = 0; + char rotate_file_with_index[PATH_MAX]; + + // This file needs to be readable by everyone + mode_t old_umask = umask(022); + + fclose(rotate_fh); + rotate_fh = nullptr; + + do + { + do + { + rotate_index++; + + // Check to see if there are any files already rotated and indexed + SnortSnprintf(rotate_file_with_index, PATH_MAX, "%s.%02d", + rotate_file, rotate_index); + } + while (stat(rotate_file_with_index, &fstats) == 0); + + // Subtract one to append to last existing file + rotate_index--; + + if (rotate_index == 0) + { + rotate_file_with_index[0] = 0; + rotate_fh = fopen(rotate_file, "a"); + } + else + { + SnortSnprintf(rotate_file_with_index, PATH_MAX, "%s.%02d", + rotate_file, rotate_index); + rotate_fh = fopen(rotate_file_with_index, "a"); + } + + if (!rotate_fh) + { + ErrorMessage("Profiler: Could not open Memtracker stats " + "archive file \"%s\" for appending: %s.\n", + *rotate_file_with_index ? rotate_file_with_index : rotate_file, + get_error(errno)); + break; + } + + old_fd = fopen(old_file, "r"); + if (!old_fd) + { + ErrorMessage("Profiler: Could not open Memtracker stats file " + "\"%s\" for reading to copy to archive \"%s\": %s.\n", + old_file, (*rotate_file_with_index ? rotate_file_with_index : + rotate_file), get_error(errno)); + break; + } + + while (!feof(old_fd)) + { + // This includes the newline from the file. + if (!fgets(read_buf, sizeof(read_buf), old_fd)) + { + if (feof(old_fd)) + break; + + if (ferror(old_fd)) + { + // A read error occurred + ErrorMessage("Profiler: Error reading Memtracker stats " + "file \"%s\": %s.\n", old_file, get_error(errno)); + break; + } + } + + num_read = strlen(read_buf); + + if (num_read > 0) + { + int rotate_fd = fileno(rotate_fh); + + if (fstat(rotate_fd, &fstats) != 0) + { + ErrorMessage("Profiler: Error getting file " + "information for \"%s\": %s.\n", + *rotate_file_with_index ? rotate_file_with_index : rotate_file, + get_error(errno)); + break; + } + + if (((uint32_t)fstats.st_size + num_read) > max_file_size) + { + fclose(rotate_fh); + + rotate_index++; + + // Create new file same as before but with an index added to the end + SnortSnprintf(rotate_file_with_index, PATH_MAX, "%s.%02d", + rotate_file, rotate_index); + + rotate_fh = fopen(rotate_file_with_index, "a"); + if (!rotate_fh) + { + ErrorMessage("Profiler: Could not open Memtracker " + "stats archive file \"%s\" for writing: %s.\n", + rotate_file_with_index, get_error(errno)); + break; + } + } + + num_wrote = fprintf(rotate_fh, "%s", read_buf); + if ((num_wrote != num_read) && ferror(rotate_fh)) + { + // A bad write occurred + ErrorMessage("Profiler: Error writing to Memtracker " + "stats archive file \"%s\": %s.\n", rotate_file, get_error(errno)); + break; + } + + fflush(rotate_fh); + } + } + } + while (false); + + if (rotate_fh) + fclose(rotate_fh); + + if (old_fd) + fclose(old_fd); + + umask(old_umask); + } + + return true; +} + +bool ProfilerNodeMap::rotate(std::string& fname, uint64_t max_file_size) +{ + if (tracker_fd) + { + if (!rotate_file(fname.c_str(), tracker_fd, max_file_size)) + return false; + + return open(fname, max_file_size, false); + } + + return false; +} + +static inline bool check_file_size(FILE* fh, uint64_t max_file_size) +{ + int fd; + struct stat fstats; + + if (!fh) + return false; + + fd = fileno(fh); + if ((fstat(fd, &fstats) == 0) + and ((uint64_t)fstats.st_size >= max_file_size)) + { + return true; + } + + return false; +} + +inline void ProfilerNodeMap::create_new_file(std::string& fname, uint64_t max_file_size) +{ + open(fname, max_file_size, true); + write_header(); +} + +void ProfilerNodeMap::auto_rotate(std::string& fname, uint64_t max_file_size) +{ + const char* file_name = fname.c_str(); + + if (tracker_fd) + { + // If file is deleted, will close the existing fd and reopen the file + if (access(file_name, F_OK) != 0) + { + fclose(tracker_fd); + tracker_fd = nullptr; + create_new_file(fname, max_file_size); + } + // If file size exceeds max size, will rotate the file and open a new one. + else if (check_file_size(tracker_fd, max_file_size)) + { + rotate(fname, max_file_size); + write_header(); + } + } + else + { + // If after restart file exists, will append to the existing file. + FILE* fd = fopen(file_name, "r"); + if (fd) + { + if (check_file_size(fd, max_file_size)) + { + fclose(fd); + tracker_fd = fopen(file_name, "a"); + return; + } + + fclose(fd); + } + + create_new_file(fname, max_file_size); + } + +} + +void ProfilerNodeMap::print_runtime_memory_stats() +{ + const auto* config = SnortConfig::get_conf()->get_profiler(); + if (!config->memory.show) + return; + + std::string fname; + get_instance_file(fname, tracker_fname.c_str()); + + auto_rotate(fname, config->memory.dump_file_size); + + timeval cur_time; + packet_gettimeofday(&cur_time); + + fprintf(tracker_fd, "%" PRIu64 ",", (uint64_t)cur_time.tv_sec); + + for ( auto it = nodes.begin(); it != nodes.end(); ++it ) + it->second.get_local_memory_stats(tracker_fd); + + write_memtracking_info(mp_active_context.get_fallback().stats, tracker_fd); + + fputs("\n", tracker_fd); + fflush(tracker_fd); +} + void ProfilerNodeMap::register_node(const std::string &n, const char* pn, Module* m) { setup_node(get_node(n), get_node(pn ? pn : ROOT_NODE), m); } diff --git a/src/profiler/profiler_nodes.h b/src/profiler/profiler_nodes.h index 44618ac87..f7687f7f6 100644 --- a/src/profiler/profiler_nodes.h +++ b/src/profiler/profiler_nodes.h @@ -65,6 +65,8 @@ public: const std::string name; + void get_local_memory_stats(FILE*); + private: std::vector children; std::shared_ptr getter; @@ -95,6 +97,14 @@ public: void clear_flex(); void reset_nodes(snort::ProfilerType = snort::PROFILER_TYPE_BOTH); + void print_runtime_memory_stats(); + + inline void create_new_file(std::string&, uint64_t); + void auto_rotate(std::string&, uint64_t); + bool rotate(std::string&, uint64_t); + bool open(std::string&, uint64_t, bool); + void write_header(); + const ProfilerNode& get_root(); private: diff --git a/src/service_inspectors/http_inspect/ips_http_test.cc b/src/service_inspectors/http_inspect/ips_http_test.cc index 27238063e..83e26b595 100644 --- a/src/service_inspectors/http_inspect/ips_http_test.cc +++ b/src/service_inspectors/http_inspect/ips_http_test.cc @@ -115,6 +115,12 @@ bool HttpTestIpsOption::operator==(const IpsOption& ips) const static int64_t get_decimal_num(enum NumericValue& is_numeric, const uint8_t* start, int32_t length) { + if (!length) + { + is_numeric = NV_FALSE; + return -1; + } + int64_t total = 0; int32_t k = 0; do