saluki_metrics/mapped.rs
1//! Support for "mapped" metrics whose labels are resolved dynamically at emission time.
2
3use std::sync::Arc;
4
5use metrics::{Label, SharedString};
6use papaya::HashMap;
7
8/// A metric that is resolved dynamically by one or more label values.
9///
10/// Unlike a plain metric handle, a mapped metric does not hold a single handle. Instead it holds a concurrent map
11/// from a set of dynamic label values to the corresponding registered handle. Handles are registered lazily on first
12/// use and cached for subsequent lookups.
13///
14/// The underlying map (and the fixed label set) is shared across clones, so every clone of the containing metrics
15/// struct observes the same cache. Lookups are lock-free once a handle has been registered.
16///
17/// This type is an implementation detail of the `static_metrics` attribute macro and is not meant to be constructed
18/// or used directly.
19pub struct MappedMetric<H> {
20 labels: Arc<Vec<Label>>,
21 handles: Arc<HashMap<Box<[SharedString]>, H>>,
22}
23
24impl<H> Clone for MappedMetric<H> {
25 fn clone(&self) -> Self {
26 // Only the shared handles are cloned (via `Arc`), never the handles themselves, so all clones share one cache.
27 Self {
28 labels: Arc::clone(&self.labels),
29 handles: Arc::clone(&self.handles),
30 }
31 }
32}
33
34impl<H> MappedMetric<H>
35where
36 H: Clone,
37{
38 /// Creates a new `MappedMetric` whose handles are registered with the given fixed labels.
39 pub fn new(labels: Arc<Vec<Label>>) -> Self {
40 Self {
41 labels,
42 handles: Arc::new(HashMap::new()),
43 }
44 }
45
46 /// Returns the handle for the given dynamic label values, registering and caching it on the first lookup.
47 ///
48 /// `keys` are the dynamic label names and `values` their stringified values, in the same order and of the same
49 /// length. The `register` closure is invoked only on a cache miss and receives the full label set (the fixed
50 /// labels followed by the dynamic labels) with which to register the handle.
51 pub fn get_or_register(
52 &self, keys: &[&'static str], values: &[SharedString], register: impl FnOnce(&[Label]) -> H,
53 ) -> H {
54 let map_key: Box<[SharedString]> = values.into();
55 self.handles
56 .pin()
57 .get_or_insert_with(map_key, || {
58 let mut labels = Vec::with_capacity(self.labels.len() + values.len());
59 labels.extend(self.labels.iter().cloned());
60 for (key, value) in keys.iter().zip(values.iter()) {
61 labels.push(Label::new(*key, value.clone()));
62 }
63 register(&labels)
64 })
65 .clone()
66 }
67}