]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3843: profiler: add json formatter
authorOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Wed, 17 May 2023 09:04:50 +0000 (09:04 +0000)
committerOleksii Shumeiko -X (oshumeik - SOFTSERVE INC at Cisco) <oshumeik@cisco.com>
Wed, 17 May 2023 09:04:50 +0000 (09:04 +0000)
Merge in SNORT/snort3 from ~ANOROKH/snort3:add_json_formatter to master

Squashed commit of the following:

commit 94832c6e4e72b9a95e644288b349eacf0560f056
Author: Anna Norokh <anorokh@cisco.com>
Date:   Wed May 3 16:55:55 2023 +0300

    profiler: add json formatter
    * separated table output;
    * added json formatter;
    * added output argument to rule_dump() command;
    * added function to put termination to json output in json_stream class;

12 files changed:
src/helpers/json_stream.cc
src/helpers/json_stream.h
src/profiler/CMakeLists.txt
src/profiler/json_view.cc [new file with mode: 0644]
src/profiler/json_view.h [new file with mode: 0644]
src/profiler/profiler_module.cc
src/profiler/profiler_module.h
src/profiler/rule_profiler.cc
src/profiler/rule_profiler.h
src/profiler/rule_profiler_defs.h
src/profiler/table_view.cc [new file with mode: 0644]
src/profiler/table_view.h [new file with mode: 0644]

index 246d9f283fab0a05f98c0293bce04c5f7efe6b94..a0b26a73924bde04e55105afbe629d4d95754334 100644 (file)
@@ -166,3 +166,7 @@ void JsonStream::split()
         sep = true;
 }
 
+void JsonStream::put_eol()
+{
+    out << std::endl;
+}
index 59af30d59efda110edda33907dd080747d02aefb..046cc85aa23b06f8b5f2f977fd22855a906f5d02 100644 (file)
@@ -48,6 +48,8 @@ public:
     void put_true(const char* key);
     void put_false(const char* key);
 
+    void put_eol();
+
 private:
     void split();
 
