Line data Source code
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 : : params_(params), stats_(stats), cluster_manager_(cluster_manager), 16 0 : url_handler_fn_(url_handler_fn) { 17 0 : switch (params_.type_) { 18 0 : case StatsType::TextReadouts: 19 0 : case StatsType::All: 20 0 : phase_ = Phase::TextReadouts; 21 0 : break; 22 0 : case StatsType::Counters: 23 0 : case StatsType::Gauges: 24 0 : phase_ = Phase::CountersAndGauges; 25 0 : break; 26 0 : case StatsType::Histograms: 27 0 : phase_ = Phase::Histograms; 28 0 : break; 29 0 : } 30 0 : } 31 : 32 0 : Http::Code StatsRequest::start(Http::ResponseHeaderMap& response_headers) { 33 0 : switch (params_.format_) { 34 0 : case StatsFormat::Json: 35 0 : render_ = std::make_unique<StatsJsonRender>(response_headers, response_, params_); 36 0 : break; 37 0 : case StatsFormat::Text: 38 0 : render_ = std::make_unique<StatsTextRender>(params_); 39 0 : break; 40 0 : #ifdef ENVOY_ADMIN_HTML 41 0 : case StatsFormat::ActiveHtml: 42 0 : case StatsFormat::Html: { 43 0 : auto html_render = std::make_unique<StatsHtmlRender>(response_headers, response_, params_); 44 0 : html_render->setupStatsPage(url_handler_fn_(), params_, response_); 45 0 : render_ = std::move(html_render); 46 0 : if (params_.format_ == StatsFormat::ActiveHtml) { 47 0 : return Http::Code::OK; 48 0 : } 49 0 : break; 50 0 : } 51 0 : #endif 52 0 : case StatsFormat::Prometheus: 53 : // TODO(#16139): once Prometheus shares this algorithm here, this becomes a legitimate choice. 54 0 : IS_ENVOY_BUG("reached Prometheus case in switch unexpectedly"); 55 0 : return Http::Code::BadRequest; 56 0 : } 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 0 : stats_.forEachScope( 64 0 : [this](size_t s) { scopes_.reserve(s); }, 65 0 : [this](const Stats::Scope& scope) { scopes_.emplace_back(scope.getConstShared()); }); 66 : 67 0 : startPhase(); 68 0 : return Http::Code::OK; 69 0 : } 70 : 71 0 : bool StatsRequest::nextChunk(Buffer::Instance& response) { 72 0 : if (response_.length() > 0) { 73 0 : ASSERT(response.length() == 0); 74 0 : response.move(response_); 75 0 : ASSERT(response_.length() == 0); 76 0 : } 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 0 : const uint64_t starting_response_length = response.length(); 81 0 : while (response.length() - starting_response_length < chunk_size_) { 82 0 : while (stat_map_.empty()) { 83 0 : if (params_.type_ != StatsType::All) { 84 0 : 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 0 : renderPerHostMetrics(response); 89 0 : } 90 : 91 0 : if (phase_stat_count_ == 0) { 92 0 : render_->noStats(response, phase_string_); 93 0 : } 94 : 95 0 : render_->finalize(response); 96 0 : return false; 97 0 : } 98 : 99 0 : if (phase_stat_count_ == 0) { 100 0 : render_->noStats(response, phase_string_); 101 0 : } 102 : 103 0 : phase_stat_count_ = 0; 104 0 : switch (phase_) { 105 0 : case Phase::TextReadouts: 106 0 : phase_ = Phase::CountersAndGauges; 107 0 : phase_string_ = "Counters and Gauges"; 108 0 : startPhase(); 109 0 : break; 110 0 : case Phase::CountersAndGauges: 111 0 : renderPerHostMetrics(response); 112 : 113 0 : phase_ = Phase::Histograms; 114 0 : phase_string_ = "Histograms"; 115 0 : startPhase(); 116 0 : break; 117 0 : case Phase::Histograms: 118 0 : render_->finalize(response); 119 0 : return false; 120 0 : } 121 0 : } 122 : 123 0 : auto iter = stat_map_.begin(); 124 0 : StatOrScopes variant = std::move(iter->second); 125 0 : StatOrScopesIndex index = static_cast<StatOrScopesIndex>(variant.index()); 126 0 : switch (index) { 127 0 : 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 0 : stat_map_.erase(iter); 133 0 : populateStatsForCurrentPhase(absl::get<ScopeVec>(variant)); 134 0 : break; 135 0 : case StatOrScopesIndex::TextReadout: 136 0 : renderStat<Stats::TextReadoutSharedPtr>(iter->first, response, variant); 137 0 : stat_map_.erase(iter); 138 0 : ++phase_stat_count_; 139 0 : break; 140 0 : case StatOrScopesIndex::Counter: 141 0 : renderStat<Stats::CounterSharedPtr>(iter->first, response, variant); 142 0 : stat_map_.erase(iter); 143 0 : ++phase_stat_count_; 144 0 : break; 145 0 : case StatOrScopesIndex::Gauge: 146 0 : renderStat<Stats::GaugeSharedPtr>(iter->first, response, variant); 147 0 : stat_map_.erase(iter); 148 0 : ++phase_stat_count_; 149 0 : break; 150 0 : case StatOrScopesIndex::Histogram: { 151 0 : auto histogram = absl::get<Stats::HistogramSharedPtr>(variant); 152 0 : auto parent_histogram = dynamic_cast<Stats::ParentHistogram*>(histogram.get()); 153 0 : if (parent_histogram != nullptr) { 154 0 : render_->generate(response, iter->first, *parent_histogram); 155 0 : ++phase_stat_count_; 156 0 : } 157 0 : stat_map_.erase(iter); 158 0 : } 159 0 : } 160 0 : } 161 0 : return true; 162 0 : } 163 : 164 0 : void StatsRequest::startPhase() { 165 0 : 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 0 : for (const Stats::ConstScopeSharedPtr& scope : scopes_) { 171 0 : StatOrScopes& variant = stat_map_[stats_.symbolTable().toString(scope->prefix())]; 172 0 : if (variant.index() == absl::variant_npos) { 173 0 : variant = ScopeVec(); 174 0 : } 175 0 : absl::get<ScopeVec>(variant).emplace_back(scope); 176 0 : } 177 0 : } 178 : 179 0 : void StatsRequest::populateStatsForCurrentPhase(const ScopeVec& scope_vec) { 180 0 : switch (phase_) { 181 0 : case Phase::TextReadouts: 182 0 : populateStatsFromScopes<Stats::TextReadout>(scope_vec); 183 0 : break; 184 0 : case Phase::CountersAndGauges: 185 0 : if (params_.type_ != StatsType::Gauges) { 186 0 : populateStatsFromScopes<Stats::Counter>(scope_vec); 187 0 : } 188 0 : if (params_.type_ != StatsType::Counters) { 189 0 : populateStatsFromScopes<Stats::Gauge>(scope_vec); 190 0 : } 191 0 : break; 192 0 : case Phase::Histograms: 193 0 : populateStatsFromScopes<Stats::Histogram>(scope_vec); 194 0 : break; 195 0 : } 196 0 : } 197 : 198 0 : template <class StatType> void StatsRequest::populateStatsFromScopes(const ScopeVec& scope_vec) { 199 0 : Stats::IterateFn<StatType> check_stat = [this](const Stats::RefcountPtr<StatType>& stat) -> bool { 200 0 : if (!params_.shouldShowMetricWithoutFilter(*stat)) { 201 0 : return true; 202 0 : } 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 0 : std::string name = stat->name(); 213 0 : if (params_.re2_filter_ != nullptr && !re2::RE2::PartialMatch(name, *params_.re2_filter_)) { 214 0 : return true; 215 0 : } 216 0 : stat_map_[name] = stat; 217 0 : return true; 218 0 : }; 219 0 : for (const Stats::ConstScopeSharedPtr& scope : scope_vec) { 220 0 : scope->iterate(check_stat); 221 0 : } 222 0 : } 223 : 224 0 : 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 0 : Upstream::HostUtility::forEachHostMetric( 230 0 : cluster_manager_, 231 0 : [&](Stats::PrimitiveCounterSnapshot&& metric) { 232 0 : if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Counters) && 233 0 : params_.shouldShowMetric(metric)) { 234 0 : ++phase_stat_count_; 235 0 : render_->generate(response, metric.name(), metric.value()); 236 0 : } 237 0 : }, 238 0 : [&](Stats::PrimitiveGaugeSnapshot&& metric) { 239 0 : if ((params_.type_ == StatsType::All || params_.type_ == StatsType::Gauges) && 240 0 : params_.shouldShowMetric(metric)) { 241 0 : ++phase_stat_count_; 242 0 : render_->generate(response, metric.name(), metric.value()); 243 0 : } 244 0 : }); 245 0 : } 246 : 247 : template <class SharedStatType> 248 : void StatsRequest::renderStat(const std::string& name, Buffer::Instance& response, 249 0 : StatOrScopes& variant) { 250 0 : auto stat = absl::get<SharedStatType>(variant); 251 0 : render_->generate(response, name, stat->value()); 252 0 : } 253 : 254 : } // namespace Server 255 : } // namespace Envoy