1use prometheus::{Encoder, IntCounter, IntGauge, Registry, TextEncoder};
4
5pub fn build_registry(const_labels: impl IntoIterator<Item = (String, String)>) -> Registry {
7 let labels: std::collections::HashMap<_, _> = const_labels.into_iter().collect();
8 let registry = Registry::new_custom(Some("laminardb".into()), Some(labels))
9 .expect("registry construction is infallible with valid label names");
10
11 #[cfg(target_os = "linux")]
12 {
13 let pc = prometheus::process_collector::ProcessCollector::for_self();
14 registry
15 .register(Box::new(pc))
16 .expect("process collector registration");
17 }
18
19 registry
20}
21
22pub fn render(registry: &Registry) -> Vec<u8> {
24 let mf = registry.gather();
25 let mut buf = Vec::with_capacity(4096);
26 TextEncoder::new()
27 .encode(&mf, &mut buf)
28 .expect("encoding is infallible");
29 buf
30}
31
32pub struct ServerMetrics {
34 pub reload_total: IntCounter,
35 pub uptime_seconds: IntGauge,
36 pub ws_connections: IntGauge,
37}
38
39impl ServerMetrics {
40 #[must_use]
42 pub fn new(registry: &Registry) -> Self {
43 macro_rules! reg {
44 ($m:expr) => {{
45 let m = $m;
46 registry.register(Box::new(m.clone())).unwrap();
47 m
48 }};
49 }
50 Self {
51 reload_total: reg!(IntCounter::new("reload_total", "Config reload count").unwrap()),
52 uptime_seconds: reg!(
53 IntGauge::new("uptime_seconds", "Server uptime in seconds").unwrap()
54 ),
55 ws_connections: reg!(
56 IntGauge::new("ws_connections", "Active WebSocket connections").unwrap()
57 ),
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use laminar_db::EngineMetrics;
66
67 #[test]
68 fn registry_renders_engine_metrics() {
69 let registry = build_registry([
70 ("instance".into(), "test".into()),
71 ("pipeline".into(), "smoke".into()),
72 ]);
73 let engine = EngineMetrics::new(®istry);
74
75 engine.events_ingested.inc_by(100);
76 engine.events_emitted.inc_by(42);
77 engine.cycles.inc();
78
79 let text = String::from_utf8(render(®istry)).unwrap();
80 assert!(text.contains("laminardb_events_ingested_total"));
81 assert!(text.contains("laminardb_events_emitted_total"));
82 assert!(text.contains("laminardb_cycles_total"));
83 }
84
85 #[test]
86 fn server_metrics_appear_in_output() {
87 let registry = build_registry([
88 ("instance".into(), "test".into()),
89 ("pipeline".into(), "t".into()),
90 ]);
91 let srv = ServerMetrics::new(®istry);
92 srv.reload_total.inc();
93 srv.uptime_seconds.set(60);
94 srv.ws_connections.set(3);
95
96 let text = String::from_utf8(render(®istry)).unwrap();
97 assert!(text.contains("laminardb_reload_total"));
98 assert!(text.contains("laminardb_uptime_seconds"));
99 assert!(text.contains("laminardb_ws_connections"));
100 }
101}