index fe47fd228d14740d5202c6a2c4583c03f8a8a112..f13d66abc4cc63bfaaf38e29a9487868596b40c1 100644 (file)
@@ -10,22 +10,26 @@ set ( PROFILER_INCLUDES
 
 set ( PROFILER_SOURCES
     active_context.h
+    json_view.cc
+    json_view.h
     memory_context.cc
     memory_profiler.cc
     memory_profiler.h
     profiler.cc
+    profiler_module.cc
+    profiler_module.h
+    profiler_nodes.cc
+    profiler_nodes.h
     profiler_printer.h
     profiler_stats_table.cc
     profiler_stats_table.h
     profiler_tree_builder.h
-    profiler_nodes.cc
-    profiler_nodes.h
     rule_profiler.cc
     rule_profiler.h
+    table_view.cc
+    table_view.h
     time_profiler.cc
     time_profiler.h
-    profiler_module.cc
-    profiler_module.h
     )
 
 add_library ( profiler OBJECT
diff --git a/src/profiler/json_view.cc b/src/profiler/json_view.cc
new file mode 100644 (file)
index 0000000..bd2a90b
--- /dev/null
@@ -0,0 +1,116 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// json_view.cc author Anna Norokh <anorokh@cisco.com>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "json_view.h"
+
+#include <sstream>
+#include <vector>
+
+#include "control/control.h"
+#include "helpers/json_stream.h"
+#include "main/snort_config.h"
+#include "utils/stats.h"
+
+#include "profiler_printer.h"
+#include "rule_profiler.h"
+
+#define PRECISION 5
+
+using namespace snort;
+
+static void print_single_entry(ControlConn* ctrlcon, const rule_stats::View& v, unsigned n,
+    unsigned count, double total_time_usec)
+{ 
+    using std::chrono::duration_cast;
+    using std::chrono::microseconds;
+
+    std::ostringstream ss;
+    JsonStream json(ss);
+
+    json.open();
+    json.put("gid", v.sig_info.gid);
+    json.put("sid", v.sig_info.sid);
+    json.put("rev", v.sig_info.rev);
+
+    json.put("checks", v.checks());
+    json.put("matches", v.matches());
+    json.put("alerts", v.alerts());
+
+    json.put("timeUs", clock_usecs(TO_USECS(v.elapsed())));
+    json.put("avgCheck", clock_usecs(TO_USECS(v.avg_check())));
+    json.put("avgMatch", clock_usecs(TO_USECS(v.avg_match())));
+    json.put("avgNonMatch", clock_usecs(TO_USECS(v.avg_no_match())));
+
+    json.put("timeouts", v.timeouts());
+    json.put("suspends", v.suspends());
+    json.put("ruleTimePercentage", v.rule_time_per(total_time_usec), PRECISION);
+    json.close();
+
+
+    if ( n < count )
+        ss << ", ";
+
+    LogRespond(ctrlcon, "%s", ss.str().c_str());
+}
+
+void print_json_entries(ControlConn* ctrlcon, std::vector<rule_stats::View>& entries,
+    ProfilerSorter<rule_stats::View>& sort, unsigned count)
+{
+    std::ostringstream ss;
+    JsonStream json(ss);
+
+    RuleContext::set_end_time(get_time_curr());
+    RuleContext::count_total_time();
+
+    double start_time_usec =
+        RuleContext::get_start_time()->tv_sec * 1000000.0 + RuleContext::get_start_time()->tv_usec;
+    double end_time_usec =
+        RuleContext::get_end_time()->tv_sec * 1000000.0 + RuleContext::get_end_time()->tv_usec;
+    double total_time_usec =
+        RuleContext::get_total_time()->tv_sec * 1000000.0 + RuleContext::get_total_time()->tv_usec;
+
+    json.open();
+    json.put("startTime", start_time_usec);
+    json.put("endTime", end_time_usec);
+    json.open_array("rules");
+    json.put_eol();
+
+    LogRespond(ctrlcon, "%s", ss.str().c_str());
+
+    if ( !count || count > entries.size() )
+        count = entries.size();
+
+    if ( sort )
+        std::partial_sort(entries.begin(), entries.begin() + count, entries.end(), sort);
+
+    for ( unsigned i = 0; i < count; ++i )
+        print_single_entry(ctrlcon, entries[i], i + 1, count, total_time_usec);
+
+    //clean the stream from previous data
+    ss.str("");
+    json.close_array();
+    json.close();
+
+    LogRespond(ctrlcon, "%s", ss.str().c_str());
+}
diff --git a/src/profiler/json_view.h b/src/profiler/json_view.h
new file mode 100644 (file)
index 0000000..1e8e59a
--- /dev/null
@@ -0,0 +1,33 @@
+//--------------------------------------------------------------------------
+// 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.
+//--------------------------------------------------------------------------
+
+// json_view.h author Anna Norokh <anorokh@cisco.com>
+
+#ifndef JSON_VIEW_H
+#define JSON_VIEW_H
+
+#include <vector>
+
+#include "main/snort_config.h"
+
+#include "profiler_printer.h"
+#include "rule_profiler.h"
+
+void print_json_entries(ControlConn*, std::vector<rule_stats::View>&, ProfilerSorter<rule_stats::View>&, unsigned);
+
+#endif
index 51c5b095880d599954bb4bd6b5c70e75b829da90..66c6a4b5eb55b5f3f9b061f26737c2560204049f 100644 (file)
 #include "control/control.h"
 #include "hash/xhash.h"
 #include "log/messages.h"
+#include "lua/lua.h"
 #include "main/analyzer_command.h"
 #include "main/reload_tuner.h"
 #include "main/snort.h"
 #include "main/snort_config.h"
 #include "managers/module_manager.h"
 
-#include "profiler/rule_profiler.h"
-#include "profiler/rule_profiler_defs.h"
+#include "rule_profiler.h"
+#include "rule_profiler_defs.h"
 
 using namespace snort;
 
@@ -86,8 +87,8 @@ private:
 class ProfilerRuleDump : public AnalyzerCommand
 {
 public:
-    ProfilerRuleDump(ControlConn* conn)
-        : AnalyzerCommand(conn), nodes(), stats()
+    ProfilerRuleDump(ControlConn* conn, OutType out_type)
+        : AnalyzerCommand(conn), nodes(), stats(), out_type(out_type)
     {
         const SnortConfig* sc = SnortConfig::get_conf();
         assert(sc);
@@ -108,7 +109,7 @@ public:
         const auto* config = SnortConfig::get_conf()->get_profiler();
         assert(config);
 
-        print_rule_profiler_stats(config->rule, stats, ctrlcon);
+        print_rule_profiler_stats(config->rule, stats, ctrlcon, out_type);
     }
 
     bool execute(Analyzer&, void**) override
@@ -123,6 +124,7 @@ public:
 private:
     std::vector<HashNode*> nodes;
     std::unordered_map<SigInfo*, OtnState> stats;
+    OutType out_type;
 };
 
 class ProfilerRuleReset : public AnalyzerCommand
@@ -213,11 +215,42 @@ static int rule_profiling_dump(lua_State* L)
         return 0;
     }
 
-    main_broadcast_command(new ProfilerRuleDump(ctrlcon), ctrlcon);
+    const int num_of_args = lua_gettop(L);
+
+    if (!L or (num_of_args == 0))
+    {
+        main_broadcast_command(new ProfilerRuleDump(ctrlcon, OutType::OUTPUT_TABLE), ctrlcon);
+        return 0;
+    }
+
+    if (num_of_args > 1)
+    {
+        LogRespond(ctrlcon, "Too many arguments for rule_dump(output) command\n");
+        return 0;
+    }
+
+    const char* arg = lua_tostring(L, 1);
+
+    if (strcmp(arg, "json") == 0)
+        main_broadcast_command(new ProfilerRuleDump(ctrlcon, OutType::OUTPUT_JSON), ctrlcon);
+
+    else if (strcmp(arg, "table") == 0)
+        main_broadcast_command(new ProfilerRuleDump(ctrlcon, OutType::OUTPUT_TABLE), ctrlcon);
+
+    else
+        LogRespond(ctrlcon, "Invalid usage of rule_dump(output), argument can be 'table' or 'json'\n");
 
     return 0;
 }
 
