1use std::fmt;
11use std::sync::Arc;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15pub struct Labels {
16 pairs: Arc<Vec<(String, String)>>,
17}
18
19impl Default for Labels {
20 fn default() -> Self {
21 Self::empty()
22 }
23}
24
25impl Labels {
26 pub fn empty() -> Self {
27 Self {
28 pairs: Arc::new(Vec::new()),
29 }
30 }
31
32 pub fn of(key: impl Into<String>, value: impl Into<String>) -> Self {
33 Self {
34 pairs: Arc::new(vec![(key.into(), value.into())]),
35 }
36 }
37
38 pub fn with(&self, key: impl Into<String>, value: impl Into<String>) -> Self {
39 let mut pairs = (*self.pairs).clone();
40 let key = key.into();
41 if let Some(pos) = pairs.iter().position(|(k, _)| k == &key) {
42 pairs[pos].1 = value.into();
43 } else {
44 pairs.push((key, value.into()));
45 }
46 Self {
47 pairs: Arc::new(pairs),
48 }
49 }
50
51 pub fn extend(&self, child: &Labels) -> Labels {
52 let mut pairs = (*self.pairs).clone();
53 for (k, v) in child.pairs.iter() {
54 if let Some(pos) = pairs.iter().position(|(pk, _)| pk == k) {
55 pairs[pos].1 = v.clone();
56 } else {
57 pairs.push((k.clone(), v.clone()));
58 }
59 }
60 Labels {
61 pairs: Arc::new(pairs),
62 }
63 }
64
65 pub fn get(&self, key: &str) -> Option<&str> {
66 self.pairs
67 .iter()
68 .find(|(k, _)| k == key)
69 .map(|(_, v)| v.as_str())
70 }
71
72 pub fn len(&self) -> usize {
73 self.pairs.len()
74 }
75 pub fn is_empty(&self) -> bool {
76 self.pairs.is_empty()
77 }
78
79 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
80 self.pairs.iter().map(|(k, v)| (k.as_str(), v.as_str()))
81 }
82
83 pub fn to_prometheus(&self) -> String {
84 if self.pairs.is_empty() {
85 return String::new();
86 }
87 let inner: Vec<String> = self
88 .pairs
89 .iter()
90 .map(|(k, v)| format!("{k}=\"{v}\""))
91 .collect();
92 format!("{{{}}}", inner.join(","))
93 }
94
95 pub fn to_dotted(&self) -> String {
96 self.pairs
97 .iter()
98 .map(|(_, v)| v.as_str())
99 .collect::<Vec<_>>()
100 .join(".")
101 }
102
103 pub fn identity_hash(&self) -> u64 {
119 let mut sorted: Vec<(&str, &str)> = self
123 .pairs
124 .iter()
125 .map(|(k, v)| (k.as_str(), v.as_str()))
126 .collect();
127 sorted.sort();
128 let mut h: u64 = 0xcbf29ce484222325; for (k, v) in &sorted {
130 for b in k.as_bytes() {
131 h ^= *b as u64;
132 h = h.wrapping_mul(0x100000001b3);
133 }
134 h ^= b'=' as u64;
136 h = h.wrapping_mul(0x100000001b3);
137 for b in v.as_bytes() {
138 h ^= *b as u64;
139 h = h.wrapping_mul(0x100000001b3);
140 }
141 h ^= 0;
143 h = h.wrapping_mul(0x100000001b3);
144 }
145 h
146 }
147
148 pub fn sorted_pairs(&self) -> Vec<(&str, &str)> {
153 let mut v: Vec<(&str, &str)> = self
154 .pairs
155 .iter()
156 .map(|(k, val)| (k.as_str(), val.as_str()))
157 .collect();
158 v.sort();
159 v
160 }
161
162 pub fn to_canonical_spec(&self, metric_name: &str) -> String {
181 let mut pairs: Vec<(&str, &str)> = self
182 .pairs
183 .iter()
184 .map(|(k, v)| (k.as_str(), v.as_str()))
185 .filter(|(k, v)| !v.is_empty() && *k != "__name__")
186 .collect();
187 pairs.sort();
188 let mut out = String::with_capacity(metric_name.len() + 32);
189 out.push_str(metric_name);
190 out.push('{');
191 let mut first = true;
192 for (k, v) in pairs {
193 if !first {
194 out.push(',');
195 }
196 first = false;
197 out.push_str(k);
198 out.push_str("=\"");
199 escape_label_value_into(&mut out, v);
200 out.push('"');
201 }
202 out.push('}');
203 out
204 }
205}
206
207pub fn escape_label_value_into(out: &mut String, v: &str) {
213 for c in v.chars() {
214 match c {
215 '\\' => out.push_str("\\\\"),
216 '"' => out.push_str("\\\""),
217 '\n' => out.push_str("\\n"),
218 c => out.push(c),
219 }
220 }
221}
222
223pub fn escape_label_value(v: &str) -> String {
226 let mut out = String::with_capacity(v.len());
227 escape_label_value_into(&mut out, v);
228 out
229}
230
231impl fmt::Display for Labels {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 write!(f, "{}", self.to_prometheus())
234 }
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum MetricCategory {
240 Core,
241 Progress,
242 Errors,
243 Driver,
244 Internals,
245 Verification,
246 Config,
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn labels_with_override() {
255 let l = Labels::of("a", "1").with("a", "2");
256 assert_eq!(l.get("a"), Some("2"));
257 assert_eq!(l.len(), 1);
258 }
259
260 #[test]
261 fn labels_extend_child_wins() {
262 let parent = Labels::of("session", "s1").with("activity", "write");
263 let child = Labels::of("name", "timer1").with("activity", "read");
264 let merged = parent.extend(&child);
265 assert_eq!(merged.get("session"), Some("s1"));
266 assert_eq!(merged.get("activity"), Some("read"));
267 assert_eq!(merged.get("name"), Some("timer1"));
268 }
269
270 #[test]
271 fn labels_prometheus_format() {
272 let l = Labels::of("session", "abc").with("name", "ops_total");
273 assert_eq!(l.to_prometheus(), r#"{session="abc",name="ops_total"}"#);
274 }
275
276 #[test]
277 fn identity_hash_order_independent() {
278 let a = Labels::of("k", "1")
284 .with("optimize_for", "recall")
285 .with("phase", "ann_query");
286 let b = Labels::of("phase", "ann_query")
287 .with("k", "1")
288 .with("optimize_for", "recall");
289 let c = Labels::of("optimize_for", "recall")
290 .with("phase", "ann_query")
291 .with("k", "1");
292 assert_eq!(a.identity_hash(), b.identity_hash());
293 assert_eq!(a.identity_hash(), c.identity_hash());
294 }
295
296 #[test]
297 fn identity_hash_distinguishes_distinct_sets() {
298 let a = Labels::of("phase", "ann_query");
299 let b = Labels::of("phase", "pvs_query");
300 assert_ne!(a.identity_hash(), b.identity_hash());
301 }
302
303 #[test]
304 fn identity_hash_no_collision_between_split_keys() {
305 let a = Labels::of("ab", "c");
308 let b = Labels::of("a", "bc");
309 assert_ne!(a.identity_hash(), b.identity_hash());
310 }
311
312 #[test]
313 fn canonical_spec_sorts_and_quotes() {
314 let l = Labels::of("phase", "ann_query")
315 .with("k", "1")
316 .with("optimize_for", "recall");
317 assert_eq!(
318 l.to_canonical_spec("recall_mean"),
319 r#"recall_mean{k="1",optimize_for="recall",phase="ann_query"}"#,
320 );
321 }
322
323 #[test]
324 fn canonical_spec_order_independent() {
325 let a = Labels::of("phase", "ann").with("k", "1");
326 let b = Labels::of("k", "1").with("phase", "ann");
327 assert_eq!(
328 a.to_canonical_spec("recall_mean"),
329 b.to_canonical_spec("recall_mean"),
330 );
331 }
332
333 #[test]
334 fn canonical_spec_drops_empty_values_and_underscore_name() {
335 let l = Labels::of("phase", "ann")
336 .with("hint", "")
337 .with("__name__", "should_be_ignored_here");
338 let spec = l.to_canonical_spec("ops_total");
339 assert_eq!(spec, r#"ops_total{phase="ann"}"#);
343 }
344
345 #[test]
346 fn canonical_spec_escapes_values() {
347 let l = Labels::of("note", r#"has "quotes" and \ slash"#);
348 assert_eq!(
349 l.to_canonical_spec("m"),
350 r#"m{note="has \"quotes\" and \\ slash"}"#,
351 );
352 }
353
354 #[test]
355 fn canonical_spec_empty_labels() {
356 assert_eq!(
357 Labels::empty().to_canonical_spec("ops_total"),
358 "ops_total{}"
359 );
360 }
361
362 #[test]
363 fn labels_clone_shares_arc() {
364 let l = Labels::of("a", "1").with("b", "2");
365 let l2 = l.clone();
366 assert!(Arc::ptr_eq(&l.pairs, &l2.pairs));
367 }
368}