Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/server/admin/stats_request.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/server/admin/stats_request.h"
2
3
#include "source/common/upstream/host_utility.h"
4
5
#ifdef ENVOY_ADMIN_HTML
6
#include "source/server/admin/stats_html_render.h"
7
#endif
8
9
namespace Envoy {
10
namespace Server {
11
12
StatsRequest::StatsRequest(Stats::Store& stats, const StatsParams& params,
13
                           const Upstream::ClusterManager& cluster_manager,
14
                           UrlHandlerFn url_handler_fn)
15
    : params_(params), stats_(stats), cluster_manager_(cluster_manager),
16
0
      url_handler_fn_(url_handler_fn) {
17
0
  switch (params_.type_) {
18
0
  case StatsType::TextReadouts:
19
0
  case StatsType::All:
20
0
    phase_ = Phase::TextReadouts;
21
0
    break;
22
0
  case StatsType::Counters:
23
0
  case StatsType::Gauges:
24
0
    phase_ = Phase::CountersAndGauges;
25
0
    break;
26
0
  case StatsType::Histograms:
27
0
    phase_ = Phase::Histograms;
28
0
    break;
29
0
  }
30
0
}
31
32
0
Http::Code StatsRequest::start(Http::ResponseHeaderMap& response_headers) {
33
0
  switch (params_.format_) {
34
0
  case StatsFormat::Json:
35
0
    render_ = std::make_unique<StatsJsonRender>(response_headers, response_, params_);
36
0
    break;
37
0
  case StatsFormat::Text:
38
0
    render_ = std::make_unique<StatsTextRender>(params_);
39
0
    break;
40
0
#ifdef ENVOY_ADMIN_HTML
41
0
  case StatsFormat::ActiveHtml:
42
0
  case StatsFormat::Html: {
43
0
    auto html_render = std::make_unique<StatsHtmlRender>(response_headers, response_, params_);
44
0
    html_render->setupStatsPage(url_handler_fn_(), params_, response_);
45
0
    render_ = std::move(html_render);
46
0
    if (params_.format_ == StatsFormat::ActiveHtml) {
47
0
      return Http::Code::OK;
48
0
    }
49
0
    break;
50
0
  }
51
0
#endif
52
0
  case StatsFormat::Prometheus:
53
    // TODO(#16139): once Prometheus shares this algorithm here, this becomes a legitimate choice.
54
0
    IS_ENVOY_BUG("reached Prometheus case in switch unexpectedly");
55
0
    return Http::Code::BadRequest;
56
0
  }
57
58
  // Populate the top-level scopes and the stats underneath any scopes with an empty name.
59
  // We will have to de-dup, but we can do that after sorting.
60
  //
61
  // First capture all the scopes and hold onto them with a SharedPtr so they
62
  // can't be deleted after the initial iteration.
63
0
  stats_.forEachScope(
64
0
      [this](size_t s) { scopes_.reserve(s); },
65
0
      [this](const Stats::Scope& scope) { scopes_.emplace_back(scope.getConstShared()); });
66
67
0
  startPhase();
68
0
  return Http::Code::OK;
69
0
}
70
71
0
bool StatsRequest::nextChunk(Buffer::Instance& response) {
72
0
  if (response_.length() > 0) {
73
0
    ASSERT(response.length() == 0);
74
0
    response.move(response_);
75
0
    ASSERT(response_.length() == 0);
76
0
  }
77
78
  // nextChunk's contract is to add up to chunk_size_ additional bytes. The
79
  // caller is not required to drain the bytes after each call to nextChunk.
80
0
  const uint64_t starting_response_length = response.length();
81
0
  while (response.length() - starting_response_length < chunk_size_) {
82
0
    while (stat_map_.empty()) {
83
0
      if (params_.type_ != StatsType::All) {
84
0
        if (phase_ == Phase::CountersAndGauges) {
85
          // In the case of filtering by type, we need to call this before checking for
86
          // no stats in the phase, and then after that this function returns without the normal
87
          // advancing to the next phase.
88
0
          renderPerHostMetrics(response);
89
0
        }
90
91
0
        if (phase_stat_count_ == 0) {
92
0
          render_->noStats(response, phase_string_);
93
0
        }
94
95
0
        render_->finalize(response);
96
0
        return false;
97
0
      }
98
99
0
      if (phase_stat_count_ == 0) {
100
0
        render_->noStats(response, phase_string_);
101
0
      }
102
103
0
      phase_stat_count_ = 0;
104
0
      switch (phase_) {
105
0
      case Phase::TextReadouts:
106
0
        phase_ = Phase::CountersAndGauges;
107
0
        phase_string_ = "Counters and Gauges";
108
0
        startPhase();
109
0
        break;
110
0
      case Phase::CountersAndGauges:
111
0
        renderPerHostMetrics(response);
112
113
0
        phase_ = Phase::Histograms;
114
0
        phase_string_ = "Histograms";
115
0
        startPhase();
116
0
        break;
117
0
      case Phase::Histograms:
118
0
        render_->finalize(response);
119
0
        return false;
120
0
      }
121
0
    }
122
123
0
    auto iter = stat_map_.begin();
124
0
    StatOrScopes variant = std::move(iter->second);
125
0
    StatOrScopesIndex index = static_cast<StatOrScopesIndex>(variant.index());
126
0
    switch (index) {
127
0
    case StatOrScopesIndex::Scopes:
128
      // Erase the current element before adding new ones, as absl::btree_map
129
      // does not have stable iterators. When we hit leaf stats we will erase
130
      // second, so that we can use the name held as a map key, and don't need
131
      // to re-serialize the name from the symbol table.
132
0
      stat_map_.erase(iter);
133
0
      populateStatsForCurrentPhase(absl::get<ScopeVec>(variant));
134
0
      break;
135
0
    case StatOrScopesIndex::TextReadout:
136
0
      renderStat<Stats::TextReadoutSharedPtr>(iter->first, response, variant);
137
0
      stat_map_.erase(iter);
138
0
      ++phase_stat_count_;
139
0
      break;
140
0
    case StatOrScopesIndex::Counter:
141
0
      renderStat<Stats::CounterSharedPtr>(iter->first, response, variant);
142
0
      stat_map_.erase(iter);
143
0
      ++phase_stat_count_;
144
0
      break;
145
0
    case StatOrScopesIndex::Gauge:
146
0
      renderStat<Stats::GaugeSharedPtr>(iter->first, response, variant);
147
0
      stat_map_.erase(iter);
148
0
      ++phase_stat_count_;
149
0
      break;
150
0
    case StatOrScopesIndex::Histogram: {
151
0
      auto histogram = absl::get<Stats::HistogramSharedPtr>(variant);
152
0
      auto parent_histogram = dynamic_cast<Stats::ParentHistogram*>(histogram.get());
153
0
      if (parent_histogram != nullptr) {
154
0
        render_->generate(response, iter->first, *parent_histogram);
155
0
        ++phase_stat_count_;
156
0
      }
157
0
      stat_map_.erase(iter);
158
0
    }
159
0
    }
160
0
  }
161
0
  return true;
162
0
}
163
164
0
void StatsRequest::startPhase() {
165
0
  ASSERT(stat_map_.empty());
166
167
  // Insert all the scopes in the alphabetically ordered map. As we iterate
168
  // through the map we'll erase the scopes and replace them with the stats held
169
  // in the scopes.
170
0
  for (const Stats::ConstScopeSharedPtr& scope : scopes_) {
171
0
    StatOrScopes& variant = stat_map_[stats_.symbolTable().toString(scope->prefix())];
172
0
    if (variant.index() == absl::variant_npos) {
173
0
      variant = ScopeVec();
174
0
    }
175
0
    absl::get<ScopeVec>(variant).emplace_back(scope);
176
0
  }
177
0
}
178
179
0
void StatsRequest::populateStatsForCurrentPhase(const ScopeVec& scope_vec) {
180
0
  switch (phase_) {
181
0
  case Phase::TextReadouts:
182
0
    populateStatsFromScopes<Stats::TextReadout>(scope_vec);
183
0
    break;
184
0
  case Phase::CountersAndGauges:
185
0
    if (params_.type_ != StatsType::Gauges) {
186
0
      populateStatsFromScopes<Stats::Counter>(scope_vec);
187
0
    }
188
0
    if (params_.type_ != StatsType::Counters) {
189
0
      populateStatsFromScopes<Stats::Gauge>(scope_vec);
190
0
    }
191
0
    break;
192
0
  case Phase::Histograms:
193
0
    populateStatsFromScopes<Stats::Histogram>(scope_vec);
194
0
    break;
195
0
  }
196
0
}
197
198
0
template <class StatType> void StatsRequest::populateStatsFromScopes(const ScopeVec& scope_vec) {
199
0
  Stats::IterateFn<StatType> check_stat = [this](const Stats::RefcountPtr<StatType>& stat) -> bool {
200
0
    if (!params_.shouldShowMetricWithoutFilter(*stat)) {
201
0
      return true;
202
0
    }
203
204
    // Capture the name if we did not early-exit due to used_only -- we'll use
205
    // the name for both filtering and for capturing the stat in the map.
206
    // stat->name() takes a symbol table lock and builds a string, so we only
207
    // want to call it once.
208
    //
209
    // This duplicates logic in shouldShowMetric in `StatsParams`, but
210
    // differs in that Prometheus only uses stat->name() for filtering, not
211
    // rendering, so it only grab the name if there's a filter.
212
0
    std::string name = stat->name();
213
0
    if (params_.re2_filter_ != nullptr && !re2::RE2::PartialMatch(name, *params_.re2_filter_)) {
214
0
      return true;
215
0
    }
216
0
    stat_map_[name] = stat;
217
0
    return true;
218
0
  };
Unexecuted instantiation: Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::TextReadout>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)::{lambda(Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout> const&)#1}::operator()(Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout> const&) const
Unexecuted instantiation: Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Counter>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)::{lambda(Envoy::Stats::RefcountPtr<Envoy::Stats::Counter> const&)#1}::operator()(Envoy::Stats::RefcountPtr<Envoy::Stats::Counter> const&) const
Unexecuted instantiation: Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Gauge>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)::{lambda(Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge> const&)#1}::operator()(Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge> const&) const
Unexecuted instantiation: Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Histogram>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)::{lambda(Envoy::Stats::RefcountPtr<Envoy::Stats::Histogram> const&)#1}::operator()(Envoy::Stats::RefcountPtr<Envoy::Stats::Histogram> const&) const
219
0
  for (const Stats::ConstScopeSharedPtr& scope : scope_vec) {
220
0
    scope->iterate(check_stat);
221
0
  }
222
0
}
Unexecuted instantiation: void Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::TextReadout>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)
Unexecuted instantiation: void Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Counter>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)
Unexecuted instantiation: void Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Gauge>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)
Unexecuted instantiation: void Envoy::Server::StatsRequest::populateStatsFromScopes<Envoy::Stats::Histogram>(std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > > const&)
223
224
0
void StatsRequest::renderPerHostMetrics(Buffer::Instance& response) {
225
  // This code does not adhere to the streaming contract, but there isn't a good way to stream
226
  // these. There isn't a shared pointer to hold, so there's no way to safely pause iteration here
227
  // without copying all of the data somewhere. But copying all of the data would be more expensive
228
  // than generating it all in one batch here.
229
0
  Upstream::HostUtility::forEachHostMetric(
230
0
      cluster_manager_,
231
0
      [&](Stats::PrimitiveCounterSnapshot&& metric) {
232
0
        if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Counters) &&
233
0
            params_.shouldShowMetric(metric)) {
234
0
          ++phase_stat_count_;
235
0
          render_->generate(response, metric.name(), metric.value());
236
0
        }
237
0
      },
