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
                                   Server::Configuration::CommonFactoryContext& context)
20
10722
    : symbol_table_(symbol_table), stat_name_pool_(std::make_unique<StatNamePool>(symbol_table)) {
21

            
22
10722
  switch (config.stats_matcher().stats_matcher_case()) {
23
5
  case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kRejectAll:
24
    // In this scenario, there are no matchers to store.
25
5
    is_inclusive_ = !config.stats_matcher().reject_all();
26
5
    break;
27
19
  case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kInclusionList:
28
    // If we have an inclusion list, we are being default-exclusive.
29
28
    for (const auto& stats_matcher : config.stats_matcher().inclusion_list().patterns()) {
30
28
      matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher, context));
31
28
      optimizeLastMatcher();
32
28
    }
33
19
    is_inclusive_ = false;
34
19
    break;
35
34
  case envoy::config::metrics::v3::StatsMatcher::StatsMatcherCase::kExclusionList:
36
    // If we have an exclusion list, we are being default-inclusive.
37
45
    for (const auto& stats_matcher : config.stats_matcher().exclusion_list().patterns()) {
38
45
      matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher, context));
39
45
      optimizeLastMatcher();
40
45
    }
41
34
    FALLTHRU;
42
10698
  default:
43
    // No matcher was supplied, so we default to inclusion.
44
10698
    is_inclusive_ = true;
45
10698
    break;
46
10722
  }
47
10722
}
48

            
49
// If the last string-matcher added is a case-sensitive prefix match, and the
50
// prefix ends in ".", then this drops that match and adds it to a list of
51
// prefixes. This is beneficial because token prefixes can be handled more
52
// efficiently as a StatName without requiring conversion to a string.
53
//
54
// In the future, other matcher patterns could be optimized in a similar way,
55
// such as:
56
//   * suffixes that begin with "."
57
//   * exact-matches
58
//   * substrings that begin and end with "."
59
//
60
// These are left unoptimized for the moment to keep the code-change simpler,
61
// and because we haven't observed an acute performance need to optimize those
62
// other patterns yet.
63
73
void StatsMatcherImpl::optimizeLastMatcher() {
64
73
  std::string prefix;
65
73
  if (matchers_.back().getCaseSensitivePrefixMatch(prefix) && absl::EndsWith(prefix, ".") &&
66
73
      prefix.size() > 1) {
67
17
    prefixes_.push_back(stat_name_pool_->add(prefix.substr(0, prefix.size() - 1)));
68
17
    matchers_.pop_back();
69
17
  }
70
73
}
71

            
72
5960048
StatsMatcher::FastResult StatsMatcherImpl::fastRejects(StatName stat_name) const {
73
5960048
  if (rejectsAll()) {
74
28
    return FastResult::Rejects;
75
28
  }
76
5960020
  bool matches = fastRejectMatch(stat_name);
77
5960022
  if ((is_inclusive_ || matchers_.empty()) && matches == is_inclusive_) {
78
    // We can short-circuit the slow matchers only if they are empty, or if
79
    // we are in inclusive-mode and we find a match.
80
69210
    return FastResult::Rejects;
81
5890813
  } else if (matches) {
82
9
    return FastResult::Matches;
83
9
  }
84
5890801
  return FastResult::NoMatch;
85
5960020
}
86

            
87
5960020
bool StatsMatcherImpl::fastRejectMatch(StatName stat_name) const {
88
5960020
  return std::any_of(prefixes_.begin(), prefixes_.end(),
89
5960020
                     [stat_name](StatName prefix) { return stat_name.startsWith(prefix); });
90
5960020
}
91

            
92
72289
bool StatsMatcherImpl::slowRejects(FastResult fast_result, StatName stat_name) const {
93
  // Skip slowRejectMatch if we already have a definitive answer from fastRejects.
94
72289
  if (fast_result != FastResult::NoMatch) {
95
16
    return fast_result == FastResult::Rejects;
96
16
  }
97

            
98
72273
  const bool match = slowRejectMatch(stat_name);
99

            
100
  //  is_inclusive_ | match | return
101
  // ---------------+-------+--------
102
  //        T       |   T   |   T   Default-inclusive and matching an (exclusion) matcher, deny.
103
  //        T       |   F   |   F   Otherwise, allow.
104
  //        F       |   T   |   F   Default-exclusive and matching an (inclusion) matcher, allow.
105
  //        F       |   F   |   T   Otherwise, deny.
106
  //
107
  // This is an XNOR, which can be evaluated by checking for equality.
108
72273
  return match == is_inclusive_;
109
72289
}
110

            
111
72273
bool StatsMatcherImpl::slowRejectMatch(StatName stat_name) const {
112
72273
  if (matchers_.empty()) {
113
1561
    return false;
114
1561
  }
115
70712
  std::string name = symbol_table_->toString(stat_name);
116
70712
  return std::any_of(matchers_.begin(), matchers_.end(),
117
71076
                     [&name](auto& matcher) { return matcher.match(name); });
118
72273
}
119

            
120
} // namespace Stats
121
} // namespace Envoy