Skip to content

Commit 0251a12

Browse files
committed
Implement per tag control in tag cardinality processor
1 parent 4ddc412 commit 0251a12

6 files changed

Lines changed: 741 additions & 113 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
The `tag_cardinality_limit` transform gained two new configuration capabilities:
2+
3+
- **Per-tag overrides**: each entry in `per_metric_limits` now supports a `per_tag_limits`
4+
map whose entries can override settings for a specific tag key on that metric. The
5+
following per-tag fields are available:
6+
- `value_limit` (optional): caps distinct values for this tag key. Inherits from the
7+
enclosing per-metric `value_limit` when unset.
8+
- `excluded` (default `false`): when `true`, opts this tag out of cardinality tracking
9+
entirely — all values pass through unchecked.
10+
11+
The tracking mode (`exact`/`probabilistic`), `cache_size_per_key`, `limit_exceeded_action`,
12+
and `internal_metrics` are always inherited from the enclosing per-metric configuration
13+
and cannot be overridden per-tag.
14+
15+
- **Metric exclusion**: `mode: excluded` is now available in `per_metric_limits` entries
16+
(not at the global level). When set, every tag on that metric is opted out of cardinality
17+
control — all tag values pass through and nothing is tracked. Any `per_tag_limits`
18+
overrides on an excluded metric are ignored.
19+
20+
authors: ArunPiduguDD

src/transforms/tag_cardinality_limit/config.rs

Lines changed: 163 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,9 @@ use crate::{
1111
transforms::{Transform, tag_cardinality_limit::TagCardinalityLimit},
1212
};
1313

14-
/// Configuration of internal metrics for the TagCardinalityLimit transform.
15-
#[configurable_component]
16-
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17-
#[serde(deny_unknown_fields)]
18-
pub struct InternalMetricsConfig {
19-
/// Whether to include extended tags (metric_name, tag_key) in the `tag_value_limit_exceeded_total` metric.
20-
///
21-
/// This helps identify which metrics and tag keys are hitting cardinality limits, but can significantly
22-
/// increase metric cardinality. Defaults to `false` because these tags have potentially unbounded cardinality.
23-
#[serde(default = "default_include_extended_tags")]
24-
#[configurable(metadata(docs::human_name = "Include Extended Tags"))]
25-
pub include_extended_tags: bool,
26-
}
14+
// =============================================================================
15+
// Top-level configuration
16+
// =============================================================================
2717

2818
/// Configuration for the `tag_cardinality_limit` transform.
2919
#[configurable_component(transform(
@@ -79,7 +69,11 @@ pub enum TrackingScope {
7969
PerMetric,
8070
}
8171

