Skip to main content

sklears_neural/
performance_testing.rs

1//! Performance testing and benchmarking utilities for neural networks.
2//!
3//! This module provides comprehensive performance testing capabilities including
4//! memory usage monitoring, execution time benchmarking, regression detection,
5//! and comparison with other frameworks. It enables systematic performance
6//! analysis and optimization of neural network implementations.
7
8use scirs2_core::ndarray::Array2;
9use sklears_core::error::SklearsError;
10use std::collections::HashMap;
11use std::fmt;
12use std::sync::{Arc, Mutex};
13use std::time::{Duration, Instant};
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18/// Result type for performance testing operations
19pub type PerformanceResult<T> = Result<T, SklearsError>;
20
21/// Memory usage statistics
22#[derive(Debug, Clone, PartialEq)]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub struct MemoryStats {
25    /// Peak memory usage in bytes
26    pub peak_usage_bytes: u64,
27    /// Current memory usage in bytes
28    pub current_usage_bytes: u64,
29    /// Memory allocated during operation
30    pub allocated_bytes: u64,
31    /// Memory deallocated during operation
32    pub deallocated_bytes: u64,
33    /// Number of allocations
34    pub allocation_count: u64,
35    /// Number of deallocations
36    pub deallocation_count: u64,
37}
38
39impl MemoryStats {
40    /// Create a zeroed `MemoryStats` with no recorded allocations or usage
41    pub fn new() -> Self {
42        Self {
43            peak_usage_bytes: 0,
44            current_usage_bytes: 0,
45            allocated_bytes: 0,
46            deallocated_bytes: 0,
47            allocation_count: 0,
48            deallocation_count: 0,
49        }
50    }
51
52    /// Calculate memory efficiency (deallocated / allocated)
53    pub fn efficiency_ratio(&self) -> f64 {
54        if self.allocated_bytes > 0 {
55            self.deallocated_bytes as f64 / self.allocated_bytes as f64
56        } else {
57            1.0
58        }
59    }
60
61    /// Calculate average allocation size
62    pub fn avg_allocation_size(&self) -> f64 {
63        if self.allocation_count > 0 {
64            self.allocated_bytes as f64 / self.allocation_count as f64
65        } else {
66            0.0
67        }
68    }
69}
70
71impl Default for MemoryStats {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77/// Performance measurement results
78#[derive(Debug, Clone)]
79#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
80pub struct PerformanceMetrics {
81    /// Execution time statistics
82    pub execution_time: Duration,
83    /// Memory usage statistics
84    pub memory_stats: MemoryStats,
85    /// Operations per second
86    pub ops_per_second: f64,
87    /// Throughput in samples per second
88    pub samples_per_second: f64,
89    /// CPU usage percentage
90    pub cpu_usage_percent: f64,
91    /// GPU usage percentage (if available)
92    pub gpu_usage_percent: Option<f64>,
93    /// Additional custom metrics
94    pub custom_metrics: HashMap<String, f64>,
95}
96
97impl PerformanceMetrics {
98    /// Create a new `PerformanceMetrics` with the given execution time and memory statistics; other fields default to zero
99    pub fn new(execution_time: Duration, memory_stats: MemoryStats) -> Self {
100        Self {
101            execution_time,
102            memory_stats,
103            ops_per_second: 0.0,
104            samples_per_second: 0.0,
105            cpu_usage_percent: 0.0,
106            gpu_usage_percent: None,
107            custom_metrics: HashMap::new(),
108        }
109    }
110
111    /// Set the throughput in operations per second
112    pub fn with_ops_per_second(mut self, ops_per_second: f64) -> Self {
113        self.ops_per_second = ops_per_second;
114        self
115    }
116
117    /// Set the throughput in training or inference samples per second
118    pub fn with_samples_per_second(mut self, samples_per_second: f64) -> Self {
119        self.samples_per_second = samples_per_second;
120        self
121    }
122
123    /// Set the measured CPU utilization percentage
124    pub fn with_cpu_usage(mut self, cpu_usage_percent: f64) -> Self {
125        self.cpu_usage_percent = cpu_usage_percent;
126        self
127    }
128
129    /// Set the measured GPU utilization percentage
130    pub fn with_gpu_usage(mut self, gpu_usage_percent: f64) -> Self {
131        self.gpu_usage_percent = Some(gpu_usage_percent);
132        self
133    }
134
135    /// Insert an arbitrary named metric into the custom metrics map
136    pub fn add_custom_metric(&mut self, name: String, value: f64) {
137        self.custom_metrics.insert(name, value);
138    }
139}
140
141/// Benchmark comparison results
142#[derive(Debug, Clone)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub struct BenchmarkComparison {
145    /// Name of the benchmark
146    pub benchmark_name: String,
147    /// Baseline performance metrics
148    pub baseline: PerformanceMetrics,
149    /// Current performance metrics
150    pub current: PerformanceMetrics,
151    /// Performance ratio (current / baseline)
152    pub performance_ratio: f64,
153    /// Memory ratio (current / baseline)
154    pub memory_ratio: f64,
155    /// Regression detected flag
156    pub regression_detected: bool,
157    /// Improvement percentage
158    pub improvement_percent: f64,
159}
160
161impl BenchmarkComparison {
162    /// Compute performance and memory ratios by comparing `current` against the `baseline`
163    pub fn new(
164        benchmark_name: String,
165        baseline: PerformanceMetrics,
166        current: PerformanceMetrics,
167    ) -> Self {
168        let baseline_time = baseline.execution_time.as_secs_f64();
169        let current_time = current.execution_time.as_secs_f64();
170
171        let performance_ratio = if baseline_time > 0.0 {
172            current_time / baseline_time
173        } else {
174            1.0
175        };
176
177        let memory_ratio = if baseline.memory_stats.peak_usage_bytes > 0 {
178            current.memory_stats.peak_usage_bytes as f64
179                / baseline.memory_stats.peak_usage_bytes as f64
180        } else {
181            1.0
182        };
183
184        let improvement_percent = (1.0 - performance_ratio) * 100.0;
185        let regression_detected = performance_ratio > 1.1; // 10% threshold
186
187        Self {
188            benchmark_name,
189            baseline,
190            current,
191            performance_ratio,
192            memory_ratio,
193            regression_detected,
194            improvement_percent,
195        }
196    }
197
198    /// Check if there's a significant improvement (>5%)
199    pub fn has_improvement(&self) -> bool {
200        self.improvement_percent > 5.0
201    }
202
203    /// Check if there's a significant memory increase (>20%)
204    pub fn has_memory_regression(&self) -> bool {
205        self.memory_ratio > 1.2
206    }
207}
208
209/// Performance profiler for monitoring execution
210pub struct PerformanceProfiler {
211    start_time: Option<Instant>,
212    memory_tracker: Arc<Mutex<MemoryStats>>,
213    operation_count: u64,
214    sample_count: u64,
215}
216
217impl PerformanceProfiler {
218    /// Create a new profiler in the stopped state with zeroed counters
219    pub fn new() -> Self {
220        Self {
221            start_time: None,
222            memory_tracker: Arc::new(Mutex::new(MemoryStats::new())),
223            operation_count: 0,
224            sample_count: 0,
225        }
226    }
227
228    /// Start profiling
229    pub fn start(&mut self) {
230        self.start_time = Some(Instant::now());
231    }
232
233    /// Stop profiling and return metrics
234    pub fn stop(&self) -> PerformanceResult<PerformanceMetrics> {
235        let start_time = self
236            .start_time
237            .ok_or_else(|| SklearsError::InvalidParameter {
238                name: "profiler_state".to_string(),
239                reason: "Profiler was not started".to_string(),
240            })?;
241
242        let execution_time = start_time.elapsed();
243        let memory_stats = self
244            .memory_tracker
245            .lock()
246            .map_err(|_| SklearsError::InvalidParameter {
247                name: "memory_tracker".to_string(),
248                reason: "Failed to acquire memory tracker lock".to_string(),
249            })?
250            .clone();
251
252        let ops_per_second = if execution_time.as_secs_f64() > 0.0 {
253            self.operation_count as f64 / execution_time.as_secs_f64()
254        } else {
255            0.0
256        };
257
258        let samples_per_second = if execution_time.as_secs_f64() > 0.0 {
259            self.sample_count as f64 / execution_time.as_secs_f64()
260        } else {
261            0.0
262        };
263
264        Ok(PerformanceMetrics::new(execution_time, memory_stats)
265            .with_ops_per_second(ops_per_second)
266            .with_samples_per_second(samples_per_second))
267    }
268
269    /// Record an operation
270    pub fn record_operation(&mut self) {
271        self.operation_count += 1;
272    }
273
274    /// Record multiple operations
275    pub fn record_operations(&mut self, count: u64) {
276        self.operation_count += count;
277    }
278
279    /// Record samples processed
280    pub fn record_samples(&mut self, count: u64) {
281        self.sample_count += count;
282    }
283
284    /// Simulate memory allocation (for testing purposes)
285    pub fn record_allocation(&self, bytes: u64) {
286        if let Ok(mut stats) = self.memory_tracker.lock() {
287            stats.allocated_bytes += bytes;
288            stats.allocation_count += 1;
289            stats.current_usage_bytes += bytes;
290
291            if stats.current_usage_bytes > stats.peak_usage_bytes {
292                stats.peak_usage_bytes = stats.current_usage_bytes;
293            }
294        }
295    }
296
297    /// Simulate memory deallocation (for testing purposes)
298    pub fn record_deallocation(&self, bytes: u64) {
299        if let Ok(mut stats) = self.memory_tracker.lock() {
300            stats.deallocated_bytes += bytes;
301            stats.deallocation_count += 1;
302            stats.current_usage_bytes = stats.current_usage_bytes.saturating_sub(bytes);
303        }
304    }
305}
306
307impl Default for PerformanceProfiler {
308    fn default() -> Self {
309        Self::new()
310    }
311}
312
313/// Benchmark suite for comprehensive performance testing
314pub struct BenchmarkSuite {
315    benchmarks: HashMap<String, Box<dyn Fn() -> PerformanceResult<PerformanceMetrics>>>,
316    baselines: HashMap<String, PerformanceMetrics>,
317    results: HashMap<String, BenchmarkComparison>,
318}
319
320impl BenchmarkSuite {
321    /// Create an empty benchmark suite with no registered benchmarks or baselines
322    pub fn new() -> Self {
323        Self {
324            benchmarks: HashMap::new(),
325            baselines: HashMap::new(),
326            results: HashMap::new(),
327        }
328    }
329
330    /// Add a benchmark function
331    pub fn add_benchmark<F>(&mut self, name: String, benchmark_fn: F)
332    where
333        F: Fn() -> PerformanceResult<PerformanceMetrics> + 'static,
334    {
335        self.benchmarks.insert(name, Box::new(benchmark_fn));
336    }
337
338    /// Set baseline for a benchmark
339    pub fn set_baseline(&mut self, name: String, baseline: PerformanceMetrics) {
340        self.baselines.insert(name, baseline);
341    }
342
343    /// Run all benchmarks
344    pub fn run_all(&mut self) -> PerformanceResult<()> {
345        for (name, benchmark_fn) in &self.benchmarks {
346            let current_metrics = benchmark_fn()?;
347
348            if let Some(baseline) = self.baselines.get(name) {
349                let comparison =
350                    BenchmarkComparison::new(name.clone(), baseline.clone(), current_metrics);
351                self.results.insert(name.clone(), comparison);
352            }
353        }
354        Ok(())
355    }
356
357    /// Run a specific benchmark
358    pub fn run_benchmark(&mut self, name: &str) -> PerformanceResult<()> {
359        if let Some(benchmark_fn) = self.benchmarks.get(name) {
360            let current_metrics = benchmark_fn()?;
361
362            if let Some(baseline) = self.baselines.get(name) {
363                let comparison =
364                    BenchmarkComparison::new(name.to_string(), baseline.clone(), current_metrics);
365                self.results.insert(name.to_string(), comparison);
366            }
367        } else {
368            return Err(SklearsError::InvalidParameter {
369                name: "benchmark_name".to_string(),
370                reason: format!("Benchmark '{}' not found", name),
371            });
372        }
373        Ok(())
374    }
375
376    /// Get benchmark results
377    pub fn get_results(&self) -> &HashMap<String, BenchmarkComparison> {
378        &self.results
379    }
380
381    /// Check if any regressions were detected
382    pub fn has_regressions(&self) -> bool {
383        self.results.values().any(|r| r.regression_detected)
384    }
385
386    /// Get regressions
387    pub fn get_regressions(&self) -> Vec<&BenchmarkComparison> {
388        self.results
389            .values()
390            .filter(|r| r.regression_detected)
391            .collect()
392    }
393
394    /// Generate performance report
395    pub fn generate_report(&self) -> PerformanceReport {
396        let total_benchmarks = self.results.len();
397        let regressions = self.get_regressions();
398        let regression_count = regressions.len();
399
400        let improvements: Vec<&BenchmarkComparison> = self
401            .results
402            .values()
403            .filter(|r| r.has_improvement())
404            .collect();
405        let improvement_count = improvements.len();
406
407        let memory_regressions: Vec<&BenchmarkComparison> = self
408            .results
409            .values()
410            .filter(|r| r.has_memory_regression())
411            .collect();
412        let memory_regression_count = memory_regressions.len();
413
414        PerformanceReport {
415            total_benchmarks,
416            regression_count,
417            improvement_count,
418            memory_regression_count,
419            overall_status: if regression_count == 0 {
420                PerformanceStatus::Pass
421            } else {
422                PerformanceStatus::Fail
423            },
424            regressions: regressions.into_iter().cloned().collect(),
425            improvements: improvements.into_iter().cloned().collect(),
426            memory_regressions: memory_regressions.into_iter().cloned().collect(),
427        }
428    }
429}
430
431impl Default for BenchmarkSuite {
432    fn default() -> Self {
433        Self::new()
434    }
435}
436
437/// Performance test status
438#[derive(Debug, Clone, PartialEq)]
439#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
440pub enum PerformanceStatus {
441    /// All benchmarks within acceptable bounds
442    Pass,
443    /// One or more regressions detected above the failure threshold
444    Fail,
445    /// At least one regression detected but below the failure threshold
446    Warning,
447}
448
449/// Comprehensive performance report
450#[derive(Debug, Clone)]
451#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
452pub struct PerformanceReport {
453    /// Total number of benchmarks that were run and compared
454    pub total_benchmarks: usize,
455    /// Number of benchmarks with a detected performance regression
456    pub regression_count: usize,
457    /// Number of benchmarks showing a measurable performance improvement
458    pub improvement_count: usize,
459    /// Number of benchmarks with a detected memory usage regression
460    pub memory_regression_count: usize,
461    /// Aggregate pass/fail/warning status for the entire test run
462    pub overall_status: PerformanceStatus,
463    /// Details of each benchmark with a detected regression
464    pub regressions: Vec<BenchmarkComparison>,
465    /// Details of each benchmark with a measured improvement
466    pub improvements: Vec<BenchmarkComparison>,
467    /// Details of each benchmark with increased memory usage
468    pub memory_regressions: Vec<BenchmarkComparison>,
469}
470
471impl fmt::Display for PerformanceReport {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        writeln!(f, "=== Performance Test Report ===")?;
474        writeln!(f, "Total Benchmarks: {}", self.total_benchmarks)?;
475        writeln!(f, "Overall Status: {:?}", self.overall_status)?;
476        writeln!(f)?;
477
478        writeln!(f, "Improvements: {} benchmarks", self.improvement_count)?;
479        for improvement in &self.improvements {
480            writeln!(
481                f,
482                "  {}: {:.1}% faster",
483                improvement.benchmark_name, improvement.improvement_percent
484            )?;
485        }
486        writeln!(f)?;
487
488        writeln!(
489            f,
490            "Performance Regressions: {} benchmarks",
491            self.regression_count
492        )?;
493        for regression in &self.regressions {
494            writeln!(
495                f,
496                "  {} {:.1}% slower (ratio: {:.2})",
497                regression.benchmark_name,
498                -regression.improvement_percent,
499                regression.performance_ratio
500            )?;
501        }
502        writeln!(f)?;
503
504        writeln!(
505            f,
506            "Memory Regressions: {} benchmarks",
507            self.memory_regression_count
508        )?;
509        for mem_regression in &self.memory_regressions {
510            writeln!(
511                f,
512                "  {}: {:.1}% more memory (ratio: {:.2})",
513                mem_regression.benchmark_name,
514                (mem_regression.memory_ratio - 1.0) * 100.0,
515                mem_regression.memory_ratio
516            )?;
517        }
518
519        Ok(())
520    }
521}
522
523/// Memory leak detector
524#[allow(dead_code)] // allocation_tracker retained for async leak detection in future multi-threaded profiling
525pub struct MemoryLeakDetector {
526    initial_memory: u64,
527    allocations: HashMap<usize, u64>,
528    allocation_tracker: Arc<Mutex<u64>>,
529}
530
531impl MemoryLeakDetector {
532    /// Create a new leak detector, recording the current memory usage as the baseline
533    pub fn new() -> Self {
534        Self {
535            initial_memory: Self::get_current_memory_usage(),
536            allocations: HashMap::new(),
537            allocation_tracker: Arc::new(Mutex::new(0)),
538        }
539    }
540
541    /// Start monitoring for memory leaks
542    pub fn start_monitoring(&mut self) {
543        self.initial_memory = Self::get_current_memory_usage();
544        self.allocations.clear();
545    }
546
547    /// Check for memory leaks
548    pub fn check_for_leaks(&self) -> MemoryLeakReport {
549        let current_memory = Self::get_current_memory_usage();
550        let memory_increase = current_memory.saturating_sub(self.initial_memory);
551
552        let leak_threshold = 1024 * 1024; // 1MB threshold
553        let has_leak = memory_increase > leak_threshold;
554
555        let active_allocations = self.allocations.len();
556        let total_allocated = self.allocations.values().sum::<u64>();
557
558        MemoryLeakReport {
559            initial_memory: self.initial_memory,
560            current_memory,
561            memory_increase,
562            has_leak,
563            leak_threshold,
564            active_allocations,
565            total_allocated,
566        }
567    }
568
569    /// Simulate allocation tracking (for testing purposes)
570    pub fn track_allocation(&mut self, ptr: usize, size: u64) {
571        self.allocations.insert(ptr, size);
572    }
573
574    /// Simulate deallocation tracking (for testing purposes)
575    pub fn track_deallocation(&mut self, ptr: usize) {
576        self.allocations.remove(&ptr);
577    }
578
579    /// Get current memory usage (simplified implementation)
580    fn get_current_memory_usage() -> u64 {
581        // This is a simplified implementation
582        // In a real scenario, you'd use platform-specific APIs
583        // or memory profiling tools to get accurate memory usage
584        std::process::id() as u64 * 1024 // Placeholder
585    }
586}
587
588impl Default for MemoryLeakDetector {
589    fn default() -> Self {
590        Self::new()
591    }
592}
593
594/// Memory leak detection report
595#[derive(Debug, Clone)]
596#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
597pub struct MemoryLeakReport {
598    /// Memory usage (bytes) when monitoring started
599    pub initial_memory: u64,
600    /// Memory usage (bytes) at the time of this report
601    pub current_memory: u64,
602    /// Bytes of memory added since monitoring started (`current - initial`, saturating)
603    pub memory_increase: u64,
604    /// `true` if `memory_increase` exceeds `leak_threshold`
605    pub has_leak: bool,
606    /// Threshold in bytes above which a leak is declared
607    pub leak_threshold: u64,
608    /// Number of tracked allocations that have not yet been freed
609    pub active_allocations: usize,
610    /// Sum of bytes across all active tracked allocations
611    pub total_allocated: u64,
612}
613
614impl fmt::Display for MemoryLeakReport {
615    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616        writeln!(f, "=== Memory Leak Detection Report ===")?;
617        writeln!(f, "Initial Memory: {} bytes", self.initial_memory)?;
618        writeln!(f, "Current Memory: {} bytes", self.current_memory)?;
619        writeln!(f, "Memory Increase: {} bytes", self.memory_increase)?;
620        writeln!(f, "Leak Detected: {}", self.has_leak)?;
621        writeln!(f, "Active Allocations: {}", self.active_allocations)?;
622        writeln!(f, "Total Allocated: {} bytes", self.total_allocated)?;
623
624        if self.has_leak {
625            writeln!(
626                f,
627                "⚠️  Memory leak detected! Increase exceeds threshold of {} bytes",
628                self.leak_threshold
629            )?;
630        } else {
631            writeln!(f, "✅ No memory leaks detected")?;
632        }
633
634        Ok(())
635    }
636}
637
638/// Utility functions for performance testing
639pub mod utils {
640    use super::*;
641
642    /// Benchmark a function and return performance metrics
643    pub fn benchmark_function<F, T>(f: F, iterations: u32) -> PerformanceResult<PerformanceMetrics>
644    where
645        F: Fn() -> T,
646    {
647        let mut profiler = PerformanceProfiler::new();
648        profiler.start();
649
650        for _ in 0..iterations {
651            let _ = f();
652            profiler.record_operation();
653        }
654
655        profiler.stop()
656    }
657
658    /// Benchmark matrix multiplication
659    pub fn benchmark_matrix_multiply(
660        a: &Array2<f64>,
661        b: &Array2<f64>,
662        iterations: u32,
663    ) -> PerformanceResult<PerformanceMetrics> {
664        benchmark_function(|| a.dot(b), iterations)
665    }
666
667    /// Benchmark neural network forward pass
668    pub fn benchmark_forward_pass<F>(
669        forward_fn: F,
670        input: &Array2<f64>,
671        iterations: u32,
672    ) -> PerformanceResult<PerformanceMetrics>
673    where
674        F: Fn(&Array2<f64>) -> Array2<f64>,
675    {
676        let mut profiler = PerformanceProfiler::new();
677        profiler.start();
678
679        for _ in 0..iterations {
680            let _ = forward_fn(input);
681            profiler.record_operation();
682            profiler.record_samples(input.nrows() as u64);
683        }
684
685        profiler.stop()
686    }
687
688    /// Create a standard benchmark suite for neural networks
689    pub fn create_standard_benchmark_suite() -> BenchmarkSuite {
690        let mut suite = BenchmarkSuite::new();
691
692        // Matrix multiplication benchmark
693        suite.add_benchmark("matrix_multiply_100x100".to_string(), || {
694            let a = Array2::ones((100, 100));
695            let b = Array2::ones((100, 100));
696            benchmark_matrix_multiply(&a, &b, 1000)
697        });
698
699        // Large matrix multiplication benchmark
700        suite.add_benchmark("matrix_multiply_1000x1000".to_string(), || {
701            let a = Array2::ones((1000, 1000));
702            let b = Array2::ones((1000, 1000));
703            benchmark_matrix_multiply(&a, &b, 10)
704        });
705
706        suite
707    }
708}
709
710#[allow(non_snake_case)]
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use approx;
715
716    #[test]
717    fn test_memory_stats() {
718        let mut stats = MemoryStats::new();
719        stats.allocated_bytes = 1000;
720        stats.deallocated_bytes = 800;
721        stats.allocation_count = 10;
722
723        assert_eq!(stats.efficiency_ratio(), 0.8);
724        assert_eq!(stats.avg_allocation_size(), 100.0);
725    }
726
727    #[test]
728    fn test_performance_profiler() {
729        let mut profiler = PerformanceProfiler::new();
730        profiler.start();
731
732        // Simulate some work
733        std::thread::sleep(Duration::from_millis(10));
734        profiler.record_operation();
735        profiler.record_samples(100);
736
737        let metrics = profiler.stop().expect("operation should succeed");
738        assert!(metrics.execution_time >= Duration::from_millis(10));
739        assert!(metrics.ops_per_second > 0.0);
740        assert!(metrics.samples_per_second > 0.0);
741    }
742
743    #[test]
744    fn test_benchmark_comparison() {
745        let baseline = PerformanceMetrics::new(Duration::from_millis(100), MemoryStats::new());
746
747        let improved = PerformanceMetrics::new(Duration::from_millis(80), MemoryStats::new());
748
749        let comparison = BenchmarkComparison::new("test_benchmark".to_string(), baseline, improved);
750
751        assert!(comparison.has_improvement());
752        assert!(!comparison.regression_detected);
753        approx::assert_abs_diff_eq!(comparison.improvement_percent, 20.0, epsilon = 1e-10);
754    }
755
756    #[test]
757    fn test_memory_leak_detector() {
758        let mut detector = MemoryLeakDetector::new();
759        detector.start_monitoring();
760
761        // Simulate allocations
762        detector.track_allocation(0x1000, 1024);
763        detector.track_allocation(0x2000, 2048);
764
765        let report = detector.check_for_leaks();
766        assert_eq!(report.active_allocations, 2);
767        assert_eq!(report.total_allocated, 3072);
768
769        // Simulate deallocation
770        detector.track_deallocation(0x1000);
771        let report = detector.check_for_leaks();
772        assert_eq!(report.active_allocations, 1);
773        assert_eq!(report.total_allocated, 2048);
774    }
775
776    #[test]
777    fn test_benchmark_suite() {
778        let mut suite = BenchmarkSuite::new();
779
780        // Add a simple benchmark
781        suite.add_benchmark("simple_add".to_string(), || {
782            let mut profiler = PerformanceProfiler::new();
783            profiler.start();
784
785            let _ = 1 + 1;
786            profiler.record_operation();
787
788            profiler.stop()
789        });
790
791        // Set a baseline
792        let baseline = PerformanceMetrics::new(Duration::from_millis(1), MemoryStats::new());
793        suite.set_baseline("simple_add".to_string(), baseline);
794
795        // Run the benchmark
796        suite
797            .run_benchmark("simple_add")
798            .expect("operation should succeed");
799
800        let results = suite.get_results();
801        assert!(results.contains_key("simple_add"));
802    }
803
804    #[test]
805    fn test_benchmark_utilities() {
806        let result = utils::benchmark_function(
807            || {
808                let a = Array2::<f64>::ones((10, 10));
809                let b = Array2::<f64>::ones((10, 10));
810                a.dot(&b)
811            },
812            100,
813        );
814
815        assert!(result.is_ok());
816        let metrics = result.expect("operation should succeed");
817        assert!(metrics.ops_per_second > 0.0);
818    }
819}