1use prometheus::{
7 exponential_buckets, histogram_opts, linear_buckets, register_histogram,
8 register_histogram_vec, register_int_counter, register_int_counter_vec, register_int_gauge,
9 register_int_gauge_vec, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge,
10 IntGaugeVec, Opts,
11};
12
13use crate::time::Instant;
14
15const LINERA_NAMESPACE: &str = "linera";
16
17pub fn register_int_counter_vec(
19 name: &str,
20 description: &str,
21 label_names: &[&str],
22) -> IntCounterVec {
23 let counter_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
24 register_int_counter_vec!(counter_opts, label_names).expect("IntCounter can be created")
25}
26
27pub fn register_int_counter_vec_with_subsystem(
30 subsystem: &str,
31 name: &str,
32 description: &str,
33 label_names: &[&str],
34) -> IntCounterVec {
35 let counter_opts = Opts::new(name, description)
36 .namespace(LINERA_NAMESPACE)
37 .subsystem(subsystem);
38 register_int_counter_vec!(counter_opts, label_names).expect("IntCounter can be created")
39}
40
41pub fn register_int_counter(name: &str, description: &str) -> IntCounter {
43 let counter_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
44 register_int_counter!(counter_opts).expect("IntCounter can be created")
45}
46
47pub fn register_int_counter_with_subsystem(
50 subsystem: &str,
51 name: &str,
52 description: &str,
53) -> IntCounter {
54 let counter_opts = Opts::new(name, description)
55 .namespace(LINERA_NAMESPACE)
56 .subsystem(subsystem);
57 register_int_counter!(counter_opts).expect("IntCounter can be created")
58}
59
60pub fn register_histogram_vec(
62 name: &str,
63 description: &str,
64 label_names: &[&str],
65 buckets: Option<Vec<f64>>,
66) -> HistogramVec {
67 let histogram_opts = if let Some(buckets) = buckets {
68 histogram_opts!(name, description, buckets).namespace(LINERA_NAMESPACE)
69 } else {
70 histogram_opts!(name, description).namespace(LINERA_NAMESPACE)
71 };
72
73 register_histogram_vec!(histogram_opts, label_names).expect("Histogram can be created")
74}
75
76pub fn register_histogram_vec_with_subsystem(
79 subsystem: &str,
80 name: &str,
81 description: &str,
82 label_names: &[&str],
83 buckets: Option<Vec<f64>>,
84) -> HistogramVec {
85 let histogram_opts = if let Some(buckets) = buckets {
86 histogram_opts!(name, description, buckets)
87 .namespace(LINERA_NAMESPACE)
88 .subsystem(subsystem)
89 } else {
90 histogram_opts!(name, description)
91 .namespace(LINERA_NAMESPACE)
92 .subsystem(subsystem)
93 };
94
95 register_histogram_vec!(histogram_opts, label_names).expect("Histogram can be created")
96}
97
98pub fn register_histogram(name: &str, description: &str, buckets: Option<Vec<f64>>) -> Histogram {
100 let histogram_opts = if let Some(buckets) = buckets {
101 histogram_opts!(name, description, buckets).namespace(LINERA_NAMESPACE)
102 } else {
103 histogram_opts!(name, description).namespace(LINERA_NAMESPACE)
104 };
105
106 register_histogram!(histogram_opts).expect("Histogram can be created")
107}
108
109pub fn register_histogram_with_subsystem(
112 subsystem: &str,
113 name: &str,
114 description: &str,
115 buckets: Option<Vec<f64>>,
116) -> Histogram {
117 let histogram_opts = if let Some(buckets) = buckets {
118 histogram_opts!(name, description, buckets)
119 .namespace(LINERA_NAMESPACE)
120 .subsystem(subsystem)
121 } else {
122 histogram_opts!(name, description)
123 .namespace(LINERA_NAMESPACE)
124 .subsystem(subsystem)
125 };
126
127 register_histogram!(histogram_opts).expect("Histogram can be created")
128}
129
130pub fn register_int_gauge(name: &str, description: &str) -> IntGauge {
132 let gauge_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
133 register_int_gauge!(gauge_opts).expect("IntGauge can be created")
134}
135
136pub fn register_int_gauge_with_subsystem(
139 subsystem: &str,
140 name: &str,
141 description: &str,
142) -> IntGauge {
143 let gauge_opts = Opts::new(name, description)
144 .namespace(LINERA_NAMESPACE)
145 .subsystem(subsystem);
146 register_int_gauge!(gauge_opts).expect("IntGauge can be created")
147}
148
149pub fn register_int_gauge_vec(name: &str, description: &str, label_names: &[&str]) -> IntGaugeVec {
151 let gauge_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
152 register_int_gauge_vec!(gauge_opts, label_names).expect("IntGauge can be created")
153}
154
155pub fn register_int_gauge_vec_with_subsystem(
158 subsystem: &str,
159 name: &str,
160 description: &str,
161 label_names: &[&str],
162) -> IntGaugeVec {
163 let gauge_opts = Opts::new(name, description)
164 .namespace(LINERA_NAMESPACE)
165 .subsystem(subsystem);
166 register_int_gauge_vec!(gauge_opts, label_names).expect("IntGauge can be created")
167}
168
169pub fn exponential_bucket_interval(start_value: f64, end_value: f64) -> Option<Vec<f64>> {
171 let quot = end_value / start_value;
172 let factor = 3.0_f64;
173 let count_approx = quot.ln() / factor.ln();
174 let count = count_approx.round() as usize;
175 let mut buckets = exponential_buckets(start_value, factor, count)
176 .expect("Exponential buckets creation should not fail!");
177 if let Some(last) = buckets.last() {
178 if *last < end_value {
179 buckets.push(end_value);
180 }
181 }
182 Some(buckets)
183}
184
185pub fn exponential_bucket_latencies(max_latency: f64) -> Option<Vec<f64>> {
187 exponential_bucket_interval(0.001_f64, max_latency)
188}
189
190pub fn linear_bucket_interval(start_value: f64, width: f64, end_value: f64) -> Option<Vec<f64>> {
192 let count = (end_value - start_value) / width;
193 let count = count.round() as usize;
194 let mut buckets = linear_buckets(start_value, width, count)
195 .expect("Linear buckets creation should not fail!");
196 buckets.push(end_value);
197 Some(buckets)
198}
199
200enum MeasurementUnit {
202 Milliseconds,
204 Microseconds,
206}
207
208pub struct ActiveMeasurementGuard<'metric, Metric>
212where
213 Metric: MeasureLatency,
214{
215 start: Instant,
216 metric: Option<&'metric Metric>,
217 unit: MeasurementUnit,
218}
219
220impl<Metric> ActiveMeasurementGuard<'_, Metric>
221where
222 Metric: MeasureLatency,
223{
224 pub fn finish(mut self) -> f64 {
227 self.finish_by_ref()
228 }
229
230 fn finish_by_ref(&mut self) -> f64 {
234 match self.metric.take() {
235 Some(metric) => {
236 let latency = match self.unit {
237 MeasurementUnit::Milliseconds => self.start.elapsed().as_secs_f64() * 1000.0,
238 MeasurementUnit::Microseconds => {
239 self.start.elapsed().as_secs_f64() * 1_000_000.0
240 }
241 };
242 metric.finish_measurement(latency);
243 latency
244 }
245 None => {
246 f64::NAN
249 }
250 }
251 }
252}
253
254impl<Metric> Drop for ActiveMeasurementGuard<'_, Metric>
255where
256 Metric: MeasureLatency,
257{
258 fn drop(&mut self) {
259 self.finish_by_ref();
260 }
261}
262
263pub trait MeasureLatency: Sized {
265 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self>;
268
269 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self>;
272
273 fn finish_measurement(&self, milliseconds: f64);
275}
276
277impl MeasureLatency for HistogramVec {
278 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self> {
279 ActiveMeasurementGuard {
280 start: Instant::now(),
281 metric: Some(self),
282 unit: MeasurementUnit::Milliseconds,
283 }
284 }
285
286 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self> {
287 ActiveMeasurementGuard {
288 start: Instant::now(),
289 metric: Some(self),
290 unit: MeasurementUnit::Microseconds,
291 }
292 }
293
294 fn finish_measurement(&self, milliseconds: f64) {
295 self.with_label_values(&[]).observe(milliseconds);
296 }
297}
298
299impl MeasureLatency for Histogram {
300 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self> {
301 ActiveMeasurementGuard {
302 start: Instant::now(),
303 metric: Some(self),
304 unit: MeasurementUnit::Milliseconds,
305 }
306 }
307
308 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self> {
309 ActiveMeasurementGuard {
310 start: Instant::now(),
311 metric: Some(self),
312 unit: MeasurementUnit::Microseconds,
313 }
314 }
315
316 fn finish_measurement(&self, milliseconds: f64) {
317 self.observe(milliseconds);
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 fn assert_float_vec_eq(left: &[f64], right: &[f64]) {
327 const EPSILON: f64 = 1e-10;
328
329 assert_eq!(left.len(), right.len(), "Vectors have different lengths");
330 for (i, (l, r)) in left.iter().zip(right.iter()).enumerate() {
331 assert!(
332 (l - r).abs() < EPSILON,
333 "Vectors differ at index {}: {} != {}",
334 i,
335 l,
336 r
337 );
338 }
339 }
340
341 #[test]
342 fn test_linear_bucket_interval() {
343 let buckets = linear_bucket_interval(0.05, 0.01, 0.1).unwrap();
345 assert_float_vec_eq(&buckets, &[0.05, 0.06, 0.07, 0.08, 0.09, 0.1]);
346
347 let buckets = linear_bucket_interval(100.0, 50.0, 500.0).unwrap();
349 assert_float_vec_eq(
350 &buckets,
351 &[
352 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0, 450.0, 500.0,
353 ],
354 );
355
356 let buckets = linear_bucket_interval(0.05, 0.12, 0.5).unwrap();
358 assert_float_vec_eq(&buckets, &[0.05, 0.17, 0.29, 0.41, 0.5]);
359
360 let buckets = linear_bucket_interval(100.0, 150.0, 500.0).unwrap();
362 assert_float_vec_eq(&buckets, &[100.0, 250.0, 400.0, 500.0]);
363 }
364}