82-
/// Configuration for the `tag_cardinality_limit` transform for a specific group of metrics.
72+
// =============================================================================
73+
// Global-level configuration block
74+
// =============================================================================
75+
76+
/// Configuration block used at the global level.
8377
#[configurable_component]
8478
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8579
pub struct Inner {
@@ -99,7 +93,7 @@ pub struct Inner {
9993
pub internal_metrics: InternalMetricsConfig,
10094
}
10195

102-
/// Controls the approach taken for tracking tag cardinality.
96+
/// Controls the approach taken for tracking tag cardinality at the global level.
10397
#[configurable_component]
10498
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10599
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
@@ -122,19 +116,131 @@ pub enum Mode {
122116
Probabilistic(BloomFilterConfig),
123117
}
124118

125-
/// Bloom filter configuration in probabilistic mode.
119+
// =============================================================================
120+
// Per-metric configuration block
121+
// =============================================================================
122+
123+
/// Tag cardinality limit configuration per metric name.
124+
#[configurable_component]
125+
#[derive(Clone, Debug, Eq, PartialEq)]
126+
pub struct PerMetricConfig {
127+
/// Namespace of the metric this configuration refers to.
128+
#[serde(default)]
129+
pub namespace: Option<String>,
130+
131+
/// Per-tag-key overrides scoped to this metric.
132+
///
133+
/// Each entry may override `value_limit` for a specific tag key or opt it out of
134+
/// tracking entirely with `excluded: true`. The tracking mode, `cache_size_per_key`,
135+
/// `limit_exceeded_action`, and `internal_metrics` are always inherited from the
136+
/// enclosing per-metric configuration and cannot be set per-tag.
137+
/// Tags not listed here use the per-metric configuration.
138+
#[configurable(
139+
derived,
140+
metadata(docs::additional_props_description = "An individual tag configuration.")
141+
)]
142+
#[serde(default)]
143+
pub per_tag_limits: HashMap<String, PerTagConfig>,
144+
145+
#[serde(flatten)]
146+
pub config: OverrideInner,
147+
}
148+
149+
/// Configuration block used at per-metric level. Same shape as the global configuration but
150+
/// with `OverrideMode`, which adds `excluded` for opting that metric out of cardinality
151+
/// control entirely.
126152
#[configurable_component]
127153
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128-
pub struct BloomFilterConfig {
129-
/// The size of the cache for detecting duplicate tags, in bytes.
154+
pub struct OverrideInner {
155+
/// How many distinct values to accept for any given key. Ignored when `mode: excluded`.
156+
#[serde(default = "default_value_limit")]
157+
pub value_limit: usize,
158+
159+
#[configurable(derived)]
160+
#[serde(default = "default_limit_exceeded_action")]
161+
pub limit_exceeded_action: LimitExceededAction,
162+
163+
#[serde(flatten)]
164+
pub mode: OverrideMode,
165+
166+
#[configurable(derived)]
167+
#[serde(default)]
168+
pub internal_metrics: InternalMetricsConfig,
169+
}
170+
171+
/// Controls the approach taken for tracking tag cardinality at the per-metric level.
172+
/// Adds `excluded` to the global `Mode` variants to allow opting a metric out entirely.
173+
#[configurable_component]
174+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
175+
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
176+
#[configurable(metadata(
177+
docs::enum_tag_description = "Controls the approach taken for tracking tag cardinality."
178+
))]
179+
pub enum OverrideMode {
180+
/// Tracks cardinality exactly. See `Mode::Exact` for details.
181+
Exact,
182+
183+
/// Tracks cardinality probabilistically. See `Mode::Probabilistic` for details.
184+
Probabilistic(BloomFilterConfig),
185+
186+
/// Skip cardinality tracking for this metric. All tag values pass through and nothing is
187+
/// recorded. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
188+
/// `internal_metrics`) are ignored when this is selected. Any `per_tag_limits` entries
189+
/// on this metric are also ignored.
130190
///
131-
/// The larger the cache size, the less likely it is to have a false positive, or a case where
132-
/// we allow a new value for tag even after we have reached the configured limits.
133-
#[serde(default = "default_cache_size")]
134-
#[configurable(metadata(docs::human_name = "Cache Size per Key"))]
135-
pub cache_size_per_key: usize,
191+
/// Only valid in `per_metric_limits` entries; using it as the global `mode` is a
192+
/// configuration error.
193+
Excluded,
194+
}
195+
196+
impl OverrideMode {
197+
/// Returns the equivalent global `Mode` if this scope is tracked, or `None` if excluded.
198+
pub const fn as_mode(&self) -> Option<Mode> {
199+
match self {
200+
OverrideMode::Exact => Some(Mode::Exact),
201+
OverrideMode::Probabilistic(b) => Some(Mode::Probabilistic(*b)),
202+
OverrideMode::Excluded => None,
203+
}
204+
}
205+
}
206+
207+
// =============================================================================
208+
// Per-tag configuration block
209+
// =============================================================================
210+
211+
/// Tag cardinality limit configuration for a specific tag key, scoped under a per-metric override.
212+
#[configurable_component]
213+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
214+
pub struct PerTagConfig {
215+
#[serde(flatten)]
216+
pub config: PerTagInner,
217+
}
218+
219+
/// Configuration block used at the per-tag level.
220+
///
221+
/// The tracking mode (`exact` or `probabilistic`) and `cache_size_per_key` are always
222+
/// inherited from the enclosing per-metric configuration and cannot be overridden per-tag.
223+
/// A tag can be opted out of cardinality tracking entirely with `excluded: true`.
224+
#[configurable_component]
225+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
226+
pub struct PerTagInner {
227+
/// How many distinct values to accept for this tag key. If unset, inherits
228+
/// the `value_limit` from the enclosing per-metric (or global) configuration.
229+
/// Ignored when `excluded: true`.
230+
#[serde(default)]
231+
pub value_limit: Option<usize>,
232+
233+
/// When `true`, opts this tag out of cardinality tracking entirely. All values
234+
/// for this tag pass through without being recorded or checked against
235+
/// `value_limit`. Defaults to `false`.
236+
#[serde(default)]
237+
pub excluded: bool,
136238
}
137239

