1
//! Metric names and recorder setup.
2
//!
3
//! Names carry the `nomisync_` prefix because the series land in a
4
//! VictoriaMetrics shared with every other app on the cluster: a bare
5
//! `http_requests_total` would collide with anything else exporting the obvious
6
//! name, and an ad-hoc query without a namespace filter would silently mix them.
7
//! Keeping the names here as constants means the exporter and any test that
8
//! asserts on them cannot drift apart.
9

            
10
use metrics_exporter_prometheus::{BuildError, Matcher, PrometheusBuilder, PrometheusHandle};
11

            
12
/// Requests served, labelled by method, matched path and status.
13
pub const HTTP_REQUESTS: &str = "nomisync_http_requests_total";
14

            
15
/// Request latency in seconds, same labels, bucketed by [`LATENCY_BUCKETS`].
16
pub const HTTP_REQUEST_DURATION: &str = "nomisync_http_requests_duration_seconds";
17

            
18
/// Web-request latencies: sub-10ms to 10s, which spans a cached page render and
19
/// a slow report query.
20
const LATENCY_BUCKETS: &[f64] = &[
21
    0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
22
];
23

            
24
/// Install the process-wide Prometheus recorder.
25
///
26
/// # Errors
27
/// [`BuildError`] if the bucket configuration is rejected or a recorder is
28
/// already installed — both are startup faults, reported rather than panicked.
29
1
pub fn install_recorder() -> Result<PrometheusHandle, BuildError> {
30
1
    PrometheusBuilder::new()
31
1
        .set_buckets_for_metric(
32
1
            Matcher::Full(HTTP_REQUEST_DURATION.to_string()),
33
1
            LATENCY_BUCKETS,
34
        )?
35
1
        .install_recorder()
36
1
}
37

            
38
#[cfg(test)]
39
mod tests {
40
    use super::*;
41

            
42
    #[test]
43
1
    fn names_carry_the_app_prefix() {
44
2
        for name in [HTTP_REQUESTS, HTTP_REQUEST_DURATION] {
45
2
            assert!(
46
2
                name.starts_with("nomisync_"),
47
                "{name} would collide with other apps in the shared VictoriaMetrics"
48
            );
49
        }
50
1
    }
51

            
52
    #[test]
53
1
    fn recorder_renders_the_registered_series() {
54
1
        let Ok(handle) = install_recorder() else {
55
            // A recorder is global and install-once; another test in this binary
56
            // may already hold it. Nothing to assert then.
57
            return;
58
        };
59
1
        metrics::counter!(HTTP_REQUESTS, &[("method", "GET")]).increment(1);
60
1
        let rendered = handle.render();
61
1
        assert!(
62
1
            rendered.contains(HTTP_REQUESTS),
63
            "exporter did not render {HTTP_REQUESTS}"
64
        );
65
1
    }
66
}