]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4112: profiler: dump memory profiler stats at frequent interval
authorAkhilesh MY (amuttuva) <amuttuva@cisco.com>
Wed, 20 Dec 2023 08:33:26 +0000 (08:33 +0000)
committerShanmugam S (shanms) <shanms@cisco.com>
Wed, 20 Dec 2023 08:33:26 +0000 (08:33 +0000)
Merge in SNORT/snort3 from ~AMUTTUVA/snort3:mem_prof_master to master

Squashed commit of the following:

commit 8f5b8f6f3fcbfe60a28429ec41266cd88a2bf2c9
Author: sunimukh <sunimukh@cisco.com>
Date:   Wed Jun 28 06:45:54 2023 +0000

    profiler: dump memory profiler stats at frequent interval

21 files changed:
cmake/create_options.cmake
src/main/snort.cc
src/main/snort.h
src/main/thread.cc
src/memory/CMakeLists.txt
src/memory/memory_allocator.h
src/memory/memory_overloads.cc
src/memory/memory_overloads.h [new file with mode: 0644]
src/memory/test/memory_cap_test.cc
src/network_inspectors/perf_monitor/perf_monitor.cc
src/profiler/memory_context.cc
src/profiler/memory_context.h
src/profiler/memory_defs.h
src/profiler/memory_profiler.cc
src/profiler/memory_profiler_defs.h
src/profiler/profiler.cc
src/profiler/profiler.h
src/profiler/profiler_module.cc
src/profiler/profiler_nodes.cc
src/profiler/profiler_nodes.h
src/service_inspectors/http_inspect/ips_http_test.cc

index a3921482ee24254ecdee49709364418f42044015..f3c64b5cb2d837379d9951dfe3216642198d6d41 100644 (file)
@@ -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 )
index 1874debd92907dab61a240315cb5352d70431161..70f1c94ed5bf93e7e47c032f1ddf52d93a38cf5b 100644 (file)
@@ -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; }
index 9390eb1c8982d9f5f66b05ee2da9a8b76d671210..5cb19a4039333dcd43b1ba64466bf7e937aec777 100644 (file)
@@ -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
index db1f00b3b494bfd966b5f24e8c8e1448406b062a..735fb32955ab1650b1cc4b0fba31ff4361df0db5 100644 (file)
@@ -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)
index 2b0f2751c207ea6a5d65ea4d41034f900d4eda97..13b75023a2bd604e3af65a7481efc84ab2dc1224 100644 (file)
@@ -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
 )
 
index e73d5fd44f30d2f774cf7a2a1688d1ca6227a550..fc07e83594ad2108ccbddb04f0c6a182622ce862 100644 (file)
@@ -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
index 620c9b70e915f2fc125300b1e7ddfd5aed8bf5ea..213dae2f29cfea4b7fa04c561e94dfafae7652bb 100644 (file)
 
 // memory_overloads.cc author Joel Cornett <jocornet@cisco.com>
 
+#include "memory_overloads.h"
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
 #include <cassert>
-#include <new>
 
+#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<Metadata*>(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<typename Allocator = MemoryAllocator>
 struct Interface
@@ -154,10 +162,11 @@ struct Interface
 template<typename Allocator>
 void* Interface<Allocator>::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<Allocator>(n);
 
     if ( !meta )
@@ -180,7 +189,13 @@ void Interface<Allocator>::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 (file)
index 0000000..acd3e5f
--- /dev/null
@@ -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<sunimukh@cisco.com>
+
+#ifndef MEMORY_OVERLOADS_H
+#define MEMORY_OVERLOADS_H
+
+#include <new>
+
+// -----------------------------------------------------------------------------
+// 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
index d8927468974d30c23a7c7b9c363781a314ff6ba2..afacfef3fd59fb8e26ceb63232b047aad048ebab 100644 (file)
@@ -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
 //--------------------------------------------------------------------------
index ec66a3ca74e0af6bec5458c01327a1bbb7f08417..9a48d33e964654f69b99b9241916ec5db403b6ec 100644 (file)
@@ -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);
index 29aa15bc140ec6e4d36e3e9f923de49e25508410..612861f5c3533856c1e0020a91f82f2ee445acbd 100644 (file)
@@ -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
index 18e4e177c13b026c69b506f17ecbe658fee2a266..98458fdd242d131a51f91e2966b8293e713c5a0d 100644 (file)
 #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();
index d844aebe777e0bc7cceefdcd2f0708eb0d9271c3..93a5dca0899e44980c7648afcffd6fb629861fac 100644 (file)
@@ -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
index 91203669fbc2af89c4eb3c4cf33c4415dff20018..1b140badefa3a7ef878c51a5ec95b7e5d2520a15 100644 (file)
@@ -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<View> 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 );
         }
 
index 12102525449302e8ec9745c5087a997fa97b8641..57561c653a40aa3f2f2c91ba638f6f48fd7a6887 100644 (file)
@@ -38,6 +38,7 @@ struct MemoryProfilerConfig
     bool show = false;
     unsigned count = 0;
     int max_depth = -1;
+    uint64_t dump_file_size = 0;
 };
 
 namespace snort
index e43cc6438d78b4786037a0c004ae366b8e756d26..46cbcaa621e211aa3ccee07c6753554a8d6fdf05 100644 (file)
@@ -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 );
         }
index 5a9fc2f15ea047190dc7b9079ba192af5fde336f..eb0cdc00459e0c31a3dcdcab1cc86328e684780a 100644 (file)
@@ -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;
index ec34c128167cb4c03a87178b8b0c18570db437b3..abe525fadfd7e3081a9b25d5ef03cc18c3ba868d 100644 (file)
@@ -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<typename T>
+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<typename T>
 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;
 
index f1392fa9c401d8269d851a73427709b5431d2343..1fca935d51f56e057fdbf5a653ae8981ce5e372c 100644 (file)
 #include "profiler_nodes.h"
 
 #include <cassert>
+#include <fstream>
 #include <mutex>
+#include <sys/stat.h>
+#include <climits>
+#include <unistd.h>
 
 #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); }
 
index 44618ac87b5ab834f3b42b47cd65f7d6c7253984..f7687f7f61697366c8710943fdc709166f3c31b8 100644 (file)
@@ -65,6 +65,8 @@ public:
 
     const std::string name;
 
+    void get_local_memory_stats(FILE*);
+
 private:
     std::vector<ProfilerNode*> children;
     std::shared_ptr<GetProfileFunctor> 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:
index 27238063ec3fb9891e5891bc83f25c1e583ef214..83e26b595b2d8c2be629850c5a7008030ce18409 100644 (file)
@@ -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