1use prometheus::{Counter, CounterVec, Gauge, Opts, core::Collector};
4use slog::{Logger, debug};
5
6use mithril_common::StdResult;
7
8pub type MetricName = str;
10
11pub type CounterValue = u32;
13
14pub trait MetricCollector {
16 fn name(&self) -> String;
18
19 fn collector(&self) -> Box<dyn Collector>;
21}
22
23pub struct MetricCounter {
25 name: String,
26 logger: Logger,
27 counter: Box<Counter>,
28}
29
30impl MetricCounter {
31 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 pub fn increment(&self) {
43 debug!(self.logger, "Incrementing '{}' counter", self.name);
44 self.counter.inc();
45 }
46
47 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 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
79pub struct MetricCounterWithLabels {
81 name: String,
82 logger: Logger,
83 counter: Box<CounterVec>,
84}
85
86impl MetricCounterWithLabels {
87 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 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 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 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
139pub struct MetricGauge {
141 name: String,
142 logger: Logger,
143 gauge: Box<Gauge>,
144}
145
146impl MetricGauge {
147 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 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 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}