Skip to main content

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`].
39#[derive(Debug)]
40pub struct Snapshot(Vec<(CompositeKey, Option<Unit>, Option<SharedString>, DebugValue)>);
41
42impl Snapshot {
43    /// Converts this snapshot to a mapping of metric data, keyed by the metric key itself.
44    #[allow(clippy::mutable_key_type)]
45    pub fn into_hashmap(
46        self,
47    ) -> HashMap<CompositeKey, (Option<Unit>, Option<SharedString>, DebugValue)> {
48        self.0
49            .into_iter()
50            .map(|(k, unit, desc, value)| (k, (unit, desc, value)))
51            .collect::<HashMap<_, _>>()
52    }
53
54    /// Converts this snapshot to a vector of metric data tuples.
55    pub fn into_vec(self) -> Vec<(CompositeKey, Option<Unit>, Option<SharedString>, DebugValue)> {
56        self.0
57    }
58}
59
60/// A point-in-time value for a metric exposing raw values.
61#[derive(Debug, PartialEq, Eq, Hash)]
62pub enum DebugValue {
63    /// Counter.
64    Counter(u64),
65    /// Gauge.
66    Gauge(OrderedFloat<f64>),
67    /// Histogram.
68    Histogram(Vec<OrderedFloat<f64>>),
69}
70
71#[derive(Debug)]
72struct Inner {
73    registry: Registry<Key, AtomicStorage>,
74    seen: Mutex<IndexMap<CompositeKey, ()>>,
75    metadata: Mutex<IndexMap<CompositeKeyName, (Option<Unit>, SharedString)>>,
76}
77
78impl Inner {
79    fn new() -> Self {
80        Self {
81            registry: Registry::atomic(),
82            seen: Mutex::new(IndexMap::new()),
83            metadata: Mutex::new(IndexMap::new()),
84        }
85    }
86}
87
88/// Captures point-in-time snapshots of [`DebuggingRecorder`].
89#[derive(Clone, Debug)]
90pub struct Snapshotter {
91    inner: Arc<Inner>,
92}
93
94impl Snapshotter {
95    /// Takes a snapshot of the recorder.
96    pub fn snapshot(&self) -> Snapshot {
97        let mut snapshot = Vec::new();
98
99        let counters = self.inner.registry.get_counter_handles();
100        let gauges = self.inner.registry.get_gauge_handles();
101        let histograms = self.inner.registry.get_histogram_handles();
102
103        let seen = self.inner.seen.lock().expect("seen lock poisoned").clone();
104        let metadata = self.inner.metadata.lock().expect("metadata lock poisoned").clone();
105
106        for (ck, _) in seen.into_iter() {
107            let value = match ck.kind() {
108                MetricKind::Counter => {
109                    counters.get(ck.key()).map(|c| DebugValue::Counter(c.swap(0, Ordering::SeqCst)))
110                }
111                MetricKind::Gauge => gauges.get(ck.key()).map(|g| {
112                    let value = f64::from_bits(g.swap(0, Ordering::SeqCst));
113                    DebugValue::Gauge(value.into())
114                }),
115                MetricKind::Histogram => histograms.get(ck.key()).map(|h| {
116                    let mut values = Vec::new();
117                    h.clear_with(|xs| values.extend(xs.iter().map(|f| OrderedFloat::from(*f))));
118                    DebugValue::Histogram(values)
119                }),
120            };
121
122            let ckn = CompositeKeyName::new(ck.kind(), ck.key().name().to_string().into());
123            let (unit, desc) = metadata
124                .get(&ckn)
125                .map(|(u, d)| (u.to_owned(), Some(d.to_owned())))
126                .unwrap_or_else(|| (None, None));
127
128            // If there's no value for the key, that means the metric was only ever described, and
129            // not registered, so don't emit it.
130            if let Some(value) = value {
131                snapshot.push((ck, unit, desc, value));
132            }
133        }
134
135        Snapshot(snapshot)
136    }
137}
138
139/// A simplistic recorder that can be installed and used for debugging or testing.
140///
141/// Callers can easily take snapshots of the metrics at any given time and get access
142/// to the raw values.
143#[derive(Debug)]
144pub struct DebuggingRecorder {
145    inner: Arc<Inner>,
146}
147
148impl DebuggingRecorder {
149    /// Creates a new `DebuggingRecorder`.
150    pub fn new() -> DebuggingRecorder {
151        DebuggingRecorder { inner: Arc::new(Inner::new()) }
152    }
153
154    /// Gets a `Snapshotter` attached to this recorder.
155    pub fn snapshotter(&self) -> Snapshotter {
156        Snapshotter { inner: Arc::clone(&self.inner) }
157    }
158
159    fn describe_metric(&self, rkey: CompositeKeyName, unit: Option<Unit>, desc: SharedString) {
160        let mut metadata = self.inner.metadata.lock().expect("metadata lock poisoned");
161        let (uentry, dentry) = metadata.entry(rkey).or_insert((None, desc.to_owned()));
162        if unit.is_some() {
163            *uentry = unit;
164        }
165        *dentry = desc;
166    }
167
168    fn track_metric(&self, ckey: CompositeKey) {
169        let mut seen = self.inner.seen.lock().expect("seen lock poisoned");
170        seen.insert(ckey, ());
171    }
172
173    /// Installs this recorder as the global recorder.
174    pub fn install(self) -> Result<(), SetRecorderError<Self>> {
175        metrics::set_global_recorder(self)
176    }
177}
178
179impl Recorder for DebuggingRecorder {
180    fn describe_counter(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
181        let ckey = CompositeKeyName::new(MetricKind::Counter, key);
182        self.describe_metric(ckey, unit, description);
183    }
184
185    fn describe_gauge(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
186        let ckey = CompositeKeyName::new(MetricKind::Gauge, key);
187        self.describe_metric(ckey, unit, description);
188    }
189
190    fn describe_histogram(&self, key: KeyName, unit: Option<Unit>, description: SharedString) {
191        let ckey = CompositeKeyName::new(MetricKind::Histogram, key);
192        self.describe_metric(ckey, unit, description);
193    }
194
195    fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter {
196        let key = key.to_retained();
197        let ckey = CompositeKey::new(MetricKind::Counter, key.clone());
198        self.track_metric(ckey);
199
200        self.inner.registry.get_or_create_counter(&key, |c| Counter::from_arc(c.clone()))
201    }
202
203    fn register_gauge(&self, key: &Key, _metadata: &Metadata<'_>) -> Gauge {
204        let key = key.to_retained();
205        let ckey = CompositeKey::new(MetricKind::Gauge, key.clone());
206        self.track_metric(ckey);
207
208        self.inner.registry.get_or_create_gauge(&key, |g| Gauge::from_arc(g.clone()))
209    }
210
211    fn register_histogram(&self, key: &Key, _metadata: &Metadata<'_>) -> Histogram {
212        let key = key.to_retained();
213        let ckey = CompositeKey::new(MetricKind::Histogram, key.clone());
214        self.track_metric(ckey);
215
216        self.inner.registry.get_or_create_histogram(&key, |h| Histogram::from_arc(h.clone()))
217    }
218}
219
220impl Default for DebuggingRecorder {
221    fn default() -> Self {
222        DebuggingRecorder::new()
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use metrics::Label;
230
231    #[test]
232    fn test_debugging_recorder() {
233        let recorder = DebuggingRecorder::default();
234        let snapshotter = recorder.snapshotter();
235        metrics::with_local_recorder(&recorder, move || {
236            // We run this multiple times to validate the counters, gauges and histograms are
237            // "consumed" after being snapshotted. Consumption refers to resetting the value of a
238            // counter and gauge to 0 and clearing observed values from a histogram.
239            for _ in 1..3 {
240                let counter = metrics::counter!("test.counter", "counter.key" => "counter.value");
241                let gauge_incr = metrics::gauge!("test.gauge.incr", "gauge.key" => "gauge.value");
242                let gauge_set = metrics::gauge!("test.gauge.set", "gauge.key" => "gauge.value");
243                let histogram =
244                    metrics::histogram!("test.histogram", "histogram.key" => "histogram.value");
245
246                counter.increment(123);
247                gauge_incr.increment(456);
248                gauge_set.set(654);
249                histogram.record(789);
250
251                let metrics = snapshotter.snapshot().into_vec();
252
253                assert_eq!(
254                    metrics,
255                    vec![
256                        (
257                            CompositeKey::new(
258                                MetricKind::Counter,
259                                Key::from_parts(
260                                    "test.counter",
261                                    vec![Label::new("counter.key", "counter.value")]
262                                )
263                            ),
264                            None,
265                            None,
266                            DebugValue::Counter(123)
267                        ),
268                        (
269                            CompositeKey::new(
270                                MetricKind::Gauge,
271                                Key::from_parts(
272                                    "test.gauge.incr",
273                                    vec![Label::new("gauge.key", "gauge.value")]
274                                )
275                            ),
276                            None,
277                            None,
278                            DebugValue::Gauge(OrderedFloat(456.0))
279                        ),
280                        (
281                            CompositeKey::new(
282                                MetricKind::Gauge,
283                                Key::from_parts(
284                                    "test.gauge.set",
285                                    vec![Label::new("gauge.key", "gauge.value")]
286                                )
287                            ),
288                            None,
289                            None,
290                            DebugValue::Gauge(OrderedFloat(654.0))
291                        ),
292                        (
293                            CompositeKey::new(
294                                MetricKind::Histogram,
295                                Key::from_parts(
296                                    "test.histogram",
297                                    vec![Label::new("histogram.key", "histogram.value")]
298                                )
299                            ),
300                            None,
301                            None,
302                            DebugValue::Histogram(vec![OrderedFloat(789.0)])
303                        )
304                    ]
305                );
306            }
307        });
308    }
309}