Skip to content

Commit 45716e6

Browse files
wbpcodeTAOXUYjwendell
authored
[bp/1.37] [stack] stats number limit and scope level stats matcher support (#44085)
Commit Message: [bp/1.37] [stack] stats number limit and scope level stats matcher support Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: [Optional Runtime guard:] [Optional Fixes #Issue] [Optional Fixes commit #PR or SHA] [Optional Deprecated:] [Optional [API Considerations](https://github.com/envoyproxy/envoy/blob/main/api/review_checklist.md):] --------- Signed-off-by: Xuyang Tao <taoxuy@google.com> Signed-off-by: wbpcode/wangbaiping <wbphub@gmail.com> Signed-off-by: wbpcode <wbphub@gmail.com> Signed-off-by: Jonh Wendell <jwendell@redhat.com> Co-authored-by: Xuyang Tao <taoxuy@google.com> Co-authored-by: Jonh Wendell <jwendell@redhat.com>
1 parent b6f9a6b commit 45716e6

28 files changed

Lines changed: 1154 additions & 214 deletions

changelogs/current.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,14 @@ removed_config_or_runtime:
2222
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`
2323

2424
new_features:
25+
- area: stats
26+
change: |
27+
Added support to limit the number of stats stored in each stats scope in the stats libarary.
28+
- area: stats
29+
change: |
30+
Added support for cluster-level stats matcher, allowing more granular control over which stats
31+
are enabled and reported at the cluster level. This the stats matcher could be configured via
32+
the xDS API dynamically on a per-cluster basis.
33+
See :ref:`envoy.stats_matcher <well_known_metadata_envoy_stats_matcher>` for more details.
2534
2635
deprecated:

docs/root/configuration/advanced/metadata_configurations.rst

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,61 @@ modifying Envoy's core API or implementation.
1717
For instance, users can add extra attributes to routes, such as the route owner or upstream service maintainer, to metadata.
1818
They can then enable Envoy to log these attributes to the access log or report them to StatsD, among other possibilities.
1919
Moreover, users can write a filter/extension to read these attributes and execute any specific logic.
20+
21+
.. _well_known_metadata:
22+
23+
Well-Known Metadata
24+
-------------------
25+
26+
The following ``typed_filter_metadata`` or ``filter_metadata`` keys are recognized by Envoy and control built-in behavior.
27+
Each entry specifies where the metadata can be configured.
28+
29+
.. _well_known_metadata_envoy_stats_matcher:
30+
31+
``envoy.stats_matcher``
32+
~~~~~~~~~~~~~~~~~~~~~~~
33+
34+
**Type:** :ref:`envoy.config.metrics.v3.StatsMatcher <envoy_v3_api_msg_config.metrics.v3.StatsMatcher>`
35+
36+
**Applicable to:** Upstream cluster (:ref:`Cluster.metadata <envoy_v3_api_field_config.cluster.v3.Cluster.metadata>`)
37+
38+
**Fields:** ``typed_filter_metadata``
39+
40+
When present in a cluster's ``typed_filter_metadata``, Envoy uses the provided
41+
:ref:`StatsMatcher <envoy_v3_api_msg_config.metrics.v3.StatsMatcher>` as the stats matcher for that
42+
cluster's stats scope. This per-cluster matcher **replaces** (not supplements) the global stats
43+
matcher configured in the bootstrap :ref:`StatsConfig
44+
<envoy_v3_api_msg_config.metrics.v3.StatsConfig>`. Child scopes created under the cluster scope
45+
inherit the matcher unless overridden.
46+
47+
This allows fine-grained control over which stats are created per cluster — for example, enabling a
48+
minimal set of stats on high-cardinality clusters to reduce memory and CPU overhead.
49+
50+
Example:
51+
52+
.. code-block:: yaml
53+
54+
clusters:
55+
- name: my_cluster
56+
connect_timeout: 0.25s
57+
type: STATIC
58+
lb_policy: ROUND_ROBIN
59+
metadata:
60+
typed_filter_metadata:
61+
envoy.stats_matcher:
62+
"@type": type.googleapis.com/envoy.config.metrics.v3.StatsMatcher
63+
inclusion_list:
64+
patterns:
65+
- prefix: "cluster.my_cluster.upstream_cx"
66+
load_assignment:
67+
cluster_name: my_cluster
68+
endpoints:
69+
- lb_endpoints:
70+
- endpoint:
71+
address:
72+
socket_address:
73+
address: 127.0.0.1
74+
port_value: 10001
75+
76+
In this example, only stats whose names start with ``cluster.my_cluster.upstream_cx`` are created
77+
for ``my_cluster``, all other cluster stats are suppressed.

docs/root/configuration/observability/statistics.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ Server related statistics are rooted at *server.* with following statistics:
3535
static_unknown_fields, Counter, Number of messages in static configuration with unknown fields
3636
dynamic_unknown_fields, Counter, Number of messages in dynamic configuration with unknown fields
3737
wip_protos, Counter, Number of messages and fields marked as work-in-progress being used
38+
stats_overflow.counter, Counter, Total number of counter lookup or creation attempts dropped due to reaching the configured limit on label cardinality.
39+
stats_overflow.gauge, Counter, Total number of gauge lookup or creation attempts dropped due to reaching the configured limit on label cardinality.
40+
stats_overflow.histogram, Counter, Total number of histogram lookup or creation attempts dropped due to reaching the configured limit on label cardinality.
3841

3942
.. _server_compilation_settings_statistics:
4043

envoy/stats/scope.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "envoy/common/pure.h"
88
#include "envoy/stats/histogram.h"
9+
#include "envoy/stats/stats_matcher.h"
910
#include "envoy/stats/tag.h"
1011

1112
#include "absl/types/optional.h"
@@ -28,6 +29,17 @@ using TextReadoutOptConstRef = absl::optional<std::reference_wrapper<const TextR
2829
using ConstScopeSharedPtr = std::shared_ptr<const Scope>;
2930
using ScopeSharedPtr = std::shared_ptr<Scope>;
3031

32+
// Settings for limiting the number of counters, gauges and histograms allowed
33+
// in a scope. This currently only supports thread local stats.
34+
struct ScopeStatsLimitSettings {
35+
// Max number of counters allowed in this scope. 0 means no limit.
36+
uint32_t max_counters = 0;
37+
// Max number of gauges allowed in this scope. 0 means no limit.
38+
uint32_t max_gauges = 0;
39+
// Max number of histograms allowed in this scope. 0 means no limit.
40+
uint32_t max_histograms = 0;
41+
};
42+
3143
template <class StatType> using IterateFn = std::function<bool(const RefcountPtr<StatType>&)>;
3244

3345
/**
@@ -73,8 +85,14 @@ class Scope : public std::enable_shared_from_this<Scope> {
7385
* @param name supplies the scope's namespace prefix.
7486
* @param evictable whether unused metrics can be deleted from the scope caches. This requires
7587
* that the metrics are not stored by reference.
88+
* @param limits metric limits for counters, gauges and histograms allowed in this scope.
89+
* @param matcher optional per-scope stats matcher; replaces the store-level matcher when set.
90+
* NOTE: If the scope specific matcher is set, then the sub scope will inherit the same matcher
91+
* unless another matcher is explicitly set.
7692
*/
77-
virtual ScopeSharedPtr createScope(const std::string& name, bool evictable = false) PURE;
93+
virtual ScopeSharedPtr createScope(const std::string& name, bool evictable = false,
94+
const ScopeStatsLimitSettings& limits = {},
95+
StatsMatcherSharedPtr matcher = nullptr) PURE;
7896

7997
/**
8098
* Allocate a new scope. NOTE: The implementation should correctly handle overlapping scopes
@@ -84,8 +102,14 @@ class Scope : public std::enable_shared_from_this<Scope> {
84102
* @param name supplies the scope's namespace prefix.
85103
* @param evictable whether unused metrics can be deleted from the scope caches. This requires
86104
* that the metrics are not stored by reference.
105+
* @param limits metric limits for counters, gauges and histograms allowed in this scope.
106+
* @param matcher optional per-scope stats matcher; replaces the store-level matcher when set.
107+
* NOTE: If the scope specific matcher is set, then the sub scope will inherit the same matcher
108+
* unless another matcher is explicitly set.
87109
*/
88-
virtual ScopeSharedPtr scopeFromStatName(StatName name, bool evictable = false) PURE;
110+
virtual ScopeSharedPtr scopeFromStatName(StatName name, bool evictable = false,
111+
const ScopeStatsLimitSettings& limits = {},
112+
StatsMatcherSharedPtr matcher = nullptr) PURE;
89113

90114
/**
91115
* Creates a Counter from the stat name. Tag extraction will be performed on the name.

envoy/stats/stats_matcher.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class StatsMatcher {
8484
};
8585

8686
using StatsMatcherPtr = std::unique_ptr<const StatsMatcher>;
87+
using StatsMatcherSharedPtr = std::shared_ptr<const StatsMatcher>;
8788

8889
} // namespace Stats
8990
} // namespace Envoy

envoy/stats/store.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ class Store {
177177
/**
178178
* @return a scope of the given name.
179179
*/
180-
ScopeSharedPtr createScope(const std::string& name, bool evictable = false) {
181-
return rootScope()->createScope(name, evictable);
180+
ScopeSharedPtr createScope(const std::string& name, bool evictable = false,
181+
const ScopeStatsLimitSettings& limits = {},
182+
StatsMatcherSharedPtr matcher = nullptr) {
183+
return rootScope()->createScope(name, evictable, limits, std::move(matcher));
182184
}
183185

184186
/**

source/common/stats/isolated_store_impl.cc

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ IsolatedStoreImpl::IsolatedStoreImpl(SymbolTable& symbol_table)
4646
return alloc_.makeTextReadout(joiner.nameWithTags(), joiner.tagExtractedName(),
4747
tagVectorFromOpt(tags));
4848
}),
49-
null_counter_(new NullCounterImpl(symbol_table)),
50-
null_gauge_(new NullGaugeImpl(symbol_table)) {}
49+
null_counter_(symbol_table), null_gauge_(symbol_table), null_histogram_(symbol_table),
50+
null_text_readout_(symbol_table) {}
5151

5252
ScopeSharedPtr IsolatedStoreImpl::rootScope() {
5353
if (lazy_default_scope_ == nullptr) {
@@ -63,20 +63,27 @@ ConstScopeSharedPtr IsolatedStoreImpl::constRootScope() const {
6363

6464
IsolatedStoreImpl::~IsolatedStoreImpl() = default;
6565

66-
ScopeSharedPtr IsolatedScopeImpl::createScope(const std::string& name, bool) {
66+
ScopeSharedPtr IsolatedScopeImpl::createScope(const std::string& name, bool,
67+
const ScopeStatsLimitSettings& limits,
68+
StatsMatcherSharedPtr matcher) {
6769
StatNameManagedStorage stat_name_storage(Utility::sanitizeStatsName(name), symbolTable());
68-
return scopeFromStatName(stat_name_storage.statName(), false);
70+
return scopeFromStatName(stat_name_storage.statName(), false, limits, std::move(matcher));
6971
}
7072

71-
ScopeSharedPtr IsolatedScopeImpl::scopeFromStatName(StatName name, bool) {
73+
ScopeSharedPtr IsolatedScopeImpl::scopeFromStatName(StatName name, bool,
74+
const ScopeStatsLimitSettings&,
75+
StatsMatcherSharedPtr matcher) {
7276
SymbolTable::StoragePtr prefix_name_storage = symbolTable().join({prefix(), name});
73-
ScopeSharedPtr scope = store_.makeScope(StatName(prefix_name_storage.get()));
77+
// Use explicit matcher if provided; otherwise inherit scope_matcher_.
78+
StatsMatcherSharedPtr child_matcher = matcher ? std::move(matcher) : scope_matcher_;
79+
ScopeSharedPtr scope =
80+
store_.makeScope(StatName(prefix_name_storage.get()), std::move(child_matcher));
7481
addScopeToStore(scope);
7582
return scope;
7683
}
7784

78-
ScopeSharedPtr IsolatedStoreImpl::makeScope(StatName name) {
79-
return std::make_shared<IsolatedScopeImpl>(name, *this);
85+
ScopeSharedPtr IsolatedStoreImpl::makeScope(StatName name, StatsMatcherSharedPtr matcher) {
86+
return std::make_shared<IsolatedScopeImpl>(name, *this, std::move(matcher));
8087
}
8188

8289
} // namespace Stats

0 commit comments

Comments
 (0)