/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 |