metrics_util/
debugging.rs

1//! Debugging utilities.
2//!
3//! While these utilities are primarily designed to help aid with testing and debugging of exporters
4//! and core parts of the `metrics` ecosystem, they can be beneficial for in-process collecting of
5//! metrics in some limited cases.
6
7use std::{
8    collections::HashMap,
9    fmt::Debug,
10    hash::Hash,
11    sync::{atomic::Ordering, Arc, Mutex},
12};
13
14use crate::{
15    kind::MetricKind,
16    registry::{AtomicStorage, Registry},
17    CompositeKey,
18};
19
20use indexmap::IndexMap;
21use metrics::{
22    Counter, Gauge, Histogram, Key, KeyName, Metadata, Recorder, SetRecorderError, SharedString,
23    Unit,
24};
25use ordered_float::OrderedFloat;
26
27/// A composite key name that stores both the metric key name and the metric kind.
28#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
29struct CompositeKeyName(MetricKind, KeyName);
30
31impl CompositeKeyName {
32    /// Creates a new `CompositeKeyName`.
33    pub const fn new(kind: MetricKind, key_name: KeyName) -> CompositeKeyName {
34        CompositeKeyName(kind, key_name)
35    }
36}
37
38/// A point-in-time snapshot of all metrics in [`DebuggingRecorder`].
39pub struct Snapshot(Vec<(CompositeKey, Option<Unit>, Option<SharedString>, DebugValue)>);
40
41impl Snapshot {
42    /// Converts this snapshot to a mapping of metric data, keyed by the metric key itself.
43    #[allow(clippy::mutable_key_type)]
44    pub fn into_hashmap(
45        self,
46    ) -> HashMap<CompositeKey, (Option<Unit>, Option<SharedString>, DebugValue)> {
47        self.0
48            .into_iter()
49            .map(|(k, unit, desc, value)| (k, (unit, desc, value)))
50            .collect::<HashMap<_, _>>()
51    }
52
53    /// Converts this snapshot to a vector of metric data tuples.
54    pub fn into_vec(self) -> Vec<(CompositeKey, Option<Unit>, Option<SharedString>, DebugValue)> {
55        self.0
56    }
57}
58
59/// A point-in-time value for a metric exposing raw values.
60#[derive(Debug, PartialEq, Eq, Hash)]
61pub enum DebugValue {
62    /// Counter.
63    Counter(u64),
64    /// Gauge.
65    Gauge(OrderedFloat<f64>),
66    /// Histogram.
67    Histogram(Vec<OrderedFloat<f64>>),
68}
69
70struct Inner {
71    registry: Registry<Key, AtomicStorage>,
72    seen: Mutex<IndexMap<CompositeKey, ()>>,
73    metadata: Mutex<IndexMap<CompositeKeyName, (Option<Unit>, SharedString)>>,
74}
75
76impl Inner {
77    fn new() -> Self {
78        Self {
79            registry: Registry::atomic(),
80            seen: Mutex::new(IndexMap::new()),
81            metadata: Mutex::new(IndexMap::new()),
82        }
83    }
84}
85
86/// Captures point-in-time snapshots of [`DebuggingRecorder`].
87#[derive(Clone)]
88pub struct Snapshotter {
89    inner: Arc<Inner>,
90}
91
92impl Snapshotter {
93    /// Takes a snapshot of the recorder.
94    pub fn snapshot(&self) -> Snapshot {
95        let mut snapshot = Vec::new();
96
97        let counters = self.inner.registry.get_counter_handles();
98        let gauges = self.inner.registry.get_gauge_handles();
99        let histograms = self.inner.registry.get_histogram_handles();
100
101        let seen = self.inner.seen.lock().expect("seen lock poisoned").clone();
102        let metadata = self.inner.metadata.lock().expect("metadata lock poisoned").clone();
103
104        for (ck, _) in seen.into_iter() {
105            let value = match ck.kind() {
106                MetricKind::Counter => {
107                    counters.get(ck.key()).map(|c| DebugValue::Counter(c.load(Ordering::SeqCst)))
108                }
109                MetricKind::Gauge => gauges.get(ck.key()).map(|g| {
110                    let value = f64::from_bits(g.load(Ordering::SeqCst));
111                    DebugValue::Gauge(value.into())
112                }),
113                MetricKind::Histogram => histograms.get(ck.key()).map(|h| {
114                    let mut values = Vec::new();
115                    h.clear_with(|xs| values.extend(xs.iter().map(|f| OrderedFloat::from(*f))));
116                    DebugValue::Histogram(values)
117                }),
118            };
119
120            let ckn = CompositeKeyName::new(ck.kind(), ck.key().name().to_string().into());
121            let (unit, desc) = metadata
122                .get(&ckn)
123                .map(|(u, d)| (u.to_owned(), Some(d.to_owned())))
124                .unwrap_or_else(|| (None, None));
125
126            // If there's no value for the key, that means the metric was only ever described, and
127            // not registered, so don't emit it.
128            if let Some(value) = value {
129                snapshot.push((ck, unit, desc, value));
130            }
131        }
132
133        Snapshot(snapshot)
134    }
135}
136
137/// A simplistic recorder that can be installed and used for debugging or testing.
138///
139/// Callers can easily take snapshots of the metrics at any given time and get access
140/// to the raw values.
141pub struct DebuggingRecorder {
142    inner: Arc<Inner>,
143}
144
145impl DebuggingRecorder {
146    /// Creates a new `DebuggingRecorder`.
147    pub fn new() -> DebuggingRecorder {
148        DebuggingRecorder { inner: Arc::new(Inner::new()) }
149    }
150
151    /// Gets a `Snapshotter` attached to this recorder.
152    pub fn snapshotter(&self) -> Snapshotter {
153        Snapshotter { inner: Arc::clone(&self.inner) }
154    }
155
156    fn describe_metric(&self, rkey: CompositeKeyName, unit: Option<Unit>, desc: SharedString) {
157        let mut metadata = self.inner.metadata.lock().expect("metadata lock poisoned");
158        let (uentry, dentry) = metadata.entry(rkey).or_insert((None, desc.to_owned()));
159        if unit.is_some() {
160            *uentry = unit;
161        }
162        *dentry = desc;
163    }
164
165    fn track_metric(&self, ckey: CompositeKey) {
166        let mut seen = self.inner.seen.lock().expect("seen lock poisoned");
167        seen.insert(ckey, ());
168    }
169
170    /// Installs this recorder as the global recorder.
171    pub fn install(self) -> Result<(), SetRecorderError<Self>> {
172        metrics::set_global_recorder(self)
173    }
174}
175
176impl Recorder for DebuggingRecorder {
177    fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
178        let ckey = CompositeKeyName::new(MetricKind::Counter, key);
179        self.describe_metric(ckey, unit, description);
180    }
181
182    fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
183        let ckey = CompositeKeyName::new(MetricKind::Gauge, key);
184        self.describe_metric(ckey, unit, description);
185    }
186
187    fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
188        let ckey = CompositeKeyName::new(MetricKind::Histogram, key);
189        self.describe_metric(ckey, unit, description);
190    }
191
192    fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter {
193        let ckey = CompositeKey::new(MetricKind::Counter, key.clone());
194        self.track_metric(ckey);
195
196        self.inner.registry.get_or_create_counter(key, |c| Counter::from_arc(c.clone()))
197    }
198
199    fn register_gauge(&self, key: &Key, _metadata: &Metadata<'_>) -> Gauge {
200        let ckey = CompositeKey::new(MetricKind::Gauge, key.clone());
201        self.track_metric(ckey);
202
203        self.inner.registry.get_or_create_gauge(key, |g| Gauge::from_arc(g.clone()))
204    }
205
206    fn register_histogram(&self, key: &Key, _metadata: &Metadata<'_>) -> Histogram {
207        let ckey = CompositeKey::new(MetricKind::Histogram, key.clone());
208        self.track_metric(ckey);
209
210        self.inner.registry.get_or_create_histogram(key, |h| Histogram::from_arc(h.clone()))
211    }
212}
213
214impl Default for DebuggingRecorder {
215    fn default() -> Self {
216        DebuggingRecorder::new()
217    }
218}