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
352
    : params_(params), stats_(stats), cluster_manager_(cluster_manager),
16
352
      url_handler_fn_(url_handler_fn) {
17
352
  switch (params_.type_) {
18
3
  case StatsType::TextReadouts:
19
340
  case StatsType::All:
20
340
    phase_ = Phase::TextReadouts;
21
340
    break;
22
7
  case StatsType::Counters:
23
11
  case StatsType::Gauges:
24
11
    phase_ = Phase::CountersAndGauges;
25
11
    break;
26
1
  case StatsType::Histograms:
27
1
    phase_ = Phase::Histograms;
28
1
    break;
29
352
  }
30
352
}
31

            
32
352
Http::Code StatsRequest::start(Http::ResponseHeaderMap& response_headers) {
33
352
  switch (params_.format_) {
34
38
  case StatsFormat::Json:
35
38
    render_ = std::make_unique<StatsJsonRender>(response_headers, response_, params_);
36
38
    break;
37
310
  case StatsFormat::Text:
38
310
    render_ = std::make_unique<StatsTextRender>(params_);
39
310
    break;
40
#ifdef ENVOY_ADMIN_HTML
41
  case StatsFormat::ActiveHtml:
42
3
  case StatsFormat::Html: {
43
3
    auto html_render = std::make_unique<StatsHtmlRender>(response_headers, response_, params_);
44
3
    html_render->setupStatsPage(url_handler_fn_(), params_, response_);
45
3
    render_ = std::move(html_render);
46
3
    if (params_.format_ == StatsFormat::ActiveHtml) {
47
      return Http::Code::OK;
48
    }
49
3
    break;
50
3
  }
51
3
#endif
52
4
  case StatsFormat::Prometheus:
53
    // TODO(#16139): once Prometheus shares this algorithm here, this becomes a legitimate choice.
54
1
    IS_ENVOY_BUG("reached Prometheus case in switch unexpectedly");
55
1
    return Http::Code::BadRequest;
56
352
  }
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
351
  stats_.forEachScope(
64
351
      [this](size_t s) { scopes_.reserve(s); },
65
39135
      [this](const Stats::Scope& scope) { scopes_.emplace_back(scope.getConstShared()); });
66

            
67
351
  startPhase();
68
351
  return Http::Code::OK;
69
352
}
70

            
71
368
bool StatsRequest::nextChunk(Buffer::Instance& response) {
72
368
  if (response_.length() > 0) {
73
41
    ASSERT(response.length() == 0);
74
41
    response.move(response_);
75
41
    ASSERT(response_.length() == 0);
76
41
  }
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
368
  const uint64_t starting_response_length = response.length();
81
22636
  while (response.length() - starting_response_length < chunk_size_) {
82
23291
    while (stat_map_.empty()) {
83
1023
      if (params_.type_ != StatsType::All) {
84
15
        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
11
          renderPerHostMetrics(response);
89
11
        }
90

            
91
15
        if (phase_stat_count_ == 0) {
92
7
          render_->noStats(response, phase_string_);
93
7
        }
94

            
95
15
        render_->finalize(response);
96
15
        return false;
97
15
      }
98

            
99
1008
      if (phase_stat_count_ == 0) {
100
625
        render_->noStats(response, phase_string_);
101
625
      }
102

            
103
1008
      phase_stat_count_ = 0;
104
1008
      switch (phase_) {
105
336
      case Phase::TextReadouts:
106
336
        phase_ = Phase::CountersAndGauges;
107
336
        phase_string_ = "Counters and Gauges";
108
336
        startPhase();
109
336
        break;
110
336
      case Phase::CountersAndGauges:
111
336
        renderPerHostMetrics(response);
112

            
113
336
        phase_ = Phase::Histograms;
114
336
        phase_string_ = "Histograms";
115
336
        startPhase();
116
336
        break;
117
336
      case Phase::Histograms:
118
336
        render_->finalize(response);
119
336
        return false;
120
1008
      }
121
1008
    }
122

            
123
22268
    auto iter = stat_map_.begin();
124
22268
    StatOrScopes variant = std::move(iter->second);
125
22268
    StatOrScopesIndex index = static_cast<StatOrScopesIndex>(variant.index());
126
22268
    switch (index) {
127
5492
    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
5492
      stat_map_.erase(iter);
133
5492
      populateStatsForCurrentPhase(absl::get<ScopeVec>(variant));
134
5492
      break;
135
37
    case StatOrScopesIndex::TextReadout:
136
37
      renderStat<Stats::TextReadoutSharedPtr>(iter->first, response, variant);
137
37
      stat_map_.erase(iter);
138
37
      ++phase_stat_count_;
139
37
      break;
140
14429
    case StatOrScopesIndex::Counter:
141
14429
      renderStat<Stats::CounterSharedPtr>(iter->first, response, variant);
142
14429
      stat_map_.erase(iter);
143
14429
      ++phase_stat_count_;
144
14429
      break;
145
1984
    case StatOrScopesIndex::Gauge:
146
1984
      renderStat<Stats::GaugeSharedPtr>(iter->first, response, variant);
147
1984
      stat_map_.erase(iter);
148
1984
      ++phase_stat_count_;
149
1984
      break;
150
326
    case StatOrScopesIndex::Histogram: {
151
326
      auto histogram = absl::get<Stats::HistogramSharedPtr>(variant);
152
326
      auto parent_histogram = dynamic_cast<Stats::ParentHistogram*>(histogram.get());
153
326
      if (parent_histogram != nullptr) {
154
312
        render_->generate(response, iter->first, *parent_histogram);
155
312
        ++phase_stat_count_;
156
312
      }
157
326
      stat_map_.erase(iter);
158
326
    }
159
22268
    }
160
22268
  }