240+
// =============================================================================
241+
// Shared building blocks
242+
// =============================================================================
243+
138244
/// Possible actions to take when an event arrives that would exceed the cardinality limit for one
139245
/// or more of its tags.
140246
#[configurable_component]
@@ -148,26 +254,45 @@ pub enum LimitExceededAction {
148254
DropEvent,
149255
}
150256

151-
/// Tag cardinality limit configuration per metric name.
257+
/// Bloom filter configuration in probabilistic mode.
152258
#[configurable_component]
153-
#[derive(Clone, Debug, Eq, PartialEq)]
154-
pub struct PerMetricConfig {
155-
/// Namespace of the metric this configuration refers to.
156-
#[serde(default)]
157-
pub namespace: Option<String>,
158-
159-
#[serde(flatten)]
160-
pub config: Inner,
259+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
260+
pub struct BloomFilterConfig {
261+
/// The size of the cache for detecting duplicate tags, in bytes.
262+
///
263+
/// The larger the cache size, the less likely it is to have a false positive, or a case where
264+
/// we allow a new value for tag even after we have reached the configured limits.
265+
#[serde(default = "default_cache_size")]
266+
#[configurable(metadata(docs::human_name = "Cache Size per Key"))]
267+
pub cache_size_per_key: usize,
161268
}
162269

163-
const fn default_limit_exceeded_action() -> LimitExceededAction {
164-
LimitExceededAction::DropTag
270+
/// Configuration of internal metrics for the TagCardinalityLimit transform.
271+
#[configurable_component]
272+
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
273+
#[serde(deny_unknown_fields)]
274+
pub struct InternalMetricsConfig {
275+
/// Whether to include extended tags (metric_name, tag_key) in the `tag_value_limit_exceeded_total` metric.
276+
///
277+
/// This helps identify which metrics and tag keys are hitting cardinality limits, but can significantly
278+
/// increase metric cardinality. Defaults to `false` because these tags have potentially unbounded cardinality.
279+
#[serde(default = "default_include_extended_tags")]
280+
#[configurable(metadata(docs::human_name = "Include Extended Tags"))]
281+
pub include_extended_tags: bool,
165282
}
166283

284+
// =============================================================================
285+
// Defaults
286+
// =============================================================================
287+
167288
const fn default_value_limit() -> usize {
168289
500
169290
}
170291

292+
const fn default_limit_exceeded_action() -> LimitExceededAction {
293+
LimitExceededAction::DropTag
294+
}
295+
171296
const fn default_include_extended_tags() -> bool {
172297
false
173298
}
@@ -176,6 +301,10 @@ pub(crate) const fn default_cache_size() -> usize {
176301
5 * 1024 // 5KB
177302
}
178303

304+
// =============================================================================
305+
// Transform plumbing
306+
// =============================================================================
307+
179308
impl GenerateConfig for Config {
180309
fn generate_config() -> toml::Value {
181310
toml::Value::try_from(Self {

0 commit comments

Comments
 (0)