Skip to content

Commit

Permalink
test(userspace/libsinsp): cover filter caching
Browse files Browse the repository at this point in the history
Signed-off-by: Jason Dellaluce <[email protected]>
  • Loading branch information
jasondellaluce authored and poiana committed Jun 20, 2024
1 parent f319ef8 commit c5f51cf
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 6 deletions.
147 changes: 147 additions & 0 deletions userspace/libsinsp/test/filter_compiler.ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <gtest/gtest.h>
#include <list>
#include <sinsp_with_test_input.h>
#include <plugins/test_plugins.h>

using namespace std;

Expand Down Expand Up @@ -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<sinsp_filter_cache_metrics> metrics = std::make_shared<sinsp_filter_cache_metrics>();

virtual ~test_sinsp_filter_cache_factory() = default;

test_sinsp_filter_cache_factory(bool cached = true): docache(cached) { }

std::shared_ptr<sinsp_filter_extract_cache> 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<sinsp_filter_compare_cache> 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<sinsp_filter_cache_metrics> 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
{
Expand Down Expand Up @@ -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<test_sinsp_filter_cache_factory>(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<test_sinsp_filter_cache_factory>();

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<sinsp_filter_factory>(&m_inspector, flist);
auto cf = std::make_shared<test_sinsp_filter_cache_factory>();
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();
}
1 change: 1 addition & 0 deletions userspace/libsinsp/test/plugins/syscall_extract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
15 changes: 11 additions & 4 deletions userspace/libsinsp/test/sinsp_with_test_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<sinsp_filter_cache_factory> 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<sinsp_filter_cache_factory> cachef)
{
auto factory = std::make_shared<sinsp_filter_factory>(&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<sinsp_filter_factory> filterf, std::shared_ptr<sinsp_filter_cache_factory> 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);
Expand Down
5 changes: 3 additions & 2 deletions userspace/libsinsp/test/sinsp_with_test_input.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<sinsp_filter_cache_factory> cachef = nullptr);
bool eval_filter(sinsp_evt* evt, std::string_view filter_str, filter_check_list&, std::shared_ptr<sinsp_filter_cache_factory> cachef = nullptr);
bool eval_filter(sinsp_evt* evt, std::string_view filter_str, std::shared_ptr<sinsp_filter_factory> filterf, std::shared_ptr<sinsp_filter_cache_factory> cachef = nullptr);
bool filter_compiles(std::string_view filter_str);
bool filter_compiles(std::string_view filter_str, filter_check_list&);

Expand Down

0 comments on commit c5f51cf

Please sign in to comment.