LCOV - code coverage report
Current view: top level - source/common/stats - stats_matcher_impl.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 18 62 29.0 %
Date: 2024-01-05 06:35:25 Functions: 3 8 37.5 %

          Line data    Source code
       1             : #include "source/common/stats/stats_matcher_impl.h"
       2             : 
       3             : #include <regex>
       4             : #include <string>
       5             : 
       6             : #include "envoy/config/metrics/v3/stats.pb.h"
       7             : 
       8             : #include "source/common/common/utility.h"
       9             : 
      10             : #include "absl/strings/match.h"
      11             : 
      12             : namespace Envoy {
      13             : namespace Stats {
      14             : 
      15             : // TODO(ambuc): Refactor this into common/matchers.cc, since StatsMatcher is really just a thin
      16             : // wrapper around what might be called a StringMatcherList.
      17             : StatsMatcherImpl::StatsMatcherImpl(const envoy::config::metrics::v3::StatsConfig& config,
      18             :                                    SymbolTable& symbol_table)
      19         134 :     : symbol_table_(symbol_table), stat_name_pool_(std::make_unique<StatNamePool>(symbol_table)) {
      20             : 
      21         134 :   switch (config.stats_matcher().stats_matcher_case()) {
      22           0 :   case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kRejectAll:
      23             :     // In this scenario, there are no matchers to store.
      24           0 :     is_inclusive_ = !config.stats_matcher().reject_all();
      25           0 :     break;
      26           0 :   case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kInclusionList:
      27             :     // If we have an inclusion list, we are being default-exclusive.
      28           0 :     for (const auto& stats_matcher : config.stats_matcher().inclusion_list().patterns()) {
      29           0 :       matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher));
      30           0 :       optimizeLastMatcher();
      31           0 :     }
      32           0 :     is_inclusive_ = false;
      33           0 :     break;
      34           0 :   case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kExclusionList:
      35             :     // If we have an exclusion list, we are being default-inclusive.
      36           0 :     for (const auto& stats_matcher : config.stats_matcher().exclusion_list().patterns()) {
      37           0 :       matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher));
      38           0 :       optimizeLastMatcher();
      39           0 :     }
      40           0 :     FALLTHRU;
      41         134 :   default:
      42             :     // No matcher was supplied, so we default to inclusion.
      43         134 :     is_inclusive_ = true;
      44         134 :     break;
      45         134 :   }
      46         134 : }
      47             : 
      48             : // If the last string-matcher added is a case-sensitive prefix match, and the
      49             : // prefix ends in ".", then this drops that match and adds it to a list of
      50             : // prefixes. This is beneficial because token prefixes can be handled more
      51             : // efficiently as a StatName without requiring conversion to a string.
      52             : //
      53             : // In the future, other matcher patterns could be optimized in a similar way,
      54             : // such as:
      55             : //   * suffixes that begin with "."
      56             : //   * exact-matches
      57             : //   * substrings that begin and end with "."
      58             : //
      59             : // These are left unoptimized for the moment to keep the code-change simpler,
      60             : // and because we haven't observed an acute performance need to optimize those
      61             : // other patterns yet.
      62           0 : void StatsMatcherImpl::optimizeLastMatcher() {
      63           0 :   std::string prefix;
      64           0 :   if (matchers_.back().getCaseSensitivePrefixMatch(prefix) && absl::EndsWith(prefix, ".") &&
      65           0 :       prefix.size() > 1) {
      66           0 :     prefixes_.push_back(stat_name_pool_->add(prefix.substr(0, prefix.size() - 1)));
      67           0 :     matchers_.pop_back();
      68           0 :   }
      69           0 : }
      70             : 
      71       53227 : StatsMatcher::FastResult StatsMatcherImpl::fastRejects(StatName stat_name) const {
      72       53227 :   if (rejectsAll()) {
      73           0 :     return FastResult::Rejects;
      74           0 :   }
      75       53227 :   bool matches = fastRejectMatch(stat_name);
      76       53227 :   if ((is_inclusive_ || matchers_.empty()) && matches == is_inclusive_) {
      77             :     // We can short-circuit the slow matchers only if they are empty, or if
      78             :     // we are in inclusive-mode and we find a match.
      79           0 :     return FastResult::Rejects;
      80       53227 :   } else if (matches) {
      81           0 :     return FastResult::Matches;
      82           0 :   }
      83       53227 :   return FastResult::NoMatch;
      84       53227 : }
      85             : 
      86       53227 : bool StatsMatcherImpl::fastRejectMatch(StatName stat_name) const {
      87       53227 :   return std::any_of(prefixes_.begin(), prefixes_.end(),
      88       53227 :                      [stat_name](StatName prefix) { return stat_name.startsWith(prefix); });
      89       53227 : }
      90             : 
      91           0 : bool StatsMatcherImpl::slowRejects(FastResult fast_result, StatName stat_name) const {
      92             :   // Skip slowRejectMatch if we already have a definitive answer from fastRejects.
      93           0 :   if (fast_result != FastResult::NoMatch) {
      94           0 :     return fast_result == FastResult::Rejects;
      95           0 :   }
      96             : 
      97           0 :   const bool match = slowRejectMatch(stat_name);
      98             : 
      99             :   //  is_inclusive_ | match | return
     100             :   // ---------------+-------+--------
     101             :   //        T       |   T   |   T   Default-inclusive and matching an (exclusion) matcher, deny.
     102             :   //        T       |   F   |   F   Otherwise, allow.
     103             :   //        F       |   T   |   F   Default-exclusive and matching an (inclusion) matcher, allow.
     104             :   //        F       |   F   |   T   Otherwise, deny.
     105             :   //
     106             :   // This is an XNOR, which can be evaluated by checking for equality.
     107           0 :   return match == is_inclusive_;
     108           0 : }
     109             : 
     110           0 : bool StatsMatcherImpl::slowRejectMatch(StatName stat_name) const {
     111           0 :   if (matchers_.empty()) {
     112           0 :     return false;
     113           0 :   }
     114           0 :   std::string name = symbol_table_->toString(stat_name);
     115           0 :   return std::any_of(matchers_.begin(), matchers_.end(),
     116           0 :                      [&name](auto& matcher) { return matcher.match(name); });
     117           0 : }
     118             : 
     119             : } // namespace Stats
     120             : } // namespace Envoy

Generated by: LCOV version 1.15