Line data Source code
1 : #pragma once 2 : 3 : #include "envoy/common/pure.h" 4 : #include "envoy/stats/scope.h" 5 : #include "envoy/stats/stats.h" 6 : 7 : #include "source/common/common/cleanup.h" 8 : #include "source/common/common/thread.h" 9 : #include "source/common/stats/symbol_table.h" 10 : #include "source/common/stats/utility.h" 11 : 12 : namespace Envoy { 13 : namespace Stats { 14 : 15 : /** 16 : * Lazy-initialization wrapper for StatsStructType, intended for deferred instantiation of a block 17 : * of stats that might not be needed in a given Envoy process. 18 : * 19 : * This class is thread-safe -- instantiations can occur on multiple concurrent threads. 20 : * This is used when 21 : * :ref:`enable_deferred_creation_stats 22 : * <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` is enabled. 23 : */ 24 : template <typename StatsStructType> 25 : class DeferredStats : public DeferredCreationCompatibleInterface<StatsStructType> { 26 : public: 27 : // Capture the stat names object and the scope with a ctor, that can be used to instantiate a 28 : // StatsStructType object later. 29 : // Caller should make sure scope and stat_names outlive this object. 30 : DeferredStats(const typename StatsStructType::StatNameType& stat_names, 31 : Stats::ScopeSharedPtr scope) 32 : : initialized_( 33 : // A lambda is used as we need to register the name into the symbol table. 34 : // Note: there is no issue to capture a reference of the scope here as this lambda is 35 : // only used to initialize the 'initialized_' Gauge. 36 0 : [&scope]() -> Gauge& { 37 0 : Stats::StatNamePool pool(scope->symbolTable()); 38 0 : return Stats::Utility::gaugeFromElements( 39 0 : *scope, {pool.add(StatsStructType::typeName()), pool.add("initialized")}, 40 0 : Stats::Gauge::ImportMode::HiddenAccumulate); 41 0 : }()), 42 0 : ctor_([this, &stat_names, stats_scope = std::move(scope)]() -> StatsStructType* { 43 0 : initialized_.inc(); 44 : // Reset ctor_ to save some RAM. 45 0 : Cleanup reset_ctor([this] { ctor_ = nullptr; }); 46 0 : return new StatsStructType(stat_names, *stats_scope); 47 0 : }) { 48 0 : if (initialized_.value() > 0) { 49 0 : getOrCreateHelper(); 50 0 : } 51 0 : } 52 0 : ~DeferredStats() override { 53 0 : if (ctor_ == nullptr) { 54 0 : initialized_.dec(); 55 0 : } 56 0 : } 57 0 : inline StatsStructType& getOrCreate() override { return getOrCreateHelper(); } 58 : 59 : private: 60 : // We can't call getOrCreate directly from constructor, otherwise the compiler complains about 61 : // bypassing virtual dispatch even though it's fine. 62 0 : inline StatsStructType& getOrCreateHelper() { return *internal_stats_.get(ctor_); } 63 : 64 : // In order to preserve stat value continuity across a config reload, we need to automatically 65 : // re-instantiate lazy stats when they are constructed, if there is already a live instantiation 66 : // to the same stats. Consider the following alternate scenarios: 67 : 68 : // Scenario 1: a cluster is instantiated but receives no requests, so its traffic-related stats 69 : // are never instantiated. When this cluster gets reloaded on a config update, a new lazy-init 70 : // block is created, but the stats should again not be instantiated. 71 : 72 : // Scenario 2: a cluster is instantiated and receives traffic, so its traffic-related stats are 73 : // instantiated. We must ensure that a new instance for the same cluster gets its lazy-stats 74 : // instantiated before the previous cluster of the same name is destructed. 75 : 76 : // To do that we keep an "initialized" gauge in the cluster's scope, which will be associated by 77 : // name to the previous generation's cluster's lazy-init block. We use the value in this shared 78 : // gauge to determine whether to instantiate the lazy block on construction. 79 : Gauge& initialized_; 80 : // TODO(#26957): Clean up this ctor_ by moving its ownership to AtomicPtr, and drop 81 : // the setter lambda when the nested object is created. 82 : std::function<StatsStructType*()> ctor_; 83 : Thread::AtomicPtr<StatsStructType, Thread::AtomicPtrAllocMode::DeleteOnDestruct> internal_stats_; 84 : }; 85 : 86 : // Non-deferred wrapper over StatsStructType. This is used when 87 : // :ref:`enable_deferred_creation_stats 88 : // <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` is not enabled. 89 : template <typename StatsStructType> 90 : class DirectStats : public DeferredCreationCompatibleInterface<StatsStructType> { 91 : public: 92 : DirectStats(const typename StatsStructType::StatNameType& stat_names, Stats::Scope& scope) 93 2376 : : stats_(stat_names, scope) {} 94 2529 : inline StatsStructType& getOrCreate() override { return stats_; } 95 : 96 : private: 97 : StatsStructType stats_; 98 : }; 99 : 100 : // Template that lazily initializes a StatsStruct. 101 : // The bootstrap config :ref:`enable_deferred_creation_stats 102 : // <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` decides if 103 : // stats lazy initialization is enabled or not. 104 : template <typename StatsStructType> 105 : DeferredCreationCompatibleStats<StatsStructType> 106 : createDeferredCompatibleStats(Stats::ScopeSharedPtr scope, 107 : const typename StatsStructType::StatNameType& stat_names, 108 2376 : bool defer_creation) { 109 2376 : if (defer_creation) { 110 0 : return DeferredCreationCompatibleStats<StatsStructType>( 111 0 : std::make_unique<DeferredStats<StatsStructType>>(stat_names, scope)); 112 2376 : } else { 113 2376 : return DeferredCreationCompatibleStats<StatsStructType>( 114 2376 : std::make_unique<DirectStats<StatsStructType>>(stat_names, *scope)); 115 2376 : } 116 2376 : } 117 : 118 : } // namespace Stats 119 : } // namespace Envoy