Skip to main content

voirs_cli/performance/
metrics.rs

1//! Performance metrics collection and analysis
2//!
3//! This module provides comprehensive metrics collection, aggregation, and analysis
4//! for performance monitoring and optimization of VoiRS synthesis operations.
5
6use super::{GpuMetrics, MemoryMetrics, PerformanceMetrics, SynthesisMetrics, SystemMetrics};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, VecDeque};
9use std::sync::Arc;
10use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
11use tokio::sync::RwLock;
12
13/// Metrics aggregation window types
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum MetricsWindow {
16    /// Last 1 minute
17    OneMinute,
18    /// Last 5 minutes
19    FiveMinutes,
20    /// Last 15 minutes
21    FifteenMinutes,
22    /// Last 1 hour
23    OneHour,
24    /// Last 24 hours
25    TwentyFourHours,
26    /// All time
27    AllTime,
28}
29
30/// Aggregated performance statistics
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AggregatedMetrics {
33    /// Time window for aggregation
34    pub window: MetricsWindow,
35    /// Number of samples included
36    pub sample_count: usize,
37    /// Time range of samples
38    pub time_range: TimeRange,
39    /// System metrics summary
40    pub system: SystemSummary,
41    /// Synthesis metrics summary
42    pub synthesis: SynthesisSummary,
43    /// Memory metrics summary
44    pub memory: MemorySummary,
45    /// GPU metrics summary (if available)
46    pub gpu: Option<GpuSummary>,
47    /// Performance trends
48    pub trends: PerformanceTrends,
49}
50
51/// Time range for metrics
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TimeRange {
54    /// Start timestamp
55    pub start: u64,
56    /// End timestamp
57    pub end: u64,
58    /// Duration in seconds
59    pub duration_seconds: u64,
60}
61
62/// System metrics summary
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SystemSummary {
65    /// CPU usage statistics
66    pub cpu: StatisticsSummary,
67    /// Memory usage statistics
68    pub memory_used: StatisticsSummary,
69    /// Memory available statistics
70    pub memory_available: StatisticsSummary,
71    /// Disk I/O read statistics
72    pub disk_read: StatisticsSummary,
73    /// Disk I/O write statistics
74    pub disk_write: StatisticsSummary,
75    /// Network I/O statistics
76    pub network: StatisticsSummary,
77    /// Thread count statistics
78    pub thread_count: StatisticsSummary,
79    /// Load average statistics (Unix only)
80    pub load_average: Option<StatisticsSummary>,
81}
82
83/// Synthesis metrics summary
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SynthesisSummary {
86    /// Total operations in window
87    pub total_operations: u64,
88    /// Success rate percentage
89    pub success_rate: f64,
90    /// Synthesis time statistics
91    pub synthesis_time: StatisticsSummary,
92    /// Real-time factor statistics
93    pub real_time_factor: StatisticsSummary,
94    /// Throughput statistics (chars/sec)
95    pub throughput: StatisticsSummary,
96    /// Queue depth statistics
97    pub queue_depth: StatisticsSummary,
98    /// Memory per operation statistics
99    pub memory_per_operation: StatisticsSummary,
100    /// Total audio duration generated
101    pub total_audio_duration: f64,
102}
103
104/// Memory metrics summary
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct MemorySummary {
107    /// Heap usage statistics
108    pub heap_used: StatisticsSummary,
109    /// Peak usage statistics
110    pub peak_usage: StatisticsSummary,
111    /// Allocation rate statistics
112    pub allocation_rate: StatisticsSummary,
113    /// Deallocation rate statistics
114    pub deallocation_rate: StatisticsSummary,
115    /// Fragmentation statistics
116    pub fragmentation: StatisticsSummary,
117    /// Cache hit rate statistics
118    pub cache_hit_rate: StatisticsSummary,
119    /// GC events count
120    pub gc_events: u64,
121}
122
123/// GPU metrics summary
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct GpuSummary {
126    /// GPU utilization statistics
127    pub utilization: StatisticsSummary,
128    /// GPU memory usage statistics
129    pub memory_used: StatisticsSummary,
130    /// GPU memory usage percentage statistics
131    pub memory_usage_percent: StatisticsSummary,
132    /// GPU temperature statistics
133    pub temperature: StatisticsSummary,
134    /// GPU power consumption statistics
135    pub power_consumption: StatisticsSummary,
136    /// Compute units active statistics
137    pub compute_units: StatisticsSummary,
138    /// Memory bandwidth utilization statistics
139    pub memory_bandwidth: StatisticsSummary,
140}
141
142/// Statistical summary for a metric
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct StatisticsSummary {
145    /// Average value
146    pub average: f64,
147    /// Minimum value
148    pub minimum: f64,
149    /// Maximum value
150    pub maximum: f64,
151    /// Standard deviation
152    pub std_deviation: f64,
153    /// 50th percentile (median)
154    pub p50: f64,
155    /// 90th percentile
156    pub p90: f64,
157    /// 95th percentile
158    pub p95: f64,
159    /// 99th percentile
160    pub p99: f64,
161    /// Sample count
162    pub count: usize,
163}
164
165/// Performance trends analysis
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct PerformanceTrends {
168    /// CPU usage trend (positive = increasing)
169    pub cpu_trend: TrendDirection,
170    /// Memory usage trend
171    pub memory_trend: TrendDirection,
172    /// Synthesis performance trend
173    pub synthesis_performance_trend: TrendDirection,
174    /// Queue depth trend
175    pub queue_depth_trend: TrendDirection,
176    /// Error rate trend
177    pub error_rate_trend: TrendDirection,
178    /// Overall performance score trend
179    pub overall_trend: TrendDirection,
180}
181
182/// Trend direction indicator
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub enum TrendDirection {
185    /// Strongly improving
186    StronglyImproving,
187    /// Improving
188    Improving,
189    /// Stable
190    Stable,
191    /// Degrading
192    Degrading,
193    /// Strongly degrading
194    StronglyDegrading,
195    /// Insufficient data
196    Unknown,
197}
198
199/// Performance metrics collector and analyzer
200pub struct MetricsCollector {
201    /// Raw metrics storage
202    raw_metrics: Arc<RwLock<VecDeque<PerformanceMetrics>>>,
203    /// Aggregated metrics cache
204    aggregated_cache: Arc<RwLock<HashMap<MetricsWindow, AggregatedMetrics>>>,
205    /// Maximum raw metrics to store
206    max_raw_metrics: usize,
207    /// Last cache update time
208    last_cache_update: Arc<RwLock<Instant>>,
209    /// Cache validity duration
210    cache_validity: Duration,
211    /// Metrics collection start time
212    start_time: Instant,
213}
214
215impl MetricsCollector {
216    /// Create a new metrics collector
217    pub fn new(max_raw_metrics: usize, cache_validity: Duration) -> Self {
218        Self {
219            raw_metrics: Arc::new(RwLock::new(VecDeque::with_capacity(max_raw_metrics))),
220            aggregated_cache: Arc::new(RwLock::new(HashMap::new())),
221            max_raw_metrics,
222            last_cache_update: Arc::new(RwLock::new(Instant::now())),
223            cache_validity,
224            start_time: Instant::now(),
225        }
226    }
227
228    /// Add a new performance metrics sample
229    pub async fn add_metrics(&self, metrics: PerformanceMetrics) {
230        let mut raw_metrics = self.raw_metrics.write().await;
231
232        // Maintain maximum size
233        if raw_metrics.len() >= self.max_raw_metrics {
234            raw_metrics.pop_front();
235        }
236
237        raw_metrics.push_back(metrics);
238
239        // Invalidate cache
240        self.invalidate_cache().await;
241    }
242
243    /// Get aggregated metrics for a specific window
244    pub async fn get_aggregated_metrics(&self, window: MetricsWindow) -> Option<AggregatedMetrics> {
245        // Check cache first
246        if let Some(cached) = self.get_cached_metrics(window).await {
247            return Some(cached);
248        }
249
250        // Generate new aggregated metrics
251        let aggregated = self.generate_aggregated_metrics(window).await?;
252
253        // Cache the result
254        self.cache_metrics(window, aggregated.clone()).await;
255
256        Some(aggregated)
257    }
258
259    /// Get performance trends for a specific window
260    pub async fn get_performance_trends(&self, window: MetricsWindow) -> Option<PerformanceTrends> {
261        let aggregated = self.get_aggregated_metrics(window).await?;
262        Some(aggregated.trends)
263    }
264
265    /// Get real-time metrics (latest sample)
266    pub async fn get_latest_metrics(&self) -> Option<PerformanceMetrics> {
267        let raw_metrics = self.raw_metrics.read().await;
268        raw_metrics.back().cloned()
269    }
270
271    /// Get metrics history for a specific time range
272    pub async fn get_metrics_history(
273        &self,
274        start_time: u64,
275        end_time: u64,
276    ) -> Vec<PerformanceMetrics> {
277        let raw_metrics = self.raw_metrics.read().await;
278
279        raw_metrics
280            .iter()
281            .filter(|m| m.timestamp >= start_time && m.timestamp <= end_time)
282            .cloned()
283            .collect()
284    }
285
286    /// Generate performance report
287    pub async fn generate_performance_report(&self) -> PerformanceReport {
288        let mut report = PerformanceReport {
289            generation_time: SystemTime::now()
290                .duration_since(UNIX_EPOCH)
291                .unwrap_or_default()
292                .as_secs(),
293            uptime_seconds: self.start_time.elapsed().as_secs(),
294            windows: HashMap::new(),
295            summary: ReportSummary::default(),
296        };
297
298        // Generate metrics for all windows
299        for &window in &[
300            MetricsWindow::OneMinute,
301            MetricsWindow::FiveMinutes,
302            MetricsWindow::FifteenMinutes,
303            MetricsWindow::OneHour,
304            MetricsWindow::TwentyFourHours,
305        ] {
306            if let Some(metrics) = self.get_aggregated_metrics(window).await {
307                report.windows.insert(window, metrics);
308            }
309        }
310
311        // Generate summary
312        report.summary = self.generate_report_summary(&report.windows).await;
313
314        report
315    }
316
317    /// Check cached metrics
318    async fn get_cached_metrics(&self, window: MetricsWindow) -> Option<AggregatedMetrics> {
319        let cache = self.aggregated_cache.read().await;
320        let last_update = *self.last_cache_update.read().await;
321
322        if last_update.elapsed() < self.cache_validity {
323            cache.get(&window).cloned()
324        } else {
325            None
326        }
327    }
328
329    /// Cache aggregated metrics
330    async fn cache_metrics(&self, window: MetricsWindow, metrics: AggregatedMetrics) {
331        let mut cache = self.aggregated_cache.write().await;
332        cache.insert(window, metrics);
333
334        let mut last_update = self.last_cache_update.write().await;
335        *last_update = Instant::now();
336    }
337
338    /// Invalidate cache
339    async fn invalidate_cache(&self) {
340        let mut cache = self.aggregated_cache.write().await;
341        cache.clear();
342    }
343
344    /// Generate aggregated metrics for a window
345    async fn generate_aggregated_metrics(
346        &self,
347        window: MetricsWindow,
348    ) -> Option<AggregatedMetrics> {
349        let raw_metrics = self.raw_metrics.read().await;
350
351        if raw_metrics.is_empty() {
352            return None;
353        }
354
355        let window_duration = self.get_window_duration(window);
356        let current_time = SystemTime::now()
357            .duration_since(UNIX_EPOCH)
358            .unwrap_or_default()
359            .as_secs();
360        let cutoff_time = current_time.saturating_sub(window_duration);
361
362        // Filter metrics within the window
363        let window_metrics: Vec<&PerformanceMetrics> = raw_metrics
364            .iter()
365            .filter(|m| m.timestamp >= cutoff_time)
366            .collect();
367
368        if window_metrics.is_empty() {
369            return None;
370        }
371
372        let sample_count = window_metrics.len();
373        let start_time = window_metrics.first()?.timestamp;
374        let end_time = window_metrics.last()?.timestamp;
375
376        let time_range = TimeRange {
377            start: start_time,
378            end: end_time,
379            duration_seconds: end_time - start_time,
380        };
381
382        // Aggregate system metrics
383        let system = self.aggregate_system_metrics(&window_metrics);
384
385        // Aggregate synthesis metrics
386        let synthesis = self.aggregate_synthesis_metrics(&window_metrics);
387
388        // Aggregate memory metrics
389        let memory = self.aggregate_memory_metrics(&window_metrics);
390
391        // Aggregate GPU metrics if available
392        let gpu = self.aggregate_gpu_metrics(&window_metrics);
393
394        // Calculate trends
395        let trends = self.calculate_trends(&window_metrics);
396
397        Some(AggregatedMetrics {
398            window,
399            sample_count,
400            time_range,
401            system,
402            synthesis,
403            memory,
404            gpu,
405            trends,
406        })
407    }
408
409    /// Get window duration in seconds
410    fn get_window_duration(&self, window: MetricsWindow) -> u64 {
411        match window {
412            MetricsWindow::OneMinute => 60,
413            MetricsWindow::FiveMinutes => 300,
414            MetricsWindow::FifteenMinutes => 900,
415            MetricsWindow::OneHour => 3600,
416            MetricsWindow::TwentyFourHours => 86400,
417            MetricsWindow::AllTime => u64::MAX,
418        }
419    }
420
421    /// Aggregate system metrics
422    fn aggregate_system_metrics(&self, metrics: &[&PerformanceMetrics]) -> SystemSummary {
423        let cpu_values: Vec<f64> = metrics.iter().map(|m| m.system.cpu_usage).collect();
424        let memory_used_values: Vec<f64> = metrics
425            .iter()
426            .map(|m| m.system.memory_used as f64)
427            .collect();
428        let memory_available_values: Vec<f64> = metrics
429            .iter()
430            .map(|m| m.system.memory_available as f64)
431            .collect();
432        let disk_read_values: Vec<f64> = metrics
433            .iter()
434            .map(|m| m.system.disk_read_bps as f64)
435            .collect();
436        let disk_write_values: Vec<f64> = metrics
437            .iter()
438            .map(|m| m.system.disk_write_bps as f64)
439            .collect();
440        let network_values: Vec<f64> = metrics
441            .iter()
442            .map(|m| m.system.network_bps as f64)
443            .collect();
444        let thread_count_values: Vec<f64> = metrics
445            .iter()
446            .map(|m| m.system.thread_count as f64)
447            .collect();
448
449        let load_average_values: Vec<f64> = metrics
450            .iter()
451            .filter_map(|m| m.system.load_average)
452            .collect();
453
454        SystemSummary {
455            cpu: StatisticsSummary::from_values(&cpu_values),
456            memory_used: StatisticsSummary::from_values(&memory_used_values),
457            memory_available: StatisticsSummary::from_values(&memory_available_values),
458            disk_read: StatisticsSummary::from_values(&disk_read_values),
459            disk_write: StatisticsSummary::from_values(&disk_write_values),
460            network: StatisticsSummary::from_values(&network_values),
461            thread_count: StatisticsSummary::from_values(&thread_count_values),
462            load_average: if load_average_values.is_empty() {
463                None
464            } else {
465                Some(StatisticsSummary::from_values(&load_average_values))
466            },
467        }
468    }
469
470    /// Aggregate synthesis metrics
471    fn aggregate_synthesis_metrics(&self, metrics: &[&PerformanceMetrics]) -> SynthesisSummary {
472        let total_operations: u64 = metrics.iter().map(|m| m.synthesis.total_operations).sum();
473        let successful_operations: u64 = metrics
474            .iter()
475            .map(|m| m.synthesis.successful_operations)
476            .sum();
477        let success_rate = if total_operations > 0 {
478            (successful_operations as f64 / total_operations as f64) * 100.0
479        } else {
480            0.0
481        };
482
483        let synthesis_time_values: Vec<f64> = metrics
484            .iter()
485            .map(|m| m.synthesis.avg_synthesis_time_ms)
486            .collect();
487        let rtf_values: Vec<f64> = metrics
488            .iter()
489            .map(|m| m.synthesis.real_time_factor)
490            .collect();
491        let throughput_values: Vec<f64> = metrics
492            .iter()
493            .map(|m| m.synthesis.throughput_chars_per_sec)
494            .collect();
495        let queue_depth_values: Vec<f64> = metrics
496            .iter()
497            .map(|m| m.synthesis.queue_depth as f64)
498            .collect();
499        let memory_per_op_values: Vec<f64> = metrics
500            .iter()
501            .map(|m| m.synthesis.memory_per_operation_mb)
502            .collect();
503
504        let total_audio_duration: f64 = metrics
505            .iter()
506            .map(|m| m.synthesis.total_audio_duration)
507            .sum();
508
509        SynthesisSummary {
510            total_operations,
511            success_rate,
512            synthesis_time: StatisticsSummary::from_values(&synthesis_time_values),
513            real_time_factor: StatisticsSummary::from_values(&rtf_values),
514            throughput: StatisticsSummary::from_values(&throughput_values),
515            queue_depth: StatisticsSummary::from_values(&queue_depth_values),
516            memory_per_operation: StatisticsSummary::from_values(&memory_per_op_values),
517            total_audio_duration,
518        }
519    }
520
521    /// Aggregate memory metrics
522    fn aggregate_memory_metrics(&self, metrics: &[&PerformanceMetrics]) -> MemorySummary {
523        let heap_used_values: Vec<f64> =
524            metrics.iter().map(|m| m.memory.heap_used as f64).collect();
525        let peak_usage_values: Vec<f64> =
526            metrics.iter().map(|m| m.memory.peak_usage as f64).collect();
527        let allocation_rate_values: Vec<f64> = metrics
528            .iter()
529            .map(|m| m.memory.allocations_per_sec)
530            .collect();
531        let deallocation_rate_values: Vec<f64> = metrics
532            .iter()
533            .map(|m| m.memory.deallocations_per_sec)
534            .collect();
535        let fragmentation_values: Vec<f64> = metrics
536            .iter()
537            .map(|m| m.memory.fragmentation_percent)
538            .collect();
539        let cache_hit_rate_values: Vec<f64> =
540            metrics.iter().map(|m| m.memory.cache_hit_rate).collect();
541
542        let gc_events: u64 = metrics.iter().map(|m| m.memory.gc_events).sum();
543
544        MemorySummary {
545            heap_used: StatisticsSummary::from_values(&heap_used_values),
546            peak_usage: StatisticsSummary::from_values(&peak_usage_values),
547            allocation_rate: StatisticsSummary::from_values(&allocation_rate_values),
548            deallocation_rate: StatisticsSummary::from_values(&deallocation_rate_values),
549            fragmentation: StatisticsSummary::from_values(&fragmentation_values),
550            cache_hit_rate: StatisticsSummary::from_values(&cache_hit_rate_values),
551            gc_events,
552        }
553    }
554
555    /// Aggregate GPU metrics
556    fn aggregate_gpu_metrics(&self, metrics: &[&PerformanceMetrics]) -> Option<GpuSummary> {
557        let gpu_metrics: Vec<&GpuMetrics> = metrics.iter().filter_map(|m| m.gpu.as_ref()).collect();
558
559        if gpu_metrics.is_empty() {
560            return None;
561        }
562
563        let utilization_values: Vec<f64> = gpu_metrics.iter().map(|g| g.utilization).collect();
564        let memory_used_values: Vec<f64> =
565            gpu_metrics.iter().map(|g| g.memory_used as f64).collect();
566        let memory_usage_percent_values: Vec<f64> = gpu_metrics
567            .iter()
568            .map(|g| (g.memory_used as f64 / g.memory_total as f64) * 100.0)
569            .collect();
570        let temperature_values: Vec<f64> = gpu_metrics.iter().map(|g| g.temperature).collect();
571        let power_values: Vec<f64> = gpu_metrics.iter().map(|g| g.power_consumption).collect();
572        let compute_units_values: Vec<f64> = gpu_metrics
573            .iter()
574            .map(|g| g.compute_units_active as f64)
575            .collect();
576        let bandwidth_values: Vec<f64> = gpu_metrics
577            .iter()
578            .map(|g| g.memory_bandwidth_util)
579            .collect();
580
581        Some(GpuSummary {
582            utilization: StatisticsSummary::from_values(&utilization_values),
583            memory_used: StatisticsSummary::from_values(&memory_used_values),
584            memory_usage_percent: StatisticsSummary::from_values(&memory_usage_percent_values),
585            temperature: StatisticsSummary::from_values(&temperature_values),
586            power_consumption: StatisticsSummary::from_values(&power_values),
587            compute_units: StatisticsSummary::from_values(&compute_units_values),
588            memory_bandwidth: StatisticsSummary::from_values(&bandwidth_values),
589        })
590    }
591
592    /// Calculate performance trends
593    fn calculate_trends(&self, metrics: &[&PerformanceMetrics]) -> PerformanceTrends {
594        if metrics.len() < 2 {
595            return PerformanceTrends {
596                cpu_trend: TrendDirection::Unknown,
597                memory_trend: TrendDirection::Unknown,
598                synthesis_performance_trend: TrendDirection::Unknown,
599                queue_depth_trend: TrendDirection::Unknown,
600                error_rate_trend: TrendDirection::Unknown,
601                overall_trend: TrendDirection::Unknown,
602            };
603        }
604
605        let cpu_values: Vec<f64> = metrics.iter().map(|m| m.system.cpu_usage).collect();
606        let memory_values: Vec<f64> = metrics
607            .iter()
608            .map(|m| m.system.memory_used as f64)
609            .collect();
610        let rtf_values: Vec<f64> = metrics
611            .iter()
612            .map(|m| m.synthesis.real_time_factor)
613            .collect();
614        let queue_values: Vec<f64> = metrics
615            .iter()
616            .map(|m| m.synthesis.queue_depth as f64)
617            .collect();
618        let error_rate_values: Vec<f64> = metrics
619            .iter()
620            .map(|m| {
621                if m.synthesis.total_operations > 0 {
622                    (m.synthesis.failed_operations as f64 / m.synthesis.total_operations as f64)
623                        * 100.0
624                } else {
625                    0.0
626                }
627            })
628            .collect();
629
630        PerformanceTrends {
631            cpu_trend: self.calculate_trend_direction(&cpu_values, false),
632            memory_trend: self.calculate_trend_direction(&memory_values, false),
633            synthesis_performance_trend: self.calculate_trend_direction(&rtf_values, true),
634            queue_depth_trend: self.calculate_trend_direction(&queue_values, false),
635            error_rate_trend: self.calculate_trend_direction(&error_rate_values, false),
636            overall_trend: self.calculate_overall_trend(
637                &cpu_values,
638                &memory_values,
639                &rtf_values,
640                &error_rate_values,
641            ),
642        }
643    }
644
645    /// Calculate trend direction for a series of values
646    fn calculate_trend_direction(&self, values: &[f64], higher_is_better: bool) -> TrendDirection {
647        if values.len() < 2 {
648            return TrendDirection::Unknown;
649        }
650
651        // Simple linear regression to find trend
652        let n = values.len() as f64;
653        let x_values: Vec<f64> = (0..values.len()).map(|i| i as f64).collect();
654
655        let sum_x: f64 = x_values.iter().sum();
656        let sum_y: f64 = values.iter().sum();
657        let sum_xy: f64 = x_values.iter().zip(values.iter()).map(|(x, y)| x * y).sum();
658        let sum_xx: f64 = x_values.iter().map(|x| x * x).sum();
659
660        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
661
662        // Determine relative change magnitude
663        let avg = sum_y / n;
664        let relative_slope = if avg != 0.0 { slope / avg } else { 0.0 };
665
666        let threshold_strong = 0.1; // 10% change
667        let threshold_weak = 0.02; // 2% change
668
669        let improving = if higher_is_better {
670            slope > 0.0
671        } else {
672            slope < 0.0
673        };
674        let abs_slope = relative_slope.abs();
675
676        if improving {
677            if abs_slope > threshold_strong {
678                TrendDirection::StronglyImproving
679            } else if abs_slope > threshold_weak {
680                TrendDirection::Improving
681            } else {
682                TrendDirection::Stable
683            }
684        } else if abs_slope > threshold_strong {
685            TrendDirection::StronglyDegrading
686        } else if abs_slope > threshold_weak {
687            TrendDirection::Degrading
688        } else {
689            TrendDirection::Stable
690        }
691    }
692
693    /// Calculate overall trend based on multiple metrics
694    fn calculate_overall_trend(
695        &self,
696        cpu: &[f64],
697        memory: &[f64],
698        rtf: &[f64],
699        error_rate: &[f64],
700    ) -> TrendDirection {
701        let cpu_trend = self.calculate_trend_direction(cpu, false);
702        let memory_trend = self.calculate_trend_direction(memory, false);
703        let rtf_trend = self.calculate_trend_direction(rtf, true);
704        let error_trend = self.calculate_trend_direction(error_rate, false);
705
706        // Weight the trends (RTF is most important for synthesis performance)
707        let trends = vec![
708            (cpu_trend, 0.2),
709            (memory_trend, 0.2),
710            (rtf_trend, 0.4),
711            (error_trend, 0.2),
712        ];
713
714        let mut score = 0.0;
715        for (trend, weight) in trends {
716            let trend_score = match trend {
717                TrendDirection::StronglyImproving => 2.0,
718                TrendDirection::Improving => 1.0,
719                TrendDirection::Stable => 0.0,
720                TrendDirection::Degrading => -1.0,
721                TrendDirection::StronglyDegrading => -2.0,
722                TrendDirection::Unknown => 0.0,
723            };
724            score += trend_score * weight;
725        }
726
727        if score > 1.0 {
728            TrendDirection::StronglyImproving
729        } else if score > 0.3 {
730            TrendDirection::Improving
731        } else if score > -0.3 {
732            TrendDirection::Stable
733        } else if score > -1.0 {
734            TrendDirection::Degrading
735        } else {
736            TrendDirection::StronglyDegrading
737        }
738    }
739
740    /// Generate report summary
741    async fn generate_report_summary(
742        &self,
743        windows: &HashMap<MetricsWindow, AggregatedMetrics>,
744    ) -> ReportSummary {
745        let mut summary = ReportSummary::default();
746
747        if let Some(current) = windows.get(&MetricsWindow::OneMinute) {
748            summary.current_cpu_usage = current.system.cpu.average;
749            summary.current_memory_usage = current.system.memory_used.average;
750            summary.current_rtf = current.synthesis.real_time_factor.average;
751            summary.current_success_rate = current.synthesis.success_rate;
752        }
753
754        if let Some(hourly) = windows.get(&MetricsWindow::OneHour) {
755            summary.hourly_operations = hourly.synthesis.total_operations;
756            summary.hourly_audio_duration = hourly.synthesis.total_audio_duration;
757        }
758
759        if let Some(daily) = windows.get(&MetricsWindow::TwentyFourHours) {
760            summary.daily_operations = daily.synthesis.total_operations;
761            summary.daily_audio_duration = daily.synthesis.total_audio_duration;
762        }
763
764        // Find best and worst performing windows
765        let mut best_rtf = 0.0;
766        let mut worst_rtf = f64::INFINITY;
767
768        for metrics in windows.values() {
769            if metrics.synthesis.real_time_factor.average > best_rtf {
770                best_rtf = metrics.synthesis.real_time_factor.average;
771                summary.best_performance_window = Some(metrics.window);
772            }
773            if metrics.synthesis.real_time_factor.average < worst_rtf {
774                worst_rtf = metrics.synthesis.real_time_factor.average;
775                summary.worst_performance_window = Some(metrics.window);
776            }
777        }
778
779        summary
780    }
781
782    /// Clear all metrics
783    pub async fn clear_metrics(&self) {
784        let mut raw_metrics = self.raw_metrics.write().await;
785        raw_metrics.clear();
786
787        self.invalidate_cache().await;
788    }
789
790    /// Get metrics count
791    pub async fn metrics_count(&self) -> usize {
792        let raw_metrics = self.raw_metrics.read().await;
793        raw_metrics.len()
794    }
795}
796
797/// Performance report structure
798#[derive(Debug, Clone, Serialize, Deserialize)]
799pub struct PerformanceReport {
800    /// Report generation timestamp
801    pub generation_time: u64,
802    /// System uptime in seconds
803    pub uptime_seconds: u64,
804    /// Metrics for different time windows
805    pub windows: HashMap<MetricsWindow, AggregatedMetrics>,
806    /// High-level summary
807    pub summary: ReportSummary,
808}
809
810/// Report summary with key metrics
811#[derive(Debug, Clone, Default, Serialize, Deserialize)]
812pub struct ReportSummary {
813    /// Current CPU usage percentage
814    pub current_cpu_usage: f64,
815    /// Current memory usage in bytes
816    pub current_memory_usage: f64,
817    /// Current real-time factor
818    pub current_rtf: f64,
819    /// Current success rate percentage
820    pub current_success_rate: f64,
821    /// Operations in the last hour
822    pub hourly_operations: u64,
823    /// Audio duration generated in the last hour
824    pub hourly_audio_duration: f64,
825    /// Operations in the last 24 hours
826    pub daily_operations: u64,
827    /// Audio duration generated in the last 24 hours
828    pub daily_audio_duration: f64,
829    /// Best performing time window
830    pub best_performance_window: Option<MetricsWindow>,
831    /// Worst performing time window
832    pub worst_performance_window: Option<MetricsWindow>,
833}
834
835impl StatisticsSummary {
836    /// Create statistics summary from a vector of values
837    pub fn from_values(values: &[f64]) -> Self {
838        if values.is_empty() {
839            return Self::default();
840        }
841
842        let mut sorted = values.to_vec();
843        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
844
845        let count = sorted.len();
846        let sum: f64 = sorted.iter().sum();
847        let average = sum / count as f64;
848        let minimum = sorted[0];
849        let maximum = sorted[count - 1];
850
851        // Calculate percentiles
852        let p50 = percentile(&sorted, 50.0);
853        let p90 = percentile(&sorted, 90.0);
854        let p95 = percentile(&sorted, 95.0);
855        let p99 = percentile(&sorted, 99.0);
856
857        // Calculate standard deviation
858        let variance: f64 =
859            values.iter().map(|v| (v - average).powi(2)).sum::<f64>() / count as f64;
860        let std_deviation = variance.sqrt();
861
862        Self {
863            average,
864            minimum,
865            maximum,
866            std_deviation,
867            p50,
868            p90,
869            p95,
870            p99,
871            count,
872        }
873    }
874}
875
876impl Default for StatisticsSummary {
877    fn default() -> Self {
878        Self {
879            average: 0.0,
880            minimum: 0.0,
881            maximum: 0.0,
882            std_deviation: 0.0,
883            p50: 0.0,
884            p90: 0.0,
885            p95: 0.0,
886            p99: 0.0,
887            count: 0,
888        }
889    }
890}
891
892/// Calculate percentile value
893fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
894    if sorted_values.is_empty() {
895        return 0.0;
896    }
897
898    let index = (percentile / 100.0) * (sorted_values.len() - 1) as f64;
899    let lower_index = index.floor() as usize;
900    let upper_index = index.ceil() as usize;
901
902    if lower_index == upper_index {
903        sorted_values[lower_index]
904    } else {
905        let lower_value = sorted_values[lower_index];
906        let upper_value = sorted_values[upper_index];
907        let fraction = index - lower_index as f64;
908        lower_value + fraction * (upper_value - lower_value)
909    }
910}
911
912#[cfg(test)]
913mod tests {
914    use super::*;
915
916    #[tokio::test]
917    async fn test_metrics_collector_creation() {
918        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
919        assert_eq!(collector.metrics_count().await, 0);
920    }
921
922    #[tokio::test]
923    async fn test_add_metrics() {
924        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
925        let metrics = PerformanceMetrics::default();
926
927        collector.add_metrics(metrics).await;
928        assert_eq!(collector.metrics_count().await, 1);
929
930        let latest = collector.get_latest_metrics().await;
931        assert!(latest.is_some());
932    }
933
934    #[tokio::test]
935    async fn test_statistics_summary() {
936        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
937        let summary = StatisticsSummary::from_values(&values);
938
939        assert_eq!(summary.count, 5);
940        assert_eq!(summary.average, 3.0);
941        assert_eq!(summary.minimum, 1.0);
942        assert_eq!(summary.maximum, 5.0);
943        assert_eq!(summary.p50, 3.0);
944    }
945
946    #[test]
947    fn test_percentile_calculation() {
948        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
949
950        assert_eq!(percentile(&values, 0.0), 1.0);
951        assert_eq!(percentile(&values, 50.0), 3.0);
952        assert_eq!(percentile(&values, 100.0), 5.0);
953    }
954
955    #[tokio::test]
956    async fn test_aggregated_metrics_generation() {
957        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
958
959        // Add some test metrics
960        for i in 0..5 {
961            let mut metrics = PerformanceMetrics::default();
962            metrics.system.cpu_usage = (i as f64) * 10.0;
963            metrics.timestamp = SystemTime::now()
964                .duration_since(UNIX_EPOCH)
965                .unwrap()
966                .as_secs();
967            collector.add_metrics(metrics).await;
968        }
969
970        let aggregated = collector
971            .get_aggregated_metrics(MetricsWindow::OneMinute)
972            .await;
973        assert!(aggregated.is_some());
974
975        let aggregated = aggregated.unwrap();
976        assert_eq!(aggregated.sample_count, 5);
977        assert_eq!(aggregated.window, MetricsWindow::OneMinute);
978    }
979
980    #[tokio::test]
981    async fn test_trend_calculation() {
982        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
983
984        // Add metrics with improving trend
985        for i in 0..10 {
986            let mut metrics = PerformanceMetrics::default();
987            metrics.synthesis.real_time_factor = 1.0 + (i as f64) * 0.1; // Improving RTF
988            metrics.timestamp = SystemTime::now()
989                .duration_since(UNIX_EPOCH)
990                .unwrap()
991                .as_secs();
992            collector.add_metrics(metrics).await;
993        }
994
995        let trends = collector
996            .get_performance_trends(MetricsWindow::OneMinute)
997            .await;
998        assert!(trends.is_some());
999
1000        let trends = trends.unwrap();
1001        assert!(matches!(
1002            trends.synthesis_performance_trend,
1003            TrendDirection::Improving | TrendDirection::StronglyImproving
1004        ));
1005    }
1006
1007    #[test]
1008    fn test_window_duration() {
1009        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
1010
1011        assert_eq!(collector.get_window_duration(MetricsWindow::OneMinute), 60);
1012        assert_eq!(
1013            collector.get_window_duration(MetricsWindow::FiveMinutes),
1014            300
1015        );
1016        assert_eq!(collector.get_window_duration(MetricsWindow::OneHour), 3600);
1017    }
1018
1019    #[tokio::test]
1020    async fn test_performance_report_generation() {
1021        let collector = MetricsCollector::new(1000, Duration::from_secs(60));
1022
1023        // Add some metrics
1024        let mut metrics = PerformanceMetrics::default();
1025        metrics.synthesis.total_operations = 100;
1026        metrics.synthesis.successful_operations = 95;
1027        collector.add_metrics(metrics).await;
1028
1029        let report = collector.generate_performance_report().await;
1030        assert!(report.generation_time > 0);
1031        // uptime_seconds is unsigned, so always >= 0 - removing redundant check
1032    }
1033}