1use prometheus::{
7 exponential_buckets, histogram_opts, linear_buckets, register_gauge_vec, register_histogram,
8 register_histogram_vec, register_int_counter, register_int_counter_vec, register_int_gauge,
9 register_int_gauge_vec, GaugeVec, 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_histogram_vec(
49 name: &str,
50 description: &str,
51 label_names: &[&str],
52 buckets: Option<Vec<f64>>,
53) -> HistogramVec {
54 let histogram_opts = if let Some(buckets) = buckets {
55 histogram_opts!(name, description, buckets).namespace(LINERA_NAMESPACE)
56 } else {
57 histogram_opts!(name, description).namespace(LINERA_NAMESPACE)
58 };
59
60 register_histogram_vec!(histogram_opts, label_names).expect("Histogram can be created")
61}
62
63pub fn register_histogram_vec_with_subsystem(
66 subsystem: &str,
67 name: &str,
68 description: &str,
69 label_names: &[&str],
70 buckets: Option<Vec<f64>>,
71) -> HistogramVec {
72 let histogram_opts = if let Some(buckets) = buckets {
73 histogram_opts!(name, description, buckets)
74 .namespace(LINERA_NAMESPACE)
75 .subsystem(subsystem)
76 } else {
77 histogram_opts!(name, description)
78 .namespace(LINERA_NAMESPACE)
79 .subsystem(subsystem)
80 };
81
82 register_histogram_vec!(histogram_opts, label_names).expect("Histogram can be created")
83}
84
85pub fn register_histogram(name: &str, description: &str, buckets: Option<Vec<f64>>) -> Histogram {
87 let histogram_opts = if let Some(buckets) = buckets {
88 histogram_opts!(name, description, buckets).namespace(LINERA_NAMESPACE)
89 } else {
90 histogram_opts!(name, description).namespace(LINERA_NAMESPACE)
91 };
92
93 register_histogram!(histogram_opts).expect("Histogram can be created")
94}
95
96pub fn register_histogram_with_subsystem(
99 subsystem: &str,
100 name: &str,
101 description: &str,
102 buckets: Option<Vec<f64>>,
103) -> Histogram {
104 let histogram_opts = if let Some(buckets) = buckets {
105 histogram_opts!(name, description, buckets)
106 .namespace(LINERA_NAMESPACE)
107 .subsystem(subsystem)
108 } else {
109 histogram_opts!(name, description)
110 .namespace(LINERA_NAMESPACE)
111 .subsystem(subsystem)
112 };
113
114 register_histogram!(histogram_opts).expect("Histogram can be created")
115}
116
117pub fn register_int_gauge(name: &str, description: &str) -> IntGauge {
119 let gauge_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
120 register_int_gauge!(gauge_opts).expect("IntGauge can be created")
121}
122
123pub fn register_int_gauge_with_subsystem(
126 subsystem: &str,
127 name: &str,
128 description: &str,
129) -> IntGauge {
130 let gauge_opts = Opts::new(name, description)
131 .namespace(LINERA_NAMESPACE)
132 .subsystem(subsystem);
133 register_int_gauge!(gauge_opts).expect("IntGauge can be created")
134}
135
136pub fn register_int_gauge_vec(name: &str, description: &str, label_names: &[&str]) -> IntGaugeVec {
138 let gauge_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
139 register_int_gauge_vec!(gauge_opts, label_names).expect("IntGauge can be created")
140}
141
142pub fn register_gauge_vec(name: &str, description: &str, label_names: &[&str]) -> GaugeVec {
145 let gauge_opts = Opts::new(name, description).namespace(LINERA_NAMESPACE);
146 register_gauge_vec!(gauge_opts, label_names).expect("Gauge can be created")
147}
148
149pub fn register_int_gauge_vec_with_subsystem(
152 subsystem: &str,
153 name: &str,
154 description: &str,
155 label_names: &[&str],
156) -> IntGaugeVec {
157 let gauge_opts = Opts::new(name, description)
158 .namespace(LINERA_NAMESPACE)
159 .subsystem(subsystem);
160 register_int_gauge_vec!(gauge_opts, label_names).expect("IntGauge can be created")
161}
162
163#[expect(
165 clippy::cast_possible_truncation,
166 clippy::cast_sign_loss,
167 reason = "histogram bucket count; loss of precision is acceptable"
168)]
169pub fn exponential_bucket_interval(start_value: f64, end_value: f64) -> Option<Vec<f64>> {
170 let quot = end_value / start_value;
171 let factor = 3.0_f64;
172 let count_approx = quot.ln() / factor.ln();
173 let count = count_approx.round() as usize;
174 let mut buckets = exponential_buckets(start_value, factor, count)
175 .expect("Exponential buckets creation should not fail!");
176 if let Some(last) = buckets.last() {
177 if *last < end_value {
178 buckets.push(end_value);
179 }
180 }
181 Some(buckets)
182}
183
184pub fn exponential_bucket_latencies(max_latency: f64) -> Option<Vec<f64>> {
186 exponential_bucket_interval(0.001_f64, max_latency)
187}
188
189#[expect(
191 clippy::cast_possible_truncation,
192 clippy::cast_sign_loss,
193 reason = "histogram bucket count; loss of precision is acceptable"
194)]
195pub fn linear_bucket_interval(start_value: f64, width: f64, end_value: f64) -> Option<Vec<f64>> {
196 let count = (end_value - start_value) / width;
197 let count = count.round() as usize;
198 let mut buckets = linear_buckets(start_value, width, count)
199 .expect("Linear buckets creation should not fail!");
200 buckets.push(end_value);
201 Some(buckets)
202}
203
204enum MeasurementUnit {
206 Milliseconds,
208 Microseconds,
210}
211
212pub struct ActiveMeasurementGuard<'metric, Metric>
216where
217 Metric: MeasureLatency,
218{
219 start: Instant,
220 metric: Option<&'metric Metric>,
221 unit: MeasurementUnit,
222}
223
224impl<Metric> ActiveMeasurementGuard<'_, Metric>
225where
226 Metric: MeasureLatency,
227{
228 pub fn finish(mut self) -> f64 {
231 self.finish_by_ref()
232 }
233
234 fn finish_by_ref(&mut self) -> f64 {
238 match self.metric.take() {
239 Some(metric) => {
240 let latency = match self.unit {
241 MeasurementUnit::Milliseconds => self.start.elapsed().as_secs_f64() * 1000.0,
242 MeasurementUnit::Microseconds => {
243 self.start.elapsed().as_secs_f64() * 1_000_000.0
244 }
245 };
246 metric.finish_measurement(latency);
247 latency
248 }
249 None => {
250 f64::NAN
253 }
254 }
255 }
256}
257
258impl<Metric> Drop for ActiveMeasurementGuard<'_, Metric>
259where
260 Metric: MeasureLatency,
261{
262 fn drop(&mut self) {
263 self.finish_by_ref();
264 }
265}
266
267pub trait MeasureLatency: Sized {
269 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self>;
272
273 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self>;
276
277 fn finish_measurement(&self, milliseconds: f64);
279}
280
281impl MeasureLatency for HistogramVec {
282 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self> {
283 ActiveMeasurementGuard {
284 start: Instant::now(),
285 metric: Some(self),
286 unit: MeasurementUnit::Milliseconds,
287 }
288 }
289
290 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self> {
291 ActiveMeasurementGuard {
292 start: Instant::now(),
293 metric: Some(self),
294 unit: MeasurementUnit::Microseconds,
295 }
296 }
297
298 fn finish_measurement(&self, milliseconds: f64) {
299 self.with_label_values(&[]).observe(milliseconds);
300 }
301}
302
303impl MeasureLatency for Histogram {
304 fn measure_latency(&self) -> ActiveMeasurementGuard<'_, Self> {
305 ActiveMeasurementGuard {
306 start: Instant::now(),
307 metric: Some(self),
308 unit: MeasurementUnit::Milliseconds,
309 }
310 }
311
312 fn measure_latency_us(&self) -> ActiveMeasurementGuard<'_, Self> {
313 ActiveMeasurementGuard {
314 start: Instant::now(),
315 metric: Some(self),
316 unit: MeasurementUnit::Microseconds,
317 }
318 }
319
320 fn finish_measurement(&self, milliseconds: f64) {
321 self.observe(milliseconds);
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 fn assert_float_vec_eq(left: &[f64], right: &[f64]) {
331 const EPSILON: f64 = 1e-10;
332
333 assert_eq!(left.len(), right.len(), "Vectors have different lengths");
334 for (i, (l, r)) in left.iter().zip(right.iter()).enumerate() {
335 assert!(
336 (l - r).abs() < EPSILON,
337 "Vectors differ at index {i}: {l} != {r}"
338 );
339 }
340 }
341
342 #[test]
343 fn test_linear_bucket_interval() {
344 let buckets = linear_bucket_interval(0.05, 0.01, 0.1).unwrap();
346 assert_float_vec_eq(&buckets, &[0.05, 0.06, 0.07, 0.08, 0.09, 0.1]);
347
348 let buckets = linear_bucket_interval(100.0, 50.0, 500.0).unwrap();
350 assert_float_vec_eq(
351 &buckets,
352 &[
353 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0, 450.0, 500.0,
354 ],
355 );
356
357 let buckets = linear_bucket_interval(0.05, 0.12, 0.5).unwrap();
359 assert_float_vec_eq(&buckets, &[0.05, 0.17, 0.29, 0.41, 0.5]);
360
361 let buckets = linear_bucket_interval(100.0, 150.0, 500.0).unwrap();
363 assert_float_vec_eq(&buckets, &[100.0, 250.0, 400.0, 500.0]);
364 }
365}