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)]
89#[allow(clippy::unwrap_used)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_server_metrics_default() {
95        let metrics = ServerMetrics::new();
96        assert_eq!(metrics.entries_total.load(Ordering::Relaxed), 0);
97        assert_eq!(metrics.errors_total.load(Ordering::Relaxed), 0);
98        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 0);
99    }
100
101    #[test]
102    fn test_record_entries() {
103        let metrics = ServerMetrics::new();
104        metrics.record_entries(100);
105        metrics.record_entries(50);
106        assert_eq!(metrics.entries_total.load(Ordering::Relaxed), 150);
107    }
108
109    #[test]
110    fn test_record_errors() {
111        let metrics = ServerMetrics::new();
112        metrics.record_error();
113        metrics.record_error();
114        assert_eq!(metrics.errors_total.load(Ordering::Relaxed), 2);
115    }
116
117    #[test]
118    fn test_session_tracking() {
119        let metrics = ServerMetrics::new();
120        metrics.session_started();
121        metrics.session_started();
122        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 2);
123        metrics.session_ended();
124        assert_eq!(metrics.active_sessions.load(Ordering::Relaxed), 1);
125    }
126
127    #[test]
128    fn test_duration_timer() {
129        let timer = DurationTimer::new("test_op");
130        std::thread::sleep(std::time::Duration::from_millis(10));
131        let duration = timer.finish();
132        assert!(duration >= 10);
133    }
134}