diff --git a/userspace/libsinsp/test/filter_compiler.ut.cpp b/userspace/libsinsp/test/filter_compiler.ut.cpp index e72b056c58..e83a803ad2 100644 --- a/userspace/libsinsp/test/filter_compiler.ut.cpp +++ b/userspace/libsinsp/test/filter_compiler.ut.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace std; @@ -67,6 +68,39 @@ class mock_compiler_filter_check : public sinsp_filter_check filtercheck_field_info m_field_info{PT_CHARBUF, 0, PF_NA, "", "", ""}; }; +struct test_sinsp_filter_cache_factory: public exprstr_sinsp_filter_cache_factory +{ + bool docache = true; + const std::shared_ptr metrics = std::make_shared(); + + virtual ~test_sinsp_filter_cache_factory() = default; + + test_sinsp_filter_cache_factory(bool cached = true): docache(cached) { } + + std::shared_ptr new_extract_cache(const ast_expr_t* e, node_info_t& info) override + { + if (!docache) + { + return nullptr; + } + return exprstr_sinsp_filter_cache_factory::new_extract_cache(e, info); + } + + std::shared_ptr new_compare_cache(const ast_expr_t* e, node_info_t& info) override + { + if (!docache) + { + return nullptr; + } + return exprstr_sinsp_filter_cache_factory::new_compare_cache(e, info); + } + + std::shared_ptr new_metrics(const ast_expr_t* e, node_info_t& info) override + { + return metrics; + } +}; + // A factory that creates mock filterchecks class mock_compiler_filter_factory: public sinsp_filter_factory { @@ -647,3 +681,116 @@ TEST_F(sinsp_with_test_input, filter_transformers_wrong_input_type) ASSERT_FALSE(filter_compiles("tolower(evt.rawres) = -1")); ASSERT_FALSE(filter_compiles("b64(evt.rawres) = -1")); } + +TEST_F(sinsp_with_test_input, filter_cache_disabled) +{ + add_default_init_thread(); + open_inspector(); + + auto evt = generate_getcwd_failed_entry_event(); + auto cf = std::make_shared(false); + + ASSERT_TRUE(eval_filter(evt, "evt.type = openat or evt.type = getcwd", cf)); + ASSERT_TRUE(eval_filter(evt, "evt.type = getcwd", cf)); + evt->set_num(evt->get_num() + 1); + ASSERT_TRUE(eval_filter(evt, "evt.type = openat or evt.type = getcwd", cf)); + + EXPECT_EQ(cf->metrics->m_num_compare, 5); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 0); + EXPECT_EQ(cf->metrics->m_num_extract, 5); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 0); +} + +TEST_F(sinsp_with_test_input, filter_cache_enabled) +{ + add_default_init_thread(); + open_inspector(); + + auto evt = generate_getcwd_failed_entry_event(); + auto cf = std::make_shared(); + + ASSERT_TRUE(eval_filter(evt, "evt.type = openat or evt.type = getcwd", cf)); + ASSERT_TRUE(eval_filter(evt, "evt.type = getcwd", cf)); + evt->set_num(evt->get_num() + 1); + ASSERT_TRUE(eval_filter(evt, "evt.type = openat or evt.type = getcwd", cf)); + + EXPECT_EQ(cf->metrics->m_num_compare, 5); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 4); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 2); +} + +TEST_F(sinsp_with_test_input, filter_cache_corner_cases) +{ + sinsp_filter_check_list flist; + + add_default_init_thread(); + open_inspector(); + + // Register a plugin with extraction capabilities + std::string err; + plugin_api papi; + get_plugin_api_sample_syscall_extract(papi); + auto pl = m_inspector.register_plugin(&papi); + ASSERT_TRUE(pl->init("", err)) << err; + flist.add_filter_check(m_inspector.new_generic_filtercheck()); + flist.add_filter_check(sinsp_plugin::new_filtercheck(pl)); + + auto ff = std::make_shared(&m_inspector, flist); + auto cf = std::make_shared(); + auto evt = generate_getcwd_failed_entry_event(); + + // plugin fields + ASSERT_TRUE(eval_filter(evt, "sample.is_open exists and sample.is_open = 0", ff, cf)); + ASSERT_TRUE(eval_filter(evt, "sample.is_open = 0", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 2); // the third extraction never happens as the check is cached + EXPECT_EQ(cf->metrics->m_num_extract_cache, 1); + cf->metrics->reset(); + + // special comparison logic + ASSERT_FALSE(eval_filter(evt, "fd.ip = 127.0.0.1 or fd.ip = 10.0.0.1", ff, cf)); + ASSERT_FALSE(eval_filter(evt, "fd.ip = 10.0.0.1", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 0); // special logic avoids extraction entirely :/ + EXPECT_EQ(cf->metrics->m_num_extract_cache, 0); + cf->metrics->reset(); + + // fields with ambiguous comparison (no caching expected) + ASSERT_FALSE(eval_filter(evt, "fd.net = 127.0.0.1/32 or fd.net = 10.0.0.1/32", ff, cf)); + ASSERT_FALSE(eval_filter(evt, "fd.net = 10.0.0.1/32", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 0); + EXPECT_EQ(cf->metrics->m_num_extract, 0); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 0); + cf->metrics->reset(); + + // fields with arguments + ASSERT_TRUE(eval_filter(evt, "evt.arg[1] startswith /etc or evt.arg[1] = /test/dir", ff, cf)); + ASSERT_TRUE(eval_filter(evt, "evt.arg[1] = /test/dir", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 2); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 1); + cf->metrics->reset(); + + // fields with transformers + ASSERT_TRUE(eval_filter(evt, "toupper(evt.source) = SYS or toupper(evt.source) = SYSCALL", ff, cf)); + ASSERT_TRUE(eval_filter(evt, "toupper(evt.source) = SYSCALL", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 2); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 1); + cf->metrics->reset(); + + // field-to-field comparisons + ASSERT_TRUE(eval_filter(evt, "evt.source = val(evt.plugininfo) or evt.source = val(evt.source)", ff, cf)); + ASSERT_TRUE(eval_filter(evt, "evt.source = val(evt.source)", ff, cf)); + EXPECT_EQ(cf->metrics->m_num_compare, 3); + EXPECT_EQ(cf->metrics->m_num_compare_cache, 1); + EXPECT_EQ(cf->metrics->m_num_extract, 4); + EXPECT_EQ(cf->metrics->m_num_extract_cache, 2); + cf->metrics->reset(); +} diff --git a/userspace/libsinsp/test/plugins/syscall_extract.cpp b/userspace/libsinsp/test/plugins/syscall_extract.cpp index 3bda56ced6..b98a6ed54f 100644 --- a/userspace/libsinsp/test/plugins/syscall_extract.cpp +++ b/userspace/libsinsp/test/plugins/syscall_extract.cpp @@ -147,6 +147,7 @@ static uint16_t* plugin_get_extract_event_types(uint32_t* num_types, ss_plugin_t PPME_SYSCALL_INOTIFY_INIT1_E, PPME_SYSCALL_INOTIFY_INIT1_X, PPME_ASYNCEVENT_E, // used for catching async events + PPME_SYSCALL_GETCWD_X, // general purpose, used for other unit tests }; *num_types = sizeof(types) / sizeof(uint16_t); return &types[0]; diff --git a/userspace/libsinsp/test/sinsp_with_test_input.cpp b/userspace/libsinsp/test/sinsp_with_test_input.cpp index 78a0f49153..61c20416d1 100644 --- a/userspace/libsinsp/test/sinsp_with_test_input.cpp +++ b/userspace/libsinsp/test/sinsp_with_test_input.cpp @@ -497,21 +497,28 @@ std::string sinsp_with_test_input::get_field_as_string(sinsp_evt* evt, std::stri return result; } -bool sinsp_with_test_input::eval_filter(sinsp_evt* evt, std::string_view filter_str) +bool sinsp_with_test_input::eval_filter(sinsp_evt* evt, std::string_view filter_str, std::shared_ptr cachef) { - return eval_filter(evt, filter_str, m_default_filterlist); + return eval_filter(evt, filter_str, m_default_filterlist, cachef); } -bool sinsp_with_test_input::eval_filter(sinsp_evt* evt, std::string_view filter_str, filter_check_list &flist) +bool sinsp_with_test_input::eval_filter(sinsp_evt* evt, std::string_view filter_str, filter_check_list &flist, std::shared_ptr cachef) { auto factory = std::make_shared(&m_inspector, flist); - sinsp_filter_compiler compiler(factory, std::string(filter_str)); + sinsp_filter_compiler compiler(factory, std::string(filter_str), cachef); auto filter = compiler.compile(); return filter->run(evt); } +bool sinsp_with_test_input::eval_filter(sinsp_evt* evt, std::string_view filter_str, std::shared_ptr filterf, std::shared_ptr cachef) +{ + sinsp_filter_compiler compiler(filterf, std::string(filter_str), cachef); + auto filter = compiler.compile(); + return filter->run(evt); +} + bool sinsp_with_test_input::filter_compiles(std::string_view filter_str) { return filter_compiles(filter_str, m_default_filterlist); diff --git a/userspace/libsinsp/test/sinsp_with_test_input.h b/userspace/libsinsp/test/sinsp_with_test_input.h index 9aad7cef01..fc569f0bcb 100644 --- a/userspace/libsinsp/test/sinsp_with_test_input.h +++ b/userspace/libsinsp/test/sinsp_with_test_input.h @@ -199,8 +199,9 @@ class sinsp_with_test_input : public ::testing::Test bool field_has_value(sinsp_evt*, std::string_view field_name, filter_check_list&); std::string get_field_as_string(sinsp_evt*, std::string_view field_name); std::string get_field_as_string(sinsp_evt*, std::string_view field_name, filter_check_list&); - bool eval_filter(sinsp_evt* evt, std::string_view filter_str); - bool eval_filter(sinsp_evt* evt, std::string_view filter_str, filter_check_list&); + bool eval_filter(sinsp_evt* evt, std::string_view filter_str, std::shared_ptr cachef = nullptr); + bool eval_filter(sinsp_evt* evt, std::string_view filter_str, filter_check_list&, std::shared_ptr cachef = nullptr); + bool eval_filter(sinsp_evt* evt, std::string_view filter_str, std::shared_ptr filterf, std::shared_ptr cachef = nullptr); bool filter_compiles(std::string_view filter_str); bool filter_compiles(std::string_view filter_str, filter_check_list&);