torsh_sparse/performance_tools/
reporting.rs

1//! # Reporting and Statistics Module
2//!
3//! This module provides comprehensive reporting and statistical analysis capabilities
4//! for sparse tensor performance data. It aggregates operation statistics, generates
5//! detailed reports, and provides analysis tools for performance optimization.
6//!
7//! ## Key Components
8//!
9//! - **OperationStatistics**: Detailed statistics for individual operations
10//! - **PerformanceReport**: Comprehensive performance reports with multiple metrics
11//! - **StatisticsCollector**: Utility for collecting and aggregating performance data
12//! - **Report generation and analysis tools**: Various utilities for report creation and analysis
13//!
14//! ## Usage Example
15//!
16//! ```rust
17//! use torsh_sparse::performance_tools::reporting::{OperationStatistics, PerformanceReport};
18//!
19//! // Create operation statistics
20//! let stats = OperationStatistics::new("sparse_matmul".to_string());
21//!
22//! // Generate comprehensive report
23//! let report = PerformanceReport::new();
24//! let fastest_op = report.find_fastest_operation("matmul");
25//! ```
26
27use std::collections::HashMap;
28use std::time::Duration;
29
30use super::core::PerformanceMeasurement;
31use super::memory::MemoryAnalysis;
32
33/// Detailed statistics for a specific operation type
34///
35/// This struct aggregates multiple performance measurements for a single operation
36/// type to provide comprehensive statistical analysis including timing, memory usage,
37/// and performance trends.
38#[derive(Debug, Clone)]
39pub struct OperationStatistics {
40    /// Operation name identifier
41    pub operation: String,
42    /// Total number of measurements
43    pub count: usize,
44    /// Total execution time across all measurements
45    pub total_time: Duration,
46    /// Minimum execution time observed
47    pub min_time: Duration,
48    /// Maximum execution time observed
49    pub max_time: Duration,
50    /// Average memory usage across measurements
51    pub avg_memory: f64,
52    /// Standard deviation of execution times
53    pub time_std_dev: f64,
54    /// Memory usage statistics
55    pub memory_stats: MemoryStatistics,
56    /// Custom operation metrics
57    pub custom_metrics: HashMap<String, MetricStatistics>,
58}
59
60/// Memory usage statistics aggregation
61#[derive(Debug, Clone, Default)]
62pub struct MemoryStatistics {
63    /// Average memory usage before operations
64    pub avg_memory_before: f64,
65    /// Average memory usage after operations
66    pub avg_memory_after: f64,
67    /// Average peak memory usage
68    pub avg_peak_memory: f64,
69    /// Average memory delta per operation
70    pub avg_memory_delta: f64,
71    /// Maximum memory delta observed
72    pub max_memory_delta: i64,
73    /// Minimum memory delta observed
74    pub min_memory_delta: i64,
75}
76
77/// Statistics for custom metrics
78#[derive(Debug, Clone)]
79pub struct MetricStatistics {
80    /// Metric name
81    pub name: String,
82    /// Number of data points
83    pub count: usize,
84    /// Sum of all values
85    pub sum: f64,
86    /// Minimum value
87    pub min: f64,
88    /// Maximum value
89    pub max: f64,
90    /// Standard deviation
91    pub std_dev: f64,
92}
93
94impl OperationStatistics {
95    /// Create new operation statistics for the given operation
96    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    /// Add a measurement to these statistics
111    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        // Update memory statistics
118        self.update_memory_stats(measurement);
119
120        // Update custom metrics
121        for (key, value) in &measurement.metrics {
122            self.update_custom_metric(key.clone(), *value);
123        }
124
125        // Recalculate derived statistics
126        self.recalculate_statistics();
127    }
128
129    /// Get average execution time
130    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    /// Get operations per second
139    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    /// Get coefficient of variation for timing (std_dev / mean)
148    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    /// Check if performance is consistent (low coefficient of variation)
158    pub fn is_consistent(&self) -> bool {
159        self.timing_consistency() < 0.2 // Less than 20% variation
160    }
161
162    /// Get memory efficiency score (0-1, higher is better)
163    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    /// Update memory statistics with new measurement
172    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        // Running average updates
177        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    /// Update custom metric statistics
192    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    /// Recalculate derived statistics (standard deviations, etc.)
212    fn recalculate_statistics(&mut self) {
213        // This is a simplified calculation - in practice, you'd track
214        // sum of squares for more accurate standard deviation calculation
215        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; // Rough approximation in milliseconds
217
218        // Update custom metric standard deviations
219        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; // Rough approximation
223            }
224        }
225    }
226}
227
228impl MetricStatistics {
229    /// Get average value
230    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    /// Get range (max - min)
239    pub fn range(&self) -> f64 {
240        self.max - self.min
241    }
242
243    /// Get coefficient of variation
244    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/// Comprehensive performance report containing aggregated statistics
255///
256/// This struct provides a complete overview of performance across all operations,
257/// including summaries, comparisons, and analysis capabilities.
258#[derive(Debug, Clone)]
259pub struct PerformanceReport {
260    /// Total number of measurements across all operations
261    pub total_measurements: usize,
262    /// Number of unique operations measured
263    pub operation_count: usize,
264    /// Statistics for each operation
265    pub operation_statistics: HashMap<String, OperationStatistics>,
266    /// Overall memory analysis results
267    pub memory_analyses: Vec<MemoryAnalysis>,
268    /// Report generation timestamp
269    pub generated_at: std::time::SystemTime,
270    /// Additional report metadata
271    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    /// Create a new empty performance report
323    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    /// Add measurements to the report
335    pub fn add_measurements(&mut self, measurements: &[PerformanceMeasurement]) {
336        for measurement in measurements {
337            self.add_measurement(measurement);
338        }
339    }
340
341    /// Add a single measurement to the report
342    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    /// Add memory analysis to the report
354    pub fn add_memory_analysis(&mut self, analysis: MemoryAnalysis) {
355        self.memory_analyses.push(analysis);
356    }
357
358    /// Find the fastest operation matching a pattern
359    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    /// Find the most memory-efficient operation matching a pattern
367    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    /// Get top N operations by throughput (operations per second)
382    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    /// Get operations with inconsistent performance
394    pub fn inconsistent_operations(&self) -> Vec<&OperationStatistics> {
395        self.operation_statistics
396            .values()
397            .filter(|stats| !stats.is_consistent())
398            .collect()
399    }
400
401    /// Get overall performance summary
402    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    /// Add metadata to the report
433    pub fn add_metadata(&mut self, key: String, value: String) {
434        self.metadata.insert(key, value);
435    }
436
437    /// Get recommendations based on performance analysis
438    pub fn get_recommendations(&self) -> Vec<String> {
439        let mut recommendations = Vec::new();
440
441        // Check for inconsistent operations
442        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        // Check for memory efficiency
456        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        // Check for slow operations
470        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        // Memory analysis recommendations
484        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    /// Calculate overall consistency score
501    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/// High-level performance summary
523#[derive(Debug, Clone)]
524pub struct PerformanceSummary {
525    /// Total number of operations measured
526    pub total_operations: usize,
527    /// Total execution time across all operations
528    pub total_time: Duration,
529    /// Average throughput across all operations
530    pub avg_throughput: f64,
531    /// Average memory efficiency score
532    pub avg_memory_efficiency: f64,
533    /// Consistency score (0-1, higher is better)
534    pub consistency_score: f64,
535}
536
537impl PerformanceSummary {
538    /// Get overall performance grade (A-F)
539    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/// Utility for collecting and aggregating performance statistics
555#[derive(Debug)]
556pub struct StatisticsCollector {
557    /// Collected measurements
558    measurements: Vec<PerformanceMeasurement>,
559    /// Memory analyses
560    memory_analyses: Vec<MemoryAnalysis>,
561    /// Collection metadata
562    metadata: HashMap<String, String>,
563}
564
565impl Default for StatisticsCollector {
566    fn default() -> Self {
567        Self::new()
568    }
569}
570
571impl StatisticsCollector {
572    /// Create a new statistics collector
573    pub fn new() -> Self {
574        Self {
575            measurements: Vec::new(),
576            memory_analyses: Vec::new(),
577            metadata: HashMap::new(),
578        }
579    }
580
581    /// Add a measurement to the collection
582    pub fn add_measurement(&mut self, measurement: PerformanceMeasurement) {
583        self.measurements.push(measurement);
584    }
585
586    /// Add multiple measurements
587    pub fn add_measurements(&mut self, measurements: Vec<PerformanceMeasurement>) {
588        self.measurements.extend(measurements);
589    }
590
591    /// Add a memory analysis
592    pub fn add_memory_analysis(&mut self, analysis: MemoryAnalysis) {
593        self.memory_analyses.push(analysis);
594    }
595
596    /// Add metadata
597    pub fn add_metadata(&mut self, key: String, value: String) {
598        self.metadata.insert(key, value);
599    }
600
601    /// Generate a comprehensive performance report
602    pub fn generate_report(&self) -> PerformanceReport {
603        let mut report = PerformanceReport::new();
604
605        // Add all measurements
606        report.add_measurements(&self.measurements);
607
608        // Add memory analyses
609        for analysis in &self.memory_analyses {
610            report.add_memory_analysis(analysis.clone());
611        }
612
613        // Add metadata
614        for (key, value) in &self.metadata {
615            report.add_metadata(key.clone(), value.clone());
616        }
617
618        report
619    }
620
621    /// Clear all collected data
622    pub fn clear(&mut self) {
623        self.measurements.clear();
624        self.memory_analyses.clear();
625        self.metadata.clear();
626    }
627
628    /// Get number of collected measurements
629    pub fn measurement_count(&self) -> usize {
630        self.measurements.len()
631    }
632
633    /// Get measurements for a specific operation
634    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)); // (100+200+300)/3
693        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        // Add measurements with similar timing
702        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        // Add measurements with varying timing
708        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)); // (100+150)/2
758    }
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        // fastest operation should have highest throughput
774        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        // Add inconsistent operation
782        report.add_measurement(&create_test_measurement("inconsistent_op", 50));
783        report.add_measurement(&create_test_measurement("inconsistent_op", 500));
784
785        // Add slow operation
786        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        // Should have recommendations about inconsistent and slow operations
794        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); // 2.5 / 5.0
847    }
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}