mithril_metric/
metric.rs

1//! This module contains wrapper to prometheus metrics for use in a metrics service.
2
3use prometheus::{Counter, CounterVec, Gauge, Opts, core::Collector};
4use slog::{Logger, debug};
5
6use mithril_common::StdResult;
7
8/// Type alias for a metric name.
9pub type MetricName = str;
10
11/// Type alias for a counter value.
12pub type CounterValue = u32;
13
14/// Metric collector
15pub trait MetricCollector {
16    /// Metric name
17    fn name(&self) -> String;
18
19    /// Wrapped prometheus collector
20    fn collector(&self) -> Box<dyn Collector>;
21}
22
23/// Metric counter
24pub struct MetricCounter {
25    name: String,
26    logger: Logger,
27    counter: Box<Counter>,
28}
29
30impl MetricCounter {
31    /// Create a new metric counter.
32    pub fn new(logger: Logger, name: &str, help: &str) -> StdResult<Self> {
33        let counter = MetricCounter::create_metric_counter(name, help)?;
34        Ok(Self {
35            logger,
36            name: name.to_string(),
37            counter: Box::new(counter),
38        })
39    }
40
41    /// Increment the counter.
42    pub fn increment(&self) {
43        debug!(self.logger, "Incrementing '{}' counter", self.name);
44        self.counter.inc();
45    }
46
47    /// Increment the counter by a value.
48    pub fn increment_by(&self, value: CounterValue) {
49        debug!(
50            self.logger,
51            "Incrementing '{}' counter by {}", self.name, value
52        );
53        self.counter.inc_by(value as f64);
54    }
55
56    /// Get the counter value.
57    pub fn get(&self) -> CounterValue {
58        self.counter.get().round() as CounterValue
59    }
60
61    fn create_metric_counter(name: &MetricName, help: &str) -> StdResult<Counter> {
62        let counter_opts = Opts::new(name, help);
63        let counter = Counter::with_opts(counter_opts)?;
64
65        Ok(counter)
66    }
67}
68
69impl MetricCollector for MetricCounter {
70    fn collector(&self) -> Box<dyn Collector> {
71        self.counter.clone()
72    }
73
74    fn name(&self) -> String {
75        self.name.clone()
76    }
77}
78
79/// Metric counter with labels
80pub struct MetricCounterWithLabels {
81    name: String,
82    logger: Logger,
83    counter: Box<CounterVec>,
84}
85
86impl MetricCounterWithLabels {
87    /// Create a new metric counter.
88    pub fn new(logger: Logger, name: &str, help: &str, labels: &[&str]) -> StdResult<Self> {
89        let counter =
90            MetricCounterWithLabels::create_metric_counter_with_labels(name, help, labels)?;
91        Ok(Self {
92            logger,
93            name: name.to_string(),
94            counter: Box::new(counter),
95        })
96    }
97
98    /// Increment the counter.
99    pub fn increment(&self, labels: &[&str]) {
100        debug!(self.logger, "Incrementing '{}' counter", self.name);
101        self.counter.with_label_values(labels).inc();
102    }
103
104    /// Increment the counter by a value.
105    pub fn increment_by(&self, labels: &[&str], value: CounterValue) {
106        debug!(
107            self.logger,
108            "Incrementing '{}' counter by {}", self.name, value
109        );
110        self.counter.with_label_values(labels).inc_by(value as f64);
111    }
112
113    /// Get the counter value.
114    pub fn get(&self, labels: &[&str]) -> CounterValue {
115        self.counter.with_label_values(labels).get().round() as CounterValue
116    }
117
118    fn create_metric_counter_with_labels(
119        name: &MetricName,
120        help: &str,
121        labels: &[&str],
122    ) -> StdResult<CounterVec> {
123        let counter_opts = Opts::new(name, help);
124        let counter = CounterVec::new(counter_opts, labels)?;
125        Ok(counter)
126    }
127}
128
129impl MetricCollector for MetricCounterWithLabels {
130    fn collector(&self) -> Box<dyn Collector> {
131        self.counter.clone()
132    }
133
134    fn name(&self) -> String {
135        self.name.clone()
136    }
137}
138
139/// Metric gauge
140pub struct MetricGauge {
141    name: String,
142    logger: Logger,
143    gauge: Box<Gauge>,
144}
145
146impl MetricGauge {
147    /// Create a new metric gauge.
148    pub fn new(logger: Logger, name: &str, help: &str) -> StdResult<Self> {
149        let gauge = MetricGauge::create_metric_gauge(name, help)?;
150        Ok(Self {
151            logger,
152            name: name.to_string(),
153            gauge: Box::new(gauge),
154        })
155    }
156
157    /// Record a value in the gauge.
158    pub fn record<T: Into<f64>>(&self, value: T) {
159        let value = value.into();
160        debug!(self.logger, "Set '{}' gauge value to {}", self.name, value);
161        self.gauge.set(value);
162    }
163
164    /// Get the gauge value.
165    pub fn get(&self) -> f64 {
166        self.gauge.get()
167    }
168
169    fn create_metric_gauge(name: &MetricName, help: &str) -> StdResult<Gauge> {
170        let gauge_opts = Opts::new(name, help);
171        let gauge = Gauge::with_opts(gauge_opts)?;
172
173        Ok(gauge)
174    }
175}
176impl MetricCollector for MetricGauge {
177    fn collector(&self) -> Box<dyn Collector> {
178        self.gauge.clone()
179    }
180    fn name(&self) -> String {
181        self.name.clone()
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use crate::helper::test_tools::TestLogger;
188
189    use super::*;
190
191    #[test]
192    fn test_metric_counter_can_be_incremented() {
193        let metric =
194            MetricCounter::new(TestLogger::stdout(), "test_counter", "test counter help").unwrap();
195        assert_eq!(metric.name(), "test_counter");
196        assert_eq!(metric.get(), 0);
197
198        metric.increment();
199        assert_eq!(metric.get(), 1);
200    }
201
202    #[test]
203    fn test_metric_gauge_can_be_recorded() {
204        let metric =
205            MetricGauge::new(TestLogger::stdout(), "test_gauge", "test gauge help").unwrap();
206        assert_eq!(metric.name(), "test_gauge");
207        assert_eq!(metric.get(), 0.0);
208
209        metric.record(12.3);
210        assert_eq!(metric.get(), 12.3);
211    }
212
213    #[test]
214    fn test_metric_counter_can_be_incremented_more_than_one() {
215        let metric =
216            MetricCounter::new(TestLogger::stdout(), "test_counter", "test counter help").unwrap();
217
218        metric.increment_by(37);
219        assert_eq!(metric.get(), 37);
220    }
221
222    #[test]
223    fn test_metric_counter_vec_can_be_recorded() {
224        let metric = MetricCounterWithLabels::new(
225            TestLogger::stdout(),
226            "test_counter_with_labels",
227            "test counter with labels help",
228            &["label_1", "label_2"],
229        )
230        .unwrap();
231
232        metric.increment(&["A", "200"]);
233        metric.increment_by(&["B", "100"], 22);
234        assert_eq!(metric.get(&["A", "200"]), 1);
235        assert_eq!(metric.get(&["B", "100"]), 22);
236    }
237}