Line data Source code
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
|