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
/// Auth attempts refused by the per-address throttle.
19
pub const AUTH_THROTTLED: &str = "nomisync_auth_throttled_total";
20

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

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

            
41
#[cfg(test)]
42
mod tests {
43
    use super::*;
44

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

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