1
#include "source/server/admin/stats_handler.h"
2

            
3
#include <functional>
4
#include <vector>
5

            
6
#include "envoy/admin/v3/mutex_stats.pb.h"
7
#include "envoy/server/admin.h"
8

            
9
#include "source/common/buffer/buffer_impl.h"
10
#include "source/common/common/empty_string.h"
11
#include "source/common/http/headers.h"
12
#include "source/common/http/utility.h"
13
#include "source/server/admin/prometheus_stats.h"
14
#include "source/server/admin/stats_request.h"
15

            
16
#include "absl/strings/numbers.h"
17

            
18
namespace Envoy {
19
namespace Server {
20

            
21
const uint64_t RecentLookupsCapacity = 100;
22

            
23
namespace {
24
// Implements a chunked request for Prometheus stats.
25
class PrometheusRequest : public Admin::Request {
26
public:
27
  PrometheusRequest(StatsHandler& handler, const StatsParams& params, AdminStream& admin_stream)
28
13
      : handler_(handler), params_(params), admin_stream_(admin_stream) {}
29

            
30
13
  Http::Code start(Http::ResponseHeaderMap& response_headers) override {
31
13
    code_ = handler_.prometheusFlushAndRender(params_, admin_stream_.getRequestHeaders(),
32
13
                                              response_headers, response_);
33
13
    return code_;
34
13
  }
35

            
36
13
  bool nextChunk(Buffer::Instance& response) override {
37
13
    response.move(response_);
38
13
    return false;
39
13
  }
40

            
41
private:
42
  StatsHandler& handler_;
43
  const StatsParams params_;
44
  AdminStream& admin_stream_;
45
  Buffer::OwnedImpl response_;
46
  Http::Code code_{Http::Code::OK};
47
};
48
} // namespace
49

            
50
10785
StatsHandler::StatsHandler(Server::Instance& server) : HandlerContextBase(server) {}
51

            
52
Http::Code StatsHandler::handlerResetCounters(Http::ResponseHeaderMap&, Buffer::Instance& response,
53
3
                                              AdminStream&) {
54
895
  for (const Stats::CounterSharedPtr& counter : server_.stats().counters()) {
55
895
    counter->reset();
56
895
  }
57
3
  server_.stats().symbolTable().clearRecentLookups();
58
3
  response.add("OK\n");
59
3
  return Http::Code::OK;
60
3
}
61

            
62
Http::Code StatsHandler::handlerStatsRecentLookups(Http::ResponseHeaderMap&,
63
14
                                                   Buffer::Instance& response, AdminStream&) {
64
14
  Stats::SymbolTable& symbol_table = server_.stats().symbolTable();
65
14
  std::string table;
66
14
  const uint64_t total =
67
15
      symbol_table.getRecentLookups([&table](absl::string_view name, uint64_t count) {
68
3
        table += fmt::format("{:8d} {}\n", count, name);
69
3
      });
70
14
  if (table.empty() && symbol_table.recentLookupCapacity() == 0) {
71
7
    table = "Lookup tracking is not enabled. Use /stats/recentlookups/enable to enable.\n";
72
7
  } else {
73
7
    response.add("   Count Lookup\n");
74
7
  }
75
14
  response.add(absl::StrCat(table, "\ntotal: ", total, "\n"));
76
14
  return Http::Code::OK;
77
14
}
78

            
79
Http::Code StatsHandler::handlerStatsRecentLookupsClear(Http::ResponseHeaderMap&,
80
3
                                                        Buffer::Instance& response, AdminStream&) {
81
3
  server_.stats().symbolTable().clearRecentLookups();
82
3
  response.add("OK\n");
83
3
  return Http::Code::OK;
84
3
}
85

            
86
Http::Code StatsHandler::handlerStatsRecentLookupsDisable(Http::ResponseHeaderMap&,
87
                                                          Buffer::Instance& response,
88
3
                                                          AdminStream&) {
89
3
  server_.stats().symbolTable().setRecentLookupCapacity(0);
90
3
  response.add("OK\n");
91
3
  return Http::Code::OK;
92
3
}
93

            
94
Http::Code StatsHandler::handlerStatsRecentLookupsEnable(Http::ResponseHeaderMap&,
95
7
                                                         Buffer::Instance& response, AdminStream&) {
96
7
  server_.stats().symbolTable().setRecentLookupCapacity(RecentLookupsCapacity);
97
7
  response.add("OK\n");
98
7
  return Http::Code::OK;
99
7
}
100

            
101
105
Admin::RequestPtr StatsHandler::makeRequest(AdminStream& admin_stream) {
102
105
  StatsParams params;
103
105
  Buffer::OwnedImpl response;
104
105
  Http::Code code = params.parse(admin_stream.getRequestHeaders().getPathValue(), response);
105
105
  if (code != Http::Code::OK) {
106
9
    return Admin::makeStaticTextRequest(response, code);
107
9
  }
108

            
109
96
  if (params.format_ == StatsFormat::Prometheus) {
110
    // TODO(#16139): modify streaming algorithm to cover Prometheus.
111
    //
112
    // This may be easiest to accomplish by populating the set
113
    // with tagExtractedName(), and allowing for vectors of
114
    // stats as multiples will have the same tag-extracted names.
115
    // Ideally we'd find a way to do this without slowing down
116
    // the non-Prometheus implementations.
117
13
    return std::make_unique<PrometheusRequest>(*this, params, admin_stream);
118
13
  }
119

            
120
83
  if (params.histogram_buckets_mode_ == Utility::HistogramBucketsMode::PrometheusNative) {
121
1
    return Admin::makeStaticTextRequest(
122
1
        "Invalid histogram_buckets type for non prometheus stats type", Http::Code::BadRequest);
123
1
  }
124

            
125
82
  if (server_.statsConfig().flushOnAdmin()) {
126
1
    server_.flushStats();
127
1
  }
128

            
129
82
  bool active_mode;
130
82
#ifdef ENVOY_ADMIN_HTML
131
82
  active_mode = params.format_ == StatsFormat::ActiveHtml;
132
#else
133
  active_mode = false;
134
#endif
135
82
  return makeRequest(
136
82
      server_.stats(), params, server_.clusterManager(),
137
82
      [this, active_mode]() -> Admin::UrlHandler { return statsHandler(active_mode); });
138
83
}
139

            
140
Admin::RequestPtr StatsHandler::makeRequest(Stats::Store& stats, const StatsParams& params,
141
                                            const Upstream::ClusterManager& cluster_manager,
142
82
                                            StatsRequest::UrlHandlerFn url_handler_fn) {
143
82
  return std::make_unique<StatsRequest>(stats, params, cluster_manager, url_handler_fn);
144
82
}
145

            
146
Http::Code StatsHandler::handlerPrometheusStats(Http::ResponseHeaderMap& response_headers,
147
                                                Buffer::Instance& response,
148
6
                                                AdminStream& admin_stream) {
149
6
  return prometheusStats(admin_stream.getRequestHeaders(), response_headers, response);
150
6
}
151

            
152
Http::Code StatsHandler::prometheusStats(const Http::RequestHeaderMap& request_headers,
153
                                         Http::ResponseHeaderMap& response_headers,
154
6
                                         Buffer::Instance& response) {
155
6
  StatsParams params;
156
6
  Http::Code code = params.parse(request_headers.getPathValue(), response);
157
6
  if (code != Http::Code::OK) {
158
    return code;
159
  }
160

            
161
6
  return prometheusFlushAndRender(params, request_headers, response_headers, response);
162
6
}
163

            
164
Http::Code StatsHandler::prometheusFlushAndRender(const StatsParams& params,
165
                                                  const Http::RequestHeaderMap& request_headers,
166
                                                  Http::ResponseHeaderMap& response_headers,
167
19
                                                  Buffer::Instance& response) {
168
19
  absl::Status paramsStatus = PrometheusStatsFormatter::validateParams(params, request_headers);
169
19
  if (!paramsStatus.ok()) {
170
2
    response.add(paramsStatus.message());
171
2
    return Http::Code::BadRequest;
172
2
  }
173
17
  if (server_.statsConfig().flushOnAdmin()) {
174
    server_.flushStats();
175
  }
176
17
  prometheusRender(server_.stats(), server_.api().customStatNamespaces(), server_.clusterManager(),
177
17
                   params, request_headers, response_headers, response);
178
17
  return Http::Code::OK;
179
19
}
180

            
181
void StatsHandler::prometheusRender(Stats::Store& stats,
182
                                    const Stats::CustomStatNamespaces& custom_namespaces,
183
                                    const Upstream::ClusterManager& cluster_manager,
184
                                    const StatsParams& params,
185
                                    const Http::RequestHeaderMap& request_headers,
186
                                    Http::ResponseHeaderMap& response_headers,
187
17
                                    Buffer::Instance& response) {
188
17
  const std::vector<Stats::TextReadoutSharedPtr>& text_readouts_vec =
189
17
      params.prometheus_text_readouts_ ? stats.textReadouts()
190
17
                                       : std::vector<Stats::TextReadoutSharedPtr>();
191
17
  PrometheusStatsFormatter::statsAsPrometheus(
192
17
      stats.counters(), stats.gauges(), stats.histograms(), text_readouts_vec, cluster_manager,
193
17
      request_headers, response_headers, response, params, custom_namespaces);
194
17
}
195

            
196
Http::Code StatsHandler::handlerContention(Http::ResponseHeaderMap& response_headers,
197
2
                                           Buffer::Instance& response, AdminStream&) {
198

            
199
2
  if (server_.options().mutexTracingEnabled() && server_.mutexTracer() != nullptr) {
200
1
    response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json);
201

            
202
1
    envoy::admin::v3::MutexStats mutex_stats;
203
1
    mutex_stats.set_num_contentions(server_.mutexTracer()->numContentions());
204
1
    mutex_stats.set_current_wait_cycles(server_.mutexTracer()->currentWaitCycles());
205
1
    mutex_stats.set_lifetime_wait_cycles(server_.mutexTracer()->lifetimeWaitCycles());
206
1
    response.add(MessageUtil::getJsonStringFromMessageOrError(mutex_stats, true, true));
207
1
  } else {
208
1
    response.add("Mutex contention tracing is not enabled. To enable, run Envoy with flag "
209
1
                 "--enable-mutex-tracing.");
210
1
  }
211
2
  return Http::Code::OK;
212
2
}
213

            
214
10738
Admin::UrlHandler StatsHandler::statsHandler(bool active_mode) {
215
10738
  Admin::ParamDescriptor usedonly{
216
10738
      Admin::ParamDescriptor::Type::Boolean, "usedonly",
217
10738
      "Only include stats that have been written by system since restart"};
218
10738
  Admin::ParamDescriptor histogram_buckets{Admin::ParamDescriptor::Type::Enum,
219
10738
                                           "histogram_buckets",
220
10738
                                           "Histogram bucket display mode",
221
10738
                                           {"cumulative", "disjoint", "detailed", "summary"}};
222
10738
  Admin::ParamDescriptor format{Admin::ParamDescriptor::Type::Enum,
223
10738
                                "format",
224
10738
                                "Format to use",
225
10738
                                {"html", "active-html", "text", "json"}};
226
10738
  Admin::ParamDescriptor filter{Admin::ParamDescriptor::Type::String, "filter",
227
10738
                                "Regular expression (Google re2) for filtering stats"};
228
10738
  Admin::ParamDescriptor type{Admin::ParamDescriptor::Type::Enum,
229
10738
                              "type",
230
10738
                              "Stat types to include.",
231
10738
                              {StatLabels::All, StatLabels::Counters, StatLabels::Histograms,
232
10738
                               StatLabels::Gauges, StatLabels::TextReadouts}};
233

            
234
10738
  Admin::ParamDescriptorVec params{usedonly, filter};
235
10738
  if (!active_mode) {
236
10738
    params.push_back(format);
237
10738
  }
238
10738
  params.push_back(type);
239
10738
  if (!active_mode) {
240
10738
    params.push_back(histogram_buckets);
241
10738
  }
242

            
243
10738
  return {
244
10738
      "/stats",
245
10738
      "print server stats",
246
10747
      [this](AdminStream& admin_stream) -> Admin::RequestPtr { return makeRequest(admin_stream); },
247
10738
      false,
248
10738
      false,
249
10738
      params};
250
10738
}
251

            
252
} // namespace Server
253
} // namespace Envoy