LCOV - code coverage report
Current view: top level - source/server/admin - stats_request.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 0 187 0.0 %
Date: 2024-01-05 06:35:25 Functions: 0 21 0.0 %

          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

Generated by: LCOV version 1.15