commonware_runtime/telemetry/metrics/
status.rs

1//! Recording metrics with a status.
2
3use prometheus_client::{
4    encoding::{EncodeLabelSet, EncodeLabelValue},
5    metrics::{counter::Counter as DefaultCounter, family::Family, gauge::Gauge},
6};
7use std::sync::atomic::Ordering;
8
9/// Metric label that indicates status.
10#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
11pub struct Label {
12    /// The value of the label.
13    status: Status,
14}
15
16/// Possible values for the status label.
17#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
18pub enum Status {
19    /// Processed successfully.
20    Success,
21    /// Processing failed.
22    Failure,
23    /// Input was malformed or invalid in some way. Indicates a client error.
24    Invalid,
25    /// Input was valid, but intentionally not processed.
26    /// For example due to a rate limit, being a duplicate, etc.
27    Dropped,
28    /// Processing returned no result before some deadline.
29    Timeout,
30}
31
32/// A counter metric with a status label.
33pub type Counter = Family<Label, DefaultCounter>;
34
35/// Trait providing convenience methods for `Counter`.
36pub trait CounterExt {
37    fn guard(&self, status: Status) -> CounterGuard;
38    fn inc(&self, status: Status);
39    fn inc_by(&self, status: Status, n: u64);
40}
41
42impl CounterExt for Counter {
43    /// Create a new CounterGuard with a given status.
44    fn guard(&self, status: Status) -> CounterGuard {
45        CounterGuard {
46            metric: self.clone(),
47            status,
48        }
49    }
50
51    /// Increment the metric with a given status.
52    fn inc(&self, status: Status) {
53        self.get_or_create(&Label { status }).inc();
54    }
55
56    /// Increment the metric with a given status.
57    fn inc_by(&self, status: Status, n: u64) {
58        self.get_or_create(&Label { status }).inc_by(n);
59    }
60}
61
62/// Increments a `Counter` metric when dropped.
63///
64/// Can be used to ensure that counters are incremented regardless of the control flow. For example,
65/// if a function returns early, the metric will still be incremented.
66pub struct CounterGuard {
67    /// The metric to increment.
68    metric: Counter,
69
70    /// The status at which the metric is set to be incremented.
71    status: Status,
72}
73
74impl CounterGuard {
75    /// Modify the status at which the metric will be incremented.
76    pub const fn set(&mut self, status: Status) {
77        self.status = status;
78    }
79}
80
81impl Drop for CounterGuard {
82    fn drop(&mut self) {
83        self.metric.inc(self.status);
84    }
85}
86
87/// Trait providing convenience methods for `Gauge`.
88pub trait GaugeExt {
89    /// Sets the [`Gauge`] using a value convertible to `i64`, if conversion is not lossy, returning the previous value if successful.
90    fn try_set<T: TryInto<i64>>(&self, val: T) -> Result<i64, T::Error>;
91
92    /// Atomically sets the [`Gauge`] to the maximum of the current value and the provided value.
93    /// Returns the previous value.
94    fn try_set_max<T: TryInto<i64> + Copy>(&self, val: T) -> Result<i64, T::Error>;
95}
96
97impl GaugeExt for Gauge {
98    fn try_set<T: TryInto<i64>>(&self, val: T) -> Result<i64, T::Error> {
99        // Prevent casting if conversion is lossy
100        let val = val.try_into()?;
101        let out = self.set(val);
102        Ok(out)
103    }
104
105    fn try_set_max<T: TryInto<i64> + Copy>(&self, val: T) -> Result<i64, T::Error> {
106        let val = val.try_into()?;
107        Ok(self.inner().fetch_max(val, Ordering::Relaxed))
108    }
109}