1use std::sync::Arc;
4
5use prometheus::{CounterVec, GaugeVec, HistogramVec, Registry};
6use solti_api::{ApiMetricsBackend, Transport};
7
8use crate::register::{Sub, ms_to_secs};
9
10pub struct PrometheusApiMetrics {
25 requests_total: CounterVec,
26 duration_seconds: HistogramVec,
27 in_flight: GaugeVec,
28}
29
30impl PrometheusApiMetrics {
31 pub fn new(registry: Arc<Registry>) -> Result<Self, prometheus::Error> {
33 let r = Sub::new(®istry, "api");
34
35 let requests_total = r.counter_vec(
36 "requests_total",
37 "Total completed API requests",
38 &["transport", "method", "path", "status"],
39 )?;
40 let duration_seconds = r.histogram_vec(
41 "request_duration_seconds",
42 "API request duration",
43 vec![
44 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
45 ],
46 &["transport", "method", "path"],
47 )?;
48 let in_flight = r.gauge_vec(
49 "in_flight_requests",
50 "Current in-flight API requests",
51 &["transport"],
52 )?;
53
54 Ok(Self {
55 requests_total,
56 duration_seconds,
57 in_flight,
58 })
59 }
60}
61
62impl std::fmt::Debug for PrometheusApiMetrics {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("PrometheusApiMetrics").finish()
65 }
66}
67
68impl ApiMetricsBackend for PrometheusApiMetrics {
69 fn record_request(
70 &self,
71 transport: Transport,
72 method: &str,
73 path: &str,
74 status: u16,
75 duration_ms: u64,
76 ) {
77 let t = transport.as_label();
78 let mut buf = itoa::Buffer::new();
80 let s = buf.format(status);
81 self.requests_total
82 .with_label_values(&[t, method, path, s])
83 .inc();
84 self.duration_seconds
85 .with_label_values(&[t, method, path])
86 .observe(ms_to_secs(duration_ms));
87 }
88
89 fn record_in_flight_delta(&self, transport: Transport, delta: i64) {
90 self.in_flight
91 .with_label_values(&[transport.as_label()])
92 .add(delta as f64);
93 }
94}