Skip to content

Commit e85bdf6

Browse files
committed
feat(libsinsp): concat transformer
Signed-off-by: Roberto Scolaro <roberto.scolaro21@gmail.com>
1 parent 64da2bd commit e85bdf6

File tree

6 files changed

+240
-2
lines changed

6 files changed

+240
-2
lines changed

userspace/libsinsp/filter/parser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static const std::vector<std::string> s_binary_list_ops = {
9191
static constexpr const char* s_field_transformer_val = "val(";
9292

9393
static const std::vector<std::string> s_field_transformers =
94-
{"tolower(", "toupper(", "b64(", "basename(", "len(", "join("};
94+
{"tolower(", "toupper(", "b64(", "basename(", "len(", "join(", "concat("};
9595

9696
static inline void update_pos(const char c, ast::pos_info& pos) {
9797
pos.col++;

userspace/libsinsp/sinsp_filtercheck_multivalue_transformer.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,62 @@ bool sinsp_filter_multivalue_transformer_join::extract(sinsp_evt* evt,
179179

180180
sinsp_filter_multivalue_transformer_join::~sinsp_filter_multivalue_transformer_join() = default;
181181

182+
// concat
183+
184+
sinsp_filter_multivalue_transformer_concat::sinsp_filter_multivalue_transformer_concat(
185+
std::vector<std::unique_ptr<sinsp_filter_check>> args):
186+
sinsp_filter_multivalue_transformer({PT_CHARBUF, false}, std::move(args)) {
187+
// Validate using argument_types()
188+
const auto& arg_types = argument_types();
189+
190+
// concat requires at least 2 arguments
191+
if(arg_types.size() < 2) {
192+
throw sinsp_exception("concat() requires at least 2 arguments");
193+
}
194+
195+
// Validate using argument_types()
196+
for(const auto& arg_t : argument_types()) {
197+
if(arg_t.type != PT_CHARBUF || arg_t.is_list) {
198+
throw sinsp_exception("concat() arguments must be strings");
199+
}
200+
}
201+
}
202+
203+
std::string
204+
sinsp_filter_multivalue_transformer_concat::sinsp_filter_multivalue_transformer_concat::name()
205+
const {
206+
return "concat";
207+
}
208+
209+
bool sinsp_filter_multivalue_transformer_concat::extract(sinsp_evt* evt,
210+
std::vector<extract_value_t>& values,
211+
bool sanitize_strings) {
212+
m_res.clear();
213+
for(const auto& arg : m_arguments) {
214+
values.clear();
215+
if(!arg->extract(evt, values, sanitize_strings)) {
216+
return false;
217+
}
218+
m_res.append((char*)values[0].ptr);
219+
}
220+
221+
extract_value_t val{(uint8_t*)m_res.c_str(), (uint32_t)m_res.length()};
222+
values.clear();
223+
values.push_back(val);
224+
return true;
225+
}
226+
227+
sinsp_filter_multivalue_transformer_concat::~sinsp_filter_multivalue_transformer_concat() = default;
228+
182229
std::unique_ptr<sinsp_filter_check> sinsp_filter_multivalue_transformer::create_transformer(
183230
const std::string& name,
184231
std::vector<std::unique_ptr<sinsp_filter_check>> args) {
185232
if(name == "join") {
186233
return std::make_unique<multivalue_transformer_filter_check>(
187234
std::make_unique<sinsp_filter_multivalue_transformer_join>(std::move(args)));
235+
} else if(name == "concat") {
236+
return std::make_unique<multivalue_transformer_filter_check>(
237+
std::make_unique<sinsp_filter_multivalue_transformer_concat>(std::move(args)));
188238
} else {
189239
throw std::runtime_error("unknown multivalue transformer");
190240
}

userspace/libsinsp/sinsp_filtercheck_multivalue_transformer.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,20 @@ class sinsp_filter_multivalue_transformer_join : public sinsp_filter_multivalue_
9999
private:
100100
std::string m_res;
101101
};
102+
103+
// concat
104+
class sinsp_filter_multivalue_transformer_concat : public sinsp_filter_multivalue_transformer {
105+
public:
106+
sinsp_filter_multivalue_transformer_concat(
107+
std::vector<std::unique_ptr<sinsp_filter_check>> args);
108+
virtual ~sinsp_filter_multivalue_transformer_concat();
109+
110+
std::string name() const;
111+
112+
virtual bool extract(sinsp_evt* evt,
113+
std::vector<extract_value_t>& values,
114+
bool sanitize_strings = true);
115+
116+
private:
117+
std::string m_res;
118+
};

userspace/libsinsp/test/eventformatter.ut.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,56 @@ TEST_F(sinsp_formatter_test, join_transformer) {
372372
EXPECT_EQ(m_last_field_values["proc.name"], "init");
373373
EXPECT_EQ(m_last_field_values["join(->,(proc.name,evt.arg.path))"], "init->/test/dir");
374374
}
375+
376+
TEST_F(sinsp_formatter_test, concat_transformer) {
377+
format("start %concat(proc.name, evt.arg.path) end");
378+
EXPECT_EQ(m_last_res, true);
379+
EXPECT_EQ(m_last_output, "start init/test/dir end");
380+
EXPECT_EQ(m_last_field_values.size(), 3) << pretty_print(m_last_field_values);
381+
EXPECT_EQ(m_last_field_values["proc.name"], "init");
382+
EXPECT_EQ(m_last_field_values["concat(proc.name,evt.arg.path)"], "init/test/dir");
383+
}
384+
385+
TEST_F(sinsp_formatter_test, concat_with_outer_transformer) {
386+
format("start %toupper(concat(proc.name, evt.arg.path)) end");
387+
EXPECT_EQ(m_last_res, true);
388+
EXPECT_EQ(m_last_output, "start INIT/TEST/DIR end");
389+
EXPECT_EQ(m_last_field_values.size(), 3) << pretty_print(m_last_field_values);
390+
EXPECT_EQ(m_last_field_values["proc.name"], "init");
391+
EXPECT_EQ(m_last_field_values["toupper(concat(proc.name,evt.arg.path))"], "INIT/TEST/DIR");
392+
}
393+
394+
TEST_F(sinsp_formatter_test, concat_with_inner_transformer) {
395+
format("start %concat(toupper(proc.name), evt.arg.path) end");
396+
EXPECT_EQ(m_last_res, true);
397+
EXPECT_EQ(m_last_output, "start INIT/test/dir end");
398+
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
399+
EXPECT_EQ(m_last_field_values["proc.name"], "init");
400+
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
401+
EXPECT_EQ(m_last_field_values["toupper(proc.name)"], "INIT");
402+
EXPECT_EQ(m_last_field_values["concat(toupper(proc.name),evt.arg.path)"], "INIT/test/dir");
403+
}
404+
405+
TEST_F(sinsp_formatter_test, join_with_inner_transformer) {
406+
format("start %join(\"->\", (toupper(proc.name), tolower(evt.arg.path))) end");
407+
EXPECT_EQ(m_last_res, true);
408+
EXPECT_EQ(m_last_output, "start INIT->/test/dir end");
409+
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
410+
EXPECT_EQ(m_last_field_values["proc.name"], "init");
411+
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
412+
EXPECT_EQ(m_last_field_values["toupper(proc.name)"], "INIT");
413+
EXPECT_EQ(m_last_field_values["join(->,(toupper(proc.name),tolower(evt.arg.path)))"],
414+
"INIT->/test/dir");
415+
}
416+
417+
TEST_F(sinsp_formatter_test, nested_concat_and_join) {
418+
format("start %toupper(join(\"->\", (concat(proc.name, evt.arg.path), proc.name))) end");
419+
EXPECT_EQ(m_last_res, true);
420+
EXPECT_EQ(m_last_output, "start INIT/TEST/DIR->INIT end");
421+
EXPECT_EQ(m_last_field_values.size(), 4) << pretty_print(m_last_field_values);
422+
EXPECT_EQ(m_last_field_values["proc.name"], "init");
423+
EXPECT_EQ(m_last_field_values["evt.arg.path"], "/test/dir");
424+
EXPECT_EQ(m_last_field_values["concat(proc.name,evt.arg.path)"], "init/test/dir");
425+
EXPECT_EQ(m_last_field_values["toupper(join(->,(concat(proc.name,evt.arg.path),proc.name)))"],
426+
"INIT/TEST/DIR->INIT");
427+
}

userspace/libsinsp/test/filter_parser.ut.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ TEST(parser, supported_operators) {
103103

104104
TEST(parser, supported_field_transformers) {
105105
std::string expected_val = "val";
106-
std::vector<std::string> expected = {"tolower", "toupper", "b64", "basename", "len", "join"};
106+
std::vector<std::string> expected =
107+
{"tolower", "toupper", "b64", "basename", "len", "join", "concat"};
107108

108109
auto actual = parser::supported_field_transformers();
109110
ASSERT_EQ(actual.size(), expected.size());

userspace/libsinsp/test/filter_transformer.ut.cpp

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,51 @@ TEST_F(sinsp_with_test_input, multivalue_transformer_join) {
465465
EXPECT_THROW(eval_filter(evt, "join(\"a\", \"b\") = foo"), sinsp_exception);
466466
}
467467

468+
TEST_F(sinsp_with_test_input, multivalue_transformer_concat) {
469+
add_default_init_thread();
470+
open_inspector();
471+
472+
sinsp_evt* evt;
473+
474+
int64_t dirfd = 3;
475+
const char* file_to_run = "/tmp/file_to_run";
476+
477+
evt = add_event_advance_ts(increasing_ts(),
478+
1,
479+
PPME_SYSCALL_OPEN_X,
480+
6,
481+
dirfd,
482+
file_to_run,
483+
0,
484+
0,
485+
0,
486+
(uint64_t)0);
487+
488+
EXPECT_TRUE(eval_filter(evt, "concat(fd.name, fd.directory) = /tmp/file_to_run/tmp"));
489+
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = aaabbb"));
490+
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\", \"ccc\") = aaabbbccc"));
491+
EXPECT_TRUE(eval_filter(evt, "concat(fd.name, \"aaa\") = /tmp/file_to_runaaa"));
492+
EXPECT_TRUE(
493+
eval_filter(evt, "concat(fd.name, \"aaa\", fd.directory) = /tmp/file_to_runaaa/tmp"));
494+
EXPECT_TRUE(eval_filter(evt, "concat(toupper(fd.name), \"aaa\") = /TMP/FILE_TO_RUNaaa"));
495+
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", toupper(fd.name)) = aaa/TMP/FILE_TO_RUN"));
496+
EXPECT_TRUE(eval_filter(
497+
evt,
498+
"concat(fd.directory, concat(\"aaa\", toupper(fd.name))) = /tmpaaa/TMP/FILE_TO_RUN"));
499+
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaabbb\""));
500+
EXPECT_FALSE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaa-bbb\""));
501+
502+
// Validation error tests
503+
// concat() requires at least 2 arguments
504+
EXPECT_THROW(eval_filter(evt, "concat() = foo"), sinsp_exception);
505+
EXPECT_THROW(eval_filter(evt, "concat(\"aaa\") = foo"), sinsp_exception);
506+
// concat() arguments must be strings (not lists)
507+
EXPECT_THROW(eval_filter(evt, "concat(fd.types, \"a\") = foo"), sinsp_exception);
508+
EXPECT_THROW(eval_filter(evt, "concat((\"a\", \"b\"), \"a\") = foo"), sinsp_exception);
509+
EXPECT_THROW(eval_filter(evt, "concat(\"a\", (\"a\", \"b\")) = foo"), sinsp_exception);
510+
EXPECT_THROW(eval_filter(evt, "concat((\"a\", \"b\"), (\"a\", \"b\")) = foo"), sinsp_exception);
511+
}
512+
468513
TEST_F(sinsp_with_test_input, multivalue_transformer_with_outer_transformer) {
469514
add_default_init_thread();
470515
open_inspector();
@@ -519,6 +564,37 @@ TEST_F(sinsp_with_test_input, multivalue_transformer_with_outer_transformer) {
519564
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) > 20"));
520565
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) >= 21"));
521566
EXPECT_TRUE(eval_filter(evt, "len(join(\"-\", (fd.name, fd.directory))) < 22"));
567+
568+
// Apply toupper to concat result
569+
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) = /TMP/FILE_TO_RUN/TMP"));
570+
EXPECT_TRUE(eval_filter(evt, "toupper(concat(\"aaa\", \"bbb\")) = AAABBB"));
571+
572+
// Apply tolower to concat result
573+
EXPECT_TRUE(eval_filter(evt, "tolower(concat(fd.name, fd.directory)) = /tmp/file_to_run/tmp"));
574+
EXPECT_TRUE(eval_filter(evt, "tolower(concat(\"AAA\", \"BBB\")) = aaabbb"));
575+
576+
// Apply len to concat result
577+
// fd.name = /tmp/file_to_run (16 chars), fd.directory = /tmp (4 chars)
578+
// Total = 16 + 4 = 20
579+
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) = 20"));
580+
EXPECT_TRUE(eval_filter(evt, "len(concat(\"aaa\", \"bbb\")) = 6"));
581+
582+
// Apply b64 to concat result
583+
// "aaabbb" in base64 is "YWFhYmJi"
584+
EXPECT_TRUE(eval_filter(evt, "concat(\"aaa\", \"bbb\") = \"aaabbb\""));
585+
EXPECT_TRUE(eval_filter(evt, "b64(concat(\"YWFh\", \"YmJi\")) = \"aaabbb\""));
586+
587+
// Chain multiple transformers on concat result
588+
EXPECT_TRUE(
589+
eval_filter(evt,
590+
"toupper(tolower(concat(fd.name, fd.directory))) = /TMP/FILE_TO_RUN/TMP"));
591+
592+
// Combine with comparison operators
593+
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) contains /TMP"));
594+
EXPECT_TRUE(eval_filter(evt, "toupper(concat(fd.name, fd.directory)) startswith /TMP"));
595+
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) > 19"));
596+
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) >= 20"));
597+
EXPECT_TRUE(eval_filter(evt, "len(concat(fd.name, fd.directory)) < 21"));
522598
}
523599

