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 FN_METRICS: OnceLock<FnMetrics> = OnceLock::new();
11static JOB_METRICS: OnceLock<JobMetrics> = OnceLock::new();
12static CONNECTIONS_GAUGE: OnceLock<ActiveConnectionsGauge> = OnceLock::new();
13
14pub struct HttpMetrics {
15    requests_total: Counter<u64>,
16    request_duration: Histogram<f64>,
17}
18
19impl HttpMetrics {
20    fn new() -> Self {
21        let meter = global::meter(METER_NAME);
22
23        let requests_total = meter
24            .u64_counter("http_requests_total")
25            .with_description("Total number of HTTP requests")
26            .with_unit("requests")
27            .build();
28
29        let request_duration = meter
30            .f64_histogram("http_request_duration_seconds")
31            .with_description("HTTP request duration in seconds")
32            .with_unit("s")
33            .build();
34
35        Self {
36            requests_total,
37            request_duration,
38        }
39    }
40
41    pub fn record(&self, method: &str, path: &str, status: u16, duration_secs: f64) {
42        let attributes = [
43            KeyValue::new("method", method.to_string()),
44            KeyValue::new("path", path.to_string()),
45            KeyValue::new("status", i64::from(status)),
46        ];
47
48        self.requests_total.add(1, &attributes);
49        self.request_duration.record(duration_secs, &attributes);
50    }
51}
52
53pub struct FnMetrics {
54    executions_total: Counter<u64>,
55    duration: Histogram<f64>,
56}
57
58impl FnMetrics {
59    fn new() -> Self {
60        let meter = global::meter(METER_NAME);
61
62        let executions_total = meter
63            .u64_counter("fn.executions_total")
64            .with_description("Total function executions")
65            .with_unit("executions")
66            .build();
67
68        let duration = meter
69            .f64_histogram("fn.duration_seconds")
70            .with_description("Function execution duration")
71            .with_unit("s")
72            .build();
73
74        Self {
75            executions_total,
76            duration,
77        }
78    }
79
80    pub fn record(&self, function: &str, kind: &str, success: bool, duration_secs: f64) {
81        let status = if success { "ok" } else { "error" };
82        let attributes = [
83            KeyValue::new("function", function.to_string()),
84            KeyValue::new("kind", kind.to_string()),
85            KeyValue::new("status", status),
86        ];
87
88        self.executions_total.add(1, &attributes);
89        self.duration.record(duration_secs, &attributes);
90    }
91}
92
93pub struct JobMetrics {
94    executions_total: Counter<u64>,
95    duration: Histogram<f64>,
96}
97
98impl JobMetrics {
99    fn new() -> Self {
100        let meter = global::meter(METER_NAME);
101
102        let executions_total = meter
103            .u64_counter("job_executions_total")
104            .with_description("Total number of job executions")
105            .with_unit("executions")
106            .build();
107
108        let duration = meter
109            .f64_histogram("job_duration_seconds")
110            .with_description("Job execution duration in seconds")
111            .with_unit("s")
112            .build();
113
114        Self {
115            executions_total,
116            duration,
117        }
118    }
119
120    pub fn record(&self, job_type: &str, status: &str, duration_secs: f64) {
121        let attributes = [
122            KeyValue::new("job_type", job_type.to_string()),
123            KeyValue::new("status", status.to_string()),
124        ];
125
126        self.executions_total.add(1, &attributes);
127        self.duration.record(duration_secs, &attributes);
128    }
129}
130
131pub struct ActiveConnectionsGauge {
132    gauge: UpDownCounter<i64>,
133}
134
135impl ActiveConnectionsGauge {
136    fn new() -> Self {
137        let meter = global::meter(METER_NAME);
138
139        let gauge = meter
140            .i64_up_down_counter("active_connections")
141            .with_description("Number of active connections")
142            .with_unit("connections")
143            .build();
144
145        Self { gauge }
146    }
147
148    pub fn increment(&self, connection_type: &str) {
149        self.gauge
150            .add(1, &[KeyValue::new("type", connection_type.to_string())]);
151    }
152
153    pub fn decrement(&self, connection_type: &str) {
154        self.gauge
155            .add(-1, &[KeyValue::new("type", connection_type.to_string())]);
156    }
157
158    pub fn set(&self, connection_type: &str, delta: i64) {
159        self.gauge
160            .add(delta, &[KeyValue::new("type", connection_type.to_string())]);
161    }
162}
163
164fn http_metrics() -> &'static HttpMetrics {
165    HTTP_METRICS.get_or_init(HttpMetrics::new)
166}
167
168fn fn_metrics() -> &'static FnMetrics {
169    FN_METRICS.get_or_init(FnMetrics::new)
170}
171
172fn job_metrics() -> &'static JobMetrics {
173    JOB_METRICS.get_or_init(JobMetrics::new)
174}
175
176fn connections_gauge() -> &'static ActiveConnectionsGauge {
177    CONNECTIONS_GAUGE.get_or_init(ActiveConnectionsGauge::new)
178}
179
180pub fn record_http_request(method: &str, path: &str, status: u16, duration_secs: f64) {
181    http_metrics().record(method, path, status, duration_secs);
182}
183
184pub fn record_fn_execution(function: &str, kind: &str, success: bool, duration_secs: f64) {
185    fn_metrics().record(function, kind, success, duration_secs);
186}
187
188pub fn record_job_execution(job_type: &str, status: &str, duration_secs: f64) {
189    job_metrics().record(job_type, status, duration_secs);
190}
191
192pub fn set_active_connections(connection_type: &str, delta: i64) {
193    connections_gauge().set(connection_type, delta);
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_http_metrics_creation() {
202        let _metrics = HttpMetrics::new();
203    }
204
205    #[test]
206    fn test_job_metrics_creation() {
207        let _metrics = JobMetrics::new();
208    }
209
210    #[test]
211    fn test_connections_gauge_creation() {
212        let _gauge = ActiveConnectionsGauge::new();
213    }
214}