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