diff --git a/userspace/libsinsp/event.h b/userspace/libsinsp/event.h index 16b08ab017..5fe7ac76e9 100644 --- a/userspace/libsinsp/event.h +++ b/userspace/libsinsp/event.h @@ -41,23 +41,6 @@ class sinsp_evt; /////////////////////////////////////////////////////////////////////////////// // Event arguments /////////////////////////////////////////////////////////////////////////////// -enum filtercheck_field_flags -{ - EPF_NONE = 0, - EPF_FILTER_ONLY = 1 << 0, ///< this field can only be used as a filter. - EPF_PRINT_ONLY = 1 << 1, ///< this field can only be printed. - EPF_ARG_REQUIRED = 1 << 2, ///< this field includes an argument, under the form 'property.argument'. - EPF_TABLE_ONLY = 1 << 3, ///< this field is designed to be used in a table and won't appear in the field listing. - EPF_INFO = 1 << 4, ///< this field contains summary information about the event. - EPF_CONVERSATION = 1 << 5, ///< this field can be used to identify conversations. - EPF_IS_LIST = 1 << 6, ///< this field is a list of values. - EPF_ARG_ALLOWED = 1 << 7, ///< this field optionally includes an argument. - EPF_ARG_INDEX = 1 << 8, ///< this field accepts numeric arguments. - EPF_ARG_KEY = 1 << 9, ///< this field accepts string arguments. - EPF_DEPRECATED = 1 << 10,///< this field is deprecated. - EPF_NO_TRANSFORMER = 1 << 11,///< this field cannot have a field transformer. - EPF_NO_RHS = 1 << 12,///< this field cannot have a right-hand side filter check, and cannot be used as a right-hand side filter check. -}; /** @defgroup event Event manipulation * Classes to manipulate events, extract their content and convert them into strings. diff --git a/userspace/libsinsp/filter.cpp b/userspace/libsinsp/filter.cpp index fa67020281..f2b2a573d0 100644 --- a/userspace/libsinsp/filter.cpp +++ b/userspace/libsinsp/filter.cpp @@ -115,11 +115,6 @@ bool sinsp_filter_expression::compare(sinsp_evt *evt) return res; } -bool sinsp_filter_expression::extract(sinsp_evt *evt, std::vector& values, bool sanitize_strings) -{ - return false; -} - int32_t sinsp_filter_expression::get_expr_boolop() const { if(m_checks.size() <= 1) @@ -149,9 +144,8 @@ int32_t sinsp_filter_expression::get_expr_boolop() const /////////////////////////////////////////////////////////////////////////////// // sinsp_filter implementation /////////////////////////////////////////////////////////////////////////////// -sinsp_filter::sinsp_filter(sinsp *inspector) +sinsp_filter::sinsp_filter() { - m_inspector = inspector; m_filter = std::make_unique(); m_curexpr = m_filter.get(); } @@ -193,25 +187,31 @@ void sinsp_filter::add_check(std::unique_ptr chk) /////////////////////////////////////////////////////////////////////////////// sinsp_filter_compiler::sinsp_filter_compiler( sinsp* inspector, - const std::string& fltstr) + const std::string& fltstr, + std::shared_ptr cache_factory) : m_flt_str(fltstr), - m_factory(std::make_shared(inspector, m_default_filterlist)) + m_factory(std::make_shared(inspector, m_default_filterlist)), + m_cache_factory(cache_factory) { } sinsp_filter_compiler::sinsp_filter_compiler( std::shared_ptr factory, - const std::string& fltstr) + const std::string& fltstr, + std::shared_ptr cache_factory) : m_flt_str(fltstr), - m_factory(factory) + m_factory(factory), + m_cache_factory(cache_factory) { } sinsp_filter_compiler::sinsp_filter_compiler( std::shared_ptr factory, - const libsinsp::filter::ast::expr* fltast) + const libsinsp::filter::ast::expr* fltast, + std::shared_ptr cache_factory) : m_flt_ast(fltast), - m_factory(factory) + m_factory(factory), + m_cache_factory(cache_factory) { } @@ -235,9 +235,16 @@ std::unique_ptr sinsp_filter_compiler::compile() } } + // make sure the cache factory is all set + if (!m_cache_factory) + { + // by default, use a factory that enables caching + m_cache_factory = std::make_shared(); + } + // create new filter using factory, // setup compiler state and start compilation - m_filter = m_factory->new_filter(); + m_filter = std::make_unique(); m_last_boolop = BO_NONE; m_last_node_field = nullptr; m_last_node_field_is_plugin = false; @@ -325,10 +332,20 @@ void sinsp_filter_compiler::visit(const libsinsp::filter::ast::unary_check_expr* { throw sinsp_exception("filter error: missing field in left-hand of unary check"); } + auto check = std::move(m_last_node_field); check->m_cmpop = str_to_cmpop(e->op); check->m_boolop = m_last_boolop; check_op_type_compatibility(*check); + + // install cache in the check + sinsp_filter_cache_factory::node_info_t node_info; + node_info.m_field = check->get_transformed_field_info(); + check->m_cache_metrics = m_cache_factory->new_metrics(e->left.get(), node_info); + check->m_extract_cache = m_cache_factory->new_extract_cache(e->left.get(), node_info); + node_info.m_compare_operator = check->m_cmpop; + check->m_compare_cache = m_cache_factory->new_compare_cache(e, node_info); + m_filter->add_check(std::move(check)); } @@ -364,6 +381,25 @@ void sinsp_filter_compiler::visit(const libsinsp::filter::ast::binary_check_expr auto left_from_plugin = m_last_node_field_is_plugin; auto check = std::move(m_last_node_field); + + // install cache on left-hand side extraction field + sinsp_filter_cache_factory::node_info_t node_info; + node_info.m_field = check->get_transformed_field_info(); + check->m_cache_metrics = m_cache_factory->new_metrics(e->left.get(), node_info); + check->m_extract_cache = m_cache_factory->new_extract_cache(e->left.get(), node_info); + + // if the extraction comes from a plugin-implemented ield, then + // we need to add a storage transformer as the cache may end up storing a + // shallow copy of the value pointers that are not valid anymore. Note that + // this should not change the right field's eligibility for caching, as + // the storage transformer does not alter the field's info. + auto left_has_storage = false; + if (left_from_plugin && check->m_extract_cache) + { + left_has_storage = true; + check->add_transformer(filter_transformer_type::FTR_STORAGE); + } + check->m_cmpop = str_to_cmpop(e->op); check->m_boolop = m_last_boolop; check_op_type_compatibility(*check); @@ -390,12 +426,42 @@ void sinsp_filter_compiler::visit(const libsinsp::filter::ast::binary_check_expr // * if yes, check if they are associated with the same plugin instance, otherwise, this is not an issue. We use the plugin name // to understand if the plugin is the same. // * if yes, add the `FTR_STORAGE` transformer to the lhs filter check. + // + // Note, adding a storage layer on only one of the two sides of the comparison is enough to solve the problem. + // + // However, we may have already added a storage modifier to the left field due to issues with caching, + // in which case we are good already. auto right_from_plugin = m_last_node_field_is_plugin; - if (left_from_plugin && right_from_plugin) + if (!left_has_storage && left_from_plugin && right_from_plugin) { check->add_transformer(filter_transformer_type::FTR_STORAGE); } + // install cache on right-hand side extraction field + auto prev_left_field_info = node_info.m_field; + node_info.m_field = m_last_node_field->get_transformed_field_info(); + m_last_node_field->m_cache_metrics = m_cache_factory->new_metrics(e->right.get(), node_info); + // note: the `val(...)` transformer is a no-op and can be ignored for better extract cache reusage + const auto* cacheable_expr = e->right.get(); + if (const auto* val_transf_expr = dynamic_cast(cacheable_expr); + val_transf_expr != nullptr && val_transf_expr->transformer == "val") + { + cacheable_expr = val_transf_expr->value.get(); + } + m_last_node_field->m_extract_cache = m_cache_factory->new_extract_cache(cacheable_expr, node_info); + + // similarly as above, if the right-hand side extraction comes from a + // plugin-implemented field, then we need to add an additional storage + // layer on it as well + if (right_from_plugin && m_last_node_field->m_extract_cache) + { + m_last_node_field->add_transformer(filter_transformer_type::FTR_STORAGE); + } + + // restore node info and set rhs one for later cache installations + node_info.m_right_field = m_last_node_field->get_transformed_field_info(); + node_info.m_field = prev_left_field_info; + // We found another field as right-hand side of the comparison check->add_filter_value(std::move(m_last_node_field)); } @@ -413,6 +479,13 @@ void sinsp_filter_compiler::visit(const libsinsp::filter::ast::binary_check_expr add_filtercheck_value(check.get(), i, m_field_values[i]); } } + + // install cache in the check comparison + // note: we don't need to re-install the metrics as the check is implemented + // by the same object responsible of the left-hand side field extraction + node_info.m_compare_operator = check->m_cmpop; + check->m_compare_cache = m_cache_factory->new_compare_cache(e, node_info); + m_filter->add_check(std::move(check)); } @@ -534,11 +607,6 @@ sinsp_filter_factory::sinsp_filter_factory(sinsp *inspector, { } -std::unique_ptr sinsp_filter_factory::new_filter() const -{ - return std::make_unique(m_inspector); -} - std::unique_ptr sinsp_filter_factory::new_filtercheck(std::string_view fldname) const { return m_available_checks.new_filter_check_from_fldname(fldname, diff --git a/userspace/libsinsp/filter.h b/userspace/libsinsp/filter.h index e65bc2be01..16d1426227 100644 --- a/userspace/libsinsp/filter.h +++ b/userspace/libsinsp/filter.h @@ -58,7 +58,6 @@ class sinsp_filter_expression : public sinsp_filter_check } bool compare(sinsp_evt*) override; - bool extract(sinsp_evt*, std::vector& values, bool sanitize_strings = true) override; void add_check(std::unique_ptr chk); @@ -81,7 +80,7 @@ class sinsp_filter_expression : public sinsp_filter_check class SINSP_PUBLIC sinsp_filter { public: - sinsp_filter(sinsp* inspector); + sinsp_filter(); virtual ~sinsp_filter() = default; bool run(sinsp_evt *evt); @@ -94,8 +93,6 @@ class SINSP_PUBLIC sinsp_filter private: sinsp_filter_expression* m_curexpr; - - sinsp* m_inspector; }; class sinsp_filter_factory @@ -163,8 +160,6 @@ class sinsp_filter_factory virtual ~sinsp_filter_factory() = default; - virtual std::unique_ptr new_filter() const; - virtual std::unique_ptr new_filtercheck(std::string_view fldname) const; virtual std::list get_fields() const; @@ -206,7 +201,8 @@ class SINSP_PUBLIC sinsp_filter_compiler: */ sinsp_filter_compiler( sinsp* inspector, - const std::string& fltstr); + const std::string& fltstr, + std::shared_ptr cache_factory = nullptr); /*! \brief Constructs the compiler @@ -217,7 +213,8 @@ class SINSP_PUBLIC sinsp_filter_compiler: */ sinsp_filter_compiler( std::shared_ptr factory, - const std::string& fltstr); + const std::string& fltstr, + std::shared_ptr cache_factory = nullptr); /*! \brief Constructs the compiler @@ -229,7 +226,8 @@ class SINSP_PUBLIC sinsp_filter_compiler: */ sinsp_filter_compiler( std::shared_ptr factory, - const libsinsp::filter::ast::expr* fltast); + const libsinsp::filter::ast::expr* fltast, + std::shared_ptr cache_factory = nullptr); /*! \brief Builds a filtercheck tree and bundles it in sinsp_filter @@ -272,6 +270,7 @@ class SINSP_PUBLIC sinsp_filter_compiler: std::shared_ptr m_internal_flt_ast; const libsinsp::filter::ast::expr* m_flt_ast = nullptr; std::shared_ptr m_factory; + std::shared_ptr m_cache_factory; std::vector m_warnings; sinsp_filter_check_list m_default_filterlist; }; diff --git a/userspace/libsinsp/filter_cache.h b/userspace/libsinsp/filter_cache.h new file mode 100644 index 0000000000..8984491915 --- /dev/null +++ b/userspace/libsinsp/filter_cache.h @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +/** + * @brief Represents a value extracted when evaluating a filter +*/ +struct extract_value_t +{ + uint8_t* ptr = nullptr; + uint32_t len = 0; +}; + +/** + * @brief Represents a cache value storage for value extraction in filters +*/ +class sinsp_filter_extract_cache +{ +public: + inline void reset() + { + m_evtnum = UINT64_MAX; + } + + inline bool is_valid(const sinsp_evt* evt) const + { + return evt->get_num() != 0 && m_evtnum != UINT64_MAX && evt->get_num() == m_evtnum; + } + + inline void update(const sinsp_evt* evt, bool res, const std::vector& values, bool deepcopy = false) + { + m_evtnum = evt->get_num(); + m_result = res; + if (!deepcopy) + { + m_values = values; + return; + } + + auto len = m_values.size(); + m_values.resize(len); + resize_if_smaller(m_storage, len); + for (size_t i = 0; i < len; i++) + { + auto v = values[i]; + resize_if_smaller(m_storage[i], v.len); + if (v.len > 0) + { + ASSERT(v.ptr != nullptr); + memcpy(m_storage[i].data(), v.ptr, v.len); + } + v.ptr = m_storage[i].data(); + m_values[i] = v; + } + } + + inline const std::vector& values() const + { + return m_values; + } + + inline bool result() const + { + return m_result; + } + +private: + template static inline void resize_if_smaller(T& v, size_t len) + { + if (v.size() < len) + { + v.resize(len); + } + } + + uint64_t m_evtnum = UINT64_MAX; + bool m_result = false; + std::vector m_values; + std::vector> m_storage; +}; + +/** + * @brief Represents a cache value storage for comparisons in filters +*/ +class sinsp_filter_compare_cache +{ +public: + inline void reset() + { + m_evtnum = UINT64_MAX; + } + + inline bool is_valid(const sinsp_evt* evt) const + { + return evt->get_num() != 0 && m_evtnum != UINT64_MAX && evt->get_num() == m_evtnum; + } + + inline void update(const sinsp_evt* evt, bool res) + { + m_evtnum = evt->get_num(); + m_result = res; + } + + inline bool result() const + { + return m_result; + } + +private: + uint64_t m_evtnum = UINT64_MAX; + bool m_result = false; +}; + +/** + * @brief Represents a set of metrics and counters related to the usage + * of cache optimizations in filters +*/ +struct sinsp_filter_cache_metrics +{ + inline void reset() + { + m_num_extract = 0; + m_num_extract_cache = 0; + m_num_compare = 0; + m_num_compare_cache = 0; + } + + // The number of times extract() was called + uint64_t m_num_extract = 0; + + // The number of times extract() could use a cached value + uint64_t m_num_extract_cache = 0; + + // The number of times compare() was called + uint64_t m_num_compare = 0; + + // The number of times compare() could use a cached value + uint64_t m_num_compare_cache = 0; +}; + +/** + * @brief Interface for factories of filter cache objects +*/ +class sinsp_filter_cache_factory +{ +public: + using ast_expr_t = libsinsp::filter::ast::expr; + + /** + * @brief Input struct representing information about a filter AST node + */ + struct node_info_t + { + // For nodes representing a field extraction, the information about the field. + // For nodes with a comparison, the information about the left-hand side field. + // Left to null in all other cases. + const filtercheck_field_info* m_field = nullptr; + + // For nodes with a comparison, the information about the right-hand side field. + // Left to null in all other cases. + const filtercheck_field_info* m_right_field = nullptr; + + // For nodes with a comparison, the comparison operator. + // Left to CO_NONE in all other cases. + cmpop m_compare_operator = cmpop::CO_NONE; + }; + + virtual ~sinsp_filter_cache_factory() = default; + + /** + * @brief Resets the state of the given factory instance + */ + virtual void reset() + { + // do nothing + } + + /** + * @brief Given the provided AST node of a filter expression, returns a pointer + * to an extraction cache usable in the compiled filter derived from that node. + * Can return `nullptr` in case no cache is available for the node. + */ + virtual std::shared_ptr new_extract_cache(const ast_expr_t* e, node_info_t& info) + { + return nullptr; + } + + /** + * @brief Given the provided AST node of a filter expression, returns a pointer + * to an comparison cache usable in the compiled filter derived from that node. + * Can return `nullptr` in case no cache is available for the node. + */ + virtual std::shared_ptr new_compare_cache(const ast_expr_t* e, node_info_t& info) + { + return nullptr; + } + + /** + * @brief Given the provided AST node of a filter expression, returns a pointer + * to an cache metrics storage usable in the compiled filter derived from that node. + * Can return `nullptr` in case no metrics are available for the node. + */ + virtual std::shared_ptr new_metrics(const ast_expr_t* e, node_info_t& info) + { + return nullptr; + } +}; + +/** + * @brief An implementation of sinsp_filter_cache_factory that creates shared + * cache objects indexed by the string representation of AST expressions + * (obtained through libsinsp::filter::ast::as_string). +*/ +class exprstr_sinsp_filter_cache_factory: public sinsp_filter_cache_factory +{ +public: + virtual ~exprstr_sinsp_filter_cache_factory() = default; + + void reset() override + { + m_extract_caches.clear(); + m_compare_caches.clear(); + } + + std::shared_ptr new_extract_cache(const ast_expr_t* e, node_info_t& info) override + { + // avoid caching fields for which it would be unsafe + if (info.m_field && info.m_field->m_type == PT_IPNET) + { + return nullptr; + } + auto key = libsinsp::filter::ast::as_string(e); + return get_or_insert_ptr(key, m_extract_caches); + } + + std::shared_ptr new_compare_cache(const ast_expr_t* e, node_info_t& info) override + { + // avoid caching fields for which it would be unsafe + if (info.m_field && info.m_field->m_type == PT_IPNET) + { + return nullptr; + } + auto key = libsinsp::filter::ast::as_string(e); + return get_or_insert_ptr(key, m_compare_caches); + } + + inline const std::unordered_map>& extract_cache() const + { + return m_extract_caches; + } + + inline const std::unordered_map>& compare_cache() const + { + return m_compare_caches; + } + +private: + template + static inline std::shared_ptr get_or_insert_ptr( + const std::string& key, + std::unordered_map>& map) + { + auto it = map.find(key); + if (it == map.end()) + { + return map.emplace(key, std::make_shared()).first->second; + } + return it->second; + } + + std::unordered_map> m_extract_caches; + std::unordered_map> m_compare_caches; +}; diff --git a/userspace/libsinsp/filter_field.h b/userspace/libsinsp/filter_field.h new file mode 100644 index 0000000000..955b8acdb2 --- /dev/null +++ b/userspace/libsinsp/filter_field.h @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +/** + * @brief Flags used for describing a field used in a filter or in a formatter +*/ +enum filtercheck_field_flags +{ + EPF_NONE = 0, + EPF_FILTER_ONLY = 1 << 0, ///< this field can only be used as a filter. + EPF_PRINT_ONLY = 1 << 1, ///< this field can only be printed. + EPF_ARG_REQUIRED = 1 << 2, ///< this field includes an argument, under the form 'property.argument'. + EPF_TABLE_ONLY = 1 << 3, ///< this field is designed to be used in a table and won't appear in the field listing. + EPF_INFO = 1 << 4, ///< this field contains summary information about the event. + EPF_CONVERSATION = 1 << 5, ///< this field can be used to identify conversations. + EPF_IS_LIST = 1 << 6, ///< this field is a list of values. + EPF_ARG_ALLOWED = 1 << 7, ///< this field optionally includes an argument. + EPF_ARG_INDEX = 1 << 8, ///< this field accepts numeric arguments. + EPF_ARG_KEY = 1 << 9, ///< this field accepts string arguments. + EPF_DEPRECATED = 1 << 10,///< this field is deprecated. + EPF_NO_TRANSFORMER = 1 << 11,///< this field cannot have a field transformer. + EPF_NO_RHS = 1 << 12,///< this field cannot have a right-hand side filter check, and cannot be used as a right-hand side filter check. +}; + +/** + * @brief Information about field using in a filter or in a formatter +*/ +struct filtercheck_field_info +{ + ppm_param_type m_type = ppm_param_type::PT_NONE; ///< Field type. + uint32_t m_flags = 0; ///< Field flags. + ppm_print_format m_print_format = ppm_print_format::PF_NA; ///< If this is a numeric field, this flag specifies if it should be rendered as octal, decimal or hex. + std::string m_name; ///< Field name. + std::string m_display; ///< Field display name (short description). May be empty. + std::string m_description; ///< Field description. + + // + // Return true if this field must have an argument + // + inline bool is_arg_required() const + { + return m_flags & EPF_ARG_REQUIRED; + } + + // + // Return true if this field can optionally have an argument + // + inline bool is_arg_allowed() const + { + return m_flags & EPF_ARG_REQUIRED; + } + + // + // Returns true if this field can have an argument, either + // optionally or mandatorily + // + inline bool is_arg_supported() const + { + return (m_flags & EPF_ARG_REQUIRED) || (m_flags & EPF_ARG_ALLOWED); + } + + // + // Returns true if this field is a list of values + // + inline bool is_list() const + { + return m_flags & EPF_IS_LIST; + } + + // + // Returns true if this filter check can support a rhs filter check instead of a const value. + // + inline bool is_rhs_field_supported() const + { + return !(m_flags & EPF_NO_RHS); + } + + // + // Returns true if this filter check can support an extraction transformer on it. + // + inline bool is_transformer_supported() const + { + return !(m_flags & EPF_NO_TRANSFORMER); + } +}; + +/** + * @brief Information about a group of filter/formatting fields. +*/ +class filter_check_info +{ +public: + enum flags: uint8_t + { + FL_NONE = 0, + FL_HIDDEN = (1 << 0), ///< This filter check class won't be shown by fields/filter listings. + }; + + std::string m_name; ///< Field class name. + std::string m_shortdesc; ///< short (< 10 words) description of this filtercheck. Can be blank. + std::string m_desc; ///< Field class description. + int32_t m_nfields = 0; ///< Number of fields in this field group. + const filtercheck_field_info* m_fields = nullptr; ///< Array containing m_nfields field descriptions. + uint32_t m_flags = flags::FL_NONE; +}; diff --git a/userspace/libsinsp/plugin.cpp b/userspace/libsinsp/plugin.cpp index cadd90a642..76aa9877f4 100755 --- a/userspace/libsinsp/plugin.cpp +++ b/userspace/libsinsp/plugin.cpp @@ -542,9 +542,9 @@ bool sinsp_plugin::resolve_dylib_symbols(std::string &errstr) string("error in plugin ") + name() + ": field JSON entry has no desc"); } - strlcpy(tf.m_name, fname.c_str(), sizeof(tf.m_name)); - strlcpy(tf.m_display, fdisplay.c_str(), sizeof(tf.m_display)); - strlcpy(tf.m_description, fdesc.c_str(), sizeof(tf.m_description)); + tf.m_name = fname; + tf.m_display = fdisplay; + tf.m_description = fdesc; tf.m_print_format = PF_DEC; if(s_pt_lut.find(ftype) != s_pt_lut.end()) { tf.m_type = s_pt_lut.at(ftype); diff --git a/userspace/libsinsp/plugin_filtercheck.cpp b/userspace/libsinsp/plugin_filtercheck.cpp index 0d46ee4c9d..37bf44f877 100755 --- a/userspace/libsinsp/plugin_filtercheck.cpp +++ b/userspace/libsinsp/plugin_filtercheck.cpp @@ -126,7 +126,7 @@ std::unique_ptr sinsp_filter_check_plugin::allocate_new() return std::make_unique(*this); } -bool sinsp_filter_check_plugin::extract(sinsp_evt *evt, std::vector& values, bool sanitize_strings) +bool sinsp_filter_check_plugin::extract_nocache(sinsp_evt *evt, std::vector& values, bool sanitize_strings) { // reject the event if it comes from an unknown event source if (evt->get_source_idx() == sinsp_no_event_source_idx) @@ -166,7 +166,7 @@ bool sinsp_filter_check_plugin::extract(sinsp_evt *evt, std::vectorm_fields[m_field_id].m_name; + efield.field = m_info->m_fields[m_field_id].m_name.c_str(); efield.arg_key = m_arg_key; efield.arg_index = m_arg_index; efield.arg_present = m_arg_present; @@ -218,7 +218,7 @@ bool sinsp_filter_check_plugin::extract(sinsp_evt *evt, std::vector& values, bool sanitize_strings = true) override; diff --git a/userspace/libsinsp/sinsp_filter_transformer.h b/userspace/libsinsp/sinsp_filter_transformer.h index e087a04fa6..697d912888 100644 --- a/userspace/libsinsp/sinsp_filter_transformer.h +++ b/userspace/libsinsp/sinsp_filter_transformer.h @@ -20,12 +20,7 @@ limitations under the License. #include #include #include - -struct extract_value_t -{ - uint8_t* ptr = nullptr; - uint32_t len = 0; -}; +#include enum filter_transformer_type: uint8_t { diff --git a/userspace/libsinsp/sinsp_filtercheck.cpp b/userspace/libsinsp/sinsp_filtercheck.cpp index 28f1dc37bb..a04711d8fa 100644 --- a/userspace/libsinsp/sinsp_filtercheck.cpp +++ b/userspace/libsinsp/sinsp_filtercheck.cpp @@ -636,7 +636,7 @@ int32_t sinsp_filter_check::parse_field_name(std::string_view str, bool alloc_st for(int32_t j = 0; j != m_info->m_nfields; ++j) { auto& fld = m_info->m_fields[j]; - int32_t fldlen = (int32_t)strlen(fld.m_name); + int32_t fldlen = (int32_t)fld.m_name.size(); if(fldlen <= max_fldlen) { continue; @@ -1095,75 +1095,63 @@ bool sinsp_filter_check::extract(sinsp_evt *evt, std::vector& v m_cache_metrics->m_num_extract++; } - // Never cache extractions for fields that contain arguments. - if(m_extraction_cache_entry != NULL && !get_transformed_field_info()->is_arg_supported()) + // no cache is installed, so just default to non-cached extraction + if (!m_extract_cache) { - uint64_t en = ((sinsp_evt *)evt)->get_num(); - - if(en != m_extraction_cache_entry->m_evtnum) - { - m_extraction_cache_entry->m_evtnum = en; - auto ok = extract_nocache(evt, m_extraction_cache_entry->m_res, sanitize_strings); - ok = ok && apply_transformers(m_extraction_cache_entry->m_res); - if (!ok) - { - // clear results in case something fails - m_extraction_cache_entry->m_res.clear(); - } - } - else - { - if(m_cache_metrics != NULL) - { - m_cache_metrics->m_num_extract_cache++; - } - } - - // Shallow-copy the m_cached values to values - values = m_extraction_cache_entry->m_res; + // extract values and apply transformers on top of them + return extract_nocache(evt, values, sanitize_strings) && apply_transformers(values); + } - return !m_extraction_cache_entry->m_res.empty(); + // cache is not valid for this event, so we perform a non-cached extraction + // and update it for the next time. We cache both failed and succeeded extractions + if (!m_extract_cache->is_valid(evt)) + { + // for now, we support only shallow copies of cached values for performance + // gains -- we rely on each filtercheck to keep owning the result values + // across different extractions + bool deepcopy = false; + auto res = extract_nocache(evt, values, sanitize_strings) && apply_transformers(values); + m_extract_cache->update(evt, res, values, deepcopy); + return res; } - else + + // cache hit, so copy the values, update the metrics, and return + values = m_extract_cache->values(); + if(m_cache_metrics != NULL) { - // extract values and apply transformers on top of them - return extract_nocache(evt, values, sanitize_strings) && apply_transformers(values); + m_cache_metrics->m_num_extract_cache++; } + return m_extract_cache->result(); } bool sinsp_filter_check::compare(sinsp_evt* evt) { - if(m_cache_metrics != NULL) + if (m_cache_metrics != NULL) { - m_cache_metrics->m_num_eval++; + m_cache_metrics->m_num_compare++; } - // Never cache extractions for fields that contain arguments. - if (m_eval_cache_entry != NULL - && !get_transformed_field_info()->is_arg_supported() - && !(has_filtercheck_value() && m_rhs_filter_check->get_transformed_field_info()->is_arg_supported())) + // no cache is installed, so just default to non-cached comparison + if (!m_compare_cache) { - uint64_t en = evt->get_num(); - - if(en != m_eval_cache_entry->m_evtnum) - { - m_eval_cache_entry->m_evtnum = en; - m_eval_cache_entry->m_res = compare_nocache(evt); - } - else - { - if(m_cache_metrics != NULL) - { - m_cache_metrics->m_num_eval_cache++; - } - } + return compare_nocache(evt); + } - return m_eval_cache_entry->m_res; + // cache is not valid for this event, so we perform a non-cached comparison + // and update it for the next time. We cache both failed and succeeded comparison + if (!m_compare_cache->is_valid(evt)) + { + auto res = compare_nocache(evt); + m_compare_cache->update(evt, res); + return res; } - else + + // cache hit, so copy the values, update the metrics, and return + if (m_cache_metrics != NULL) { - return compare_nocache(evt); + m_cache_metrics->m_num_compare_cache++; } + return m_compare_cache->result(); } bool sinsp_filter_check::compare_nocache(sinsp_evt* evt) diff --git a/userspace/libsinsp/sinsp_filtercheck.h b/userspace/libsinsp/sinsp_filtercheck.h index 18d30b9f09..1c195c2667 100644 --- a/userspace/libsinsp/sinsp_filtercheck.h +++ b/userspace/libsinsp/sinsp_filtercheck.h @@ -22,6 +22,8 @@ limitations under the License. #include #include #include +#include +#include #include #include @@ -47,118 +49,6 @@ namespace std std::string to_string(boolop); } -class check_extraction_cache_entry -{ -public: - uint64_t m_evtnum = UINT64_MAX; - std::vector m_res; -}; - -class check_eval_cache_entry -{ -public: - uint64_t m_evtnum = UINT64_MAX; - bool m_res = false; -}; - -class check_cache_metrics -{ -public: - // The number of times extract() was called - uint64_t m_num_extract = 0; - - // The number of times extract() could use a cached value - uint64_t m_num_extract_cache = 0; - - // The number of times compare() was called - uint64_t m_num_eval = 0; - - // The number of times compare() could use a cached value - uint64_t m_num_eval_cache = 0; -}; - -/*! - \brief Information about a filter/formatting field. -*/ -struct filtercheck_field_info -{ - ppm_param_type m_type = PT_NONE; ///< Field type. - uint32_t m_flags = 0; ///< Field flags. - ppm_print_format m_print_format = PF_NA; ///< If this is a numeric field, this flag specifies if it should be rendered as octal, decimal or hex. - char m_name[64]; ///< Field name. - char m_display[64]; ///< Field display name (short description). May be empty. - char m_description[1024]; ///< Field description. - - // - // Return true if this field must have an argument - // - inline bool is_arg_required() const - { - return m_flags & EPF_ARG_REQUIRED; - } - - // - // Return true if this field can optionally have an argument - // - inline bool is_arg_allowed() const - { - return m_flags & EPF_ARG_REQUIRED; - } - - // - // Returns true if this field can have an argument, either - // optionally or mandatorily - // - inline bool is_arg_supported() const - { - return (m_flags & EPF_ARG_REQUIRED) ||(m_flags & EPF_ARG_ALLOWED); - } - - // - // Returns true if this field is a list of values - // - inline bool is_list() const - { - return m_flags & EPF_IS_LIST; - } - - // - // Returns true if this filter check can support a rhs filter check instead of a const value. - // - inline bool is_rhs_field_supported() const - { - return !(m_flags & EPF_NO_RHS); - } - - // - // Returns true if this filter check can support an extraction transformer on it. - // - inline bool is_transformer_supported() const - { - return !(m_flags & EPF_NO_TRANSFORMER); - } -}; - -/*! - \brief Information about a group of filter/formatting fields. -*/ -class filter_check_info -{ -public: - enum flags: uint8_t - { - FL_NONE = 0, - FL_HIDDEN = (1 << 0), ///< This filter check class won't be shown by fields/filter listings. - }; - - std::string m_name; ///< Field class name. - std::string m_shortdesc; ///< short (< 10 words) description of this filtercheck. Can be blank. - std::string m_desc; ///< Field class description. - int32_t m_nfields = 0; ///< Number of fields in this field group. - const filtercheck_field_info* m_fields = nullptr; ///< Array containing m_nfields field descriptions. - uint32_t m_flags = FL_NONE; -}; - /////////////////////////////////////////////////////////////////////////////// // The filter check interface // NOTE: in order to add a new type of filter check, you need to add a class for @@ -270,7 +160,7 @@ class sinsp_filter_check // Subclasses are meant to either override this, or the single-valued extract method. // // \param values [out] the values extracted from the filter check - virtual bool extract(sinsp_evt*, std::vector& values, bool sanitize_strings = true); + bool extract(sinsp_evt*, std::vector& values, bool sanitize_strings = true); // // Compare the field with the constant value obtained from parse_filter_value() @@ -290,9 +180,9 @@ class sinsp_filter_check sinsp* m_inspector = nullptr; std::vector m_extracted_values; - check_eval_cache_entry* m_eval_cache_entry = nullptr; - check_extraction_cache_entry* m_extraction_cache_entry = nullptr; - check_cache_metrics *m_cache_metrics = nullptr; + std::shared_ptr m_compare_cache = nullptr; + std::shared_ptr m_extract_cache = nullptr; + std::shared_ptr m_cache_metrics = nullptr; boolop m_boolop = BO_NONE; cmpop m_cmpop = CO_NONE; @@ -323,7 +213,7 @@ class sinsp_filter_check // Subclasses are meant to either override this, or the multi-valued extract method. // // \param values [out] the values extracted from the filter check - bool extract_nocache(sinsp_evt *evt, std::vector& values, bool sanitize_strings = true); + virtual bool extract_nocache(sinsp_evt *evt, std::vector& values, bool sanitize_strings = true); // \param len [out] length in bytes for the returned value virtual uint8_t* extract_single(sinsp_evt*, uint32_t* len, bool sanitize_strings = true); diff --git a/userspace/libsinsp/sinsp_filtercheck_fd.cpp b/userspace/libsinsp/sinsp_filtercheck_fd.cpp index e3e61cfd79..3fa9f07d19 100644 --- a/userspace/libsinsp/sinsp_filtercheck_fd.cpp +++ b/userspace/libsinsp/sinsp_filtercheck_fd.cpp @@ -418,7 +418,7 @@ uint8_t* sinsp_filter_check_fd::extract_from_null_fd(sinsp_evt *evt, uint32_t* l } } -bool sinsp_filter_check_fd::extract(sinsp_evt *evt, std::vector& values, bool sanitize_strings) +bool sinsp_filter_check_fd::extract_nocache(sinsp_evt *evt, std::vector& values, bool sanitize_strings) { values.clear(); @@ -463,7 +463,7 @@ bool sinsp_filter_check_fd::extract(sinsp_evt *evt, std::vector return true; } - return sinsp_filter_check::extract(evt, values, sanitize_strings); + return sinsp_filter_check::extract_nocache(evt, values, sanitize_strings); } uint8_t* sinsp_filter_check_fd::extract_single(sinsp_evt *evt, uint32_t* len, bool sanitize_strings) diff --git a/userspace/libsinsp/sinsp_filtercheck_fd.h b/userspace/libsinsp/sinsp_filtercheck_fd.h index 16f86bef4a..0f94bbeeb8 100644 --- a/userspace/libsinsp/sinsp_filtercheck_fd.h +++ b/userspace/libsinsp/sinsp_filtercheck_fd.h @@ -76,9 +76,9 @@ class sinsp_filter_check_fd : public sinsp_filter_check std::unique_ptr allocate_new() override; int32_t parse_field_name(std::string_view, bool alloc_state, bool needed_for_filtering) override; - bool extract(sinsp_evt*, std::vector& values, bool sanitize_strings = true) override; protected: + bool extract_nocache(sinsp_evt*, std::vector& values, bool sanitize_strings = true) override; uint8_t* extract_single(sinsp_evt*, uint32_t* len, bool sanitize_strings = true) override; bool compare_nocache(sinsp_evt*) override; diff --git a/userspace/libsinsp/test/filter_compiler.ut.cpp b/userspace/libsinsp/test/filter_compiler.ut.cpp index a489d0e3e5..e72b056c58 100644 --- a/userspace/libsinsp/test/filter_compiler.ut.cpp +++ b/userspace/libsinsp/test/filter_compiler.ut.cpp @@ -57,7 +57,7 @@ class mock_compiler_filter_check : public sinsp_filter_check throw sinsp_exception("unexpected right-hand side filter comparison"); } - inline bool extract(sinsp_evt *e, vector& v, bool) override + inline bool extract_nocache(sinsp_evt *e, vector& v, bool) override { return false; } @@ -73,11 +73,6 @@ class mock_compiler_filter_factory: public sinsp_filter_factory public: mock_compiler_filter_factory(sinsp *inspector): sinsp_filter_factory(inspector, m_filterlist) {} - inline std::unique_ptr new_filter() const override - { - return std::make_unique(m_inspector); - } - inline std::unique_ptr new_filtercheck(std::string_view fldname) const override { return std::make_unique(); diff --git a/userspace/libsinsp/test/filterchecks/mock.cpp b/userspace/libsinsp/test/filterchecks/mock.cpp index 6902e366b3..4b1a971f95 100644 --- a/userspace/libsinsp/test/filterchecks/mock.cpp +++ b/userspace/libsinsp/test/filterchecks/mock.cpp @@ -76,7 +76,7 @@ class sinsp_filter_check_mock : public sinsp_filter_check } protected: - bool extract(sinsp_evt* evt, std::vector& values, bool sanitize_strings) override + bool extract_nocache(sinsp_evt* evt, std::vector& values, bool sanitize_strings) override { static const char* list_value_1 = "value1"; static const char* list_value_2 = "charbuf"; @@ -96,7 +96,7 @@ class sinsp_filter_check_mock : public sinsp_filter_check values.push_back(val2); return true; } - return sinsp_filter_check::extract(evt, values, sanitize_strings); + return sinsp_filter_check::extract_nocache(evt, values, sanitize_strings); } uint8_t* extract_single(sinsp_evt*, uint32_t* len, bool sanitize_strings = true) override