Coverage Report

Created: 2023-11-12 09:30

/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