provide_telemetry/
cardinality.rs1use std::collections::{BTreeMap, HashMap};
7use std::sync::{Mutex, OnceLock};
8
9pub const OVERFLOW_VALUE: &str = "__overflow__";
10
11#[derive(Clone, Debug, PartialEq)]
12pub struct CardinalityLimit {
13 pub max_values: usize,
14 pub ttl_seconds: f64,
15}
16
17static LIMITS: OnceLock<Mutex<BTreeMap<String, CardinalityLimit>>> = OnceLock::new();
18
19fn limits() -> &'static Mutex<BTreeMap<String, CardinalityLimit>> {
20 LIMITS.get_or_init(|| Mutex::new(BTreeMap::new()))
21}
22
23pub fn register_cardinality_limit(key: impl Into<String>, limit: CardinalityLimit) {
24 limits().lock().expect("cardinality lock poisoned").insert(
25 key.into(),
26 CardinalityLimit {
27 max_values: limit.max_values.max(1),
28 ttl_seconds: limit.ttl_seconds.max(1.0),
29 },
30 );
31}
32
33pub fn get_cardinality_limits() -> BTreeMap<String, CardinalityLimit> {
34 limits().lock().expect("cardinality lock poisoned").clone()
35}
36
37pub fn clear_cardinality_limits() {
38 limits().lock().expect("cardinality lock poisoned").clear();
39}
40
41pub fn guard_attributes(attributes: HashMap<String, String>) -> HashMap<String, String> {
44 let snapshot = limits().lock().expect("cardinality lock poisoned").clone();
45 if snapshot.is_empty() {
46 return attributes;
47 }
48 let mut seen_counts: HashMap<String, usize> = HashMap::new();
49 let mut out = HashMap::new();
50 for (key, value) in attributes {
51 if let Some(limit) = snapshot.get(&key) {
52 let count = seen_counts.entry(key.clone()).or_insert(0);
53 if *count < limit.max_values {
54 *count += 1;
55 out.insert(key, value);
56 } else {
57 out.insert(key, OVERFLOW_VALUE.to_string());
58 }
59 } else {
60 out.insert(key, value);
61 }
62 }
63 out
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::testing::acquire_test_state_lock;
70
71 #[test]
72 fn cardinality_test_clear_limits_removes_registered_entries() {
73 let _guard = acquire_test_state_lock();
74 clear_cardinality_limits();
75 register_cardinality_limit(
76 "user.id",
77 CardinalityLimit {
78 max_values: 5,
79 ttl_seconds: 60.0,
80 },
81 );
82 assert!(get_cardinality_limits().contains_key("user.id"));
83
84 clear_cardinality_limits();
85
86 assert!(get_cardinality_limits().is_empty());
87 }
88}