things3_core/
performance.rs

1//! Performance monitoring and metrics for Things 3 operations
2
3use anyhow::Result;
4use chrono::{DateTime, Utc};
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10use sysinfo::System;
11
12/// Performance metrics for a single operation
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct OperationMetrics {
15    pub operation_name: String,
16    pub duration: Duration,
17    pub timestamp: DateTime<Utc>,
18    pub success: bool,
19    pub error_message: Option<String>,
20}
21
22/// Aggregated performance statistics
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct PerformanceStats {
25    pub operation_name: String,
26    pub total_calls: u64,
27    pub successful_calls: u64,
28    pub failed_calls: u64,
29    pub total_duration: Duration,
30    pub average_duration: Duration,
31    pub min_duration: Duration,
32    pub max_duration: Duration,
33    pub success_rate: f64,
34    pub last_called: Option<DateTime<Utc>>,
35}
36
37impl PerformanceStats {
38    pub fn new(operation_name: String) -> Self {
39        Self {
40            operation_name,
41            total_calls: 0,
42            successful_calls: 0,
43            failed_calls: 0,
44            total_duration: Duration::ZERO,
45            average_duration: Duration::ZERO,
46            min_duration: Duration::MAX,
47            max_duration: Duration::ZERO,
48            success_rate: 0.0,
49            last_called: None,
50        }
51    }
52
53    pub fn add_metric(&mut self, metric: &OperationMetrics) {
54        self.total_calls += 1;
55        self.total_duration += metric.duration;
56        self.last_called = Some(metric.timestamp);
57
58        if metric.success {
59            self.successful_calls += 1;
60        } else {
61            self.failed_calls += 1;
62        }
63
64        if metric.duration < self.min_duration {
65            self.min_duration = metric.duration;
66        }
67        if metric.duration > self.max_duration {
68            self.max_duration = metric.duration;
69        }
70
71        self.average_duration =
72            Duration::from_nanos(self.total_duration.as_nanos() as u64 / self.total_calls);
73
74        self.success_rate = if self.total_calls > 0 {
75            self.successful_calls as f64 / self.total_calls as f64
76        } else {
77            0.0
78        };
79    }
80}
81
82/// System resource metrics
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct SystemMetrics {
85    pub timestamp: DateTime<Utc>,
86    pub memory_usage_mb: f64,
87    pub cpu_usage_percent: f64,
88    pub available_memory_mb: f64,
89    pub total_memory_mb: f64,
90}
91
92/// Performance monitor for tracking operations and system metrics
93pub struct PerformanceMonitor {
94    /// Individual operation metrics
95    metrics: Arc<RwLock<Vec<OperationMetrics>>>,
96    /// Aggregated statistics by operation name
97    stats: Arc<RwLock<HashMap<String, PerformanceStats>>>,
98    /// System information
99    system: Arc<RwLock<System>>,
100    /// Maximum number of metrics to keep in memory
101    max_metrics: usize,
102}
103
104impl PerformanceMonitor {
105    /// Create a new performance monitor
106    pub fn new(max_metrics: usize) -> Self {
107        Self {
108            metrics: Arc::new(RwLock::new(Vec::new())),
109            stats: Arc::new(RwLock::new(HashMap::new())),
110            system: Arc::new(RwLock::new(System::new_all())),
111            max_metrics,
112        }
113    }
114
115    /// Create a new performance monitor with default settings
116    pub fn new_default() -> Self {
117        Self::new(10000) // Keep last 10,000 metrics
118    }
119
120    /// Start timing an operation
121    pub fn start_operation(&self, operation_name: &str) -> OperationTimer {
122        OperationTimer {
123            monitor: self.clone(),
124            operation_name: operation_name.to_string(),
125            start_time: Instant::now(),
126        }
127    }
128
129    /// Record a completed operation
130    pub fn record_operation(&self, metric: OperationMetrics) {
131        // Add to metrics list
132        {
133            let mut metrics = self.metrics.write();
134            metrics.push(metric.clone());
135
136            // Trim if we exceed max_metrics
137            if metrics.len() > self.max_metrics {
138                let excess = metrics.len() - self.max_metrics;
139                metrics.drain(0..excess);
140            }
141        }
142
143        // Update aggregated stats
144        {
145            let mut stats = self.stats.write();
146            let operation_stats = stats
147                .entry(metric.operation_name.clone())
148                .or_insert_with(|| PerformanceStats::new(metric.operation_name.clone()));
149            operation_stats.add_metric(&metric);
150        }
151    }
152
153    /// Get all operation metrics
154    pub fn get_metrics(&self) -> Vec<OperationMetrics> {
155        self.metrics.read().clone()
156    }
157
158    /// Get aggregated statistics for all operations
159    pub fn get_all_stats(&self) -> HashMap<String, PerformanceStats> {
160        self.stats.read().clone()
161    }
162
163    /// Get statistics for a specific operation
164    pub fn get_operation_stats(&self, operation_name: &str) -> Option<PerformanceStats> {
165        self.stats.read().get(operation_name).cloned()
166    }
167
168    /// Get current system metrics
169    pub fn get_system_metrics(&self) -> Result<SystemMetrics> {
170        let mut system = self.system.write();
171        system.refresh_all();
172
173        Ok(SystemMetrics {
174            timestamp: Utc::now(),
175            memory_usage_mb: system.used_memory() as f64 / 1024.0 / 1024.0,
176            cpu_usage_percent: system
177                .cpus()
178                .iter()
179                .map(|cpu| cpu.cpu_usage() as f64)
180                .sum::<f64>()
181                / system.cpus().len() as f64,
182            available_memory_mb: system.available_memory() as f64 / 1024.0 / 1024.0,
183            total_memory_mb: system.total_memory() as f64 / 1024.0 / 1024.0,
184        })
185    }
186
187    /// Clear all metrics and statistics
188    pub fn clear(&self) {
189        self.metrics.write().clear();
190        self.stats.write().clear();
191    }
192
193    /// Get performance summary
194    pub fn get_summary(&self) -> PerformanceSummary {
195        let stats = self.get_all_stats();
196        let total_operations: u64 = stats.values().map(|s| s.total_calls).sum();
197        let total_successful: u64 = stats.values().map(|s| s.successful_calls).sum();
198        let total_duration: Duration = stats.values().map(|s| s.total_duration).sum();
199
200        PerformanceSummary {
201            total_operations,
202            total_successful,
203            total_failed: total_operations - total_successful,
204            overall_success_rate: if total_operations > 0 {
205                total_successful as f64 / total_operations as f64
206            } else {
207                0.0
208            },
209            total_duration,
210            average_operation_duration: if total_operations > 0 {
211                Duration::from_nanos(total_duration.as_nanos() as u64 / total_operations)
212            } else {
213                Duration::ZERO
214            },
215            operation_count: stats.len(),
216        }
217    }
218}
219
220impl Clone for PerformanceMonitor {
221    fn clone(&self) -> Self {
222        Self {
223            metrics: Arc::clone(&self.metrics),
224            stats: Arc::clone(&self.stats),
225            system: Arc::clone(&self.system),
226            max_metrics: self.max_metrics,
227        }
228    }
229}
230
231/// Timer for tracking operation duration
232pub struct OperationTimer {
233    monitor: PerformanceMonitor,
234    operation_name: String,
235    start_time: Instant,
236}
237
238impl OperationTimer {
239    /// Complete the operation successfully
240    pub fn success(self) {
241        let duration = self.start_time.elapsed();
242        let metric = OperationMetrics {
243            operation_name: self.operation_name,
244            duration,
245            timestamp: Utc::now(),
246            success: true,
247            error_message: None,
248        };
249        self.monitor.record_operation(metric);
250    }
251
252    /// Complete the operation with an error
253    pub fn error(self, error_message: String) {
254        let duration = self.start_time.elapsed();
255        let metric = OperationMetrics {
256            operation_name: self.operation_name,
257            duration,
258            timestamp: Utc::now(),
259            success: false,
260            error_message: Some(error_message),
261        };
262        self.monitor.record_operation(metric);
263    }
264}
265
266/// Performance summary
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct PerformanceSummary {
269    pub total_operations: u64,
270    pub total_successful: u64,
271    pub total_failed: u64,
272    pub overall_success_rate: f64,
273    pub total_duration: Duration,
274    pub average_operation_duration: Duration,
275    pub operation_count: usize,
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use std::thread;
282
283    #[test]
284    fn test_performance_monitor() {
285        let monitor = PerformanceMonitor::new_default();
286
287        // Record some operations
288        let metric1 = OperationMetrics {
289            operation_name: "test_op".to_string(),
290            duration: Duration::from_millis(100),
291            timestamp: Utc::now(),
292            success: true,
293            error_message: None,
294        };
295
296        monitor.record_operation(metric1);
297
298        let stats = monitor.get_operation_stats("test_op");
299        assert!(stats.is_some());
300        let stats = stats.unwrap();
301        assert_eq!(stats.total_calls, 1);
302        assert_eq!(stats.successful_calls, 1);
303        assert_eq!(stats.failed_calls, 0);
304    }
305
306    #[test]
307    fn test_operation_timer() {
308        let monitor = PerformanceMonitor::new_default();
309
310        // Test successful operation
311        let timer = monitor.start_operation("test_timer");
312        thread::sleep(Duration::from_millis(10));
313        timer.success();
314
315        let stats = monitor.get_operation_stats("test_timer");
316        assert!(stats.is_some());
317        let stats = stats.unwrap();
318        assert_eq!(stats.total_calls, 1);
319        assert!(stats.successful_calls > 0);
320    }
321
322    #[test]
323    fn test_performance_monitor_failed_operation() {
324        let monitor = PerformanceMonitor::new_default();
325
326        // Record a failed operation
327        let metric = OperationMetrics {
328            operation_name: "failed_op".to_string(),
329            duration: Duration::from_millis(50),
330            timestamp: Utc::now(),
331            success: false,
332            error_message: Some("Test error".to_string()),
333        };
334
335        monitor.record_operation(metric);
336
337        let stats = monitor.get_operation_stats("failed_op");
338        assert!(stats.is_some());
339        let stats = stats.unwrap();
340        assert_eq!(stats.total_calls, 1);
341        assert_eq!(stats.successful_calls, 0);
342        assert_eq!(stats.failed_calls, 1);
343    }
344
345    #[test]
346    fn test_performance_monitor_multiple_operations() {
347        let monitor = PerformanceMonitor::new_default();
348
349        // Record multiple operations
350        for i in 0..5 {
351            let metric = OperationMetrics {
352                operation_name: "multi_op".to_string(),
353                duration: Duration::from_millis(i * 10),
354                timestamp: Utc::now(),
355                success: i % 2 == 0,
356                error_message: if i % 2 == 0 {
357                    None
358                } else {
359                    Some("Error".to_string())
360                },
361            };
362            monitor.record_operation(metric);
363        }
364
365        let stats = monitor.get_operation_stats("multi_op");
366        assert!(stats.is_some());
367        let stats = stats.unwrap();
368        assert_eq!(stats.total_calls, 5);
369        assert_eq!(stats.successful_calls, 3);
370        assert_eq!(stats.failed_calls, 2);
371    }
372
373    #[test]
374    fn test_performance_monitor_get_all_stats() {
375        let monitor = PerformanceMonitor::new_default();
376
377        // Record operations for different types
378        let operations = vec![("op1", true), ("op1", false), ("op2", true), ("op2", true)];
379
380        for (name, success) in operations {
381            let metric = OperationMetrics {
382                operation_name: name.to_string(),
383                duration: Duration::from_millis(100),
384                timestamp: Utc::now(),
385                success,
386                error_message: if success {
387                    None
388                } else {
389                    Some("Error".to_string())
390                },
391            };
392            monitor.record_operation(metric);
393        }
394
395        let all_stats = monitor.get_all_stats();
396        assert_eq!(all_stats.len(), 2);
397        assert!(all_stats.contains_key("op1"));
398        assert!(all_stats.contains_key("op2"));
399
400        let op1_stats = &all_stats["op1"];
401        assert_eq!(op1_stats.total_calls, 2);
402        assert_eq!(op1_stats.successful_calls, 1);
403        assert_eq!(op1_stats.failed_calls, 1);
404
405        let op2_stats = &all_stats["op2"];
406        assert_eq!(op2_stats.total_calls, 2);
407        assert_eq!(op2_stats.successful_calls, 2);
408        assert_eq!(op2_stats.failed_calls, 0);
409    }
410
411    #[test]
412    fn test_performance_monitor_get_summary() {
413        let monitor = PerformanceMonitor::new_default();
414
415        // Record some operations
416        let operations = vec![("op1", true, 100), ("op1", false, 200), ("op2", true, 150)];
417
418        for (name, success, duration_ms) in operations {
419            let metric = OperationMetrics {
420                operation_name: name.to_string(),
421                duration: Duration::from_millis(duration_ms),
422                timestamp: Utc::now(),
423                success,
424                error_message: if success {
425                    None
426                } else {
427                    Some("Error".to_string())
428                },
429            };
430            monitor.record_operation(metric);
431        }
432
433        let summary = monitor.get_summary();
434        assert_eq!(summary.total_operations, 3);
435        assert_eq!(summary.total_successful, 2);
436        assert_eq!(summary.total_failed, 1);
437        assert!((summary.overall_success_rate - 2.0 / 3.0).abs() < 0.001);
438        assert_eq!(summary.operation_count, 2);
439    }
440
441    #[test]
442    fn test_performance_monitor_get_summary_empty() {
443        let monitor = PerformanceMonitor::new_default();
444        let summary = monitor.get_summary();
445
446        assert_eq!(summary.total_operations, 0);
447        assert_eq!(summary.total_successful, 0);
448        assert_eq!(summary.total_failed, 0);
449        assert_eq!(summary.overall_success_rate, 0.0);
450        assert_eq!(summary.operation_count, 0);
451    }
452
453    #[test]
454    fn test_operation_timer_failure() {
455        let monitor = PerformanceMonitor::new_default();
456
457        // Test failed operation by recording it directly
458        let metric = OperationMetrics {
459            operation_name: "test_failure".to_string(),
460            duration: Duration::from_millis(5),
461            timestamp: Utc::now(),
462            success: false,
463            error_message: Some("Test failure".to_string()),
464        };
465        monitor.record_operation(metric);
466
467        let stats = monitor.get_operation_stats("test_failure");
468        assert!(stats.is_some());
469        let stats = stats.unwrap();
470        assert_eq!(stats.total_calls, 1);
471        assert_eq!(stats.successful_calls, 0);
472        assert_eq!(stats.failed_calls, 1);
473    }
474
475    #[test]
476    fn test_operation_timer_drop() {
477        let monitor = PerformanceMonitor::new_default();
478
479        // Test that dropping the timer records the operation
480        {
481            let timer = monitor.start_operation("test_drop");
482            thread::sleep(Duration::from_millis(5));
483            // Explicitly call success before dropping
484            timer.success();
485        }
486
487        let stats = monitor.get_operation_stats("test_drop");
488        assert!(stats.is_some());
489        let stats = stats.unwrap();
490        assert_eq!(stats.total_calls, 1);
491        assert_eq!(stats.successful_calls, 1);
492        assert_eq!(stats.failed_calls, 0);
493    }
494
495    #[test]
496    fn test_performance_monitor_clone() {
497        let monitor1 = PerformanceMonitor::new_default();
498
499        // Record an operation
500        let metric = OperationMetrics {
501            operation_name: "clone_test".to_string(),
502            duration: Duration::from_millis(100),
503            timestamp: Utc::now(),
504            success: true,
505            error_message: None,
506        };
507        monitor1.record_operation(metric);
508
509        // Clone the monitor
510        let monitor2 = monitor1.clone();
511
512        // Both should have the same stats
513        let stats1 = monitor1.get_operation_stats("clone_test");
514        let stats2 = monitor2.get_operation_stats("clone_test");
515
516        assert!(stats1.is_some());
517        assert!(stats2.is_some());
518        assert_eq!(stats1.unwrap().total_calls, stats2.unwrap().total_calls);
519    }
520}