Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/server/admin/stats_request.h
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#include "envoy/server/admin.h"
4
5
#include "source/server/admin/stats_params.h"
6
#include "source/server/admin/stats_render.h"
7
#include "source/server/admin/utils.h"
8
9
#include "absl/container/btree_map.h"
10
#include "absl/types/variant.h"
11
12
namespace Envoy {
13
namespace Server {
14
15
// Captures context for a streaming request, implementing the AdminHandler interface.
16
class StatsRequest : public Admin::Request {
17
  using ScopeVec = std::vector<Stats::ConstScopeSharedPtr>;
18
  using StatOrScopes = absl::variant<ScopeVec, Stats::TextReadoutSharedPtr, Stats::CounterSharedPtr,
19
                                     Stats::GaugeSharedPtr, Stats::HistogramSharedPtr>;
20
21
  // Ordered to match the StatsOrScopes variant.
22
  enum class StatOrScopesIndex { Scopes, TextReadout, Counter, Gauge, Histogram };
23
24
  // In order to keep the output consistent with the fully buffered behavior
25
  // prior to the chunked implementation that buffered each type, we iterate
26
  // over all scopes for each type. This enables the complex chunking
27
  // implementation to pass the tests that capture the buffered behavior. There
28
  // is not a significant cost to this, but in a future PR we may choose to
29
  // co-mingle the types. Note that histograms are groups together in the data
30
  // JSON data model, so we won't be able to fully co-mingle.
31
  enum class Phase {
32
    TextReadouts,
33
    CountersAndGauges,
34
    Histograms,
35
  };
36
37
public:
38
  using UrlHandlerFn = std::function<Admin::UrlHandler()>;
39
40
  static constexpr uint64_t DefaultChunkSize = 2 * 1000 * 1000;
41
42
  StatsRequest(Stats::Store& stats, const StatsParams& params,
43
               const Upstream::ClusterManager& cluster_manager,
44
               UrlHandlerFn url_handler_fn = nullptr);
45
46
  // Admin::Request
47
  Http::Code start(Http::ResponseHeaderMap& response_headers) override;
48
49
  // Streams out the next chunk of stats to the client, visiting only the scopes
50
  // that can plausibly contribute the next set of named stats. This enables us
51
  // to linearly traverse the entire set of stats without buffering all of them
52
  // and sorting.
53
  //
54
  // Instead we keep the a set of candidate stats to emit in stat_map_ an
55
  // alphabetically ordered btree, which heterogeneously stores stats of all
56
  // types and scopes. Note that there can be multiple scopes with the same
57
  // name, so we keep same-named scopes in a vector. However leaf metrics cannot
58
  // have duplicates. It would also be feasible to use a multi-map for this.
59
  //
60
  // So in start() above, we initially populate all the scopes, as well as the
61
  // metrics contained in all scopes with an empty name. So in nextChunk we can
62
  // emit and remove the first element of stat_map_. When we encounter a vector
63
  // of scopes then we add the contained metrics to the map and continue
64
  // iterating.
65
  //
66
  // Whenever the desired chunk size is reached we end the current chunk so that
67
  // the current buffer can be flushed to the network. In #19898 we will
68
  // introduce flow-control so that we don't buffer the all the serialized stats
69
  // while waiting for a slow client.
70
  //
71
  // Note that we do 3 passes through all the scopes_, so that we can emit
72
  // text-readouts first, then the intermingled counters and gauges, and finally
73
  // the histograms.
74
  bool nextChunk(Buffer::Instance& response) override;
75
76
  // To duplicate prior behavior for this class, we do three passes over all the stats:
77
  //   1. text readouts across all scopes
78
  //   2. counters and gauges, co-mingled, across all scopes
79
  //   3. histograms across all scopes.
80
  // It would be little more efficient to co-mingle all the stats, but three
81
  // passes over the scopes is OK. In the future we may decide to organize the
82
  // result data differently, but in the process of changing from buffering
83
  // the entire /stats response to streaming the data out in chunks, it's easier
84
  // to reason about if the tests don't change their expectations.
85
  void startPhase();
86
87
  // Iterates over scope_vec and populates the metric types associated with the
88
  // current phase.
89
  void populateStatsForCurrentPhase(const ScopeVec& scope_vec);
90
91
  // Populates all the metrics of the templatized type from scope_vec. Here we
92
  // exploit that Scope::iterate is a generic templatized function to avoid code
93
  // duplication.
94
  template <class StatType> void populateStatsFromScopes(const ScopeVec& scope_vec);
95
96
  void renderPerHostMetrics(Buffer::Instance& response);
97
98
  // Renders the templatized type, exploiting the fact that Render::generate is
99
  // generic to avoid code duplication.
100
  template <class SharedStatType>
101
  void renderStat(const std::string& name, Buffer::Instance& response, StatOrScopes& variant);
102
103
  // Sets the chunk size.
104
0
  void setChunkSize(uint64_t chunk_size) { chunk_size_ = chunk_size; }
105
106
private:
107
  StatsParams params_;
108
  std::unique_ptr<StatsRender> render_;
109
  Stats::Store& stats_;
110
  ScopeVec scopes_;
111
  absl::btree_map<std::string, StatOrScopes> stat_map_;
112
  Phase phase_{Phase::TextReadouts};
113
  uint64_t phase_stat_count_{0};
114
  absl::string_view phase_string_{"text readouts"};
115
  Buffer::OwnedImpl response_;
116
  const Upstream::ClusterManager& cluster_manager_;
117
  UrlHandlerFn url_handler_fn_;
118
  uint64_t chunk_size_{DefaultChunkSize};
119
};
120
121
} // namespace Server
122
} // namespace Envoy