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