forge_runtime/observability/
metrics.rs1use 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}