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}