1use std::collections::HashMap;
28use std::time::Duration;
29
30use super::core::PerformanceMeasurement;
31use super::memory::MemoryAnalysis;
32
33#[derive(Debug, Clone)]
39pub struct OperationStatistics {
40 pub operation: String,
42 pub count: usize,
44 pub total_time: Duration,
46 pub min_time: Duration,
48 pub max_time: Duration,
50 pub avg_memory: f64,
52 pub time_std_dev: f64,
54 pub memory_stats: MemoryStatistics,
56 pub custom_metrics: HashMap<String, MetricStatistics>,
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct MemoryStatistics {
63 pub avg_memory_before: f64,
65 pub avg_memory_after: f64,
67 pub avg_peak_memory: f64,
69 pub avg_memory_delta: f64,
71 pub max_memory_delta: i64,
73 pub min_memory_delta: i64,
75}
76
77#[derive(Debug, Clone)]
79pub struct MetricStatistics {
80 pub name: String,
82 pub count: usize,
84 pub sum: f64,
86 pub min: f64,
88 pub max: f64,
90 pub std_dev: f64,
92}
93
94impl OperationStatistics {
95 pub fn new(operation: String) -> Self {
97 Self {
98 operation,
99 count: 0,
100 total_time: Duration::new(0, 0),
101 min_time: Duration::MAX,
102 max_time: Duration::new(0, 0),
103 avg_memory: 0.0,
104 time_std_dev: 0.0,
105 memory_stats: MemoryStatistics::default(),
106 custom_metrics: HashMap::new(),
107 }
108 }
109
110 pub fn add_measurement(&mut self, measurement: &PerformanceMeasurement) {
112 self.count += 1;
113 self.total_time += measurement.duration;
114 self.min_time = self.min_time.min(measurement.duration);
115 self.max_time = self.max_time.max(measurement.duration);
116
117 self.update_memory_stats(measurement);
119
120 for (key, value) in &measurement.metrics {
122 self.update_custom_metric(key.clone(), *value);
123 }
124
125 self.recalculate_statistics();
127 }
128
129 pub fn avg_time(&self) -> Duration {
131 if self.count > 0 {
132 self.total_time / self.count as u32
133 } else {
134 Duration::new(0, 0)
135 }
136 }
137
138 pub fn operations_per_second(&self) -> f64 {
140 if self.total_time.as_secs_f64() > 0.0 {
141 self.count as f64 / self.total_time.as_secs_f64()
142 } else {
143 0.0
144 }
145 }
146
147 pub fn timing_consistency(&self) -> f64 {
149 let avg_time_ms = self.avg_time().as_secs_f64() * 1000.0;
150 if avg_time_ms > 0.0 {
151 self.time_std_dev / avg_time_ms
152 } else {
153 0.0
154 }
155 }
156
157 pub fn is_consistent(&self) -> bool {
159 self.timing_consistency() < 0.2 }
161
162 pub fn memory_efficiency(&self) -> f64 {
164 if self.memory_stats.avg_peak_memory > self.memory_stats.avg_memory_after {
165 self.memory_stats.avg_memory_after / self.memory_stats.avg_peak_memory
166 } else {
167 1.0
168 }
169 }
170
171 fn update_memory_stats(&mut self, measurement: &PerformanceMeasurement) {
173 let n = self.count as f64;
174 let prev_n = (self.count - 1) as f64;
175
176 self.memory_stats.avg_memory_before =
178 (self.memory_stats.avg_memory_before * prev_n + measurement.memory_before as f64) / n;
179 self.memory_stats.avg_memory_after =
180 (self.memory_stats.avg_memory_after * prev_n + measurement.memory_after as f64) / n;
181 self.memory_stats.avg_peak_memory =
182 (self.memory_stats.avg_peak_memory * prev_n + measurement.peak_memory as f64) / n;
183
184 let memory_delta = measurement.memory_delta();
185 self.memory_stats.avg_memory_delta =
186 (self.memory_stats.avg_memory_delta * prev_n + memory_delta as f64) / n;
187 self.memory_stats.max_memory_delta = self.memory_stats.max_memory_delta.max(memory_delta);
188 self.memory_stats.min_memory_delta = self.memory_stats.min_memory_delta.min(memory_delta);
189 }
190
191 fn update_custom_metric(&mut self, metric_name: String, value: f64) {
193 let metric_stats = self
194 .custom_metrics
195 .entry(metric_name.clone())
196 .or_insert_with(|| MetricStatistics {
197 name: metric_name,
198 count: 0,
199 sum: 0.0,
200 min: f64::INFINITY,
201 max: f64::NEG_INFINITY,
202 std_dev: 0.0,
203 });
204
205 metric_stats.count += 1;
206 metric_stats.sum += value;
207 metric_stats.min = metric_stats.min.min(value);
208 metric_stats.max = metric_stats.max.max(value);
209 }
210
211 fn recalculate_statistics(&mut self) {
213 let time_range = self.max_time.as_secs_f64() - self.min_time.as_secs_f64();
216 self.time_std_dev = time_range * 1000.0 / 4.0; for metric_stats in self.custom_metrics.values_mut() {
220 if metric_stats.count > 1 {
221 let range = metric_stats.max - metric_stats.min;
222 metric_stats.std_dev = range / 4.0; }
224 }
225 }
226}
227
228impl MetricStatistics {
229 pub fn average(&self) -> f64 {
231 if self.count > 0 {
232 self.sum / self.count as f64
233 } else {
234 0.0
235 }
236 }
237
238 pub fn range(&self) -> f64 {
240 self.max - self.min
241 }
242
243 pub fn coefficient_of_variation(&self) -> f64 {
245 let avg = self.average();
246 if avg != 0.0 {
247 self.std_dev / avg.abs()
248 } else {
249 0.0
250 }
251 }
252}
253
254#[derive(Debug, Clone)]
259pub struct PerformanceReport {
260 pub total_measurements: usize,
262 pub operation_count: usize,
264 pub operation_statistics: HashMap<String, OperationStatistics>,
266 pub memory_analyses: Vec<MemoryAnalysis>,
268 pub generated_at: std::time::SystemTime,
270 pub metadata: HashMap<String, String>,
272}
273
274impl std::fmt::Display for PerformanceReport {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 writeln!(f, "=== Sparse Tensor Performance Report ===")?;
277 writeln!(f, "Generated at: {:?}", self.generated_at)?;
278 writeln!(f, "Total measurements: {}", self.total_measurements)?;
279 writeln!(f, "Unique operations: {}", self.operation_count)?;
280 writeln!(f)?;
281
282 writeln!(f, "Operation Statistics:")?;
283 writeln!(
284 f,
285 "{:<30} {:<8} {:<12} {:<12} {:<12} {:<10}",
286 "Operation", "Count", "Avg Time", "Min Time", "Max Time", "Ops/Sec"
287 )?;
288 writeln!(f, "{}", "-".repeat(90))?;
289
290 for (operation, stats) in &self.operation_statistics {
291 writeln!(
292 f,
293 "{:<30} {:<8} {:<12.3} {:<12.3} {:<12.3} {:<10.2}",
294 operation,
295 stats.count,
296 stats.avg_time().as_secs_f64() * 1000.0,
297 stats.min_time.as_secs_f64() * 1000.0,
298 stats.max_time.as_secs_f64() * 1000.0,
299 stats.operations_per_second()
300 )?;
301 }
302
303 if !self.memory_analyses.is_empty() {
304 writeln!(f)?;
305 writeln!(f, "Memory Analysis Summary:")?;
306 for analysis in &self.memory_analyses {
307 writeln!(
308 f,
309 " Format: {:?}, Compression: {:.1}x, Efficiency: {}",
310 analysis.format,
311 analysis.compression_ratio,
312 analysis.memory_efficiency_rating()
313 )?;
314 }
315 }
316
317 Ok(())
318 }
319}
320
321impl PerformanceReport {
322 pub fn new() -> Self {
324 Self {
325 total_measurements: 0,
326 operation_count: 0,
327 operation_statistics: HashMap::new(),
328 memory_analyses: Vec::new(),
329 generated_at: std::time::SystemTime::now(),
330 metadata: HashMap::new(),
331 }
332 }
333
334 pub fn add_measurements(&mut self, measurements: &[PerformanceMeasurement]) {
336 for measurement in measurements {
337 self.add_measurement(measurement);
338 }
339 }
340
341 pub fn add_measurement(&mut self, measurement: &PerformanceMeasurement) {
343 let stats = self
344 .operation_statistics
345 .entry(measurement.operation.clone())
346 .or_insert_with(|| OperationStatistics::new(measurement.operation.clone()));
347
348 stats.add_measurement(measurement);
349 self.total_measurements += 1;
350 self.operation_count = self.operation_statistics.len();
351 }
352
353 pub fn add_memory_analysis(&mut self, analysis: MemoryAnalysis) {
355 self.memory_analyses.push(analysis);
356 }
357
358 pub fn find_fastest_operation(&self, operation_pattern: &str) -> Option<&OperationStatistics> {
360 self.operation_statistics
361 .values()
362 .filter(|stats| stats.operation.contains(operation_pattern))
363 .min_by_key(|stats| stats.avg_time())
364 }
365
366 pub fn find_memory_efficient_operation(
368 &self,
369 operation_pattern: &str,
370 ) -> Option<&OperationStatistics> {
371 self.operation_statistics
372 .values()
373 .filter(|stats| stats.operation.contains(operation_pattern))
374 .max_by(|a, b| {
375 a.memory_efficiency()
376 .partial_cmp(&b.memory_efficiency())
377 .unwrap()
378 })
379 }
380
381 pub fn top_operations_by_throughput(&self, n: usize) -> Vec<&OperationStatistics> {
383 let mut operations: Vec<&OperationStatistics> =
384 self.operation_statistics.values().collect();
385 operations.sort_by(|a, b| {
386 b.operations_per_second()
387 .partial_cmp(&a.operations_per_second())
388 .unwrap()
389 });
390 operations.into_iter().take(n).collect()
391 }
392
393 pub fn inconsistent_operations(&self) -> Vec<&OperationStatistics> {
395 self.operation_statistics
396 .values()
397 .filter(|stats| !stats.is_consistent())
398 .collect()
399 }
400
401 pub fn performance_summary(&self) -> PerformanceSummary {
403 let total_time: Duration = self
404 .operation_statistics
405 .values()
406 .map(|stats| stats.total_time)
407 .sum();
408
409 let avg_operations_per_second: f64 = self
410 .operation_statistics
411 .values()
412 .map(|stats| stats.operations_per_second())
413 .sum::<f64>()
414 / self.operation_count.max(1) as f64;
415
416 let memory_efficiency: f64 = self
417 .operation_statistics
418 .values()
419 .map(|stats| stats.memory_efficiency())
420 .sum::<f64>()
421 / self.operation_count.max(1) as f64;
422
423 PerformanceSummary {
424 total_operations: self.total_measurements,
425 total_time,
426 avg_throughput: avg_operations_per_second,
427 avg_memory_efficiency: memory_efficiency,
428 consistency_score: self.calculate_consistency_score(),
429 }
430 }
431
432 pub fn add_metadata(&mut self, key: String, value: String) {
434 self.metadata.insert(key, value);
435 }
436
437 pub fn get_recommendations(&self) -> Vec<String> {
439 let mut recommendations = Vec::new();
440
441 let inconsistent = self.inconsistent_operations();
443 if !inconsistent.is_empty() {
444 recommendations.push(format!(
445 "Found {} operations with inconsistent performance - consider investigating: {}",
446 inconsistent.len(),
447 inconsistent
448 .iter()
449 .map(|op| op.operation.as_str())
450 .collect::<Vec<_>>()
451 .join(", ")
452 ));
453 }
454
455 let inefficient_ops: Vec<&OperationStatistics> = self
457 .operation_statistics
458 .values()
459 .filter(|stats| stats.memory_efficiency() < 0.7)
460 .collect();
461
462 if !inefficient_ops.is_empty() {
463 recommendations.push(format!(
464 "Found {} memory-inefficient operations - consider optimization",
465 inefficient_ops.len()
466 ));
467 }
468
469 let slow_ops: Vec<&OperationStatistics> = self
471 .operation_statistics
472 .values()
473 .filter(|stats| stats.operations_per_second() < 100.0)
474 .collect();
475
476 if !slow_ops.is_empty() {
477 recommendations.push(format!(
478 "Found {} slow operations (< 100 ops/sec) - consider algorithmic improvements",
479 slow_ops.len()
480 ));
481 }
482
483 for analysis in &self.memory_analyses {
485 if !analysis.is_memory_efficient() {
486 recommendations.push(format!(
487 "Format {:?} has poor compression ratio ({:.1}x) - consider alternative format",
488 analysis.format, analysis.compression_ratio
489 ));
490 }
491 }
492
493 if recommendations.is_empty() {
494 recommendations.push("Performance appears optimal across all metrics".to_string());
495 }
496
497 recommendations
498 }
499
500 fn calculate_consistency_score(&self) -> f64 {
502 if self.operation_statistics.is_empty() {
503 return 1.0;
504 }
505
506 let consistent_count = self
507 .operation_statistics
508 .values()
509 .filter(|stats| stats.is_consistent())
510 .count();
511
512 consistent_count as f64 / self.operation_statistics.len() as f64
513 }
514}
515
516impl Default for PerformanceReport {
517 fn default() -> Self {
518 Self::new()
519 }
520}
521
522#[derive(Debug, Clone)]
524pub struct PerformanceSummary {
525 pub total_operations: usize,
527 pub total_time: Duration,
529 pub avg_throughput: f64,
531 pub avg_memory_efficiency: f64,
533 pub consistency_score: f64,
535}
536
537impl PerformanceSummary {
538 pub fn performance_grade(&self) -> String {
540 let score =
541 (self.avg_throughput.log10() + self.avg_memory_efficiency + self.consistency_score)
542 / 3.0;
543
544 match score {
545 s if s >= 0.9 => "A".to_string(),
546 s if s >= 0.8 => "B".to_string(),
547 s if s >= 0.7 => "C".to_string(),
548 s if s >= 0.6 => "D".to_string(),
549 _ => "F".to_string(),
550 }
551 }
552}
553
554#[derive(Debug)]
556pub struct StatisticsCollector {
557 measurements: Vec<PerformanceMeasurement>,
559 memory_analyses: Vec<MemoryAnalysis>,
561 metadata: HashMap<String, String>,
563}
564
565impl Default for StatisticsCollector {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571impl StatisticsCollector {
572 pub fn new() -> Self {
574 Self {
575 measurements: Vec::new(),
576 memory_analyses: Vec::new(),
577 metadata: HashMap::new(),
578 }
579 }
580
581 pub fn add_measurement(&mut self, measurement: PerformanceMeasurement) {
583 self.measurements.push(measurement);
584 }
585
586 pub fn add_measurements(&mut self, measurements: Vec<PerformanceMeasurement>) {
588 self.measurements.extend(measurements);
589 }
590
591 pub fn add_memory_analysis(&mut self, analysis: MemoryAnalysis) {
593 self.memory_analyses.push(analysis);
594 }
595
596 pub fn add_metadata(&mut self, key: String, value: String) {
598 self.metadata.insert(key, value);
599 }
600
601 pub fn generate_report(&self) -> PerformanceReport {
603 let mut report = PerformanceReport::new();
604
605 report.add_measurements(&self.measurements);
607
608 for analysis in &self.memory_analyses {
610 report.add_memory_analysis(analysis.clone());
611 }
612
613 for (key, value) in &self.metadata {
615 report.add_metadata(key.clone(), value.clone());
616 }
617
618 report
619 }
620
621 pub fn clear(&mut self) {
623 self.measurements.clear();
624 self.memory_analyses.clear();
625 self.metadata.clear();
626 }
627
628 pub fn measurement_count(&self) -> usize {
630 self.measurements.len()
631 }
632
633 pub fn get_measurements_for_operation(
635 &self,
636 operation_pattern: &str,
637 ) -> Vec<&PerformanceMeasurement> {
638 self.measurements
639 .iter()
640 .filter(|m| m.operation.contains(operation_pattern))
641 .collect()
642 }
643}
644
645#[cfg(test)]
646mod tests {
647 use super::*;
648 use std::time::Duration;
649
650 fn create_test_measurement(operation: &str, duration_ms: u64) -> PerformanceMeasurement {
651 let mut measurement = PerformanceMeasurement::new(operation.to_string());
652 measurement.duration = Duration::from_millis(duration_ms);
653 measurement.memory_before = 1000;
654 measurement.memory_after = 1100;
655 measurement.peak_memory = 1200;
656 measurement
657 }
658
659 #[test]
660 fn test_operation_statistics_creation() {
661 let stats = OperationStatistics::new("test_operation".to_string());
662
663 assert_eq!(stats.operation, "test_operation");
664 assert_eq!(stats.count, 0);
665 assert_eq!(stats.total_time, Duration::new(0, 0));
666 assert_eq!(stats.min_time, Duration::MAX);
667 assert_eq!(stats.max_time, Duration::new(0, 0));
668 }
669
670 #[test]
671 fn test_operation_statistics_add_measurement() {
672 let mut stats = OperationStatistics::new("test".to_string());
673 let measurement = create_test_measurement("test", 100);
674
675 stats.add_measurement(&measurement);
676
677 assert_eq!(stats.count, 1);
678 assert_eq!(stats.avg_time(), Duration::from_millis(100));
679 assert_eq!(stats.min_time, Duration::from_millis(100));
680 assert_eq!(stats.max_time, Duration::from_millis(100));
681 }
682
683 #[test]
684 fn test_operation_statistics_multiple_measurements() {
685 let mut stats = OperationStatistics::new("test".to_string());
686
687 stats.add_measurement(&create_test_measurement("test", 100));
688 stats.add_measurement(&create_test_measurement("test", 200));
689 stats.add_measurement(&create_test_measurement("test", 300));
690
691 assert_eq!(stats.count, 3);
692 assert_eq!(stats.avg_time(), Duration::from_millis(200)); assert_eq!(stats.min_time, Duration::from_millis(100));
694 assert_eq!(stats.max_time, Duration::from_millis(300));
695 assert!(stats.operations_per_second() > 0.0);
696 }
697
698 #[test]
699 fn test_operation_statistics_consistency() {
700 let mut consistent_stats = OperationStatistics::new("consistent".to_string());
701 for _ in 0..5 {
703 consistent_stats.add_measurement(&create_test_measurement("consistent", 100));
704 }
705
706 let mut inconsistent_stats = OperationStatistics::new("inconsistent".to_string());
707 inconsistent_stats.add_measurement(&create_test_measurement("inconsistent", 50));
709 inconsistent_stats.add_measurement(&create_test_measurement("inconsistent", 200));
710 inconsistent_stats.add_measurement(&create_test_measurement("inconsistent", 500));
711
712 assert!(consistent_stats.is_consistent());
713 assert!(!inconsistent_stats.is_consistent());
714 }
715
716 #[test]
717 fn test_memory_statistics() {
718 let mut stats = OperationStatistics::new("test".to_string());
719 let measurement = create_test_measurement("test", 100);
720
721 stats.add_measurement(&measurement);
722
723 assert_eq!(stats.memory_stats.avg_memory_before, 1000.0);
724 assert_eq!(stats.memory_stats.avg_memory_after, 1100.0);
725 assert_eq!(stats.memory_stats.avg_peak_memory, 1200.0);
726 assert_eq!(stats.memory_stats.avg_memory_delta, 100.0);
727 }
728
729 #[test]
730 fn test_performance_report_creation() {
731 let report = PerformanceReport::new();
732
733 assert_eq!(report.total_measurements, 0);
734 assert_eq!(report.operation_count, 0);
735 assert!(report.operation_statistics.is_empty());
736 assert!(report.memory_analyses.is_empty());
737 }
738
739 #[test]
740 fn test_performance_report_add_measurements() {
741 let mut report = PerformanceReport::new();
742 let measurements = vec![
743 create_test_measurement("op1", 100),
744 create_test_measurement("op2", 200),
745 create_test_measurement("op1", 150),
746 ];
747
748 report.add_measurements(&measurements);
749
750 assert_eq!(report.total_measurements, 3);
751 assert_eq!(report.operation_count, 2);
752 assert!(report.operation_statistics.contains_key("op1"));
753 assert!(report.operation_statistics.contains_key("op2"));
754
755 let op1_stats = &report.operation_statistics["op1"];
756 assert_eq!(op1_stats.count, 2);
757 assert_eq!(op1_stats.avg_time(), Duration::from_millis(125)); }
759
760 #[test]
761 fn test_performance_report_find_operations() {
762 let mut report = PerformanceReport::new();
763 report.add_measurement(&create_test_measurement("fast_operation", 50));
764 report.add_measurement(&create_test_measurement("slow_operation", 500));
765 report.add_measurement(&create_test_measurement("medium_operation", 200));
766
767 let fastest = report.find_fastest_operation("operation");
768 assert!(fastest.is_some());
769 assert_eq!(fastest.unwrap().operation, "fast_operation");
770
771 let top_ops = report.top_operations_by_throughput(2);
772 assert_eq!(top_ops.len(), 2);
773 assert_eq!(top_ops[0].operation, "fast_operation");
775 }
776
777 #[test]
778 fn test_performance_report_recommendations() {
779 let mut report = PerformanceReport::new();
780
781 report.add_measurement(&create_test_measurement("inconsistent_op", 50));
783 report.add_measurement(&create_test_measurement("inconsistent_op", 500));
784
785 let mut slow_measurement = create_test_measurement("slow_op", 10000);
787 slow_measurement.add_metric("custom_metric".to_string(), 1.0);
788 report.add_measurement(&slow_measurement);
789
790 let recommendations = report.get_recommendations();
791 assert!(!recommendations.is_empty());
792
793 let rec_text = recommendations.join(" ");
795 assert!(rec_text.contains("inconsistent") || rec_text.contains("slow"));
796 }
797
798 #[test]
799 fn test_performance_summary() {
800 let mut report = PerformanceReport::new();
801 report.add_measurement(&create_test_measurement("op1", 100));
802 report.add_measurement(&create_test_measurement("op2", 200));
803
804 let summary = report.performance_summary();
805 assert_eq!(summary.total_operations, 2);
806 assert!(summary.avg_throughput > 0.0);
807 assert!(summary.avg_memory_efficiency >= 0.0 && summary.avg_memory_efficiency <= 1.0);
808 assert!(summary.consistency_score >= 0.0 && summary.consistency_score <= 1.0);
809
810 let grade = summary.performance_grade();
811 assert!(["A", "B", "C", "D", "F"].contains(&grade.as_str()));
812 }
813
814 #[test]
815 fn test_statistics_collector() {
816 let mut collector = StatisticsCollector::new();
817
818 collector.add_measurement(create_test_measurement("op1", 100));
819 collector.add_measurement(create_test_measurement("op2", 200));
820 collector.add_metadata("test_key".to_string(), "test_value".to_string());
821
822 assert_eq!(collector.measurement_count(), 2);
823
824 let report = collector.generate_report();
825 assert_eq!(report.total_measurements, 2);
826 assert_eq!(report.operation_count, 2);
827 assert!(report.metadata.contains_key("test_key"));
828
829 collector.clear();
830 assert_eq!(collector.measurement_count(), 0);
831 }
832
833 #[test]
834 fn test_metric_statistics() {
835 let mut metric_stats = MetricStatistics {
836 name: "test_metric".to_string(),
837 count: 3,
838 sum: 15.0,
839 min: 2.0,
840 max: 8.0,
841 std_dev: 2.5,
842 };
843
844 assert_eq!(metric_stats.average(), 5.0);
845 assert_eq!(metric_stats.range(), 6.0);
846 assert_eq!(metric_stats.coefficient_of_variation(), 0.5); }
848
849 #[test]
850 fn test_performance_report_display() {
851 let mut report = PerformanceReport::new();
852 report.add_measurement(&create_test_measurement("test_op", 100));
853
854 let display_string = format!("{}", report);
855 assert!(display_string.contains("Sparse Tensor Performance Report"));
856 assert!(display_string.contains("test_op"));
857 assert!(display_string.contains("Total measurements: 1"));
858 }
859
860 #[test]
861 fn test_memory_statistics_defaults() {
862 let memory_stats = MemoryStatistics::default();
863 assert_eq!(memory_stats.avg_memory_before, 0.0);
864 assert_eq!(memory_stats.avg_memory_after, 0.0);
865 assert_eq!(memory_stats.avg_peak_memory, 0.0);
866 assert_eq!(memory_stats.avg_memory_delta, 0.0);
867 assert_eq!(memory_stats.max_memory_delta, 0);
868 assert_eq!(memory_stats.min_memory_delta, 0);
869 }
870}