/proc/self/cwd/source/server/admin/stats_render.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/server/admin/stats_render.h" |
2 | | |
3 | | #include "source/common/stats/histogram_impl.h" |
4 | | |
5 | | #include "absl/strings/str_format.h" |
6 | | |
7 | | namespace Envoy { |
8 | | namespace Server { |
9 | | |
10 | | StatsTextRender::StatsTextRender(const StatsParams& params) |
11 | 0 | : histogram_buckets_mode_(params.histogram_buckets_mode_) {} |
12 | | |
13 | | void StatsTextRender::generate(Buffer::Instance& response, const std::string& name, |
14 | 0 | uint64_t value) { |
15 | 0 | response.addFragments({name, ": ", absl::StrCat(value), "\n"}); |
16 | 0 | } |
17 | | |
18 | | void StatsTextRender::generate(Buffer::Instance& response, const std::string& name, |
19 | 0 | const std::string& value) { |
20 | 0 | response.addFragments({name, ": \"", value, "\"\n"}); |
21 | 0 | } |
22 | | |
23 | | void StatsTextRender::generate(Buffer::Instance& response, const std::string& name, |
24 | 0 | const Stats::ParentHistogram& histogram) { |
25 | 0 | if (!histogram.used()) { |
26 | 0 | response.addFragments({name, ": No recorded values\n"}); |
27 | 0 | return; |
28 | 0 | } |
29 | | |
30 | 0 | switch (histogram_buckets_mode_) { |
31 | 0 | case Utility::HistogramBucketsMode::NoBuckets: |
32 | 0 | response.addFragments({name, ": ", histogram.quantileSummary(), "\n"}); |
33 | 0 | break; |
34 | 0 | case Utility::HistogramBucketsMode::Cumulative: |
35 | 0 | response.addFragments({name, ": ", histogram.bucketSummary(), "\n"}); |
36 | 0 | break; |
37 | 0 | case Utility::HistogramBucketsMode::Disjoint: |
38 | 0 | addDisjointBuckets(name, histogram, response); |
39 | 0 | break; |
40 | 0 | case Utility::HistogramBucketsMode::Detailed: |
41 | 0 | response.addFragments({name, ":\n totals="}); |
42 | 0 | addDetail(histogram.detailedTotalBuckets(), response); |
43 | 0 | response.add("\n intervals="); |
44 | 0 | addDetail(histogram.detailedIntervalBuckets(), response); |
45 | 0 | response.addFragments({"\n summary=", histogram.quantileSummary(), "\n"}); |
46 | 0 | break; |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | 0 | void StatsTextRender::finalize(Buffer::Instance&) {} |
51 | | |
52 | | void StatsTextRender::addDetail(const std::vector<Stats::ParentHistogram::Bucket>& buckets, |
53 | 0 | Buffer::Instance& response) { |
54 | 0 | absl::string_view delim = ""; |
55 | 0 | for (const Stats::ParentHistogram::Bucket& bucket : buckets) { |
56 | 0 | response.addFragments({delim, absl::StrFormat("%.15g,%.15g:%lu", bucket.lower_bound_, |
57 | 0 | bucket.width_, bucket.count_)}); |
58 | 0 | delim = ", "; |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | | // Computes disjoint buckets as text and adds them to the response buffer. |
63 | | void StatsTextRender::addDisjointBuckets(const std::string& name, |
64 | | const Stats::ParentHistogram& histogram, |
65 | 0 | Buffer::Instance& response) { |
66 | 0 | if (!histogram.used()) { |
67 | 0 | response.addFragments({name, ": No recorded values\n"}); |
68 | 0 | return; |
69 | 0 | } |
70 | 0 | response.addFragments({name, ": "}); |
71 | 0 | std::vector<absl::string_view> bucket_summary; |
72 | |
|
73 | 0 | const Stats::HistogramStatistics& interval_statistics = histogram.intervalStatistics(); |
74 | 0 | Stats::ConstSupportedBuckets& supported_buckets = interval_statistics.supportedBuckets(); |
75 | 0 | const std::vector<uint64_t> disjoint_interval_buckets = |
76 | 0 | interval_statistics.computeDisjointBuckets(); |
77 | 0 | const std::vector<uint64_t> disjoint_cumulative_buckets = |
78 | 0 | histogram.cumulativeStatistics().computeDisjointBuckets(); |
79 | | // Make sure all vectors are the same size. |
80 | 0 | ASSERT(disjoint_interval_buckets.size() == disjoint_cumulative_buckets.size()); |
81 | 0 | ASSERT(disjoint_cumulative_buckets.size() == supported_buckets.size()); |
82 | 0 | const size_t min_size = std::min({disjoint_interval_buckets.size(), |
83 | 0 | disjoint_cumulative_buckets.size(), supported_buckets.size()}); |
84 | 0 | std::vector<std::string> bucket_strings; |
85 | 0 | bucket_strings.reserve(min_size); |
86 | 0 | for (size_t i = 0; i < min_size; ++i) { |
87 | 0 | if (i != 0) { |
88 | 0 | bucket_summary.push_back(" "); |
89 | 0 | } |
90 | 0 | bucket_strings.push_back(fmt::format("B{:g}({},{})", supported_buckets[i], |
91 | 0 | disjoint_interval_buckets[i], |
92 | 0 | disjoint_cumulative_buckets[i])); |
93 | 0 | bucket_summary.push_back(bucket_strings.back()); |
94 | 0 | } |
95 | 0 | bucket_summary.push_back("\n"); |
96 | 0 | response.addFragments(bucket_summary); |
97 | 0 | } |
98 | | |
99 | | StatsJsonRender::StatsJsonRender(Http::ResponseHeaderMap& response_headers, |
100 | | Buffer::Instance& response, const StatsParams& params) |
101 | | : histogram_buckets_mode_(params.histogram_buckets_mode_), |
102 | 0 | json_(std::make_unique<JsonContext>(response)), response_(response) { |
103 | 0 | response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json); |
104 | 0 | } |
105 | | |
106 | | StatsJsonRender::JsonContext::JsonContext(Buffer::Instance& response) |
107 | 0 | : streamer_(response), stats_map_(streamer_.makeRootMap()) { |
108 | | // We don't create a JSON data model for the stats output, as that makes |
109 | | // streaming difficult. Instead we emit the preamble in the constructor here, |
110 | | // and create json models for each stats entry. |
111 | 0 | stats_map_->addKey("stats"); |
112 | 0 | stats_array_ = stats_map_->addArray(); |
113 | 0 | } |
114 | | |
115 | 0 | void StatsJsonRender::drainIfNeeded(Buffer::Instance& response) { |
116 | 0 | if (&response_ != &response) { |
117 | 0 | response.move(response_); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | // Buffers a JSON fragment for a numeric stats, flushing to the response |
122 | | // buffer once we exceed JsonStatsFlushCount stats. |
123 | | void StatsJsonRender::generate(Buffer::Instance& response, const std::string& name, |
124 | 0 | uint64_t value) { |
125 | 0 | ASSERT(!histograms_initialized_); |
126 | 0 | json_->stats_array_->addMap()->addEntries({{"name", name}, {"value", value}}); |
127 | 0 | drainIfNeeded(response); |
128 | 0 | } |
129 | | |
130 | | // Buffers a JSON fragment for a text-readout stat, flushing to the response |
131 | | // buffer once we exceed JsonStatsFlushCount stats. |
132 | | void StatsJsonRender::generate(Buffer::Instance& response, const std::string& name, |
133 | 0 | const std::string& value) { |
134 | 0 | ASSERT(!histograms_initialized_); |
135 | 0 | json_->stats_array_->addMap()->addEntries({{"name", name}, {"value", value}}); |
136 | 0 | drainIfNeeded(response); |
137 | 0 | } |
138 | | |
139 | | // In JSON we buffer all histograms and don't write them immediately, so we |
140 | | // can, in one JSON structure, emit shared attributes of all histograms and |
141 | | // each individual histogram. |
142 | | // |
143 | | // This is counter to the goals of streaming and chunked interfaces, but |
144 | | // usually there are far fewer histograms than counters or gauges. |
145 | | // |
146 | | // We can further optimize this by streaming out the histograms object, one |
147 | | // histogram at a time, in case buffering all the histograms in Envoy |
148 | | // buffers up too much memory. |
149 | | void StatsJsonRender::generate(Buffer::Instance& response, const std::string& name, |
150 | 0 | const Stats::ParentHistogram& histogram) { |
151 | 0 | if (!histograms_initialized_) { |
152 | 0 | renderHistogramStart(); |
153 | 0 | } |
154 | |
|
155 | 0 | switch (histogram_buckets_mode_) { |
156 | 0 | case Utility::HistogramBucketsMode::NoBuckets: { |
157 | 0 | Json::Streamer::MapPtr map = json_->histogram_array_->addMap(); |
158 | 0 | map->addEntries({{"name", name}}); |
159 | 0 | map->addKey("values"); |
160 | 0 | populatePercentiles(histogram, *map); |
161 | 0 | break; |
162 | 0 | } |
163 | 0 | case Utility::HistogramBucketsMode::Cumulative: { |
164 | 0 | const Stats::HistogramStatistics& interval_statistics = histogram.intervalStatistics(); |
165 | 0 | const std::vector<uint64_t>& interval_buckets = interval_statistics.computedBuckets(); |
166 | 0 | const std::vector<uint64_t>& cumulative_buckets = |
167 | 0 | histogram.cumulativeStatistics().computedBuckets(); |
168 | 0 | collectBuckets(name, histogram, interval_buckets, cumulative_buckets); |
169 | 0 | break; |
170 | 0 | } |
171 | 0 | case Utility::HistogramBucketsMode::Disjoint: { |
172 | 0 | const Stats::HistogramStatistics& interval_statistics = histogram.intervalStatistics(); |
173 | 0 | const std::vector<uint64_t> interval_buckets = interval_statistics.computeDisjointBuckets(); |
174 | 0 | const std::vector<uint64_t> cumulative_buckets = |
175 | 0 | histogram.cumulativeStatistics().computeDisjointBuckets(); |
176 | 0 | collectBuckets(name, histogram, interval_buckets, cumulative_buckets); |
177 | 0 | break; |
178 | 0 | } |
179 | 0 | case Utility::HistogramBucketsMode::Detailed: { |
180 | 0 | generateHistogramDetail(name, histogram, *json_->histogram_array_->addMap()); |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | } |
184 | 0 | drainIfNeeded(response); |
185 | 0 | } |
186 | | |
187 | 0 | void StatsJsonRender::populateSupportedPercentiles(Json::Streamer::Array& array) { |
188 | 0 | Stats::HistogramStatisticsImpl empty_statistics; |
189 | 0 | std::vector<double> supported = empty_statistics.supportedQuantiles(); |
190 | 0 | std::vector<Json::Streamer::Value> views(supported.size()); |
191 | 0 | for (uint32_t i = 0, n = supported.size(); i < n; ++i) { |
192 | 0 | views[i] = supported[i] * 100; |
193 | 0 | } |
194 | 0 | array.addEntries(views); |
195 | 0 | } |
196 | | |
197 | | void StatsJsonRender::populatePercentiles(const Stats::ParentHistogram& histogram, |
198 | 0 | Json::Streamer::Map& map) { |
199 | 0 | Json::Streamer::ArrayPtr array = map.addArray(); |
200 | 0 | std::vector<double> totals = histogram.cumulativeStatistics().computedQuantiles(), |
201 | 0 | intervals = histogram.intervalStatistics().computedQuantiles(); |
202 | 0 | uint32_t min_size = std::min(totals.size(), intervals.size()); |
203 | 0 | ASSERT(totals.size() == min_size); |
204 | 0 | ASSERT(intervals.size() == min_size); |
205 | 0 | for (uint32_t i = 0; i < min_size; ++i) { |
206 | 0 | array->addMap()->addEntries({{"cumulative", totals[i]}, {"interval", intervals[i]}}); |
207 | 0 | } |
208 | 0 | }; |
209 | | |
210 | 0 | void StatsJsonRender::renderHistogramStart() { |
211 | 0 | histograms_initialized_ = true; |
212 | 0 | json_->histogram_map1_ = json_->stats_array_->addMap(); |
213 | 0 | json_->histogram_map1_->addKey("histograms"); |
214 | 0 | switch (histogram_buckets_mode_) { |
215 | 0 | case Utility::HistogramBucketsMode::Detailed: |
216 | 0 | json_->histogram_map2_ = json_->histogram_map1_->addMap(); |
217 | 0 | json_->histogram_map2_->addKey("supported_percentiles"); |
218 | 0 | { populateSupportedPercentiles(*json_->histogram_map2_->addArray()); } |
219 | 0 | json_->histogram_map2_->addKey("details"); |
220 | 0 | json_->histogram_array_ = json_->histogram_map2_->addArray(); |
221 | 0 | break; |
222 | 0 | case Utility::HistogramBucketsMode::NoBuckets: |
223 | 0 | json_->histogram_map2_ = json_->histogram_map1_->addMap(); |
224 | 0 | json_->histogram_map2_->addKey("supported_quantiles"); |
225 | 0 | { populateSupportedPercentiles(*json_->histogram_map2_->addArray()); } |
226 | 0 | json_->histogram_map2_->addKey("computed_quantiles"); |
227 | 0 | json_->histogram_array_ = json_->histogram_map2_->addArray(); |
228 | 0 | break; |
229 | 0 | case Utility::HistogramBucketsMode::Cumulative: |
230 | 0 | case Utility::HistogramBucketsMode::Disjoint: |
231 | 0 | json_->histogram_array_ = json_->histogram_map1_->addArray(); |
232 | 0 | break; |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | void StatsJsonRender::generateHistogramDetail(const std::string& name, |
237 | | const Stats::ParentHistogram& histogram, |
238 | 0 | Json::Streamer::Map& map) { |
239 | | // Now we produce the stream-able histogram records, without using the json intermediate |
240 | | // representation or serializer. |
241 | 0 | map.addEntries({{"name", name}}); |
242 | 0 | map.addKey("totals"); |
243 | 0 | populateBucketsVerbose(histogram.detailedTotalBuckets(), map); |
244 | 0 | map.addKey("intervals"); |
245 | 0 | populateBucketsVerbose(histogram.detailedIntervalBuckets(), map); |
246 | 0 | map.addKey("percentiles"); |
247 | 0 | populatePercentiles(histogram, map); |
248 | 0 | } |
249 | | |
250 | | void StatsJsonRender::populateBucketsVerbose( |
251 | 0 | const std::vector<Stats::ParentHistogram::Bucket>& buckets, Json::Streamer::Map& map) { |
252 | 0 | Json::Streamer::ArrayPtr buckets_array = map.addArray(); |
253 | 0 | for (const Stats::ParentHistogram::Bucket& bucket : buckets) { |
254 | 0 | buckets_array->addMap()->addEntries( |
255 | 0 | {{"lower_bound", bucket.lower_bound_}, {"width", bucket.width_}, {"count", bucket.count_}}); |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | | // Since histograms are buffered (see above), the finalize() method generates |
260 | | // all of them. |
261 | 0 | void StatsJsonRender::finalize(Buffer::Instance& response) { |
262 | 0 | json_.reset(); |
263 | 0 | drainIfNeeded(response); |
264 | 0 | } |
265 | | |
266 | | // Collects the buckets from the specified histogram, using either the |
267 | | // cumulative or disjoint views, as controlled by buckets_fn. |
268 | | void StatsJsonRender::collectBuckets(const std::string& name, |
269 | | const Stats::ParentHistogram& histogram, |
270 | | const std::vector<uint64_t>& interval_buckets, |
271 | 0 | const std::vector<uint64_t>& cumulative_buckets) { |
272 | 0 | const Stats::HistogramStatistics& interval_statistics = histogram.intervalStatistics(); |
273 | 0 | Stats::ConstSupportedBuckets& supported_buckets = interval_statistics.supportedBuckets(); |
274 | | |
275 | | // Make sure all vectors are the same size. |
276 | 0 | ASSERT(interval_buckets.size() == cumulative_buckets.size()); |
277 | 0 | ASSERT(cumulative_buckets.size() == supported_buckets.size()); |
278 | 0 | size_t min_size = |
279 | 0 | std::min({interval_buckets.size(), cumulative_buckets.size(), supported_buckets.size()}); |
280 | |
|
281 | 0 | Json::Streamer::MapPtr map = json_->histogram_array_->addMap(); |
282 | 0 | map->addEntries({{"name", name}}); |
283 | 0 | map->addKey("buckets"); |
284 | 0 | Json::Streamer::ArrayPtr buckets = map->addArray(); |
285 | 0 | for (uint32_t i = 0; i < min_size; ++i) { |
286 | 0 | Json::Streamer::MapPtr bucket_map = buckets->addMap(); |
287 | 0 | bucket_map->addEntries({{"upper_bound", supported_buckets[i]}, |
288 | 0 | {"interval", interval_buckets[i]}, |
289 | 0 | {"cumulative", cumulative_buckets[i]}}); |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | } // namespace Server |
294 | | } // namespace Envoy |