161
17
  return true;
162
368
}
163

            
164
1023
void StatsRequest::startPhase() {
165
1023
  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
117369
  for (const Stats::ConstScopeSharedPtr& scope : scopes_) {
171
117369
    StatOrScopes& variant = stat_map_[stats_.symbolTable().toString(scope->prefix())];
172
117369
    if (variant.index() == absl::variant_npos) {
173
      variant = ScopeVec();
174
    }
175
117369
    absl::get<ScopeVec>(variant).emplace_back(scope);
176
117369
  }
177
1023
}
178

            
179
5492
void StatsRequest::populateStatsForCurrentPhase(const ScopeVec& scope_vec) {
180
5492
  switch (phase_) {
181
1828
  case Phase::TextReadouts:
182
1828
    populateStatsFromScopes<Stats::TextReadout>(scope_vec);
183
1828
    break;
184
1838
  case Phase::CountersAndGauges:
185
1838
    if (params_.type_ != StatsType::Gauges) {
186
1834
      populateStatsFromScopes<Stats::Counter>(scope_vec);
187
1834
    }
188
1838
    if (params_.type_ != StatsType::Counters) {
189
1829
      populateStatsFromScopes<Stats::Gauge>(scope_vec);
190
1829
    }
191
1838
    break;
192
1826
  case Phase::Histograms:
193
1826
    populateStatsFromScopes<Stats::Histogram>(scope_vec);
194
1826
    break;
195
5492
  }
196
5492
}
197

            
198
7317
template <class StatType> void StatsRequest::populateStatsFromScopes(const ScopeVec& scope_vec) {
199
207966
  Stats::IterateFn<StatType> check_stat = [this](const Stats::RefcountPtr<StatType>& stat) -> bool {
200
207966
    if (!params_.shouldShowMetricWithoutFilter(*stat)) {
201
2940
      return true;
202
2940
    }
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
205026
    std::string name = stat->name();
213
205026
    if (params_.re2_filter_ != nullptr && !re2::RE2::PartialMatch(name, *params_.re2_filter_)) {
214
6038
      return true;
215
6038
    }
216
198988
    stat_map_[name] = stat;
217
198988
    return true;
218
205026
  };
219
156486
  for (const Stats::ConstScopeSharedPtr& scope : scope_vec) {
220
156486
    scope->iterate(check_stat);
221
156486
  }
222
7317
}
223

            
224
347
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
347
  Upstream::HostUtility::forEachHostMetric(
230
347
      cluster_manager_,
231
347
      [&](Stats::PrimitiveCounterSnapshot&& metric) {
232
7
        if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Counters) &&
233
7
            params_.shouldShowMetric(metric)) {
234
6
          ++phase_stat_count_;
235
6
          render_->generate(response, metric.name(), metric.value());
236
6
        }
237
7
      },
238
347
      [&](Stats::PrimitiveGaugeSnapshot&& metric) {
239
15
        if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Gauges) &&
240
15
            params_.shouldShowMetric(metric)) {
241
12
          ++phase_stat_count_;
242
12
          render_->generate(response, metric.name(), metric.value());
243
12
        }
244
15
      });
245
347
}
246

            
247
template <class SharedStatType>
248
void StatsRequest::renderStat(const std::string& name, Buffer::Instance& response,
249
16450
                              StatOrScopes& variant) {
250
16450
  auto stat = absl::get<SharedStatType>(variant);
251
16450
  render_->generate(response, name, stat->value());
252
16450
}
253

            
254
} // namespace Server
255
} // namespace Envoy