+static const Parameter profiler_dump_params[] =
+{
+    { "output", Parameter::PT_ENUM, "table | json",
+      "table", "print rule statistics in table or json format" },
+
+    { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr }
+};
+
 static const Command profiler_cmds[] =
 {
     { "rule_start", rule_profiling_start,
@@ -230,7 +263,7 @@ static const Command profiler_cmds[] =
       nullptr, "print rule profiler status" },
 
     { "rule_dump", rule_profiling_dump,
-      nullptr, "print rule statistics" },
+      profiler_dump_params, "print rule statistics" },
 
     { nullptr, nullptr, nullptr, nullptr }
 };
index d53add5f0591c171ed8b1c2c62d5deb7359941ee..237a521769f4aad3c55d4b2c501faa6bc182513f 100644 (file)
@@ -23,7 +23,7 @@
 
 #include "framework/module.h"
 
-#include "profiler/profiler.h"
+#include "profiler.h"
 
 class ProfilerModule : public snort::Module
 {
index c28508977c91edcf085bdea864bdf8b298fc5c2a..5d84ee1c0171554c90827ef90e2fd3c0b735bebc 100644 (file)
 #include <sstream>
 #include <vector>
 
+#include "control/control.h"
 // this include eventually leads to possible issues with std::chrono:
 // 1.  Undefined or garbage value returned to caller (rep count())
 // 2.  The left expression of the compound assignment is an uninitialized value.
 //     The computed value will also be garbage (duration& operator+=(const duration& __d))
 #include "detection/detection_options.h"  // ... FIXIT-W
-#include "control/control.h"
 #include "detection/treenodes.h"
 #include "hash/ghash.h"
 #include "hash/xhash.h"
 #include "main/thread_config.h"
 #include "parser/parser.h"
 #include "target_based/snort_protocols.h"
-#include "utils/stats.h"
 #include "time/timersub.h"
+#include "utils/stats.h"
 
+#include "json_view.h"
 #include "profiler_printer.h"
 #include "profiler_stats_table.h"
 #include "rule_profiler_defs.h"
+#include "table_view.h"
 
 #ifdef UNIT_TEST
 #include "catch/snort_catch.h"
@@ -66,90 +68,6 @@ struct timeval RuleContext::total_time = {0, 0};
 namespace rule_stats
 {
 
-static const StatsTable::Field fields[] =
-{
-    { "#", 5, '\0', 0, std::ios_base::left },
-    { "gid", 6, '\0', 0, std::ios_base::fmtflags() },
-    { "sid", 6, '\0', 0, std::ios_base::fmtflags() },
-    { "rev", 4, '\0', 0, std::ios_base::fmtflags() },
-    { "checks", 10, '\0', 0, std::ios_base::fmtflags() },
-    { "matches", 8, '\0', 0, std::ios_base::fmtflags() },
-    { "alerts", 7, '\0', 0, std::ios_base::fmtflags() },
-    { "time (us)", 10, '\0', 0, std::ios_base::fmtflags() },
-    { "avg/check", 10, '\0', 1, std::ios_base::fmtflags() },
-    { "avg/match", 10, '\0', 1, std::ios_base::fmtflags() },
-    { "avg/non-match", 14, '\0', 1, std::ios_base::fmtflags() },
-    { "timeouts", 9, '\0', 0, std::ios_base::fmtflags() },
-    { "suspends", 9, '\0', 0, std::ios_base::fmtflags() },
-    { "rule_time (%)", 14, '\0', 5, std::ios_base::fmtflags() },
-    { nullptr, 0, '\0', 0, std::ios_base::fmtflags() }
-};
-
-struct View
-{
-    OtnState state;
-    SigInfo sig_info;
-
-    hr_duration elapsed() const
-    { return state.elapsed; }
-
-    hr_duration elapsed_match() const
-    { return state.elapsed_match; }
-
-    hr_duration elapsed_no_match() const
-    { return elapsed() - elapsed_match(); }
-
-    uint64_t checks() const
-    { return state.checks; }
-
-    uint64_t matches() const
-    { return state.matches; }
-
-    uint64_t no_matches() const
-    { return checks() - matches(); }
-
-    uint64_t alerts() const
-    { return state.alerts; }
-
-    uint64_t timeouts() const
-    { return state.latency_timeouts; }
-
-    uint64_t suspends() const
-    { return state.latency_suspends; }
-
-    hr_duration time_per(hr_duration d, uint64_t v) const
-    {
-        if ( v  == 0 )
-            return CLOCK_ZERO;
-
-        return hr_duration(d / v);
-    }
-
-    hr_duration avg_match() const
-    { return time_per(elapsed_match(), matches()); }
-
-    hr_duration avg_no_match() const
-    { return time_per(elapsed_no_match(), no_matches()); }
-
-    hr_duration avg_check() const
-    { return time_per(elapsed(), checks()); }
-
-    double rule_time_per(double total_time_usec) const
-    {
-        if (total_time_usec < 1.)
-            return 100.0;
-        return clock_usecs(TO_USECS(elapsed())) / total_time_usec * 100;
-    }
-
-    View(const OtnState& otn_state, const SigInfo* si = nullptr) :
-        state(otn_state)
-    {
-        if ( si )
-            // FIXIT-L does sig_info need to be initialized otherwise?
-            sig_info = *si;
-    }
-};
-
 static const ProfilerSorter<View> sorters[] =
 {
     { "", nullptr },
@@ -201,89 +119,10 @@ static std::vector<View> build_entries(const std::unordered_map<SigInfo*, OtnSta
     return entries;
 }
 
-// FIXIT-L logic duplicated from ProfilerPrinter
-static void print_single_entry(ControlConn* ctrlcon, const View& v, unsigned n,
-    double total_time_usec)
-{
-    using std::chrono::duration_cast;
-    using std::chrono::microseconds;
-
-    std::ostringstream ss;
-
-    {
-        StatsTable table(fields, ss);
-
-        table << StatsTable::ROW;
-
-        table << n; // #
-
-        table << v.sig_info.gid;
-        table << v.sig_info.sid;
-        table << v.sig_info.rev;
-
-        table << v.checks();
-        table << v.matches();
-        table << v.alerts();
-
-        table << clock_usecs(TO_USECS(v.elapsed()));
-        table << clock_usecs(TO_USECS(v.avg_check()));
-        table << clock_usecs(TO_USECS(v.avg_match()));
-        table << clock_usecs(TO_USECS(v.avg_no_match()));
-
-        table << v.timeouts();
-        table << v.suspends();
-        table << v.rule_time_per(total_time_usec);
-    }
-
-    LogRespond(ctrlcon, "%s", ss.str().c_str());
-}
-
-// FIXIT-L logic duplicated from ProfilerPrinter
-static void print_entries(ControlConn* ctrlcon, std::vector<View>& entries,
-    ProfilerSorter<View>& sort, unsigned count)
-{
-    std::ostringstream ss;
-    RuleContext::set_end_time(get_time_curr());
-    RuleContext::count_total_time();
-
-    double total_time_usec =
-        RuleContext::get_total_time()->tv_sec * 1000000.0 + RuleContext::get_total_time()->tv_usec;
-
-    {
-        StatsTable table(fields, ss);
-
-        table << StatsTable::SEP;
-
-        table << s_rule_table_title;
-        if ( count )
-            table << " (worst " << count;
-        else
-            table << " (all";
-
-        if ( sort )
-            table << ", sorted by " << sort.name;
-
-        table << ")\n";
-
-        table << StatsTable::HEADER;
-    }
-
-    LogRespond(ctrlcon, "%s", ss.str().c_str());
-
-    if ( !count || count > entries.size() )
-        count = entries.size();
-
-    if ( sort )
-        std::partial_sort(entries.begin(), entries.begin() + count, entries.end(), sort);
-
-    for ( unsigned i = 0; i < count; ++i )
-        print_single_entry(ctrlcon, entries[i], i + 1, total_time_usec);
-}
-
 }
 
 void print_rule_profiler_stats(const RuleProfilerConfig& config, const std::unordered_map<SigInfo*, OtnState>& stats,
-    ControlConn* ctrlcon)
+    ControlConn* ctrlcon, OutType out_type)
 {
     auto entries = rule_stats::build_entries(stats);
 
@@ -294,7 +133,11 @@ void print_rule_profiler_stats(const RuleProfilerConfig& config, const std::unor
     auto sort = rule_stats::sorters[config.sort];
 
     // FIXIT-L do we eventually want to be able print rule totals, too?
-    print_entries(ctrlcon, entries, sort, config.count);
+    if ( out_type == OutType::OUTPUT_TABLE )
+        print_entries(ctrlcon, entries, sort, config.count);
+
+    else if ( out_type == OutType::OUTPUT_JSON )
+        print_json_entries(ctrlcon, entries, sort, config.count);
 }
 
 void show_rule_profiler_stats(const RuleProfilerConfig& config)
index 36b9d76013a54865cf7b3c438ac651c99348eb16..5c83eb4af5c095f8aceda387ae43011fa250ad79 100644 (file)
 #ifndef RULE_PROFILER_H
 #define RULE_PROFILER_H
 
-#include "detection/treenodes.h"
 #include <unordered_map>
 #include <vector>
 
+#include "detection/treenodes.h"
+#include "main/snort_config.h"
+#include "main/thread_config.h"
+
+#include "rule_profiler_defs.h"
+
 struct RuleProfilerConfig;
 class ControlConn;
 namespace snort
@@ -33,9 +38,79 @@ namespace snort
 }
 
 void prepare_rule_profiler_stats(std::vector<snort::HashNode*>&, std::unordered_map<SigInfo*, OtnState>&, unsigned);
-void print_rule_profiler_stats(const RuleProfilerConfig&, const std::unordered_map<SigInfo*, OtnState>&, ControlConn* = nullptr);
+void print_rule_profiler_stats(const RuleProfilerConfig&, const std::unordered_map<SigInfo*, OtnState>&,
+    ControlConn* = nullptr, OutType = OutType::OUTPUT_TABLE);
 void show_rule_profiler_stats(const RuleProfilerConfig&);
 void reset_rule_profiler_stats();
 void reset_thread_rule_profiler_stats(std::vector<snort::HashNode*>&, unsigned);
 
+namespace rule_stats
+{
+
+struct View
+{
+    OtnState state;
+    SigInfo sig_info;
+
+    hr_duration elapsed() const
+    { return state.elapsed; }
+
+    hr_duration elapsed_match() const
+    { return state.elapsed_match; }
+
+    hr_duration elapsed_no_match() const
+    { return elapsed() - elapsed_match(); }
+
+    uint64_t checks() const
+    { return state.checks; }
+
+    uint64_t matches() const
+    { return state.matches; }
+
+    uint64_t no_matches() const
+    { return checks() - matches(); }
+
+    uint64_t alerts() const
+    { return state.alerts; }
+
+    uint64_t timeouts() const
+    { return state.latency_timeouts; }
+
+    uint64_t suspends() const
+    { return state.latency_suspends; }
+
+    hr_duration time_per(hr_duration d, uint64_t v) const
+    {
+        if ( v  == 0 )
+            return CLOCK_ZERO;
+
+        return hr_duration(d / v);
+    }
+
+    hr_duration avg_match() const
+    { return time_per(elapsed_match(), matches()); }
+
+    hr_duration avg_no_match() const
+    { return time_per(elapsed_no_match(), no_matches()); }
+
+    hr_duration avg_check() const
+    { return time_per(elapsed(), checks()); }
+
+    double rule_time_per(double total_time_usec) const
+    {
+        if (total_time_usec < 1.)
+            return 100.0;
+        return clock_usecs(TO_USECS(elapsed())) / total_time_usec * 100;
+    }
+
+    View(const OtnState& otn_state, const SigInfo* si = nullptr) :
+        state(otn_state)
+    {
+        if ( si )
+            // FIXIT-L does sig_info need to be initialized otherwise?
+            sig_info = *si;
+    }
+};
+
+}
 #endif
index 1092763701e7b6ceaac2061e8dc4db3ee5f5bcc1..ee0307ce5905c79fc7d1fa66ccda7d32773084d8 100644 (file)
 
 struct dot_node_state_t;
 
+enum OutType
+{
+    OUTPUT_TABLE = 0,
+    OUTPUT_JSON
+};
+
 struct RuleProfilerConfig
 {
     enum Sort
diff --git a/src/profiler/table_view.cc b/src/profiler/table_view.cc
new file mode 100644 (file)
index 0000000..675a034
--- /dev/null
@@ -0,0 +1,137 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-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.
+//--------------------------------------------------------------------------
+
+// table_view.cc author Joel Cornett <jocornet@cisco.com>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "table_view.h"
+
+#include <sstream>
+#include <vector>
+
+#include "control/control.h"
+#include "detection/treenodes.h"
+#include "utils/stats.h"
+
+#include "profiler_printer.h"
+#include "profiler_stats_table.h"
+#include "rule_profiler.h"
+
+#define s_rule_table_title "rule profile"
+
+using namespace snort;
+
+const StatsTable::Field fields[] =
+{
+    { "#", 5, '\0', 0, std::ios_base::left },
+    { "gid", 6, '\0', 0, std::ios_base::fmtflags() },
+    { "sid", 6, '\0', 0, std::ios_base::fmtflags() },
+    { "rev", 4, '\0', 0, std::ios_base::fmtflags() },
+    { "checks", 10, '\0', 0, std::ios_base::fmtflags() },
+    { "matches", 8, '\0', 0, std::ios_base::fmtflags() },
+    { "alerts", 7, '\0', 0, std::ios_base::fmtflags() },
+    { "time (us)", 10, '\0', 0, std::ios_base::fmtflags() },
+    { "avg/check", 10, '\0', 1, std::ios_base::fmtflags() },
+    { "avg/match", 10, '\0', 1, std::ios_base::fmtflags() },
+    { "avg/non-match", 14, '\0', 1, std::ios_base::fmtflags() },
+    { "timeouts", 9, '\0', 0, std::ios_base::fmtflags() },
+    { "suspends", 9, '\0', 0, std::ios_base::fmtflags() },
+    { "rule_time (%)", 14, '\0', 5, std::ios_base::fmtflags() },
+    { nullptr, 0, '\0', 0, std::ios_base::fmtflags() }
+};
+
+// FIXIT-L logic duplicated from ProfilerPrinter
+static void print_single_entry(ControlConn* ctrlcon, const rule_stats::View& v, unsigned n,
+    double total_time_usec)
+{
+    using std::chrono::duration_cast;
+    using std::chrono::microseconds;
+
+    std::ostringstream ss;
+
+    {
+        StatsTable table(fields, ss);
+
+        table << StatsTable::ROW;
+
+        table << n; // #
+
+        table << v.sig_info.gid;
+        table << v.sig_info.sid;
+        table << v.sig_info.rev;
+
+        table << v.checks();
+        table << v.matches();
+        table << v.alerts();
+
+        table << clock_usecs(TO_USECS(v.elapsed()));
+        table << clock_usecs(TO_USECS(v.avg_check()));
+        table << clock_usecs(TO_USECS(v.avg_match()));
+        table << clock_usecs(TO_USECS(v.avg_no_match()));
+
+        table << v.timeouts();
+        table << v.suspends();
+        table << v.rule_time_per(total_time_usec);
+    }
+
+    LogRespond(ctrlcon, "%s", ss.str().c_str());
+}
+
+// FIXIT-L logic duplicated from ProfilerPrinter
+void print_entries(ControlConn* ctrlcon, std::vector<rule_stats::View>& entries,
+    ProfilerSorter<rule_stats::View>& sort, unsigned count)
+{
+    std::ostringstream ss;
+    RuleContext::set_end_time(get_time_curr());
+    RuleContext::count_total_time();
+
+    double total_time_usec =
+        RuleContext::get_total_time()->tv_sec * 1000000.0 + RuleContext::get_total_time()->tv_usec;
+
+    StatsTable table(fields, ss);
+
+    table << StatsTable::SEP;
+
+    table << s_rule_table_title;
+    if ( count )
+        table << " (worst " << count;
+    else
+        table << " (all";
+
+    if ( sort )
+        table << ", sorted by " << sort.name;
+
+    table << ")\n";
+
+    table << StatsTable::HEADER;
+
+    LogRespond(ctrlcon, "%s", ss.str().c_str());
+
+    if ( !count || count > entries.size() )
+        count = entries.size();
+
+    if ( sort )
+        std::partial_sort(entries.begin(), entries.begin() + count, entries.end(), sort);
+
+    for ( unsigned i = 0; i < count; ++i )
+        print_single_entry(ctrlcon, entries[i], i + 1, total_time_usec);
+
+}
diff --git a/src/profiler/table_view.h b/src/profiler/table_view.h
new file mode 100644 (file)
index 0000000..2dc7fff
--- /dev/null
@@ -0,0 +1,33 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2015-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.
+//--------------------------------------------------------------------------
+
+// table_view.h author Joel Cornett <jocornet@cisco.com>
+
+#ifndef TABLE_VIEW_H
+#define TABLE_VIEW_H
+
+#include <vector>
+
+#include "main/snort_config.h"
+
+#include "profiler_printer.h"
+#include "rule_profiler.h"
+
+void print_entries(ControlConn*, std::vector<rule_stats::View>&, ProfilerSorter<rule_stats::View>&, unsigned);
+
+#endif