238
0
      [&](Stats::PrimitiveGaugeSnapshot&& metric) {
239
0
        if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Gauges) &&
240
0
            params_.shouldShowMetric(metric)) {
241
0
          ++phase_stat_count_;
242
0
          render_->generate(response, metric.name(), metric.value());
243
0
        }
244
0
      });
245
0
}
246
247
template <class SharedStatType>
248
void StatsRequest::renderStat(const std::string& name, Buffer::Instance& response,
249
0
                              StatOrScopes& variant) {
250
0
  auto stat = absl::get<SharedStatType>(variant);
251
0
  render_->generate(response, name, stat->value());
252
0
}
Unexecuted instantiation: void Envoy::Server::StatsRequest::renderStat<Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout> >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, Envoy::Buffer::Instance&, std::__1::variant<std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > >, Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout>, Envoy::Stats::RefcountPtr<Envoy::Stats::Counter>, Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge>, Envoy::Stats::RefcountPtr<Envoy::Stats::Histogram> >&)
Unexecuted instantiation: void Envoy::Server::StatsRequest::renderStat<Envoy::Stats::RefcountPtr<Envoy::Stats::Counter> >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, Envoy::Buffer::Instance&, std::__1::variant<std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > >, Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout>, Envoy::Stats::RefcountPtr<Envoy::Stats::Counter>, Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge>, Envoy::Stats::RefcountPtr<Envoy::Stats::Histogram> >&)
Unexecuted instantiation: void Envoy::Server::StatsRequest::renderStat<Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge> >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, Envoy::Buffer::Instance&, std::__1::variant<std::__1::vector<std::__1::shared_ptr<Envoy::Stats::Scope const>, std::__1::allocator<std::__1::shared_ptr<Envoy::Stats::Scope const> > >, Envoy::Stats::RefcountPtr<Envoy::Stats::TextReadout>, Envoy::Stats::RefcountPtr<Envoy::Stats::Counter>, Envoy::Stats::RefcountPtr<Envoy::Stats::Gauge>, Envoy::Stats::RefcountPtr<Envoy::Stats::Histogram> >&)
253
254
} // namespace Server
255
} // namespace Envoy