Skip to main content

forge_runtime/observability/
metrics.rs

1use opentelemetry::{
2    KeyValue, global,
3    metrics::{Counter, Histogram, UpDownCounter},
4};
5use std::sync::OnceLock;
6
7const METER_NAME: &str = "forge-runtime";
8
9static HTTP_METRICS: OnceLock<HttpMetrics> = OnceLock::new();
10static JOB_METRICS: OnceLock<JobMetrics> = OnceLock::new();
11static CONNECTIONS_GAUGE: OnceLock<ActiveConnectionsGauge> = OnceLock::new();
12
13pub struct HttpMetrics {
14    requests_total: Counter<u64>,
15    request_duration: Histogram<f64>,
16}
17
18impl HttpMetrics {
19    fn new() -> Self {
20        let meter = global::meter(METER_NAME);
21
22        let requests_total = meter
23            .u64_counter("http_requests_total")
24            .with_description("Total number of HTTP requests")
25            .with_unit("requests")
26            .build();
27
28        let request_duration = meter
29            .f64_histogram("http_request_duration_seconds")
30            .with_description("HTTP request duration in seconds")
31            .with_unit("s")
32            .build();
33
34        Self {
35            requests_total,
36            request_duration,
37        }
38    }
39
40    pub fn record(&self, method: &str, path: &str, status: u16, duration_secs: f64) {
41        let attributes = [
42            KeyValue::new("method", method.to_string()),
43            KeyValue::new("path", path.to_string()),
44            KeyValue::new("status", i64::from(status)),
45        ];
46
47        self.requests_total.add(1, &attributes);
48        self.request_duration.record(duration_secs, &attributes);
49    }
50}
51
52pub struct JobMetrics {
53    executions_total: Counter<u64>,
54    duration: Histogram<f64>,
55}
56
57impl JobMetrics {
58    fn new() -> Self {
59        let meter = global::meter(METER_NAME);
60
61        let executions_total = meter
62            .u64_counter("job_executions_total")
63            .with_description("Total number of job executions")
64            .with_unit("executions")
65            .build();
66
67        let duration = meter
68            .f64_histogram("job_duration_seconds")
69            .with_description("Job execution duration in seconds")
70            .with_unit("s")
71            .build();
72
73        Self {
74            executions_total,
75            duration,
76        }
77    }
78
79    pub fn record(&self, job_type: &str, status: &str, duration_secs: f64) {
80        let attributes = [
81            KeyValue::new("job_type", job_type.to_string()),
82            KeyValue::new("status", status.to_string()),
83        ];
84
85        self.executions_total.add(1, &attributes);
86        self.duration.record(duration_secs, &attributes);
87    }
88}
89
90pub struct ActiveConnectionsGauge {
91    gauge: UpDownCounter<i64>,
92}
93
94impl ActiveConnectionsGauge {
95    fn new() -> Self {
96        let meter = global::meter(METER_NAME);
97
98        let gauge = meter
99            .i64_up_down_counter("active_connections")
100            .with_description("Number of active connections")
101            .with_unit("connections")
102            .build();
103
104        Self { gauge }
105    }
106
107    pub fn increment(&self, connection_type: &str) {
108        self.gauge
109            .add(1, &[KeyValue::new("type", connection_type.to_string())]);
110    }
111
112    pub fn decrement(&self, connection_type: &str) {
113        self.gauge
114            .add(-1, &[KeyValue::new("type", connection_type.to_string())]);
115    }
116
117    pub fn set(&self, connection_type: &str, delta: i64) {
118        self.gauge
119            .add(delta, &[KeyValue::new("type", connection_type.to_string())]);
120    }
121}
122
123fn http_metrics() -> &'static HttpMetrics {
124    HTTP_METRICS.get_or_init(HttpMetrics::new)
125}
126
127fn job_metrics() -> &'static JobMetrics {
128    JOB_METRICS.get_or_init(JobMetrics::new)
129}
130
131fn connections_gauge() -> &'static ActiveConnectionsGauge {
132    CONNECTIONS_GAUGE.get_or_init(ActiveConnectionsGauge::new)
133}
134
135pub fn record_http_request(method: &str, path: &str, status: u16, duration_secs: f64) {
136    http_metrics().record(method, path, status, duration_secs);
137}
138
139pub fn record_job_execution(job_type: &str, status: &str, duration_secs: f64) {
140    job_metrics().record(job_type, status, duration_secs);
141}
142
143pub fn set_active_connections(connection_type: &str, delta: i64) {
144    connections_gauge().set(connection_type, delta);
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_http_metrics_creation() {
153        let _metrics = HttpMetrics::new();
154    }
155
156    #[test]
157    fn test_job_metrics_creation() {
158        let _metrics = JobMetrics::new();
159    }
160
161    #[test]
162    fn test_connections_gauge_creation() {
163        let _gauge = ActiveConnectionsGauge::new();
164    }
165}