fast_telemetry/metric/dynamic/
mod.rs1mod cache;
14mod counter;
15mod counter_set;
16mod distribution;
17mod gauge;
18mod gauge_i64;
19mod histogram;
20
21pub use counter::{DynamicCounter, DynamicCounterSeries};
22pub use counter_set::{DynamicCounterSet, DynamicCounterSetSeries};
23pub use distribution::{DynamicDistribution, DynamicDistributionSeries};
24pub use gauge::{DynamicGauge, DynamicGaugeSeries};
25pub use gauge_i64::{DynamicGaugeI64, DynamicGaugeI64Series};
26pub use histogram::{DynamicHistogram, DynamicHistogramSeries, DynamicHistogramSeriesView};
27
28use std::collections::BTreeMap;
29use std::sync::atomic::AtomicUsize;
30#[cfg(feature = "eviction")]
31use std::sync::atomic::{AtomicU32, Ordering};
32
33pub(crate) use crate::thread_id::thread_id;
34
35#[cfg(feature = "eviction")]
36static EVICTION_CYCLE: AtomicU32 = AtomicU32::new(0);
37
38#[cfg(all(test, feature = "eviction"))]
39static EVICTION_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
40
41const VEC_CANONICALIZATION_MAX_LABELS: usize = 16;
42
43#[cfg(feature = "eviction")]
45#[inline]
46pub fn current_cycle() -> u32 {
47 EVICTION_CYCLE.load(Ordering::Relaxed)
48}
49
50#[cfg(feature = "eviction")]
54#[inline]
55pub fn advance_cycle() -> u32 {
56 EVICTION_CYCLE.fetch_add(1, Ordering::Relaxed) + 1
57}
58
59#[cfg(all(test, feature = "eviction"))]
60pub(crate) fn lock_eviction_cycle_for_test() -> std::sync::MutexGuard<'static, ()> {
61 let guard = EVICTION_TEST_LOCK
62 .lock()
63 .unwrap_or_else(|poisoned| poisoned.into_inner());
64 EVICTION_CYCLE.store(0, Ordering::Relaxed);
65 guard
66}
67
68#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
73pub struct DynamicLabelSet {
74 labels: Vec<(String, String)>,
75}
76
77impl DynamicLabelSet {
78 #[inline]
80 pub fn from_pairs(labels: &[(&str, &str)]) -> Self {
81 Self {
82 labels: canonicalize_labels(labels),
83 }
84 }
85
86 #[doc(hidden)]
90 pub fn from_canonical_pairs(labels: &[(String, String)]) -> Self {
91 Self {
92 labels: labels.to_vec(),
93 }
94 }
95
96 pub fn pairs(&self) -> &[(String, String)] {
98 &self.labels
99 }
100
101 #[inline]
102 pub(crate) fn shard_index(&self, shard_mask: usize) -> usize {
103 let hash = label_set_fingerprint(&self.labels);
104 ((hash ^ (hash >> 32)) as usize) & shard_mask
105 }
106}
107
108pub(crate) type DynamicIndexMap<T> = BTreeMap<DynamicLabelSet, T>;
109
110#[inline]
111pub(crate) fn dynamic_index_map<T>() -> DynamicIndexMap<T> {
112 BTreeMap::new()
113}
114
115#[inline]
116fn canonicalize_labels(labels: &[(&str, &str)]) -> Vec<(String, String)> {
117 match labels {
118 [] => Vec::new(),
119 [(key, value)] => vec![(key.to_string(), value.to_string())],
120 labels if labels.len() <= VEC_CANONICALIZATION_MAX_LABELS => {
121 canonicalize_labels_vec(labels)
122 }
123 labels => canonicalize_labels_btree(labels),
124 }
125}
126
127#[inline]
128fn canonicalize_labels_vec(labels: &[(&str, &str)]) -> Vec<(String, String)> {
129 let mut ordered: Vec<(String, String)> = Vec::with_capacity(labels.len());
130 for (k, v) in labels {
131 match ordered.binary_search_by(|(key, _)| key.as_str().cmp(k)) {
132 Ok(index) => ordered[index].1 = (*v).to_string(),
133 Err(index) => ordered.insert(index, ((*k).to_string(), (*v).to_string())),
134 }
135 }
136 ordered
137}
138
139#[inline]
140fn canonicalize_labels_btree(labels: &[(&str, &str)]) -> Vec<(String, String)> {
141 let mut map = BTreeMap::new();
142 for (k, v) in labels {
143 map.insert((*k).to_string(), (*v).to_string());
144 }
145 map.into_iter().collect()
146}
147
148#[inline]
149fn label_set_fingerprint(labels: &[(String, String)]) -> u64 {
150 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
151 const FNV_PRIME: u64 = 0x100000001b3;
152
153 let mut hash = FNV_OFFSET;
154 for (key, value) in labels {
155 for byte in key.as_bytes() {
156 hash ^= u64::from(*byte);
157 hash = hash.wrapping_mul(FNV_PRIME);
158 }
159 hash ^= 0xff;
160 hash = hash.wrapping_mul(FNV_PRIME);
161 for byte in value.as_bytes() {
162 hash ^= u64::from(*byte);
163 hash = hash.wrapping_mul(FNV_PRIME);
164 }
165 hash ^= 0xfe;
166 hash = hash.wrapping_mul(FNV_PRIME);
167 }
168 hash
169}
170
171pub(crate) static COUNTER_IDS: AtomicUsize = AtomicUsize::new(1);
173pub(crate) static GAUGE_IDS: AtomicUsize = AtomicUsize::new(1);
174pub(crate) static HISTOGRAM_IDS: AtomicUsize = AtomicUsize::new(1);
175pub(crate) static DISTRIBUTION_IDS: AtomicUsize = AtomicUsize::new(1);
176
177#[cfg(test)]
178mod tests {
179 use super::DynamicLabelSet;
180
181 #[test]
182 fn dynamic_label_set_is_order_independent() {
183 let left = DynamicLabelSet::from_pairs(&[("b", "2"), ("a", "1")]);
184 let right = DynamicLabelSet::from_pairs(&[("a", "1"), ("b", "2")]);
185
186 assert_eq!(left, right);
187 assert_eq!(
188 left.pairs(),
189 &[
190 ("a".to_string(), "1".to_string()),
191 ("b".to_string(), "2".to_string())
192 ]
193 );
194 }
195
196 #[test]
197 fn dynamic_label_set_duplicate_key_last_value_wins() {
198 let labels = DynamicLabelSet::from_pairs(&[("b", "1"), ("a", "1"), ("b", "2")]);
199
200 assert_eq!(
201 labels.pairs(),
202 &[
203 ("a".to_string(), "1".to_string()),
204 ("b".to_string(), "2".to_string())
205 ]
206 );
207 }
208
209 #[test]
210 fn dynamic_label_set_canonicalizes_large_label_sets() {
211 let labels = DynamicLabelSet::from_pairs(&[
212 ("m", "13"),
213 ("l", "12"),
214 ("k", "11"),
215 ("j", "10"),
216 ("i", "9"),
217 ("h", "8"),
218 ("g", "7"),
219 ("f", "6"),
220 ("e", "5"),
221 ("d", "4"),
222 ("c", "3"),
223 ("b", "2"),
224 ("a", "1"),
225 ]);
226
227 assert_eq!(
228 labels
229 .pairs()
230 .first()
231 .map(|(k, v)| (k.as_str(), v.as_str())),
232 Some(("a", "1"))
233 );
234 assert_eq!(
235 labels.pairs().last().map(|(k, v)| (k.as_str(), v.as_str())),
236 Some(("m", "13"))
237 );
238 }
239}