-
Notifications
You must be signed in to change notification settings - Fork 5.4k
add deferred_creation util into stats #27899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b8b489
8f3119a
17cfb10
f8ba76a
68d3c6b
19d75d8
2bbace4
7d69cbf
8318935
91e47e3
cfc9859
f6bd430
97c2b86
8e9dfe5
bca128a
d1ee36c
448e474
1fa16e7
6e9a861
c1d2aa4
4e72f1e
35e7cb1
0a11cb5
8a80f36
448074d
28db9d3
8134314
c22db02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; | |
| // <config_overview_bootstrap>` for more detail. | ||
|
|
||
| // Bootstrap :ref:`configuration overview <config_overview_bootstrap>`. | ||
| // [#next-free-field: 39] | ||
| // [#next-free-field: 40] | ||
| message Bootstrap { | ||
| option (udpa.annotations.versioning).previous_message_type = | ||
| "envoy.config.bootstrap.v2.Bootstrap"; | ||
|
|
@@ -124,6 +124,18 @@ message Bootstrap { | |
| LogFormat log_format = 1; | ||
| } | ||
|
|
||
| message DeferredStatOptions { | ||
| // When the flag is enabled, Envoy will lazily initialize a subset of the stats (see below). | ||
| // This will save memory and CPU cycles when creating the objects that own these stats, if those | ||
| // stats are never referenced throughout the lifetime of the process. However, it will incur additional | ||
| // memory overhead for these objects, and a small increase of CPU usage when a at least one of the stats | ||
| // is updated for the first time. | ||
| // Groups of stats that will be lazily initialized: | ||
| // - Cluster traffic stats: a subgroup of the :ref:`cluster statistics <config_cluster_manager_cluster_stats>` | ||
| // that are used when requests are routed to the cluster. | ||
| bool enable_deferred_creation_stats = 1; | ||
| } | ||
|
|
||
| reserved 10, 11; | ||
|
|
||
| reserved "runtime"; | ||
|
|
@@ -186,6 +198,10 @@ message Bootstrap { | |
| // Optional set of stats sinks. | ||
| repeated metrics.v3.StatsSink stats_sinks = 6; | ||
|
|
||
| // Options to control behaviors of deferred creation compatible stats. | ||
| // [#not-implemented-hide:] | ||
| DeferredStatOptions deferred_stat_options = 39; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment to the field.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| // Configuration for internal processing of stats. | ||
| metrics.v3.StatsConfig stats_config = 13; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -158,16 +158,18 @@ static inline std::string statPrefixJoin(absl::string_view prefix, absl::string_ | |
| */ | ||
| #define MAKE_STATS_STRUCT(StatsStruct, StatNamesStruct, ALL_STATS) \ | ||
| struct StatsStruct { \ | ||
| /* Also referenced in Stats::createDeferredCompatibleStats. */ \ | ||
| using StatNameType = StatNamesStruct; \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I understand why you need to create this alias. But can you add a comment explaining that?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, added a line comment. |
||
| static const absl::string_view typeName() { return #StatsStruct; } \ | ||
| StatsStruct(const StatNamesStruct& stat_names, Envoy::Stats::Scope& scope, \ | ||
| Envoy::Stats::StatName prefix = Envoy::Stats::StatName()) \ | ||
| : stat_names_(stat_names) \ | ||
| ALL_STATS(MAKE_STATS_STRUCT_COUNTER_HELPER_, MAKE_STATS_STRUCT_GAUGE_HELPER_, \ | ||
| MAKE_STATS_STRUCT_HISTOGRAM_HELPER_, \ | ||
| MAKE_STATS_STRUCT_TEXT_READOUT_HELPER_, \ | ||
| MAKE_STATS_STRUCT_STATNAME_HELPER_) {} \ | ||
| const StatNamesStruct& stat_names_; \ | ||
| const StatNameType& stat_names_; \ | ||
| ALL_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT, \ | ||
| GENERATE_TEXT_READOUT_STRUCT, GENERATE_STATNAME_STRUCT) \ | ||
| } | ||
|
|
||
| } // namespace Envoy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| #pragma once | ||
|
|
||
| #include "envoy/common/pure.h" | ||
| #include "envoy/stats/scope.h" | ||
| #include "envoy/stats/stats.h" | ||
|
|
||
| #include "source/common/common/cleanup.h" | ||
| #include "source/common/common/thread.h" | ||
| #include "source/common/stats/symbol_table.h" | ||
| #include "source/common/stats/utility.h" | ||
|
|
||
| namespace Envoy { | ||
| namespace Stats { | ||
|
|
||
| /** | ||
| * Lazy-initialization wrapper for StatsStructType, intended for deferred instantiation of a block | ||
| * of stats that might not be needed in a given Envoy process. | ||
| * | ||
| * This class is thread-safe -- instantiations can occur on multiple concurrent threads. | ||
| * This is used when | ||
| * :ref:`enable_deferred_creation_stats | ||
| * <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` is enabled. | ||
| */ | ||
| template <typename StatsStructType> | ||
| class DeferredStats : public DeferredCreationCompatibleInterface<StatsStructType> { | ||
| public: | ||
| // Capture the stat names object and the scope with a ctor, that can be used to instantiate a | ||
| // StatsStructType object later. | ||
| // Caller should make sure scope and stat_names outlive this object. | ||
| DeferredStats(const typename StatsStructType::StatNameType& stat_names, | ||
| Stats::ScopeSharedPtr scope) | ||
| : initialized_( | ||
| // A lambda is used as we need to register the name into the symbol table. | ||
| // Note: there is no issue to capture a reference of the scope here as this lambda is | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/issue/need/
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, I think the comment is saying "it's okay to capture the reference here" per our offline discussion on the life cycle of the "scope", so "no issue" seems fine. |
||
| // only used to initialize the 'initialized_' Gauge. | ||
| [&scope]() -> Gauge& { | ||
| Stats::StatNamePool pool(scope->symbolTable()); | ||
| return Stats::Utility::gaugeFromElements( | ||
| *scope, {pool.add(StatsStructType::typeName()), pool.add("initialized")}, | ||
| Stats::Gauge::ImportMode::HiddenAccumulate); | ||
| }()), | ||
| ctor_([this, &stat_names, stats_scope = std::move(scope)]() -> StatsStructType* { | ||
| initialized_.inc(); | ||
| // Reset ctor_ to save some RAM. | ||
| Cleanup reset_ctor([this] { ctor_ = nullptr; }); | ||
| return new StatsStructType(stat_names, *stats_scope); | ||
| }) { | ||
| if (initialized_.value() > 0) { | ||
| getOrCreateHelper(); | ||
| } | ||
| } | ||
| ~DeferredStats() { | ||
| if (ctor_ == nullptr) { | ||
| initialized_.dec(); | ||
| } | ||
| } | ||
| inline StatsStructType& getOrCreate() override { return getOrCreateHelper(); } | ||
|
|
||
| private: | ||
| // We can't call getOrCreate directly from constructor, otherwise the compiler complains about | ||
| // bypassing virtual dispatch even though it's fine. | ||
| inline StatsStructType& getOrCreateHelper() { return *internal_stats_.get(ctor_); } | ||
|
|
||
| // In order to preserve stat value continuity across a config reload, we need to automatically | ||
| // re-instantiate lazy stats when they are constructed, if there is already a live instantiation | ||
| // to the same stats. Consider the following alternate scenarios: | ||
|
|
||
| // Scenario 1: a cluster is instantiated but receives no requests, so its traffic-related stats | ||
| // are never instantiated. When this cluster gets reloaded on a config update, a new lazy-init | ||
| // block is created, but the stats should again not be instantiated. | ||
|
|
||
| // Scenario 2: a cluster is instantiated and receives traffic, so its traffic-related stats are | ||
| // instantiated. We must ensure that a new instance for the same cluster gets its lazy-stats | ||
| // instantiated before the previous cluster of the same name is destructed. | ||
|
|
||
| // To do that we keep an "initialized" gauge in the cluster's scope, which will be associated by | ||
| // name to the previous generation's cluster's lazy-init block. We use the value in this shared | ||
| // gauge to determine whether to instantiate the lazy block on construction. | ||
| Gauge& initialized_; | ||
| // TODO(#26957): Clean up this ctor_ by moving its ownership to AtomicPtr, and drop | ||
| // the setter lambda when the nested object is created. | ||
| std::function<StatsStructType*()> ctor_; | ||
| Thread::AtomicPtr<StatsStructType, Thread::AtomicPtrAllocMode::DeleteOnDestruct> internal_stats_; | ||
| }; | ||
|
|
||
| // Non-deferred wrapper over StatsStructType. This is used when | ||
| // :ref:`enable_deferred_creation_stats | ||
| // <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` is not enabled. | ||
| template <typename StatsStructType> | ||
| class DirectStats : public DeferredCreationCompatibleInterface<StatsStructType> { | ||
| public: | ||
| DirectStats(const typename StatsStructType::StatNameType& stat_names, Stats::Scope& scope) | ||
| : stats_(stat_names, scope) {} | ||
| inline StatsStructType& getOrCreate() override { return stats_; } | ||
|
|
||
| private: | ||
| StatsStructType stats_; | ||
| }; | ||
|
|
||
| // Template that lazily initializes a StatsStruct. | ||
| // The bootstrap config :ref:`enable_deferred_creation_stats | ||
| // <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.deferred_stat_options>` decides if | ||
| // stats lazy initialization is enabled or not. | ||
| template <typename StatsStructType> | ||
| DeferredCreationCompatibleStats<StatsStructType> | ||
| createDeferredCompatibleStats(Stats::ScopeSharedPtr scope, | ||
| const typename StatsStructType::StatNameType& stat_names, | ||
| bool deferred_creation) { | ||
| if (deferred_creation) { | ||
| return DeferredCreationCompatibleStats<StatsStructType>( | ||
| std::make_unique<DeferredStats<StatsStructType>>(stat_names, scope)); | ||
| } else { | ||
| return DeferredCreationCompatibleStats<StatsStructType>( | ||
| std::make_unique<DirectStats<StatsStructType>>(stat_names, *scope)); | ||
| } | ||
| } | ||
|
|
||
| } // namespace Stats | ||
| } // namespace Envoy | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Format wants a blank line here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done