524600
TEST(multivalue_transformer, argument_types) {
@@ -584,3 +660,44 @@ TEST(multivalue_transformer, result_type) {
584660
EXPECT_EQ(result.type, PT_CHARBUF);
585661
EXPECT_FALSE(result.is_list);
586662
}
663+
664+
TEST(multivalue_transformer_concat, argument_types) {
665+
// Create arguments for concat: multiple strings
666+
std::vector<std::unique_ptr<sinsp_filter_check>> args;
667+
668+
// First argument: a string
669+
args.push_back(std::make_unique<rawstring_check>("aaa"));
670+
671+
// Second argument: another string
672+
args.push_back(std::make_unique<rawstring_check>("bbb"));
673+
674+
// Third argument: another string
675+
args.push_back(std::make_unique<rawstring_check>("ccc"));
676+
677+
// Create the concat transformer
678+
sinsp_filter_multivalue_transformer_concat concat_transformer(std::move(args));
679+
680+
// Test argument_types()
681+
const auto& arg_types = concat_transformer.argument_types();
682+
683+
ASSERT_EQ(arg_types.size(), 3);
684+
685+
// All arguments should be PT_CHARBUF and not lists
686+
for(size_t i = 0; i < arg_types.size(); i++) {
687+
EXPECT_EQ(arg_types[i].type, PT_CHARBUF);
688+
EXPECT_FALSE(arg_types[i].is_list);
689+
}
690+
}
691+
692+
TEST(multivalue_transformer_concat, result_type) {
693+
std::vector<std::unique_ptr<sinsp_filter_check>> args;
694+
args.push_back(std::make_unique<rawstring_check>("aaa"));
695+
args.push_back(std::make_unique<rawstring_check>("bbb"));
696+
697+
sinsp_filter_multivalue_transformer_concat concat_transformer(std::move(args));
698+
699+
// concat should return PT_CHARBUF and not a list
700+
auto result = concat_transformer.result_type();
701+
EXPECT_EQ(result.type, PT_CHARBUF);
702+
EXPECT_FALSE(result.is_list);
703+
}

0 commit comments

Comments
 (0)