Skip to content

Commit 63f5251

Browse files
committed
Merge branch 'arun.pidugu/add-more-fine-grained-controls-tag-cardinality' of github.com:vectordotdev/vector into arun.pidugu/add-more-fine-grained-controls-tag-cardinality
2 parents 9d7f1b6 + 26239f0 commit 63f5251

8 files changed

Lines changed: 296 additions & 434 deletions

File tree

changelog.d/exclude_cardinality_limit.enhancement.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

changelog.d/per_tag_cardinality_limits.enhancement.md

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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` map
4+
whose entries can override `value_limit`, `mode`, and `internal_metrics` for a specific tag
5+
key. `limit_exceeded_action` is always inherited from the enclosing per-metric (or global)
6+
configuration — it is not per-tag-overridable. Resolution order for the overridable fields is
7+
per-tag → per-metric → global.
8+
- **Exclusion**: `mode: excluded` is now available as a third mode option in `per_metric_limits`
9+
and `per_tag_limits` entries (not at the global level). When set, the metric or tag is opted
10+
out of cardinality control — all tag values pass through and nothing is tracked. Other
11+
tracking fields on the entry (`value_limit`, `internal_metrics`) are ignored when
12+
`mode: excluded` is selected. Per-metric exclusion is blanket: when a metric's `mode` is
13+
`excluded`, every tag on that metric is excluded and any `per_tag_limits` overrides on it
14+
are ignored.
15+
16+
authors: ArunPiduguDD

lib/vector-core/src/event/metric/tags.rs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -777,34 +777,4 @@ mod tests {
777777
}
778778
}
779779
}
780-
781-
fn make_tags(pairs: &[(&str, &str)]) -> MetricTags {
782-
pairs
783-
.iter()
784-
.map(|(k, v)| (k.to_string(), v.to_string()))
785-
.collect()
786-
}
787-
788-
#[test]
789-
fn rename_with_replacement_basic() {
790-
let mut tags = make_tags(&[("old", "v1")]);
791-
assert!(tags.rename_with_replacement("old", "new".to_string()));
792-
assert_eq!(tags.get("new"), Some("v1"));
793-
assert!(!tags.contains_key("old"));
794-
}
795-
796-
#[test]
797-
fn rename_with_replacement_missing_old_returns_false() {
798-
let mut tags = make_tags(&[("a", "1")]);
799-
assert!(!tags.rename_with_replacement("missing", "b".to_string()));
800-
assert!(tags.contains_key("a"));
801-
}
802-
803-
#[test]
804-
fn rename_with_replacement_overwrites_existing_destination() {
805-
let mut tags = make_tags(&[("old", "v1"), ("new", "v2")]);
806-
assert!(tags.rename_with_replacement("old", "new".to_string()));
807-
assert_eq!(tags.get("new"), Some("v1"));
808-
assert!(!tags.contains_key("old"));
809-
}
810780
}

src/transforms/tag_cardinality_limit/config.rs

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -44,60 +44,66 @@ pub struct Config {
4444
pub per_metric_limits: HashMap<String, PerMetricConfig>,
4545
}
4646

47-
/// Configuration for the `tag_cardinality_limit` transform for a specific group of metrics.
47+
/// Configuration block used at the global level.
4848
#[configurable_component]
4949
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5050
pub struct Inner {
51-
/// How many distinct values to accept for any given key. Ignored when `exclude` is `true`.
51+
/// How many distinct values to accept for any given key.
5252
#[serde(default = "default_value_limit")]
5353
pub value_limit: usize,
5454

5555
#[configurable(derived)]
5656
#[serde(default = "default_limit_exceeded_action")]
5757
pub limit_exceeded_action: LimitExceededAction,
5858

59-
/// Cardinality tracking mode. Required unless `exclude` is `true`. When both `exclude` and
60-
/// `mode` are set, `exclude` takes precedence and `mode` is ignored.
61-
#[serde(flatten, default)]
62-
pub mode: Option<Mode>,
59+
#[serde(flatten)]
60+
pub mode: Mode,
6361

6462
#[configurable(derived)]
6563
#[serde(default)]
6664
pub internal_metrics: InternalMetricsConfig,
65+
}
66+
67+
/// Configuration block used at per-metric level. Same shape as the global configuration but
68+
/// with `OverrideMode`, which adds `excluded` for opting that metric out of cardinality
69+
/// control entirely.
70+
#[configurable_component]
71+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72+
pub struct OverrideInner {
73+
/// How many distinct values to accept for any given key. Ignored when `mode: excluded`.
74+
#[serde(default = "default_value_limit")]
75+
pub value_limit: usize,
76+
77+
#[configurable(derived)]
78+
#[serde(default = "default_limit_exceeded_action")]
79+
pub limit_exceeded_action: LimitExceededAction,
6780

68-
/// Exclude this metric or tag from cardinality control entirely. When `true`, all tag values
69-
/// pass through and nothing is tracked, regardless of any other fields on this entry.
81+
#[serde(flatten)]
82+
pub mode: OverrideMode,
83+
84+
#[configurable(derived)]
7085
#[serde(default)]
71-
pub exclude: bool,
86+
pub internal_metrics: InternalMetricsConfig,
7287
}
7388

74-
impl Inner {
75-
/// Validate that at least one of `exclude` or `mode` is set.
76-
pub(super) fn validate(&self, location: &str) -> crate::Result<()> {
77-
if !self.exclude && self.mode.is_none() {
78-
return Err(
79-
format!("{location}: `mode` is required unless `exclude: true` is set").into(),
80-
);
81-
}
82-
Ok(())
83-
}
84-
}
89+
/// Configuration block used at the per-tag level. Same as `OverrideInner` minus
90+
/// `limit_exceeded_action` — that field is inherited from the enclosing per-metric config
91+
#[configurable_component]
92+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93+
pub struct PerTagInner {
94+
/// How many distinct values to accept for this tag key. Ignored when `mode: excluded`.
95+
#[serde(default = "default_value_limit")]
96+
pub value_limit: usize,
8597

86-
impl Config {
87-
pub(super) fn validate(&self) -> crate::Result<()> {
88-
self.global.validate("global")?;
89-
for (name, pmc) in &self.per_metric_limits {
90-
pmc.config.validate(&format!("per_metric_limits[{name}]"))?;
91-
for (tag, ptc) in &pmc.per_tag_limits {
92-
ptc.config
93-
.validate(&format!("per_metric_limits[{name}].per_tag_limits[{tag}]"))?;
94-
}
95-
}
96-
Ok(())
97-
}
98+
#[serde(flatten)]
99+
pub mode: OverrideMode,
100+
101+
#[configurable(derived)]
102+
#[serde(default)]
103+
pub internal_metrics: InternalMetricsConfig,
98104
}
99105

100-
/// Controls the approach taken for tracking tag cardinality.
106+
/// Controls the approach taken for tracking tag cardinality at the global level.
101107
#[configurable_component]
102108
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103109
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
@@ -120,6 +126,41 @@ pub enum Mode {
120126
Probabilistic(BloomFilterConfig),
121127
}
122128

129+
/// Controls the approach taken for tracking tag cardinality at the per-metric or per-tag level.
130+
/// Adds `excluded` to the global `Mode` variants.
131+
#[configurable_component]
132+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133+
#[serde(tag = "mode", rename_all = "snake_case", deny_unknown_fields)]
134+
#[configurable(metadata(
135+
docs::enum_tag_description = "Controls the approach taken for tracking tag cardinality."
136+
))]
137+
pub enum OverrideMode {
138+
/// Tracks cardinality exactly. See `Mode::Exact` for details.
139+
Exact,
140+
141+
/// Tracks cardinality probabilistically. See `Mode::Probabilistic` for details.
142+
Probabilistic(BloomFilterConfig),
143+
144+
/// Skip cardinality tracking for this scope. All tag values pass through and nothing is
145+
/// recorded. Other tracking fields on the entry (`value_limit`, `limit_exceeded_action`,
146+
/// `internal_metrics`) are ignored when this is selected.
147+
///
148+
/// Only valid in `per_metric_limits` and `per_tag_limits` entries; using it as the global
149+
/// `mode` is a configuration error.
150+
Excluded,
151+
}
152+
153+
impl OverrideMode {
154+
/// Returns the equivalent global `Mode` if this scope is tracked, or `None` if excluded.
155+
pub const fn as_mode(&self) -> Option<Mode> {
156+
match self {
157+
OverrideMode::Exact => Some(Mode::Exact),
158+
OverrideMode::Probabilistic(b) => Some(Mode::Probabilistic(*b)),
159+
OverrideMode::Excluded => None,
160+
}
161+
}
162+
}
163+
123164
/// Bloom filter configuration in probabilistic mode.
124165
#[configurable_component]
125166
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -156,9 +197,10 @@ pub struct PerMetricConfig {
156197

157198
/// Per-tag-key overrides scoped to this metric.
158199
///
159-
/// Each entry has the same fields as a per-metric configuration. When a tag has an entry here,
160-
/// that entry replaces the per-metric configuration for that tag. Tags not listed here use this
161-
/// per-metric configuration.
200+
/// Each entry has the same fields as a per-metric configuration except `limit_exceeded_action`,
201+
/// which is always inherited from the enclosing per-metric (or global) configuration. When a
202+
/// tag has an entry here, that entry replaces the per-metric `value_limit`, `mode`, and
203+
/// `internal_metrics` for that tag. Tags not listed here use the per-metric configuration.
162204
#[configurable(
163205
derived,
164206
metadata(docs::additional_props_description = "An individual tag configuration.")
@@ -167,15 +209,15 @@ pub struct PerMetricConfig {
167209
pub per_tag_limits: HashMap<String, PerTagConfig>,
168210

169211
#[serde(flatten)]
170-
pub config: Inner,
212+
pub config: OverrideInner,
171213
}
172214

173215
/// Tag cardinality limit configuration for a specific tag key, scoped under a per-metric override.
174216
#[configurable_component]
175217
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176218
pub struct PerTagConfig {
177219
#[serde(flatten)]
178-
pub config: Inner,
220+
pub config: PerTagInner,
179221
}
180222

181223
const fn default_limit_exceeded_action() -> LimitExceededAction {
@@ -198,11 +240,10 @@ impl GenerateConfig for Config {
198240
fn generate_config() -> toml::Value {
199241
toml::Value::try_from(Self {
200242
global: Inner {
201-
mode: Some(Mode::Exact),
243+
mode: Mode::Exact,
202244
value_limit: default_value_limit(),
203245
limit_exceeded_action: default_limit_exceeded_action(),
204246
internal_metrics: InternalMetricsConfig::default(),
205-
exclude: false,
206247
},
207248
per_metric_limits: HashMap::default(),
208249
})
@@ -214,7 +255,6 @@ impl GenerateConfig for Config {
214255
#[typetag::serde(name = "tag_cardinality_limit")]
215256
impl TransformConfig for Config {
216257
async fn build(&self, _context: &TransformContext) -> crate::Result<Transform> {
217-
self.validate()?;
218258
Ok(Transform::event_task(TagCardinalityLimit::new(
219259
self.clone(),
220260
)))

0 commit comments

Comments
 (0)