1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum MetricsWindow {
16 OneMinute,
18 FiveMinutes,
20 FifteenMinutes,
22 OneHour,
24 TwentyFourHours,
26 AllTime,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AggregatedMetrics {
33 pub window: MetricsWindow,
35 pub sample_count: usize,
37 pub time_range: TimeRange,
39 pub system: SystemSummary,
41 pub synthesis: SynthesisSummary,
43 pub memory: MemorySummary,
45 pub gpu: Option<GpuSummary>,
47 pub trends: PerformanceTrends,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TimeRange {
54 pub start: u64,
56 pub end: u64,
58 pub duration_seconds: u64,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SystemSummary {
65 pub cpu: StatisticsSummary,
67 pub memory_used: StatisticsSummary,
69 pub memory_available: StatisticsSummary,
71 pub disk_read: StatisticsSummary,
73 pub disk_write: StatisticsSummary,
75 pub network: StatisticsSummary,
77 pub thread_count: StatisticsSummary,
79 pub load_average: Option<StatisticsSummary>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SynthesisSummary {
86 pub total_operations: u64,
88 pub success_rate: f64,
90 pub synthesis_time: StatisticsSummary,
92 pub real_time_factor: StatisticsSummary,
94 pub throughput: StatisticsSummary,
96 pub queue_depth: StatisticsSummary,
98 pub memory_per_operation: StatisticsSummary,
100 pub total_audio_duration: f64,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct MemorySummary {
107 pub heap_used: StatisticsSummary,
109 pub peak_usage: StatisticsSummary,
111 pub allocation_rate: StatisticsSummary,
113 pub deallocation_rate: StatisticsSummary,
115 pub fragmentation: StatisticsSummary,
117 pub cache_hit_rate: StatisticsSummary,
119 pub gc_events: u64,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct GpuSummary {
126 pub utilization: StatisticsSummary,
128 pub memory_used: StatisticsSummary,
130 pub memory_usage_percent: StatisticsSummary,
132 pub temperature: StatisticsSummary,
134 pub power_consumption: StatisticsSummary,
136 pub compute_units: StatisticsSummary,
138 pub memory_bandwidth: StatisticsSummary,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct StatisticsSummary {
145 pub average: f64,
147 pub minimum: f64,
149 pub maximum: f64,
151 pub std_deviation: f64,
153 pub p50: f64,
155 pub p90: f64,
157 pub p95: f64,
159 pub p99: f64,
161 pub count: usize,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct PerformanceTrends {
168 pub cpu_trend: TrendDirection,
170 pub memory_trend: TrendDirection,
172 pub synthesis_performance_trend: TrendDirection,
174 pub queue_depth_trend: TrendDirection,
176 pub error_rate_trend: TrendDirection,
178 pub overall_trend: TrendDirection,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub enum TrendDirection {
185 StronglyImproving,
187 Improving,
189 Stable,
191 Degrading,
193 StronglyDegrading,
195 Unknown,
197}
198
199pub struct MetricsCollector {
201 raw_metrics: Arc<RwLock<VecDeque<PerformanceMetrics>>>,
203 aggregated_cache: Arc<RwLock<HashMap<MetricsWindow, AggregatedMetrics>>>,
205 max_raw_metrics: usize,
207 last_cache_update: Arc<RwLock<Instant>>,
209 cache_validity: Duration,
211 start_time: Instant,
213}
214
215impl MetricsCollector {
216 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 pub async fn add_metrics(&self, metrics: PerformanceMetrics) {
230 let mut raw_metrics = self.raw_metrics.write().await;
231
232 if raw_metrics.len() >= self.max_raw_metrics {
234 raw_metrics.pop_front();
235 }
236
237 raw_metrics.push_back(metrics);
238
239 self.invalidate_cache().await;
241 }
242
243 pub async fn get_aggregated_metrics(&self, window: MetricsWindow) -> Option<AggregatedMetrics> {
245 if let Some(cached) = self.get_cached_metrics(window).await {
247 return Some(cached);
248 }
249
250 let aggregated = self.generate_aggregated_metrics(window).await?;
252
253 self.cache_metrics(window, aggregated.clone()).await;
255
256 Some(aggregated)
257 }
258
259 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 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 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 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 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 report.summary = self.generate_report_summary(&report.windows).await;
313
314 report
315 }
316
317 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 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 async fn invalidate_cache(&self) {
340 let mut cache = self.aggregated_cache.write().await;
341 cache.clear();
342 }
343
344 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 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 let system = self.aggregate_system_metrics(&window_metrics);
384
385 let synthesis = self.aggregate_synthesis_metrics(&window_metrics);
387
388 let memory = self.aggregate_memory_metrics(&window_metrics);
390
391 let gpu = self.aggregate_gpu_metrics(&window_metrics);
393
394 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 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 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 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 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 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 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 fn calculate_trend_direction(&self, values: &[f64], higher_is_better: bool) -> TrendDirection {
647 if values.len() < 2 {
648 return TrendDirection::Unknown;
649 }
650
651 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 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; let threshold_weak = 0.02; 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 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 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 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 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 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 pub async fn metrics_count(&self) -> usize {
792 let raw_metrics = self.raw_metrics.read().await;
793 raw_metrics.len()
794 }
795}
796
797#[derive(Debug, Clone, Serialize, Deserialize)]
799pub struct PerformanceReport {
800 pub generation_time: u64,
802 pub uptime_seconds: u64,
804 pub windows: HashMap<MetricsWindow, AggregatedMetrics>,
806 pub summary: ReportSummary,
808}
809
810#[derive(Debug, Clone, Default, Serialize, Deserialize)]
812pub struct ReportSummary {
813 pub current_cpu_usage: f64,
815 pub current_memory_usage: f64,
817 pub current_rtf: f64,
819 pub current_success_rate: f64,
821 pub hourly_operations: u64,
823 pub hourly_audio_duration: f64,
825 pub daily_operations: u64,
827 pub daily_audio_duration: f64,
829 pub best_performance_window: Option<MetricsWindow>,
831 pub worst_performance_window: Option<MetricsWindow>,
833}
834
835impl StatisticsSummary {
836 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 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 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
892fn 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 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 for i in 0..10 {
986 let mut metrics = PerformanceMetrics::default();
987 metrics.synthesis.real_time_factor = 1.0 + (i as f64) * 0.1; 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 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 }
1033}