Skip to main content

rust_serv/management/
stats.rs

1//! Server statistics collection
2//!
3//! This module provides statistics collection for the management API.
4
5use serde::{Deserialize, Serialize};
6use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
7use std::sync::Arc;
8use std::time::Instant;
9
10/// Server statistics
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct ServerStats {
13    /// Number of active connections
14    pub active_connections: usize,
15    /// Total number of requests served
16    pub total_requests: u64,
17    /// Cache hit rate (0.0 to 1.0)
18    pub cache_hit_rate: f64,
19    /// Server uptime in seconds
20    pub uptime_secs: u64,
21    /// Total bytes sent
22    pub bytes_sent: u64,
23    /// Total bytes received
24    pub bytes_received: u64,
25}
26
27/// Statistics collector
28#[derive(Debug)]
29pub struct StatsCollector {
30    /// Start time for uptime calculation
31    start_time: Instant,
32    /// Active connections counter
33    active_connections: Arc<AtomicUsize>,
34    /// Total requests counter
35    total_requests: Arc<AtomicU64>,
36    /// Cache hits counter
37    cache_hits: Arc<AtomicU64>,
38    /// Cache misses counter
39    cache_misses: Arc<AtomicU64>,
40    /// Bytes sent counter
41    bytes_sent: Arc<AtomicU64>,
42    /// Bytes received counter
43    bytes_received: Arc<AtomicU64>,
44    /// Server ready flag
45    ready: Arc<AtomicBool>,
46}
47
48impl Default for StatsCollector {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl StatsCollector {
55    /// Create a new statistics collector
56    pub fn new() -> Self {
57        Self {
58            start_time: Instant::now(),
59            active_connections: Arc::new(AtomicUsize::new(0)),
60            total_requests: Arc::new(AtomicU64::new(0)),
61            cache_hits: Arc::new(AtomicU64::new(0)),
62            cache_misses: Arc::new(AtomicU64::new(0)),
63            bytes_sent: Arc::new(AtomicU64::new(0)),
64            bytes_received: Arc::new(AtomicU64::new(0)),
65            ready: Arc::new(AtomicBool::new(true)),
66        }
67    }
68
69    /// Get the current statistics snapshot
70    pub fn get_stats(&self) -> ServerStats {
71        let cache_hits = self.cache_hits.load(Ordering::Relaxed);
72        let cache_misses = self.cache_misses.load(Ordering::Relaxed);
73        let total_cache_requests = cache_hits + cache_misses;
74
75        let cache_hit_rate = if total_cache_requests > 0 {
76            cache_hits as f64 / total_cache_requests as f64
77        } else {
78            0.0
79        };
80
81        ServerStats {
82            active_connections: self.active_connections.load(Ordering::Relaxed),
83            total_requests: self.total_requests.load(Ordering::Relaxed),
84            cache_hit_rate,
85            uptime_secs: self.start_time.elapsed().as_secs(),
86            bytes_sent: self.bytes_sent.load(Ordering::Relaxed),
87            bytes_received: self.bytes_received.load(Ordering::Relaxed),
88        }
89    }
90
91    /// Increment active connections count
92    pub fn increment_connections(&self) {
93        self.active_connections.fetch_add(1, Ordering::Relaxed);
94    }
95
96    /// Decrement active connections count
97    pub fn decrement_connections(&self) {
98        self.active_connections.fetch_sub(1, Ordering::Relaxed);
99    }
100
101    /// Increment total requests count
102    pub fn increment_requests(&self) {
103        self.total_requests.fetch_add(1, Ordering::Relaxed);
104    }
105
106    /// Record a cache hit
107    pub fn record_cache_hit(&self) {
108        self.cache_hits.fetch_add(1, Ordering::Relaxed);
109    }
110
111    /// Record a cache miss
112    pub fn record_cache_miss(&self) {
113        self.cache_misses.fetch_add(1, Ordering::Relaxed);
114    }
115
116    /// Add to bytes sent counter
117    pub fn add_bytes_sent(&self, bytes: u64) {
118        self.bytes_sent.fetch_add(bytes, Ordering::Relaxed);
119    }
120
121    /// Add to bytes received counter
122    pub fn add_bytes_received(&self, bytes: u64) {
123        self.bytes_received.fetch_add(bytes, Ordering::Relaxed);
124    }
125
126    /// Check if the server is ready
127    pub fn is_ready(&self) -> bool {
128        self.ready.load(Ordering::Relaxed)
129    }
130
131    /// Set the server ready state
132    pub fn set_ready(&self, ready: bool) {
133        self.ready.store(ready, Ordering::Relaxed);
134    }
135
136    /// Get a clone of the active connections counter
137    pub fn active_connections_counter(&self) -> Arc<AtomicUsize> {
138        Arc::clone(&self.active_connections)
139    }
140
141    /// Get a clone of the total requests counter
142    pub fn total_requests_counter(&self) -> Arc<AtomicU64> {
143        Arc::clone(&self.total_requests)
144    }
145
146    /// Get a clone of the cache hits counter
147    pub fn cache_hits_counter(&self) -> Arc<AtomicU64> {
148        Arc::clone(&self.cache_hits)
149    }
150
151    /// Get a clone of the cache misses counter
152    pub fn cache_misses_counter(&self) -> Arc<AtomicU64> {
153        Arc::clone(&self.cache_misses)
154    }
155
156    /// Get a clone of the bytes sent counter
157    pub fn bytes_sent_counter(&self) -> Arc<AtomicU64> {
158        Arc::clone(&self.bytes_sent)
159    }
160
161    /// Get a clone of the bytes received counter
162    pub fn bytes_received_counter(&self) -> Arc<AtomicU64> {
163        Arc::clone(&self.bytes_received)
164    }
165}
166
167impl Clone for StatsCollector {
168    fn clone(&self) -> Self {
169        Self {
170            start_time: self.start_time,
171            active_connections: Arc::clone(&self.active_connections),
172            total_requests: Arc::clone(&self.total_requests),
173            cache_hits: Arc::clone(&self.cache_hits),
174            cache_misses: Arc::clone(&self.cache_misses),
175            bytes_sent: Arc::clone(&self.bytes_sent),
176            bytes_received: Arc::clone(&self.bytes_received),
177            ready: Arc::clone(&self.ready),
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185    use std::thread;
186    use std::time::Duration;
187
188    #[test]
189    fn test_stats_collector_creation() {
190        let collector = StatsCollector::new();
191        let stats = collector.get_stats();
192
193        assert_eq!(stats.active_connections, 0);
194        assert_eq!(stats.total_requests, 0);
195        assert_eq!(stats.cache_hit_rate, 0.0);
196        assert_eq!(stats.bytes_sent, 0);
197        assert_eq!(stats.bytes_received, 0);
198    }
199
200    #[test]
201    fn test_increment_connections() {
202        let collector = StatsCollector::new();
203        collector.increment_connections();
204        collector.increment_connections();
205
206        let stats = collector.get_stats();
207        assert_eq!(stats.active_connections, 2);
208    }
209
210    #[test]
211    fn test_decrement_connections() {
212        let collector = StatsCollector::new();
213        collector.increment_connections();
214        collector.increment_connections();
215        collector.decrement_connections();
216
217        let stats = collector.get_stats();
218        assert_eq!(stats.active_connections, 1);
219    }
220
221    #[test]
222    fn test_increment_requests() {
223        let collector = StatsCollector::new();
224        collector.increment_requests();
225        collector.increment_requests();
226        collector.increment_requests();
227
228        let stats = collector.get_stats();
229        assert_eq!(stats.total_requests, 3);
230    }
231
232    #[test]
233    fn test_cache_hit_rate_no_requests() {
234        let collector = StatsCollector::new();
235        let stats = collector.get_stats();
236        assert_eq!(stats.cache_hit_rate, 0.0);
237    }
238
239    #[test]
240    fn test_cache_hit_rate_all_hits() {
241        let collector = StatsCollector::new();
242        collector.record_cache_hit();
243        collector.record_cache_hit();
244        collector.record_cache_hit();
245
246        let stats = collector.get_stats();
247        assert!((stats.cache_hit_rate - 1.0).abs() < f64::EPSILON);
248    }
249
250    #[test]
251    fn test_cache_hit_rate_all_misses() {
252        let collector = StatsCollector::new();
253        collector.record_cache_miss();
254        collector.record_cache_miss();
255
256        let stats = collector.get_stats();
257        assert!((stats.cache_hit_rate - 0.0).abs() < f64::EPSILON);
258    }
259
260    #[test]
261    fn test_cache_hit_rate_mixed() {
262        let collector = StatsCollector::new();
263        collector.record_cache_hit();
264        collector.record_cache_hit();
265        collector.record_cache_miss();
266
267        let stats = collector.get_stats();
268        assert!((stats.cache_hit_rate - 0.6666666666666666).abs() < 0.0001);
269    }
270
271    #[test]
272    fn test_bytes_sent() {
273        let collector = StatsCollector::new();
274        collector.add_bytes_sent(100);
275        collector.add_bytes_sent(200);
276
277        let stats = collector.get_stats();
278        assert_eq!(stats.bytes_sent, 300);
279    }
280
281    #[test]
282    fn test_bytes_received() {
283        let collector = StatsCollector::new();
284        collector.add_bytes_received(50);
285        collector.add_bytes_received(150);
286
287        let stats = collector.get_stats();
288        assert_eq!(stats.bytes_received, 200);
289    }
290
291    #[test]
292    fn test_uptime() {
293        let collector = StatsCollector::new();
294        thread::sleep(Duration::from_millis(100));
295        let stats = collector.get_stats();
296        // Uptime should be at least 0 seconds (might be 0 due to rounding)
297        assert!(stats.uptime_secs < 2);
298    }
299
300    #[test]
301    fn test_ready_state() {
302        let collector = StatsCollector::new();
303        assert!(collector.is_ready());
304
305        collector.set_ready(false);
306        assert!(!collector.is_ready());
307
308        collector.set_ready(true);
309        assert!(collector.is_ready());
310    }
311
312    #[test]
313    fn test_stats_collector_clone() {
314        let collector = StatsCollector::new();
315        collector.increment_requests();
316
317        let cloned = collector.clone();
318        cloned.increment_requests();
319
320        let stats = collector.get_stats();
321        assert_eq!(stats.total_requests, 2); // Both share the same counter
322    }
323
324    #[test]
325    fn test_server_stats_serialization() {
326        let stats = ServerStats {
327            active_connections: 5,
328            total_requests: 100,
329            cache_hit_rate: 0.85,
330            uptime_secs: 3600,
331            bytes_sent: 1024000,
332            bytes_received: 512000,
333        };
334
335        let json = serde_json::to_string(&stats).unwrap();
336        assert!(json.contains("\"active_connections\":5"));
337        assert!(json.contains("\"total_requests\":100"));
338        assert!(json.contains("\"cache_hit_rate\":0.85"));
339    }
340
341    #[test]
342    fn test_server_stats_deserialization() {
343        let json = r#"{
344            "active_connections": 10,
345            "total_requests": 500,
346            "cache_hit_rate": 0.75,
347            "uptime_secs": 7200,
348            "bytes_sent": 2048000,
349            "bytes_received": 1024000
350        }"#;
351
352        let stats: ServerStats = serde_json::from_str(json).unwrap();
353        assert_eq!(stats.active_connections, 10);
354        assert_eq!(stats.total_requests, 500);
355        assert!((stats.cache_hit_rate - 0.75).abs() < f64::EPSILON);
356        assert_eq!(stats.uptime_secs, 7200);
357        assert_eq!(stats.bytes_sent, 2048000);
358        assert_eq!(stats.bytes_received, 1024000);
359    }
360
361    #[test]
362    fn test_server_stats_equality() {
363        let stats1 = ServerStats {
364            active_connections: 5,
365            total_requests: 100,
366            cache_hit_rate: 0.85,
367            uptime_secs: 3600,
368            bytes_sent: 1024000,
369            bytes_received: 512000,
370        };
371        let stats2 = ServerStats {
372            active_connections: 5,
373            total_requests: 100,
374            cache_hit_rate: 0.85,
375            uptime_secs: 3600,
376            bytes_sent: 1024000,
377            bytes_received: 512000,
378        };
379        assert_eq!(stats1, stats2);
380    }
381
382    #[test]
383    fn test_server_stats_clone() {
384        let stats = ServerStats {
385            active_connections: 5,
386            total_requests: 100,
387            cache_hit_rate: 0.85,
388            uptime_secs: 3600,
389            bytes_sent: 1024000,
390            bytes_received: 512000,
391        };
392        let cloned = stats.clone();
393        assert_eq!(stats, cloned);
394    }
395
396    #[test]
397    fn test_server_stats_debug() {
398        let stats = ServerStats {
399            active_connections: 5,
400            total_requests: 100,
401            cache_hit_rate: 0.85,
402            uptime_secs: 3600,
403            bytes_sent: 1024000,
404            bytes_received: 512000,
405        };
406        let debug_str = format!("{:?}", stats);
407        assert!(debug_str.contains("ServerStats"));
408        assert!(debug_str.contains("active_connections"));
409    }
410
411    #[test]
412    fn test_stats_collector_default() {
413        let collector = StatsCollector::default();
414        let stats = collector.get_stats();
415        assert_eq!(stats.active_connections, 0);
416        assert_eq!(stats.total_requests, 0);
417    }
418
419    #[test]
420    fn test_concurrent_increment_requests() {
421        let collector = Arc::new(StatsCollector::new());
422        let mut handles = vec![];
423
424        for _ in 0..10 {
425            let collector_clone = Arc::clone(&collector);
426            handles.push(thread::spawn(move || {
427                for _ in 0..100 {
428                    collector_clone.increment_requests();
429                }
430            }));
431        }
432
433        for handle in handles {
434            handle.join().unwrap();
435        }
436
437        let stats = collector.get_stats();
438        assert_eq!(stats.total_requests, 1000);
439    }
440
441    #[test]
442    fn test_concurrent_connections() {
443        let collector = Arc::new(StatsCollector::new());
444        let mut handles = vec![];
445
446        for _ in 0..5 {
447            let collector_clone = Arc::clone(&collector);
448            handles.push(thread::spawn(move || {
449                collector_clone.increment_connections();
450                thread::sleep(Duration::from_millis(10));
451                collector_clone.decrement_connections();
452            }));
453        }
454
455        for handle in handles {
456            handle.join().unwrap();
457        }
458
459        let stats = collector.get_stats();
460        assert_eq!(stats.active_connections, 0);
461    }
462
463    #[test]
464    fn test_get_counter_clones() {
465        let collector = StatsCollector::new();
466
467        let active_conn = collector.active_connections_counter();
468        active_conn.fetch_add(5, Ordering::Relaxed);
469
470        let stats = collector.get_stats();
471        assert_eq!(stats.active_connections, 5);
472    }
473
474    #[test]
475    fn test_cache_counters_clone() {
476        let collector = StatsCollector::new();
477
478        let hits = collector.cache_hits_counter();
479        let misses = collector.cache_misses_counter();
480
481        hits.fetch_add(10, Ordering::Relaxed);
482        misses.fetch_add(5, Ordering::Relaxed);
483
484        let stats = collector.get_stats();
485        assert!((stats.cache_hit_rate - 0.6666666666666666).abs() < 0.0001);
486    }
487
488    #[test]
489    fn test_bytes_counters_clone() {
490        let collector = StatsCollector::new();
491
492        let sent = collector.bytes_sent_counter();
493        let received = collector.bytes_received_counter();
494
495        sent.fetch_add(1000, Ordering::Relaxed);
496        received.fetch_add(500, Ordering::Relaxed);
497
498        let stats = collector.get_stats();
499        assert_eq!(stats.bytes_sent, 1000);
500        assert_eq!(stats.bytes_received, 500);
501    }
502
503    #[test]
504    fn test_large_numbers() {
505        let collector = StatsCollector::new();
506
507        collector.add_bytes_sent(u64::MAX / 2);
508        collector.add_bytes_sent(u64::MAX / 2 + 1);
509
510        let stats = collector.get_stats();
511        assert_eq!(stats.bytes_sent, u64::MAX);
512    }
513
514    #[test]
515    fn test_cache_hit_rate_precision() {
516        let collector = StatsCollector::new();
517
518        // 7 hits, 3 misses = 70% hit rate
519        for _ in 0..7 {
520            collector.record_cache_hit();
521        }
522        for _ in 0..3 {
523            collector.record_cache_miss();
524        }
525
526        let stats = collector.get_stats();
527        assert!((stats.cache_hit_rate - 0.7).abs() < 0.0001);
528    }
529}