Skip to main content

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    #[must_use]
39    pub const fn new(operation_name: String) -> Self {
40        Self {
41            operation_name,
42            total_calls: 0,
43            successful_calls: 0,
44            failed_calls: 0,
45            total_duration: Duration::ZERO,
46            average_duration: Duration::ZERO,
47            min_duration: Duration::MAX,
48            max_duration: Duration::ZERO,
49            success_rate: 0.0,
50            last_called: None,
51        }
52    }
53
54    pub fn add_metric(&mut self, metric: &OperationMetrics) {
55        self.total_calls += 1;
56        self.total_duration += metric.duration;
57        self.last_called = Some(metric.timestamp);
58
59        if metric.success {
60            self.successful_calls += 1;
61        } else {
62            self.failed_calls += 1;
63        }
64
65        if metric.duration < self.min_duration {
66            self.min_duration = metric.duration;
67        }
68        if metric.duration > self.max_duration {
69            self.max_duration = metric.duration;
70        }
71
72        self.average_duration = Duration::from_nanos(
73            u64::try_from(self.total_duration.as_nanos()).unwrap_or(u64::MAX) / self.total_calls,
74        );
75
76        self.success_rate = if self.total_calls > 0 {
77            #[allow(clippy::cast_precision_loss)]
78            {
79                self.successful_calls as f64 / self.total_calls as f64
80            }
81        } else {
82            0.0
83        };
84    }
85}
86
87/// System resource metrics
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct SystemMetrics {
90    pub timestamp: DateTime<Utc>,
91    pub memory_usage_mb: f64,
92    pub cpu_usage_percent: f64,
93    pub available_memory_mb: f64,
94    pub total_memory_mb: f64,
95}
96
97/// Cache-specific performance metrics
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CacheMetrics {
100    pub cache_type: String, // "l1", "l2", "l3", "query"
101    pub hits: u64,
102    pub misses: u64,
103    pub hit_rate: f64,
104    pub total_entries: u64,
105    pub memory_usage_bytes: u64,
106    pub evictions: u64,
107    pub insertions: u64,
108    pub invalidations: u64,
109    pub warming_entries: u64,
110    pub average_access_time_ms: f64,
111    pub last_accessed: Option<DateTime<Utc>>,
112}
113
114/// Database query performance metrics
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct QueryMetrics {
117    pub query_type: String, // "tasks", "projects", "areas", "search"
118    pub total_queries: u64,
119    pub cached_queries: u64,
120    pub database_queries: u64,
121    pub cache_hit_rate: f64,
122    pub average_query_time_ms: f64,
123    pub average_cache_time_ms: f64,
124    pub average_database_time_ms: f64,
125    pub slowest_query_ms: u64,
126    pub fastest_query_ms: u64,
127    pub query_size_bytes: u64,
128    pub compression_ratio: f64,
129}
130
131/// Comprehensive performance summary including all metrics
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ComprehensivePerformanceSummary {
134    pub timestamp: DateTime<Utc>,
135    pub operation_stats: HashMap<String, PerformanceStats>,
136    pub system_metrics: SystemMetrics,
137    pub cache_metrics: HashMap<String, CacheMetrics>,
138    pub query_metrics: HashMap<String, QueryMetrics>,
139    pub overall_health_score: f64,
140}
141
142/// Performance monitor for tracking operations and system metrics
143pub struct PerformanceMonitor {
144    /// Individual operation metrics
145    metrics: Arc<RwLock<Vec<OperationMetrics>>>,
146    /// Aggregated statistics by operation name
147    stats: Arc<RwLock<HashMap<String, PerformanceStats>>>,
148    /// Cache-specific metrics by cache type
149    cache_metrics: Arc<RwLock<HashMap<String, CacheMetrics>>>,
150    /// Query performance metrics by query type
151    query_metrics: Arc<RwLock<HashMap<String, QueryMetrics>>>,
152    /// System information
153    system: Arc<RwLock<System>>,
154    /// Maximum number of metrics to keep in memory
155    max_metrics: usize,
156}
157
158impl PerformanceMonitor {
159    /// Create a new performance monitor
160    #[must_use]
161    pub fn new(max_metrics: usize) -> Self {
162        Self {
163            metrics: Arc::new(RwLock::new(Vec::new())),
164            stats: Arc::new(RwLock::new(HashMap::new())),
165            cache_metrics: Arc::new(RwLock::new(HashMap::new())),
166            query_metrics: Arc::new(RwLock::new(HashMap::new())),
167            system: Arc::new(RwLock::new(System::new_all())),
168            max_metrics,
169        }
170    }
171
172    /// Create a new performance monitor with default settings
173    #[must_use]
174    pub fn new_default() -> Self {
175        Self::new(10000) // Keep last 10,000 metrics
176    }
177
178    /// Start timing an operation
179    #[must_use]
180    pub fn start_operation(&self, operation_name: &str) -> OperationTimer {
181        OperationTimer {
182            monitor: self.clone(),
183            operation_name: operation_name.to_string(),
184            start_time: Instant::now(),
185        }
186    }
187
188    /// Record a completed operation
189    pub fn record_operation(&self, metric: &OperationMetrics) {
190        // Add to metrics list
191        {
192            let mut metrics = self.metrics.write();
193            metrics.push(metric.clone());
194
195            // Trim if we exceed max_metrics
196            if metrics.len() > self.max_metrics {
197                let excess = metrics.len() - self.max_metrics;
198                metrics.drain(0..excess);
199            }
200        }
201
202        // Update aggregated stats
203        let operation_name = metric.operation_name.clone();
204        let mut stats = self.stats.write();
205        let operation_stats = stats
206            .entry(operation_name)
207            .or_insert_with(|| PerformanceStats::new(metric.operation_name.clone()));
208        operation_stats.add_metric(metric);
209        drop(stats);
210    }
211
212    /// Get all operation metrics
213    #[must_use]
214    pub fn get_metrics(&self) -> Vec<OperationMetrics> {
215        self.metrics.read().clone()
216    }
217
218    /// Get aggregated statistics for all operations
219    #[must_use]
220    pub fn get_all_stats(&self) -> HashMap<String, PerformanceStats> {
221        self.stats.read().clone()
222    }
223
224    /// Get statistics for a specific operation
225    #[must_use]
226    pub fn get_operation_stats(&self, operation_name: &str) -> Option<PerformanceStats> {
227        self.stats.read().get(operation_name).cloned()
228    }
229
230    /// Get current system metrics
231    /// Get system metrics
232    ///
233    /// # Errors
234    ///
235    /// Returns an error if system information cannot be retrieved.
236    pub fn get_system_metrics(&self) -> Result<SystemMetrics> {
237        let mut system = self.system.write();
238        system.refresh_all();
239
240        Ok(SystemMetrics {
241            timestamp: Utc::now(),
242            #[allow(clippy::cast_precision_loss)]
243            memory_usage_mb: system.used_memory() as f64 / 1024.0 / 1024.0,
244            cpu_usage_percent: {
245                let cpu_count = system.cpus().len();
246                #[allow(clippy::cast_precision_loss)]
247                let cpu_usage: f64 = system
248                    .cpus()
249                    .iter()
250                    .map(|cpu| f64::from(cpu.cpu_usage()))
251                    .sum::<f64>()
252                    / cpu_count as f64;
253                cpu_usage
254            },
255            #[allow(clippy::cast_precision_loss)]
256            available_memory_mb: {
257                // macOS does not always populate `available_memory()` via sysinfo;
258                // fall back to total − used when it returns 0.
259                let avail = system.available_memory();
260                let bytes = if avail > 0 {
261                    avail
262                } else {
263                    system.total_memory().saturating_sub(system.used_memory())
264                };
265                bytes as f64 / 1024.0 / 1024.0
266            },
267            #[allow(clippy::cast_precision_loss)]
268            total_memory_mb: system.total_memory() as f64 / 1024.0 / 1024.0,
269        })
270    }
271
272    /// Clear all metrics and statistics
273    pub fn clear(&self) {
274        self.metrics.write().clear();
275        self.stats.write().clear();
276    }
277
278    /// Get performance summary
279    #[must_use]
280    pub fn get_summary(&self) -> PerformanceSummary {
281        let stats = self.get_all_stats();
282        let total_operations: u64 = stats.values().map(|s| s.total_calls).sum();
283        let total_successful: u64 = stats.values().map(|s| s.successful_calls).sum();
284        let total_duration: Duration = stats.values().map(|s| s.total_duration).sum();
285
286        PerformanceSummary {
287            total_operations,
288            total_successful,
289            total_failed: total_operations - total_successful,
290            overall_success_rate: if total_operations > 0 {
291                #[allow(clippy::cast_precision_loss)]
292                {
293                    total_successful as f64 / total_operations as f64
294                }
295            } else {
296                0.0
297            },
298            total_duration,
299            average_operation_duration: u64::try_from(total_duration.as_nanos())
300                .unwrap_or(0)
301                .checked_div(total_operations)
302                .map_or(Duration::ZERO, Duration::from_nanos),
303            operation_count: stats.len(),
304        }
305    }
306
307    /// Record cache metrics
308    pub fn record_cache_metrics(&self, cache_type: &str, metrics: CacheMetrics) {
309        let mut cache_metrics = self.cache_metrics.write();
310        cache_metrics.insert(cache_type.to_string(), metrics);
311    }
312
313    /// Get cache metrics for a specific cache type
314    #[must_use]
315    pub fn get_cache_metrics(&self, cache_type: &str) -> Option<CacheMetrics> {
316        let cache_metrics = self.cache_metrics.read();
317        cache_metrics.get(cache_type).cloned()
318    }
319
320    /// Get all cache metrics
321    #[must_use]
322    pub fn get_all_cache_metrics(&self) -> HashMap<String, CacheMetrics> {
323        let cache_metrics = self.cache_metrics.read();
324        cache_metrics.clone()
325    }
326
327    /// Record query metrics
328    pub fn record_query_metrics(&self, query_type: &str, metrics: QueryMetrics) {
329        let mut query_metrics = self.query_metrics.write();
330        query_metrics.insert(query_type.to_string(), metrics);
331    }
332
333    /// Get query metrics for a specific query type
334    #[must_use]
335    pub fn get_query_metrics(&self, query_type: &str) -> Option<QueryMetrics> {
336        let query_metrics = self.query_metrics.read();
337        query_metrics.get(query_type).cloned()
338    }
339
340    /// Get all query metrics
341    #[must_use]
342    pub fn get_all_query_metrics(&self) -> HashMap<String, QueryMetrics> {
343        let query_metrics = self.query_metrics.read();
344        query_metrics.clone()
345    }
346
347    /// Get comprehensive performance summary including cache and query metrics
348    #[must_use]
349    pub fn get_comprehensive_summary(&self) -> ComprehensivePerformanceSummary {
350        let operation_stats = self.get_all_stats();
351        let system_metrics = self.get_system_metrics().unwrap_or_else(|_| SystemMetrics {
352            timestamp: Utc::now(),
353            memory_usage_mb: 0.0,
354            cpu_usage_percent: 0.0,
355            available_memory_mb: 0.0,
356            total_memory_mb: 0.0,
357        });
358        let cache_metrics = self.get_all_cache_metrics();
359        let query_metrics = self.get_all_query_metrics();
360
361        // Calculate overall health score including cache performance
362        let health_score = Self::calculate_comprehensive_health_score(
363            &operation_stats,
364            &cache_metrics,
365            &query_metrics,
366        );
367
368        ComprehensivePerformanceSummary {
369            timestamp: Utc::now(),
370            operation_stats,
371            system_metrics,
372            cache_metrics,
373            query_metrics,
374            overall_health_score: health_score,
375        }
376    }
377
378    /// Calculate comprehensive health score including cache and query performance
379    #[allow(clippy::cast_precision_loss)]
380    fn calculate_comprehensive_health_score(
381        operation_stats: &HashMap<String, PerformanceStats>,
382        cache_metrics: &HashMap<String, CacheMetrics>,
383        query_metrics: &HashMap<String, QueryMetrics>,
384    ) -> f64 {
385        let mut total_score = 0.0;
386        let mut weight_sum = 0.0;
387
388        // Operation performance (40% weight)
389        if !operation_stats.is_empty() {
390            let avg_success_rate = operation_stats
391                .values()
392                .map(|s| s.success_rate)
393                .sum::<f64>()
394                / operation_stats.len() as f64;
395            let avg_response_time = operation_stats
396                .values()
397                .map(|s| s.average_duration.as_millis() as f64)
398                .sum::<f64>()
399                / operation_stats.len() as f64;
400
401            let operation_score =
402                (avg_success_rate * 70.0) + ((1000.0 - avg_response_time.min(1000.0)) * 0.3);
403            total_score += operation_score * 0.4;
404            weight_sum += 0.4;
405        }
406
407        // Cache performance (35% weight)
408        if !cache_metrics.is_empty() {
409            let avg_hit_rate = cache_metrics.values().map(|c| c.hit_rate).sum::<f64>()
410                / cache_metrics.len() as f64;
411            let avg_access_time = cache_metrics
412                .values()
413                .map(|c| c.average_access_time_ms)
414                .sum::<f64>()
415                / cache_metrics.len() as f64;
416
417            let cache_score = (avg_hit_rate * 60.0) + ((100.0 - avg_access_time.min(100.0)) * 0.4);
418            total_score += cache_score * 0.35;
419            weight_sum += 0.35;
420        }
421
422        // Query performance (25% weight)
423        if !query_metrics.is_empty() {
424            let avg_query_hit_rate = query_metrics
425                .values()
426                .map(|q| q.cache_hit_rate)
427                .sum::<f64>()
428                / query_metrics.len() as f64;
429            let avg_query_time = query_metrics
430                .values()
431                .map(|q| q.average_query_time_ms)
432                .sum::<f64>()
433                / query_metrics.len() as f64;
434
435            let query_score =
436                (avg_query_hit_rate * 50.0) + ((1000.0 - avg_query_time.min(1000.0)) * 0.5);
437            total_score += query_score * 0.25;
438            weight_sum += 0.25;
439        }
440
441        if weight_sum > 0.0 {
442            (total_score / weight_sum).clamp(0.0, 100.0)
443        } else {
444            100.0
445        }
446    }
447}
448
449impl Clone for PerformanceMonitor {
450    fn clone(&self) -> Self {
451        Self {
452            metrics: Arc::clone(&self.metrics),
453            stats: Arc::clone(&self.stats),
454            cache_metrics: Arc::clone(&self.cache_metrics),
455            query_metrics: Arc::clone(&self.query_metrics),
456            system: Arc::clone(&self.system),
457            max_metrics: self.max_metrics,
458        }
459    }
460}
461
462/// Timer for tracking operation duration
463pub struct OperationTimer {
464    monitor: PerformanceMonitor,
465    operation_name: String,
466    start_time: Instant,
467}
468
469impl OperationTimer {
470    /// Complete the operation successfully
471    pub fn success(self) {
472        let duration = self.start_time.elapsed();
473        let metric = OperationMetrics {
474            operation_name: self.operation_name,
475            duration,
476            timestamp: Utc::now(),
477            success: true,
478            error_message: None,
479        };
480        self.monitor.record_operation(&metric);
481    }
482
483    /// Complete the operation with an error
484    pub fn error(self, error_message: String) {
485        let duration = self.start_time.elapsed();
486        let metric = OperationMetrics {
487            operation_name: self.operation_name,
488            duration,
489            timestamp: Utc::now(),
490            success: false,
491            error_message: Some(error_message),
492        };
493        self.monitor.record_operation(&metric);
494    }
495}
496
497/// Performance summary
498#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct PerformanceSummary {
500    pub total_operations: u64,
501    pub total_successful: u64,
502    pub total_failed: u64,
503    pub overall_success_rate: f64,
504    pub total_duration: Duration,
505    pub average_operation_duration: Duration,
506    pub operation_count: usize,
507}
508
509#[cfg(test)]
510mod tests {
511    use super::*;
512    use std::thread;
513
514    #[test]
515    fn test_performance_monitor() {
516        let monitor = PerformanceMonitor::new_default();
517
518        // Record some operations
519        let metric1 = OperationMetrics {
520            operation_name: "test_op".to_string(),
521            duration: Duration::from_millis(100),
522            timestamp: Utc::now(),
523            success: true,
524            error_message: None,
525        };
526
527        monitor.record_operation(&metric1);
528
529        let stats = monitor.get_operation_stats("test_op");
530        assert!(stats.is_some());
531        let stats = stats.unwrap();
532        assert_eq!(stats.total_calls, 1);
533        assert_eq!(stats.successful_calls, 1);
534        assert_eq!(stats.failed_calls, 0);
535    }
536
537    #[test]
538    fn test_operation_timer() {
539        let monitor = PerformanceMonitor::new_default();
540
541        // Test successful operation
542        let timer = monitor.start_operation("test_timer");
543        thread::sleep(Duration::from_millis(10));
544        timer.success();
545
546        let stats = monitor.get_operation_stats("test_timer");
547        assert!(stats.is_some());
548        let stats = stats.unwrap();
549        assert_eq!(stats.total_calls, 1);
550        assert!(stats.successful_calls > 0);
551    }
552
553    #[test]
554    fn test_performance_monitor_failed_operation() {
555        let monitor = PerformanceMonitor::new_default();
556
557        // Record a failed operation
558        let metric = OperationMetrics {
559            operation_name: "failed_op".to_string(),
560            duration: Duration::from_millis(50),
561            timestamp: Utc::now(),
562            success: false,
563            error_message: Some("Test error".to_string()),
564        };
565
566        monitor.record_operation(&metric);
567
568        let stats = monitor.get_operation_stats("failed_op");
569        assert!(stats.is_some());
570        let stats = stats.unwrap();
571        assert_eq!(stats.total_calls, 1);
572        assert_eq!(stats.successful_calls, 0);
573        assert_eq!(stats.failed_calls, 1);
574    }
575
576    #[test]
577    fn test_performance_monitor_multiple_operations() {
578        let monitor = PerformanceMonitor::new_default();
579
580        // Record multiple operations
581        for i in 0..5 {
582            let metric = OperationMetrics {
583                operation_name: "multi_op".to_string(),
584                duration: Duration::from_millis(i * 10),
585                timestamp: Utc::now(),
586                success: i % 2 == 0,
587                error_message: if i % 2 == 0 {
588                    None
589                } else {
590                    Some("Error".to_string())
591                },
592            };
593            monitor.record_operation(&metric);
594        }
595
596        let stats = monitor.get_operation_stats("multi_op");
597        assert!(stats.is_some());
598        let stats = stats.unwrap();
599        assert_eq!(stats.total_calls, 5);
600        assert_eq!(stats.successful_calls, 3);
601        assert_eq!(stats.failed_calls, 2);
602    }
603
604    #[test]
605    fn test_performance_monitor_get_all_stats() {
606        let monitor = PerformanceMonitor::new_default();
607
608        // Record operations for different types
609        let operations = vec![("op1", true), ("op1", false), ("op2", true), ("op2", true)];
610
611        for (name, success) in operations {
612            let metric = OperationMetrics {
613                operation_name: name.to_string(),
614                duration: Duration::from_millis(100),
615                timestamp: Utc::now(),
616                success,
617                error_message: if success {
618                    None
619                } else {
620                    Some("Error".to_string())
621                },
622            };
623            monitor.record_operation(&metric);
624        }
625
626        let all_stats = monitor.get_all_stats();
627        assert_eq!(all_stats.len(), 2);
628        assert!(all_stats.contains_key("op1"));
629        assert!(all_stats.contains_key("op2"));
630
631        let op1_stats = &all_stats["op1"];
632        assert_eq!(op1_stats.total_calls, 2);
633        assert_eq!(op1_stats.successful_calls, 1);
634        assert_eq!(op1_stats.failed_calls, 1);
635
636        let op2_stats = &all_stats["op2"];
637        assert_eq!(op2_stats.total_calls, 2);
638        assert_eq!(op2_stats.successful_calls, 2);
639        assert_eq!(op2_stats.failed_calls, 0);
640    }
641
642    #[test]
643    fn test_performance_monitor_get_summary() {
644        let monitor = PerformanceMonitor::new_default();
645
646        // Record some operations
647        let operations = vec![("op1", true, 100), ("op1", false, 200), ("op2", true, 150)];
648
649        for (name, success, duration_ms) in operations {
650            let metric = OperationMetrics {
651                operation_name: name.to_string(),
652                duration: Duration::from_millis(duration_ms),
653                timestamp: Utc::now(),
654                success,
655                error_message: if success {
656                    None
657                } else {
658                    Some("Error".to_string())
659                },
660            };
661            monitor.record_operation(&metric);
662        }
663
664        let summary = monitor.get_summary();
665        assert_eq!(summary.total_operations, 3);
666        assert_eq!(summary.total_successful, 2);
667        assert_eq!(summary.total_failed, 1);
668        assert!((summary.overall_success_rate - 2.0 / 3.0).abs() < 0.001);
669        assert_eq!(summary.operation_count, 2);
670    }
671
672    #[test]
673    fn test_performance_monitor_get_summary_empty() {
674        let monitor = PerformanceMonitor::new_default();
675        let summary = monitor.get_summary();
676
677        assert_eq!(summary.total_operations, 0);
678        assert_eq!(summary.total_successful, 0);
679        assert_eq!(summary.total_failed, 0);
680        assert!((summary.overall_success_rate - 0.0).abs() < f64::EPSILON);
681        assert_eq!(summary.operation_count, 0);
682    }
683
684    #[test]
685    fn test_operation_timer_failure() {
686        let monitor = PerformanceMonitor::new_default();
687
688        // Test failed operation by recording it directly
689        let metric = OperationMetrics {
690            operation_name: "test_failure".to_string(),
691            duration: Duration::from_millis(5),
692            timestamp: Utc::now(),
693            success: false,
694            error_message: Some("Test failure".to_string()),
695        };
696        monitor.record_operation(&metric);
697
698        let stats = monitor.get_operation_stats("test_failure");
699        assert!(stats.is_some());
700        let stats = stats.unwrap();
701        assert_eq!(stats.total_calls, 1);
702        assert_eq!(stats.successful_calls, 0);
703        assert_eq!(stats.failed_calls, 1);
704    }
705
706    #[test]
707    fn test_operation_timer_drop() {
708        let monitor = PerformanceMonitor::new_default();
709
710        // Test that dropping the timer records the operation
711        {
712            let timer = monitor.start_operation("test_drop");
713            thread::sleep(Duration::from_millis(5));
714            // Explicitly call success before dropping
715            timer.success();
716        }
717
718        let stats = monitor.get_operation_stats("test_drop");
719        assert!(stats.is_some());
720        let stats = stats.unwrap();
721        assert_eq!(stats.total_calls, 1);
722        assert_eq!(stats.successful_calls, 1);
723        assert_eq!(stats.failed_calls, 0);
724    }
725
726    #[test]
727    fn test_performance_monitor_clone() {
728        let monitor1 = PerformanceMonitor::new_default();
729
730        // Record an operation
731        let metric = OperationMetrics {
732            operation_name: "clone_test".to_string(),
733            duration: Duration::from_millis(100),
734            timestamp: Utc::now(),
735            success: true,
736            error_message: None,
737        };
738        monitor1.record_operation(&metric);
739
740        // Clone the monitor
741        let monitor2 = monitor1.clone();
742
743        // Both should have the same stats
744        let stats1 = monitor1.get_operation_stats("clone_test");
745        let stats2 = monitor2.get_operation_stats("clone_test");
746
747        assert!(stats1.is_some());
748        assert!(stats2.is_some());
749        assert_eq!(stats1.unwrap().total_calls, stats2.unwrap().total_calls);
750    }
751
752    #[test]
753    fn test_performance_monitor_additional_operations() {
754        let monitor = PerformanceMonitor::new_default();
755
756        // Record multiple operations
757        let operations = vec![
758            ("op1", Duration::from_millis(100), true),
759            ("op1", Duration::from_millis(150), true),
760            ("op1", Duration::from_millis(200), false),
761            ("op2", Duration::from_millis(50), true),
762            ("op2", Duration::from_millis(75), true),
763        ];
764
765        for (op_name, duration, success) in operations {
766            let metric = OperationMetrics {
767                operation_name: op_name.to_string(),
768                duration,
769                timestamp: Utc::now(),
770                success,
771                error_message: if success {
772                    None
773                } else {
774                    Some("Test error".to_string())
775                },
776            };
777            monitor.record_operation(&metric);
778        }
779
780        // Check op1 stats
781        let op1_stats = monitor.get_operation_stats("op1").unwrap();
782        assert_eq!(op1_stats.total_calls, 3);
783        assert_eq!(op1_stats.successful_calls, 2);
784        assert_eq!(op1_stats.failed_calls, 1);
785
786        // Check op2 stats
787        let op2_stats = monitor.get_operation_stats("op2").unwrap();
788        assert_eq!(op2_stats.total_calls, 2);
789        assert_eq!(op2_stats.successful_calls, 2);
790        assert_eq!(op2_stats.failed_calls, 0);
791    }
792
793    #[test]
794    fn test_performance_monitor_get_metrics() {
795        let monitor = PerformanceMonitor::new_default();
796
797        // Record some operations
798        let metric1 = OperationMetrics {
799            operation_name: "test_op".to_string(),
800            duration: Duration::from_millis(100),
801            timestamp: Utc::now(),
802            success: true,
803            error_message: None,
804        };
805        monitor.record_operation(&metric1);
806
807        let metric2 = OperationMetrics {
808            operation_name: "test_op2".to_string(),
809            duration: Duration::from_millis(200),
810            timestamp: Utc::now(),
811            success: false,
812            error_message: Some("Test error".to_string()),
813        };
814        monitor.record_operation(&metric2);
815
816        let all_metrics = monitor.get_metrics();
817        assert_eq!(all_metrics.len(), 2);
818        assert!(all_metrics.iter().any(|m| m.operation_name == "test_op"));
819        assert!(all_metrics.iter().any(|m| m.operation_name == "test_op2"));
820    }
821
822    #[test]
823    fn test_performance_stats_new_initialization() {
824        let stats = PerformanceStats::new("test_operation".to_string());
825        assert_eq!(stats.operation_name, "test_operation");
826        assert_eq!(stats.total_calls, 0);
827        assert_eq!(stats.successful_calls, 0);
828        assert_eq!(stats.failed_calls, 0);
829        assert!((stats.success_rate - 0.0).abs() < f64::EPSILON);
830        assert_eq!(stats.average_duration, Duration::from_millis(0));
831        assert_eq!(stats.min_duration, Duration::MAX);
832        assert_eq!(stats.max_duration, Duration::ZERO);
833        assert!(stats.last_called.is_none());
834    }
835
836    #[test]
837    fn test_performance_stats_update_single_success() {
838        let mut stats = PerformanceStats::new("test_op".to_string());
839
840        let metric = OperationMetrics {
841            operation_name: "test_op".to_string(),
842            duration: Duration::from_millis(100),
843            timestamp: Utc::now(),
844            success: true,
845            error_message: None,
846        };
847
848        stats.add_metric(&metric);
849
850        assert_eq!(stats.total_calls, 1);
851        assert_eq!(stats.successful_calls, 1);
852        assert_eq!(stats.failed_calls, 0);
853        assert!((stats.success_rate - 1.0).abs() < f64::EPSILON);
854        assert_eq!(stats.average_duration, Duration::from_millis(100));
855        assert_eq!(stats.min_duration, Duration::from_millis(100));
856        assert_eq!(stats.max_duration, Duration::from_millis(100));
857        assert!(stats.last_called.is_some());
858    }
859
860    #[test]
861    fn test_performance_stats_update_multiple_operations() {
862        let mut stats = PerformanceStats::new("test_op".to_string());
863
864        // Add successful operation
865        let metric1 = OperationMetrics {
866            operation_name: "test_op".to_string(),
867            duration: Duration::from_millis(100),
868            timestamp: Utc::now(),
869            success: true,
870            error_message: None,
871        };
872        stats.add_metric(&metric1);
873
874        // Add failed operation
875        let metric2 = OperationMetrics {
876            operation_name: "test_op".to_string(),
877            duration: Duration::from_millis(200),
878            timestamp: Utc::now(),
879            success: false,
880            error_message: Some("Error".to_string()),
881        };
882        stats.add_metric(&metric2);
883
884        // Add another successful operation
885        let metric3 = OperationMetrics {
886            operation_name: "test_op".to_string(),
887            duration: Duration::from_millis(50),
888            timestamp: Utc::now(),
889            success: true,
890            error_message: None,
891        };
892        stats.add_metric(&metric3);
893
894        assert_eq!(stats.total_calls, 3);
895        assert_eq!(stats.successful_calls, 2);
896        assert_eq!(stats.failed_calls, 1);
897        assert!((stats.success_rate - 0.666_666_666_666_666_6).abs() < 0.001); // 2/3
898                                                                               // Average should be approximately 116-117ms due to rounding
899        assert!(
900            stats.average_duration >= Duration::from_millis(116)
901                && stats.average_duration <= Duration::from_millis(117)
902        );
903        assert_eq!(stats.min_duration, Duration::from_millis(50));
904        assert_eq!(stats.max_duration, Duration::from_millis(200));
905    }
906
907    #[test]
908    fn test_system_metrics_creation() {
909        let metrics = SystemMetrics {
910            timestamp: Utc::now(),
911            memory_usage_mb: 512.0,
912            cpu_usage_percent: 25.5,
913            available_memory_mb: 1024.0,
914            total_memory_mb: 2048.0,
915        };
916
917        assert!((metrics.cpu_usage_percent - 25.5).abs() < f64::EPSILON);
918        assert!((metrics.memory_usage_mb - 512.0).abs() < f64::EPSILON);
919        assert!((metrics.total_memory_mb - 2048.0).abs() < f64::EPSILON);
920    }
921
922    #[test]
923    fn test_cache_metrics_creation() {
924        let metrics = CacheMetrics {
925            cache_type: "l1".to_string(),
926            hits: 100,
927            misses: 25,
928            hit_rate: 0.8,
929            total_entries: 125,
930            memory_usage_bytes: 1024 * 1024,
931            evictions: 5,
932            insertions: 130,
933            invalidations: 2,
934            warming_entries: 0,
935            average_access_time_ms: 1.5,
936            last_accessed: Some(Utc::now()),
937        };
938
939        assert_eq!(metrics.hits, 100);
940        assert_eq!(metrics.misses, 25);
941        assert!((metrics.hit_rate - 0.8).abs() < f64::EPSILON);
942        assert_eq!(metrics.total_entries, 125);
943        assert_eq!(metrics.evictions, 5);
944    }
945
946    #[test]
947    fn test_query_metrics_creation() {
948        let metrics = QueryMetrics {
949            query_type: "tasks".to_string(),
950            total_queries: 1000,
951            cached_queries: 950,
952            database_queries: 50,
953            cache_hit_rate: 0.95,
954            average_query_time_ms: 150.0,
955            average_cache_time_ms: 5.0,
956            average_database_time_ms: 200.0,
957            slowest_query_ms: 2000,
958            fastest_query_ms: 10,
959            query_size_bytes: 1024,
960            compression_ratio: 0.8,
961        };
962
963        assert_eq!(metrics.total_queries, 1000);
964        assert_eq!(metrics.cached_queries, 950);
965        assert_eq!(metrics.database_queries, 50);
966        assert!((metrics.average_query_time_ms - 150.0).abs() < f64::EPSILON);
967        assert_eq!(metrics.slowest_query_ms, 2000);
968        assert_eq!(metrics.fastest_query_ms, 10);
969    }
970
971    #[test]
972    fn test_operation_timer_success() {
973        let monitor = PerformanceMonitor::new_default();
974        let timer = monitor.start_operation("test_operation");
975
976        // Simulate some work
977        std::thread::sleep(Duration::from_millis(10));
978
979        timer.success();
980
981        let stats = monitor.get_operation_stats("test_operation").unwrap();
982        assert_eq!(stats.operation_name, "test_operation");
983        assert_eq!(stats.total_calls, 1);
984        assert_eq!(stats.successful_calls, 1);
985        assert!(stats.average_duration >= Duration::from_millis(10));
986    }
987
988    #[test]
989    fn test_operation_timer_error() {
990        let monitor = PerformanceMonitor::new_default();
991        let timer = monitor.start_operation("test_operation");
992
993        timer.error("Test error occurred".to_string());
994
995        let stats = monitor.get_operation_stats("test_operation").unwrap();
996        assert_eq!(stats.operation_name, "test_operation");
997        assert_eq!(stats.total_calls, 1);
998        assert_eq!(stats.failed_calls, 1);
999    }
1000
1001    #[test]
1002    fn test_performance_monitor_get_summary_empty_comprehensive() {
1003        let monitor = PerformanceMonitor::new_default();
1004        let summary = monitor.get_summary();
1005
1006        assert_eq!(summary.total_operations, 0);
1007        assert_eq!(summary.operation_count, 0);
1008    }
1009
1010    #[test]
1011    fn test_performance_monitor_record_and_get_summary() {
1012        let monitor = PerformanceMonitor::new_default();
1013
1014        // Record some operations
1015        let metric1 = OperationMetrics {
1016            operation_name: "op1".to_string(),
1017            duration: Duration::from_millis(100),
1018            timestamp: Utc::now(),
1019            success: true,
1020            error_message: None,
1021        };
1022        monitor.record_operation(&metric1);
1023
1024        let metric2 = OperationMetrics {
1025            operation_name: "op2".to_string(),
1026            duration: Duration::from_millis(200),
1027            timestamp: Utc::now(),
1028            success: false,
1029            error_message: Some("Error".to_string()),
1030        };
1031        monitor.record_operation(&metric2);
1032
1033        let summary = monitor.get_summary();
1034        assert_eq!(summary.total_operations, 2);
1035        assert_eq!(summary.operation_count, 2);
1036
1037        let op1_stats = monitor.get_operation_stats("op1").unwrap();
1038        assert_eq!(op1_stats.total_calls, 1);
1039        assert_eq!(op1_stats.successful_calls, 1);
1040
1041        let op2_stats = monitor.get_operation_stats("op2").unwrap();
1042        assert_eq!(op2_stats.total_calls, 1);
1043        assert_eq!(op2_stats.failed_calls, 1);
1044    }
1045}