Skip to main content

datasynth_server/observability/
metrics.rs

1//! Server metrics abstraction.
2//!
3//! Provides a unified metrics interface that works with or without the `otel` feature.
4//! When `otel` is enabled, metrics are recorded via OpenTelemetry instruments.
5//! When disabled, they proxy to the existing `ServerState` atomics.
6
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::Instant;
10
11/// Server metrics collector.
12///
13/// This is a lightweight wrapper that records metrics.
14/// Without the `otel` feature, it delegates to `ServerState` atomics.
15/// With `otel`, it additionally records via OTEL instruments.
16#[derive(Clone)]
17pub struct ServerMetrics {
18    /// Total entries generated (counter)
19    pub entries_total: Arc<AtomicU64>,
20    /// Total errors (counter)
21    pub errors_total: Arc<AtomicU64>,
22    /// Active sessions (gauge)
23    pub active_sessions: Arc<AtomicU64>,
24}
25
26impl Default for ServerMetrics {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl ServerMetrics {
33    pub fn new() -> Self {
34        Self {
35            entries_total: Arc::new(AtomicU64::new(0)),
36            errors_total: Arc::new(AtomicU64::new(0)),
37            active_sessions: Arc::new(AtomicU64::new(0)),
38        }
39    }
40
41    /// Record entries generated.
42    pub fn record_entries(&self, count: u64) {
43        self.entries_total.fetch_add(count, Ordering::Relaxed);
44    }
45
46    /// Record an error.
47    pub fn record_error(&self) {
48        self.errors_total.fetch_add(1, Ordering::Relaxed);
49    }
50
51    /// Increment active sessions.
52    pub fn session_started(&self) {
53        self.active_sessions.fetch_add(1, Ordering::Relaxed);
54    }
55
56    /// Decrement active sessions.
57    pub fn session_ended(&self) {
58        self.active_sessions.fetch_sub(1, Ordering::Relaxed);
59    }
60}
61
62/// Timer for measuring operation duration.
63pub struct DurationTimer {
64    start: Instant,
65    label: String,
66}
67
68impl DurationTimer {
69    pub fn new(label: impl Into<String>) -> Self {
70        Self {
71            start: Instant::now(),
72            label: label.into(),
73        }
74    }
75
76    /// Finish the timer and return duration in milliseconds.
77    pub fn finish(self) -> u64 {
78        let duration_ms = self.start.elapsed().as_millis() as u64;
79        tracing::debug!(
80            label = %self.label,
81            duration_ms = duration_ms,
82            "Operation completed"
83        );
84        duration_ms
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_server_metrics_default() {
94        let metrics = ServerMetrics::new();
95        assert_eq!(metrics.entries_total.load(Ordering::Relaxed), 0);
96        assert_eq!(metrics.errors_total.load(Ordering::Relaxed), 0);
97        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 0);
98    }
99
100    #[test]
101    fn test_record_entries() {
102        let metrics = ServerMetrics::new();
103        metrics.record_entries(100);
104        metrics.record_entries(50);
105        assert_eq!(metrics.entries_total.load(Ordering::Relaxed), 150);
106    }
107
108    #[test]
109    fn test_record_errors() {
110        let metrics = ServerMetrics::new();
111        metrics.record_error();
112        metrics.record_error();
113        assert_eq!(metrics.errors_total.load(Ordering::Relaxed), 2);
114    }
115
116    #[test]
117    fn test_session_tracking() {
118        let metrics = ServerMetrics::new();
119        metrics.session_started();
120        metrics.session_started();
121        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 2);
122        metrics.session_ended();
123        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 1);
124    }
125
126    #[test]
127    fn test_duration_timer() {
128        let timer = DurationTimer::new("test_op");
129        std::thread::sleep(std::time::Duration::from_millis(10));
130        let duration = timer.finish();
131        assert!(duration >= 10);
132    }
133}