-
Notifications
You must be signed in to change notification settings - Fork 5.5k
stats: Factor out creation of cluster-stats StatNames from creation of the stats, to save CPU during xDS updates #14028
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 13 commits
98d6a27
618834e
bd5c8a8
50976f1
56259be
f6b5fd5
6b0c333
d798bb0
d437b12
6d1e6c7
63d0d21
a34cb47
c046273
11e9bd7
c4bb1f8
9060912
0498546
02c56ae
e845693
5f6b47b
f702021
588d16e
a595f32
4554d16
5678a02
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 |
|---|---|---|
|
|
@@ -5,6 +5,9 @@ | |
| #include "envoy/stats/histogram.h" | ||
| #include "envoy/stats/stats.h" | ||
|
|
||
| #include "common/stats/symbol_table_impl.h" | ||
| #include "common/stats/utility.h" | ||
|
|
||
| #include "absl/strings/match.h" | ||
| #include "absl/strings/str_cat.h" | ||
|
|
||
|
|
@@ -33,6 +36,36 @@ namespace Envoy { | |
| * Finally, when you want to actually instantiate the above struct using a Stats::Pool, you do: | ||
| * MyCoolStats stats{ | ||
| * MY_COOL_STATS(POOL_COUNTER(...), POOL_GAUGE(...), POOL_HISTOGRAM(...))}; | ||
| * | ||
| * | ||
|
jmarantz marked this conversation as resolved.
|
||
| * The above constructs are the simplest way to declare counters, gauges, | ||
| * histograms, and text-readouts in your data structures. However they incur | ||
| * some overhead to symbolize the names every time they are instantiated. For | ||
| * data structures that are re-instantiated extensively during operation, | ||
| * e.g. in response to an xDS update, We can separately instantiate a | ||
| * StatNameStruct, containing symbolized names for each stat. That should be | ||
| * instantiated once at startup and held in some context or factory. Do that | ||
| * with: | ||
| * | ||
| * MAKE_STAT_NAMES_STRUCT(MyStatNames, MY_COOL_STATS); | ||
| * | ||
| * This generates a structure definition with a constructor that requires a | ||
| * SymbolTable. So you must, in a context instantiated once, initialize with: | ||
| * | ||
| * : my_cool_stat_names_(stat_store.symbolTable()) | ||
| * | ||
| * Once you have the StatNamesStruct instance declared, you can create a stats | ||
| * struct efficiently during operation (e.g. in an xDS handler) with | ||
| * | ||
| * MAKE_STATS_STRUCT(MyStats, MyStatNames, MY_COOL_STATS); | ||
| * | ||
| * This new structure is constructed with 2 or 3 args: | ||
| * 1. The stat_names struct created from MAKE_STAT_NAMES_STRUCT | ||
| * 2. The scope in which to instantiate the stats | ||
| * 3. An optional prefix, which will be prepended to each stat name. | ||
| * For example: | ||
| * | ||
| * : my_cool_stats_(context.my_cool_stat_names_, scope, opt_prefix) | ||
| */ | ||
|
|
||
| // Fully-qualified for use in external callsites. | ||
|
|
@@ -59,6 +92,7 @@ static inline std::string statPrefixJoin(absl::string_view prefix, absl::string_ | |
| #define POOL_GAUGE_PREFIX(POOL, PREFIX) (POOL).gaugeFromString(Envoy::statPrefixJoin(PREFIX, FINISH_STAT_DECL_MODE_ | ||
| #define POOL_HISTOGRAM_PREFIX(POOL, PREFIX) (POOL).histogramFromString(Envoy::statPrefixJoin(PREFIX, FINISH_STAT_DECL_UNIT_ | ||
| #define POOL_TEXT_READOUT_PREFIX(POOL, PREFIX) (POOL).textReadoutFromString(Envoy::statPrefixJoin(PREFIX, FINISH_STAT_DECL_ | ||
| #define POOL_STAT_NAME_PREFIX(POOL, PREFIX) (POOL).symbolTable().textReadoutFromString(Envoy::statPrefixJoin(PREFIX, FINISH_STAT_DECL_ | ||
|
|
||
| #define POOL_COUNTER(POOL) POOL_COUNTER_PREFIX(POOL, "") | ||
| #define POOL_GAUGE(POOL) POOL_GAUGE_PREFIX(POOL, "") | ||
|
|
@@ -69,4 +103,65 @@ static inline std::string statPrefixJoin(absl::string_view prefix, absl::string_ | |
| #define NULL_STAT_DECL_IGNORE_MODE_(X, MODE) std::string(#X)), | ||
|
|
||
| #define NULL_POOL_GAUGE(POOL) (POOL).nullGauge(NULL_STAT_DECL_IGNORE_MODE_ | ||
|
|
||
| // Used for declaring StatNames in a structure. | ||
| #define GENERATE_STAT_NAME_STRUCT(NAME, ...) Envoy::Stats::StatName NAME##_; | ||
| #define GENERATE_STAT_NAME_INIT(NAME, ...) , NAME##_(pool_.add(#NAME)) | ||
|
|
||
| // Macros for declaring stat-structures using StatNames, for those that must be | ||
| // instantiated during operation, and where speed and scale matters. These | ||
| // macros are not for direct use; they are only for use from | ||
| // MAKE_STAT_NAMES_STRUCT. and MAKE_STAT_STRUCT. | ||
| #define MAKE_STATS_STRUCT_COUNTER_HELPER_(NAME) \ | ||
| , NAME##_(Envoy::Stats::Utility::counterFromStatNames(scope, {prefix, stat_names.NAME##_})) | ||
| #define MAKE_STATS_STRUCT_GAUGE_HELPER_(NAME, MODE) \ | ||
| , NAME##_(Envoy::Stats::Utility::gaugeFromStatNames(scope, {prefix, stat_names.NAME##_}, \ | ||
| Envoy::Stats::Gauge::ImportMode::MODE)) | ||
| #define MAKE_STATS_STRUCT_HISTOGRAM_HELPER_(NAME, UNIT) \ | ||
| , NAME##_(Envoy::Stats::Utility::histogramFromStatNames(scope, {prefix, stat_names.NAME##_}, \ | ||
| Envoy::Stats::Histogram::Unit::UNIT)) | ||
| #define MAKE_STATS_STRUCT_TEXT_READOUT_HELPER_(NAME) \ | ||
| , NAME##_(Envoy::Stats::Utility::textReadoutFromStatNames(scope, {prefix, stat_names.NAME##_})) | ||
|
|
||
| #define MAKE_STATS_STRUCT_STATNAME_HELPER_(name) | ||
| #define GENERATE_STATNAME_STRUCT(name) | ||
|
|
||
| /** | ||
|
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. Note that MAKE_STAT_NAMES_STRUCT and MAKE_STATS_STRUCT do not follow the pattern of the existing macros as they require distinct initializers based on data constructed from the ctor params. There's also a bit of hackery where the HELPER_ functions above depend on formal-param names established in other macros. I couldn't figure out how to make something like your FINISH_DECL macros work for this. |
||
| * Generates a struct with StatNames for a subsystem, based on the stats macro | ||
| * with COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, and STATNAME calls. The | ||
| * ALL_STATS macro must have all 5 parameters. | ||
| */ | ||
| #define MAKE_STAT_NAMES_STRUCT(StatNamesStruct, ALL_STATS) \ | ||
| struct StatNamesStruct { \ | ||
| explicit StatNamesStruct(Envoy::Stats::SymbolTable& symbol_table) \ | ||
| : pool_(symbol_table) \ | ||
| ALL_STATS(GENERATE_STAT_NAME_INIT, GENERATE_STAT_NAME_INIT, GENERATE_STAT_NAME_INIT, \ | ||
| GENERATE_STAT_NAME_INIT, GENERATE_STAT_NAME_INIT) {} \ | ||
| Envoy::Stats::StatNamePool pool_; \ | ||
| ALL_STATS(GENERATE_STAT_NAME_STRUCT, GENERATE_STAT_NAME_STRUCT, GENERATE_STAT_NAME_STRUCT, \ | ||
| GENERATE_STAT_NAME_STRUCT, GENERATE_STAT_NAME_STRUCT) \ | ||
| } | ||
|
|
||
| /** | ||
| * Instantiates a structure of stats based on a new scope and optional prefix, | ||
| * using a predefined structure of stat names. A reference to the stat_names is | ||
| * also stored in the structure, for two reasons: (a) as a syntactic convenience | ||
| * for using macros to generate the comma separators for the initializer and (b) | ||
| * as a convenience at the call-site to access STATNAME-declared names from the | ||
| * stats structure. | ||
| */ | ||
| #define MAKE_STATS_STRUCT(StatsStruct, StatNamesStruct, ALL_STATS) \ | ||
| struct 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_; \ | ||
| 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 |
|---|---|---|
|
|
@@ -24,6 +24,7 @@ | |
| #include "envoy/singleton/manager.h" | ||
| #include "envoy/ssl/context_manager.h" | ||
| #include "envoy/stats/store.h" | ||
| #include "envoy/stats/symbol_table.h" | ||
| #include "envoy/tcp/conn_pool.h" | ||
| #include "envoy/thread_local/thread_local.h" | ||
| #include "envoy/upstream/health_checker.h" | ||
|
|
@@ -35,6 +36,11 @@ | |
| #include "absl/container/node_hash_map.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| namespace Router { | ||
| struct RouterStatNames; | ||
| } | ||
|
|
||
| namespace Upstream { | ||
|
|
||
| /** | ||
|
|
@@ -300,6 +306,8 @@ class CdsApi { | |
|
|
||
| using CdsApiPtr = std::unique_ptr<CdsApi>; | ||
|
|
||
| struct ClusterManagerStatNames; | ||
|
|
||
| /** | ||
| * Factory for objects needed during cluster manager operation. | ||
| */ | ||
|
|
@@ -350,6 +358,26 @@ class ClusterManagerFactory { | |
| * Returns the secret manager. | ||
| */ | ||
| virtual Secret::SecretManager& secretManager() PURE; | ||
|
|
||
| /** | ||
| * Returns a struct with all the Stats::StatName objects needed by | ||
| * ClusterManager. This helps factor out some relatively heavy name | ||
| * construction which occur when there is a large CDS update during operation, | ||
| * relative to recreating all stats from strings on-the-fly. | ||
|
Member
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. Wouldn't these be Cluster/upstream stats and not cluster manager stats which I think would be global and created once?
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. yes, will rename.
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. I reverted the ClusterManager stats to their previous state. |
||
| * | ||
| * @return the stat names. | ||
| */ | ||
| virtual const ClusterManagerStatNames& clusterManagerStatNames() const PURE; | ||
|
|
||
| /** | ||
| * Returns a struct with all the Stats::StatName objects needed by the | ||
| * router. This helps factor out some relatively heavy name construction which | ||
| * occurs when there is a large CDS update during operation, relative to | ||
| * recreating all stats from strings on-the-fly. | ||
| * | ||
| * @return the stat names. | ||
| */ | ||
| virtual const Router::RouterStatNames& routerStatNames() const PURE; | ||
|
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. we hang the routerStatNames off the clusterManagerFactory because we happen to have that available in the place where we construct the router's FilterConfig. It's arguably not too clean. I could also just add a RouterContext and plumb that through the chain of constructors.
Member
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. From a cleanliness/tech debt perspective, we are trying to remove protocol specific stuff from cluster manager (@alyssawilk just started on some of this in terms of moving hard coded HTTP logic out to extensions). It seems to me the best way to do this would be create a singleton somewhere where the router config is created? Then it would just be created once and lookup?
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. I think I have a better place to put it: the Http::Context. I thought of that because of your comment about 'hard coded HTTP logic". Does that seem reasonable? And I have an Http::Context available to me where I needed the router stat names without excess plumbing.
Member
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. This is very router filter specific though. Can't it just be a singleton provided by the router filter factory?
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. Per slack, this is not the router filter, it was in source/common/router. I earlier had this PR in a state where I had put the names needed for Router::FilterStats into Http::Context, and that was pretty straightforward, but wasn't great for separation. Now I have a Router::Context. Plumbing that through was trivial but involved a lot of file edits due to parallel universes for mocks, validation servers, integration test servers, and whatnot. LMK what you think; I'm OK trying this out as a prod singleton too, but I think it would involve a few implicit dependencies that are now explicit (if verbose). For example in every test method we'd need to re-init the singleton with the test's symbol table.
Member
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. Yeah sorry I had forgotten the AsyncClient angle to this. Given that this is basically part of the core I'm fine putting it wherever you think is easiest.
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. I think it's a bit cleaner to have the router stats in their won context, though it expanded the number of files in the PR a bit. |
||
| }; | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #include "envoy/stats/stats_macros.h" | ||
|
|
||
| #pragma once | ||
|
|
||
| namespace Envoy { | ||
| namespace Router { | ||
|
|
||
| /** | ||
| * All router filter stats. @see stats_macros.h | ||
| */ | ||
| #define ALL_ROUTER_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ | ||
| COUNTER(no_cluster) \ | ||
| COUNTER(no_route) \ | ||
| COUNTER(passthrough_internal_redirect_bad_location) \ | ||
| COUNTER(passthrough_internal_redirect_no_route) \ | ||
| COUNTER(passthrough_internal_redirect_predicate) \ | ||
| COUNTER(passthrough_internal_redirect_too_many_redirects) \ | ||
| COUNTER(passthrough_internal_redirect_unsafe_scheme) \ | ||
| COUNTER(rq_direct_response) \ | ||
| COUNTER(rq_redirect) \ | ||
| COUNTER(rq_reset_after_downstream_response_started) \ | ||
| COUNTER(rq_total) \ | ||
| STATNAME(retry) | ||
|
|
||
| MAKE_STAT_NAMES_STRUCT(RouterStatNames, ALL_ROUTER_STATS); | ||
|
|
||
| } // namespace Router | ||
| } // namespace Envoy |
Uh oh!
There was an error while loading. Please reload this page.