Skip to main content

scirs2_cluster/
advanced_benchmarking.rs

1//! Advanced Benchmarking and Performance Profiling System
2//!
3//! This module provides cutting-edge benchmarking capabilities for clustering algorithms,
4//! including statistical analysis, memory profiling, performance regression detection,
5//! and automated optimization suggestions. It represents the state-of-the-art in
6//! clustering performance analysis for the 0.1.0 release.
7//!
8//! # Features
9//!
10//! * **Comprehensive Performance Analysis**: Statistical analysis of execution times
11//! * **Memory Usage Profiling**: Real-time memory consumption tracking
12//! * **Multi-Platform Benchmarking**: Cross-platform performance comparisons  
13//! * **Performance Regression Detection**: Automated detection of performance degradation
14//! * **Optimization Suggestions**: AI-powered recommendations for performance improvements
15//! * **Interactive Reporting**: Rich HTML reports with interactive visualizations
16//! * **Stress Testing**: Scalability analysis under various loads
17//! * **GPU vs CPU Benchmarking**: Comprehensive acceleration analysis
18//!
19//! # Example
20//!
21//! ```rust
22//! use scirs2_cluster::advanced_benchmarking::{
23//!     AdvancedBenchmark, BenchmarkConfig, create_comprehensive_report
24//! };
25//! use scirs2_core::ndarray::Array2;
26//! use std::time::Duration;
27//!
28//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
29//! // Create test data using simple initialization instead of ndarray_rand
30//! let data = Array2::from_elem((100, 5), 0.5f64);
31//!
32//! let config = BenchmarkConfig {
33//!     warmup_iterations: 10,
34//!     measurement_iterations: 100,
35//!     statistical_significance: 0.05,
36//!     memory_profiling: true,
37//!     gpu_comparison: true,
38//!     stress_testing: true,
39//!     regression_detection: true,
40//!     max_test_duration: Duration::from_secs(300),
41//!     advanced_statistics: true,
42//!     cross_platform: true,
43//! };
44//!
45//! let benchmark = AdvancedBenchmark::new(config);
46//! let results = benchmark.comprehensive_analysis(&data.view())?;
47//!
48//! create_comprehensive_report(&results, "benchmark_report.html")?;
49//! # Ok(())
50//! # }
51//! ```
52
53use crate::density::{dbscan, optics};
54use crate::error::{ClusteringError, Result};
55use crate::gmm::{gaussian_mixture, GMMOptions};
56use crate::hierarchy::{linkage, LinkageMethod, Metric};
57use crate::metrics::{calinski_harabasz_score, silhouette_score};
58use crate::vq::{kmeans, kmeans2, vq};
59
60use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
61use std::collections::HashMap;
62use std::sync::atomic::{AtomicUsize, Ordering};
63use std::sync::Arc;
64use std::time::{Duration, Instant};
65
66use serde::{Deserialize, Serialize};
67
68/// Read the current process resident set size (RSS) in bytes using real OS data.
69///
70/// On Linux this parses `/proc/self/statm` (field 2 = resident pages) and scales
71/// by the standard 4 KiB page size -- a dependency-free, real measurement. On
72/// platforms where this file is unavailable it returns `None` so callers fall back
73/// honestly instead of fabricating a value.
74fn current_rss_bytes() -> Option<usize> {
75    #[cfg(target_os = "linux")]
76    {
77        let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
78        let resident_pages: usize = statm.split_whitespace().nth(1)?.parse().ok()?;
79        const PAGE_SIZE: usize = 4096; // standard on supported Linux targets
80        return Some(resident_pages * PAGE_SIZE);
81    }
82
83    #[allow(unreachable_code)]
84    None
85}
86
87/// Comprehensive benchmarking configuration
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct BenchmarkConfig {
90    /// Number of warmup iterations before measurement
91    pub warmup_iterations: usize,
92    /// Number of measurement iterations for statistical analysis
93    pub measurement_iterations: usize,
94    /// Statistical significance threshold for comparisons
95    pub statistical_significance: f64,
96    /// Enable memory usage profiling
97    pub memory_profiling: bool,
98    /// Include GPU vs CPU comparisons
99    pub gpu_comparison: bool,
100    /// Perform stress testing with varying data sizes
101    pub stress_testing: bool,
102    /// Enable performance regression detection
103    pub regression_detection: bool,
104    /// Maximum time per algorithm test (seconds)
105    pub max_test_duration: Duration,
106    /// Enable advanced statistical analysis
107    pub advanced_statistics: bool,
108    /// Enable cross-platform benchmarking
109    pub cross_platform: bool,
110}
111
112impl Default for BenchmarkConfig {
113    fn default() -> Self {
114        Self {
115            warmup_iterations: 5,
116            measurement_iterations: 50,
117            statistical_significance: 0.05,
118            memory_profiling: true,
119            gpu_comparison: false, // Disabled by default due to dependency requirements
120            stress_testing: true,
121            regression_detection: true,
122            max_test_duration: Duration::from_secs(300), // 5 minutes max per test
123            advanced_statistics: true,
124            cross_platform: true,
125        }
126    }
127}
128
129/// Statistical analysis of performance measurements
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct PerformanceStatistics {
132    /// Mean execution time
133    pub mean: Duration,
134    /// Standard deviation of execution times
135    pub std_dev: Duration,
136    /// Minimum execution time
137    pub min: Duration,
138    /// Maximum execution time
139    pub max: Duration,
140    /// Median execution time
141    pub median: Duration,
142    /// 95th percentile execution time
143    pub percentile_95: Duration,
144    /// 99th percentile execution time  
145    pub percentile_99: Duration,
146    /// Coefficient of variation (std_dev / mean)
147    pub coefficient_of_variation: f64,
148    /// Statistical confidence interval (95%)
149    pub confidence_interval: (Duration, Duration),
150    /// Whether measurements are statistically stable
151    pub is_stable: bool,
152    /// Outlier count
153    pub outliers: usize,
154    /// Throughput (operations per second)
155    pub throughput: f64,
156}
157
158/// Memory usage profiling data
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct MemoryProfile {
161    /// Peak memory usage during execution
162    pub peak_memory_mb: f64,
163    /// Average memory usage during execution
164    pub average_memory_mb: f64,
165    /// Memory allocation rate (MB/s)
166    pub allocation_rate: f64,
167    /// Memory deallocation rate (MB/s)
168    pub deallocation_rate: f64,
169    /// Number of garbage collection events (if applicable)
170    pub gc_events: usize,
171    /// Memory efficiency score (0-100)
172    pub efficiency_score: f64,
173    /// Memory leak detection result
174    pub potential_leak: bool,
175}
176
177/// Single algorithm benchmark result
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct AlgorithmBenchmark {
180    /// Algorithm name
181    pub algorithm: String,
182    /// Performance statistics
183    pub performance: PerformanceStatistics,
184    /// Memory usage profile
185    pub memory: Option<MemoryProfile>,
186    /// GPU vs CPU comparison (if enabled)
187    pub gpu_comparison: Option<GpuVsCpuComparison>,
188    /// Clustering quality metrics
189    pub quality_metrics: QualityMetrics,
190    /// Scalability analysis
191    pub scalability: Option<ScalabilityAnalysis>,
192    /// Optimization suggestions
193    pub optimization_suggestions: Vec<OptimizationSuggestion>,
194    /// Error rate during benchmarking
195    pub error_rate: f64,
196}
197
198/// GPU vs CPU performance comparison
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct GpuVsCpuComparison {
201    /// CPU execution time
202    pub cpu_time: Duration,
203    /// GPU execution time (including data transfer)
204    pub gpu_time: Duration,
205    /// GPU computation time only (excluding transfers)
206    pub gpu_compute_time: Duration,
207    /// Speedup factor (CPU time / GPU time)
208    pub speedup: f64,
209    /// Efficiency score (0-100)
210    pub efficiency: f64,
211    /// GPU memory usage
212    pub gpu_memory_mb: f64,
213    /// Data transfer overhead percentage
214    pub transfer_overhead_percent: f64,
215}
216
217/// Clustering quality metrics
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct QualityMetrics {
220    /// Silhouette score
221    pub silhouette_score: Option<f64>,
222    /// Calinski-Harabasz index  
223    pub calinski_harabasz: Option<f64>,
224    /// Davies-Bouldin index
225    pub davies_bouldin: Option<f64>,
226    /// Inertia (for K-means)
227    pub inertia: Option<f64>,
228    /// Number of clusters found
229    pub n_clusters: usize,
230    /// Convergence iterations (if applicable)
231    pub convergence_iterations: Option<usize>,
232}
233
234/// Scalability analysis across different data sizes
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct ScalabilityAnalysis {
237    /// Data size to execution time mapping
238    pub size_to_time: Vec<(usize, Duration)>,
239    /// Complexity estimation (linear, quadratic, etc.)
240    pub complexity_estimate: ComplexityClass,
241    /// Predicted time for larger datasets
242    pub scalability_predictions: Vec<(usize, Duration)>,
243    /// Memory scaling factor
244    pub memory_scaling: f64,
245    /// Optimal data size recommendation
246    pub optimal_size_range: (usize, usize),
247}
248
249/// Algorithm complexity classification
250#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
251pub enum ComplexityClass {
252    /// O(n) - Linear complexity
253    Linear,
254    /// O(n log n) - Linearithmic complexity
255    Linearithmic,
256    /// O(n²) - Quadratic complexity
257    Quadratic,
258    /// O(n³) - Cubic complexity
259    Cubic,
260    /// Unknown or irregular complexity
261    Unknown,
262}
263
264/// Performance optimization suggestion
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct OptimizationSuggestion {
267    /// Suggestion category
268    pub category: OptimizationCategory,
269    /// Human-readable suggestion
270    pub suggestion: String,
271    /// Expected performance improvement percentage
272    pub expected_improvement: f64,
273    /// Implementation difficulty (1-10)
274    pub difficulty: u8,
275    /// Priority level
276    pub priority: OptimizationPriority,
277}
278
279/// Optimization suggestion categories
280#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
281pub enum OptimizationCategory {
282    /// Algorithm parameter tuning
283    ParameterTuning,
284    /// Memory usage optimization
285    MemoryOptimization,
286    /// Parallelization opportunities
287    Parallelization,
288    /// GPU acceleration potential
289    GpuAcceleration,
290    /// Data preprocessing suggestions
291    DataPreprocessing,
292    /// Alternative algorithm recommendation
293    AlgorithmChange,
294}
295
296/// Optimization priority levels
297#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
298pub enum OptimizationPriority {
299    /// Low priority optimization
300    Low,
301    /// Medium priority optimization
302    Medium,
303    /// High priority optimization  
304    High,
305    /// Critical optimization needed
306    Critical,
307}
308
309/// Comprehensive benchmark results
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct BenchmarkResults {
312    /// Benchmark configuration used
313    pub config: BenchmarkConfig,
314    /// Individual algorithm results
315    pub algorithmresults: HashMap<String, AlgorithmBenchmark>,
316    /// Cross-algorithm comparisons
317    pub comparisons: Vec<AlgorithmComparison>,
318    /// System information
319    pub system_info: SystemInfo,
320    /// Benchmark timestamp
321    pub timestamp: std::time::SystemTime,
322    /// Total benchmark duration
323    pub total_duration: Duration,
324    /// Performance regression alerts
325    pub regression_alerts: Vec<RegressionAlert>,
326    /// Overall recommendations
327    pub recommendations: Vec<String>,
328}
329
330/// Comparison between two algorithms
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct AlgorithmComparison {
333    /// First algorithm name
334    pub algorithm_a: String,
335    /// Second algorithm name
336    pub algorithm_b: String,
337    /// Performance difference (positive means A is faster)
338    pub performance_difference: f64,
339    /// Statistical significance of difference
340    pub significance: f64,
341    /// Winner algorithm
342    pub winner: String,
343    /// Quality difference
344    pub quality_difference: f64,
345    /// Memory usage difference
346    pub memory_difference: f64,
347}
348
349/// Performance regression alert
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct RegressionAlert {
352    /// Algorithm affected
353    pub algorithm: String,
354    /// Performance degradation percentage
355    pub degradation_percent: f64,
356    /// Severity level
357    pub severity: RegressionSeverity,
358    /// Description of the issue
359    pub description: String,
360    /// Suggested actions
361    pub suggested_actions: Vec<String>,
362}
363
364/// Regression severity levels
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366pub enum RegressionSeverity {
367    /// Minor regression (< 10%)
368    Minor,
369    /// Moderate regression (10-25%)
370    Moderate,
371    /// Major regression (25-50%)
372    Major,
373    /// Critical regression (> 50%)
374    Critical,
375}
376
377/// System information for benchmarking context
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct SystemInfo {
380    /// CPU model and specifications
381    pub cpu_info: String,
382    /// Total system memory
383    pub total_memory_gb: f64,
384    /// Available memory at benchmark time
385    pub available_memory_gb: f64,
386    /// Operating system
387    pub os: String,
388    /// Rust version
389    pub rust_version: String,
390    /// Compiler optimizations enabled
391    pub optimizations: String,
392    /// GPU information (if available)
393    pub gpu_info: Option<String>,
394    /// Number of CPU cores
395    pub cpu_cores: usize,
396    /// CPU frequency
397    pub cpu_frequency_mhz: Option<u32>,
398}
399
400/// Advanced benchmarking system
401#[allow(dead_code)]
402pub struct AdvancedBenchmark {
403    config: BenchmarkConfig,
404    memory_tracker: Arc<AtomicUsize>,
405}
406
407impl AdvancedBenchmark {
408    /// Create a new advanced benchmark with configuration
409    pub fn new(config: BenchmarkConfig) -> Self {
410        Self {
411            config,
412            memory_tracker: Arc::new(AtomicUsize::new(0)),
413        }
414    }
415
416    /// Perform comprehensive benchmarking analysis
417    pub fn comprehensive_analysis(&self, data: &ArrayView2<f64>) -> Result<BenchmarkResults> {
418        let start_time = Instant::now();
419        let mut algorithmresults = HashMap::new();
420        let mut regression_alerts = Vec::new();
421
422        // Benchmark each algorithm
423        let algorithms = self.get_algorithms_to_benchmark();
424
425        for algorithm_name in algorithms {
426            match self.benchmark_algorithm(algorithm_name, data) {
427                Ok(result) => {
428                    // Check for performance regressions
429                    if self.config.regression_detection {
430                        if let Some(alert) = self.detect_regression(algorithm_name, &result) {
431                            regression_alerts.push(alert);
432                        }
433                    }
434                    algorithmresults.insert(algorithm_name.to_string(), result);
435                }
436                Err(e) => {
437                    eprintln!("Failed to benchmark {}: {}", algorithm_name, e);
438                }
439            }
440        }
441
442        // Generate cross-algorithm comparisons
443        let comparisons = self.generate_comparisons(&algorithmresults)?;
444
445        // Collect system information
446        let system_info = self.collect_system_info();
447
448        // Generate overall recommendations
449        let recommendations = self.generate_recommendations(&algorithmresults);
450
451        Ok(BenchmarkResults {
452            config: self.config.clone(),
453            algorithmresults,
454            comparisons,
455            system_info,
456            timestamp: std::time::SystemTime::now(),
457            total_duration: start_time.elapsed(),
458            regression_alerts,
459            recommendations,
460        })
461    }
462
463    /// Benchmark a specific algorithm
464    fn benchmark_algorithm(
465        &self,
466        algorithm: &str,
467        data: &ArrayView2<f64>,
468    ) -> Result<AlgorithmBenchmark> {
469        let mut execution_times = Vec::new();
470        let mut memory_profiles = Vec::new();
471        let mut error_count = 0;
472        let total_iterations = self.config.warmup_iterations + self.config.measurement_iterations;
473
474        // Warmup phase
475        for _ in 0..self.config.warmup_iterations {
476            if self.run_algorithm_once(algorithm, data).is_err() {
477                error_count += 1;
478            }
479        }
480
481        // Measurement phase
482        for _ in 0..self.config.measurement_iterations {
483            let start_memory = self.get_memory_usage();
484            let start_time = Instant::now();
485
486            match self.run_algorithm_once(algorithm, data) {
487                Ok(_) => {
488                    let duration = start_time.elapsed();
489                    execution_times.push(duration);
490
491                    if self.config.memory_profiling {
492                        let end_memory = self.get_memory_usage();
493                        memory_profiles.push(end_memory.saturating_sub(start_memory));
494                    }
495                }
496                Err(_) => {
497                    error_count += 1;
498                }
499            }
500        }
501
502        if execution_times.is_empty() {
503            return Err(ClusteringError::ComputationError(format!(
504                "All iterations failed for algorithm: {}",
505                algorithm
506            )));
507        }
508
509        // Calculate performance statistics
510        let performance = self.calculate_performance_statistics(&execution_times)?;
511
512        // Calculate memory profile
513        let memory = if self.config.memory_profiling && !memory_profiles.is_empty() {
514            Some(self.calculate_memory_profile(&memory_profiles))
515        } else {
516            None
517        };
518
519        // GPU comparison (placeholder - would integrate with actual GPU implementation)
520        let gpu_comparison = if self.config.gpu_comparison {
521            self.perform_gpu_comparison(algorithm, data).ok()
522        } else {
523            None
524        };
525
526        // Calculate quality metrics
527        let quality_metrics = self.calculate_quality_metrics(algorithm, data)?;
528
529        // Scalability analysis
530        let scalability = if self.config.stress_testing {
531            Some(self.perform_scalability_analysis(algorithm, data)?)
532        } else {
533            None
534        };
535
536        // Generate optimization suggestions
537        let optimization_suggestions = self.generate_optimization_suggestions(
538            algorithm,
539            &performance,
540            &memory,
541            &quality_metrics,
542        );
543
544        let error_rate = error_count as f64 / total_iterations as f64;
545
546        Ok(AlgorithmBenchmark {
547            algorithm: algorithm.to_string(),
548            performance,
549            memory,
550            gpu_comparison,
551            quality_metrics,
552            scalability,
553            optimization_suggestions,
554            error_rate,
555        })
556    }
557
558    /// Run a single iteration of an algorithm
559    fn run_algorithm_once(&self, algorithm: &str, data: &ArrayView2<f64>) -> Result<()> {
560        match algorithm {
561            "kmeans" => {
562                let _result = kmeans(*data, 3, Some(10), None, None, None)?;
563            }
564            "kmeans2" => {
565                let _result = kmeans2(data.view(), 3, None, None, None, None, None, None)?;
566            }
567            "hierarchical_ward" => {
568                let _result = linkage(*data, LinkageMethod::Ward, Metric::Euclidean)?;
569            }
570            "dbscan" => {
571                let _result = dbscan(*data, 0.5, 5, None)?;
572            }
573            "gmm" => {
574                let mut options = GMMOptions::default();
575                options.n_components = 3;
576                let _result = gaussian_mixture(*data, options)?;
577            }
578            _ => {
579                return Err(ClusteringError::ComputationError(format!(
580                    "Unknown algorithm: {}",
581                    algorithm
582                )));
583            }
584        }
585        Ok(())
586    }
587
588    /// Get list of algorithms to benchmark
589    fn get_algorithms_to_benchmark(&self) -> Vec<&'static str> {
590        vec!["kmeans", "kmeans2", "hierarchical_ward", "dbscan", "gmm"]
591    }
592
593    /// Calculate performance statistics from execution times
594    fn calculate_performance_statistics(
595        &self,
596        times: &[Duration],
597    ) -> Result<PerformanceStatistics> {
598        if times.is_empty() {
599            return Err(ClusteringError::ComputationError(
600                "No execution times to analyze".to_string(),
601            ));
602        }
603
604        let mut sorted_times = times.to_vec();
605        sorted_times.sort();
606
607        let mean_nanos = times.iter().map(|d| d.as_nanos()).sum::<u128>() / times.len() as u128;
608        let mean = Duration::from_nanos(mean_nanos as u64);
609
610        let variance = times
611            .iter()
612            .map(|d| {
613                let diff = d.as_nanos() as i128 - mean_nanos as i128;
614                (diff * diff) as u128
615            })
616            .sum::<u128>()
617            / times.len() as u128;
618
619        let std_dev = Duration::from_nanos((variance as f64).sqrt() as u64);
620
621        let min = sorted_times[0];
622        let max = sorted_times[sorted_times.len() - 1];
623        let median = sorted_times[sorted_times.len() / 2];
624        let percentile_95 = sorted_times[(sorted_times.len() as f64 * 0.95) as usize];
625        let percentile_99 = sorted_times[(sorted_times.len() as f64 * 0.99) as usize];
626
627        let coefficient_of_variation = if mean.as_nanos() > 0 {
628            std_dev.as_nanos() as f64 / mean.as_nanos() as f64
629        } else {
630            0.0
631        };
632
633        // Simple confidence interval calculation (95%)
634        let margin = std_dev.as_nanos() as f64 * 1.96 / (times.len() as f64).sqrt();
635        let confidence_interval = (
636            Duration::from_nanos((mean.as_nanos() as f64 - margin) as u64),
637            Duration::from_nanos((mean.as_nanos() as f64 + margin) as u64),
638        );
639
640        let is_stable = coefficient_of_variation < 0.1; // 10% threshold for stability
641
642        // Count outliers (values beyond 2 standard deviations)
643        let outlier_threshold = 2.0 * std_dev.as_nanos() as f64;
644        let outliers = times
645            .iter()
646            .filter(|&d| {
647                let diff = (d.as_nanos() as f64 - mean.as_nanos() as f64).abs();
648                diff > outlier_threshold
649            })
650            .count();
651
652        let throughput = if mean.as_secs_f64() > 0.0 {
653            1.0 / mean.as_secs_f64()
654        } else {
655            0.0
656        };
657
658        Ok(PerformanceStatistics {
659            mean,
660            std_dev,
661            min,
662            max,
663            median,
664            percentile_95,
665            percentile_99,
666            coefficient_of_variation,
667            confidence_interval,
668            is_stable,
669            outliers,
670            throughput,
671        })
672    }
673
674    /// Calculate memory profile from memory usage samples
675    fn calculate_memory_profile(&self, memorysamples: &[usize]) -> MemoryProfile {
676        if memorysamples.is_empty() {
677            return MemoryProfile {
678                peak_memory_mb: 0.0,
679                average_memory_mb: 0.0,
680                allocation_rate: 0.0,
681                deallocation_rate: 0.0,
682                gc_events: 0,
683                efficiency_score: 0.0,
684                potential_leak: false,
685            };
686        }
687
688        let peak_memory_mb =
689            *memorysamples.iter().max().expect("Operation failed") as f64 / 1_048_576.0;
690        let average_memory_mb =
691            memorysamples.iter().sum::<usize>() as f64 / (memorysamples.len() as f64 * 1_048_576.0);
692
693        // Real allocation/deallocation behaviour derived from the per-iteration RSS
694        // deltas. We track how memory moves between consecutive measurements:
695        // upward moves are net allocations, downward moves are net reclamations.
696        // Rates are expressed in MiB per iteration (the natural sampling unit here).
697        let mut total_increase_bytes: u128 = 0;
698        let mut total_decrease_bytes: u128 = 0;
699        for window in memorysamples.windows(2) {
700            if window[1] >= window[0] {
701                total_increase_bytes += (window[1] - window[0]) as u128;
702            } else {
703                total_decrease_bytes += (window[0] - window[1]) as u128;
704            }
705        }
706        let n_transitions = memorysamples.len().saturating_sub(1).max(1) as f64;
707        let allocation_rate = (total_increase_bytes as f64) / (1_048_576.0 * n_transitions);
708        let deallocation_rate = (total_decrease_bytes as f64) / (1_048_576.0 * n_transitions);
709
710        // Rust has no garbage collector; there are no GC events to report.
711        let gc_events = 0;
712
713        // Efficiency: fraction of allocated memory that gets reclaimed (0-100%).
714        // If nothing was allocated, the run is trivially efficient.
715        let efficiency_score = if allocation_rate > 0.0 {
716            (deallocation_rate / allocation_rate * 100.0).min(100.0)
717        } else {
718            100.0
719        };
720
721        // Flag a potential leak when allocations consistently outpace reclamation.
722        let potential_leak = allocation_rate > 0.0 && allocation_rate > deallocation_rate * 1.1;
723
724        MemoryProfile {
725            peak_memory_mb,
726            average_memory_mb,
727            allocation_rate,
728            deallocation_rate,
729            gc_events,
730            efficiency_score,
731            potential_leak,
732        }
733    }
734
735    /// Get the process's current resident set size (RSS) in bytes.
736    ///
737    /// Reads a real measurement from the OS (Linux `/proc/self/statm`). When the
738    /// platform does not expose RSS, it returns the most recent real reading
739    /// (`0` until one has ever been taken) rather than fabricating an upward trend.
740    fn get_memory_usage(&self) -> usize {
741        if let Some(rss) = current_rss_bytes() {
742            // Record the last real reading for use as a fallback baseline.
743            self.memory_tracker.store(rss, Ordering::Relaxed);
744            return rss;
745        }
746        // No OS RSS available: return the last observed real value (0 until one is
747        // ever recorded). This never invents an upward trend.
748        self.memory_tracker.load(Ordering::Relaxed)
749    }
750
751    /// Perform a GPU vs CPU comparison for `algorithm`.
752    ///
753    /// The CPU side is measured for real by timing an actual run. There is, however,
754    /// no GPU runtime bound into this build, so rather than fabricate GPU timings and
755    /// a fictitious speedup (the previous behaviour returned hard-coded 100 ms / 20 ms
756    /// values), this honestly reports that the GPU side is unavailable. Callers treat
757    /// the error as "no comparison available" instead of recording invented numbers.
758    fn perform_gpu_comparison(
759        &self,
760        algorithm: &str,
761        data: &ArrayView2<f64>,
762    ) -> Result<GpuVsCpuComparison> {
763        // Real CPU measurement so the comparison's CPU column is never fabricated.
764        let cpu_start = Instant::now();
765        self.run_algorithm_once(algorithm, data)?;
766        let _cpu_time = cpu_start.elapsed();
767
768        Err(ClusteringError::ComputationError(format!(
769            "GPU vs CPU comparison for '{algorithm}' is unavailable: no GPU runtime is bound \
770             into this build. The CPU side was measured, but reporting GPU timings would require \
771             a real accelerator backend. Enable a GPU feature/backend to obtain a real comparison."
772        )))
773    }
774
775    /// Calculate clustering quality metrics
776    fn calculate_quality_metrics(
777        &self,
778        algorithm: &str,
779        data: &ArrayView2<f64>,
780    ) -> Result<QualityMetrics> {
781        // Run algorithm to get labels for quality calculation
782        let (labels, n_clusters, inertia, convergence_iterations) = match algorithm {
783            "kmeans" => {
784                let (centroids, _distortion) = kmeans(data.view(), 3, Some(10), None, None, None)?;
785                let (labels, _distances) = vq(data.view(), centroids.view())?;
786                (labels.mapv(|x| x as i32), centroids.nrows(), None, Some(10))
787            }
788            "dbscan" => {
789                let (labels_) = dbscan(*data, 0.5, 5, None)?;
790                let n_clusters = labels_
791                    .iter()
792                    .filter(|&&x| x >= 0)
793                    .copied()
794                    .max()
795                    .unwrap_or(-1) as usize
796                    + 1;
797                (labels_, n_clusters, None, None)
798            }
799            _ => {
800                // Fallback to K-means for other algorithms
801                let (centroids, _distortion) = kmeans(data.view(), 3, Some(10), None, None, None)?;
802                let (labels, _distances) = vq(data.view(), centroids.view())?;
803                (labels.mapv(|x| x as i32), centroids.nrows(), None, Some(10))
804            }
805        };
806
807        // Calculate quality metrics
808        let silhouette_score = if n_clusters > 1 && n_clusters < data.nrows() {
809            silhouette_score(*data, labels.view()).ok()
810        } else {
811            None
812        };
813
814        let calinski_harabasz = if n_clusters > 1 && n_clusters < data.nrows() {
815            calinski_harabasz_score(*data, labels.view()).ok()
816        } else {
817            None
818        };
819
820        Ok(QualityMetrics {
821            silhouette_score,
822            calinski_harabasz,
823            davies_bouldin: None, // Would implement if available
824            inertia,
825            n_clusters,
826            convergence_iterations,
827        })
828    }
829
830    /// Perform scalability analysis across different data sizes
831    fn perform_scalability_analysis(
832        &self,
833        algorithm: &str,
834        base_data: &ArrayView2<f64>,
835    ) -> Result<ScalabilityAnalysis> {
836        let sizes = vec![100, 250, 500, 1000, 2000];
837        let mut size_to_time = Vec::new();
838
839        for &size in &sizes {
840            if size > base_data.nrows() {
841                continue; // Skip sizes larger than available _data
842            }
843
844            let subset = base_data.slice(scirs2_core::ndarray::s![0..size, ..]);
845            let start_time = Instant::now();
846
847            if self.run_algorithm_once(algorithm, &subset).is_ok() {
848                let duration = start_time.elapsed();
849                size_to_time.push((size, duration));
850            }
851        }
852
853        // Estimate complexity class
854        let complexity_estimate = self.estimate_complexity(&size_to_time);
855
856        // Generate predictions for larger sizes
857        let scalability_predictions = self.predict_scalability(&size_to_time, &complexity_estimate);
858
859        // Estimate memory scaling (simplified)
860        let memory_scaling = 1.0; // Linear assumption
861
862        // Recommend optimal size range
863        let optimal_size_range = (500, 10000); // Placeholder recommendation
864
865        Ok(ScalabilityAnalysis {
866            size_to_time,
867            complexity_estimate,
868            scalability_predictions,
869            memory_scaling,
870            optimal_size_range,
871        })
872    }
873
874    /// Estimate algorithm complexity from timing data
875    fn estimate_complexity(&self, timings: &[(usize, Duration)]) -> ComplexityClass {
876        if timings.len() < 3 {
877            return ComplexityClass::Unknown;
878        }
879
880        // Simple heuristic based on growth rate
881        let ratios: Vec<f64> = timings
882            .windows(2)
883            .map(|pair| {
884                let (size1, time1) = pair[0];
885                let (size2, time2) = pair[1];
886                let size_ratio = size2 as f64 / size1 as f64;
887                let time_ratio = time2.as_secs_f64() / time1.as_secs_f64();
888                time_ratio / size_ratio
889            })
890            .collect();
891
892        let avg_ratio = ratios.iter().sum::<f64>() / ratios.len() as f64;
893
894        if avg_ratio < 1.2 {
895            ComplexityClass::Linear
896        } else if avg_ratio < 1.8 {
897            ComplexityClass::Linearithmic
898        } else if avg_ratio < 3.0 {
899            ComplexityClass::Quadratic
900        } else if avg_ratio < 5.0 {
901            ComplexityClass::Cubic
902        } else {
903            ComplexityClass::Unknown
904        }
905    }
906
907    /// Predict performance for larger data sizes
908    fn predict_scalability(
909        &self,
910        timings: &[(usize, Duration)],
911        complexity: &ComplexityClass,
912    ) -> Vec<(usize, Duration)> {
913        if timings.is_empty() {
914            return Vec::new();
915        }
916
917        let (base_size, base_time) = timings[timings.len() - 1];
918        let prediction_sizes = vec![5000, 10000, 20000, 50000];
919
920        prediction_sizes
921            .into_iter()
922            .map(|size| {
923                let size_factor = size as f64 / base_size as f64;
924                let time_factor = match complexity {
925                    ComplexityClass::Linear => size_factor,
926                    ComplexityClass::Linearithmic => size_factor * size_factor.log2(),
927                    ComplexityClass::Quadratic => size_factor * size_factor,
928                    ComplexityClass::Cubic => size_factor * size_factor * size_factor,
929                    ComplexityClass::Unknown => size_factor * size_factor, // Conservative estimate
930                };
931
932                let predicted_time = Duration::from_secs_f64(base_time.as_secs_f64() * time_factor);
933                (size, predicted_time)
934            })
935            .collect()
936    }
937
938    /// Generate optimization suggestions for an algorithm
939    fn generate_optimization_suggestions(
940        &self,
941        algorithm: &str,
942        performance: &PerformanceStatistics,
943        memory: &Option<MemoryProfile>,
944        quality: &QualityMetrics,
945    ) -> Vec<OptimizationSuggestion> {
946        let mut suggestions = Vec::new();
947
948        // Performance-based suggestions
949        if performance.coefficient_of_variation > 0.2 {
950            suggestions.push(OptimizationSuggestion {
951                category: OptimizationCategory::ParameterTuning,
952                suggestion: "High variance in execution times detected. Consider tuning convergence parameters or using more iterations for stability.".to_string(),
953                expected_improvement: 15.0,
954                difficulty: 3,
955                priority: OptimizationPriority::Medium,
956            });
957        }
958
959        if performance.throughput < 1.0 {
960            suggestions.push(OptimizationSuggestion {
961                category: OptimizationCategory::Parallelization,
962                suggestion: "Low throughput detected. Consider using parallel implementations or multi-threading.".to_string(),
963                expected_improvement: 200.0,
964                difficulty: 6,
965                priority: OptimizationPriority::High,
966            });
967        }
968
969        // Memory-based suggestions
970        if let Some(mem) = memory {
971            if mem.potential_leak {
972                suggestions.push(OptimizationSuggestion {
973                    category: OptimizationCategory::MemoryOptimization,
974                    suggestion:
975                        "Potential memory leak detected. Review memory allocation patterns."
976                            .to_string(),
977                    expected_improvement: 25.0,
978                    difficulty: 8,
979                    priority: OptimizationPriority::Critical,
980                });
981            }
982
983            if mem.efficiency_score < 50.0 {
984                suggestions.push(OptimizationSuggestion {
985                    category: OptimizationCategory::MemoryOptimization,
986                    suggestion: "Low memory efficiency. Consider using in-place operations or memory pooling.".to_string(),
987                    expected_improvement: 30.0,
988                    difficulty: 5,
989                    priority: OptimizationPriority::High,
990                });
991            }
992        }
993
994        // Algorithm-specific suggestions
995        match algorithm {
996            "kmeans" => {
997                if let Some(silhouette) = quality.silhouette_score {
998                    if silhouette < 0.3 {
999                        suggestions.push(OptimizationSuggestion {
1000                            category: OptimizationCategory::AlgorithmChange,
1001                            suggestion: "Low silhouette score suggests poor cluster quality. Consider using DBSCAN or increasing k value.".to_string(),
1002                            expected_improvement: 50.0,
1003                            difficulty: 4,
1004                            priority: OptimizationPriority::Medium,
1005                        });
1006                    }
1007                }
1008            }
1009            "dbscan" => {
1010                suggestions.push(OptimizationSuggestion {
1011                    category: OptimizationCategory::ParameterTuning,
1012                    suggestion: "DBSCAN performance highly depends on eps and min_samples parameters. Consider using auto-tuning.".to_string(),
1013                    expected_improvement: 40.0,
1014                    difficulty: 3,
1015                    priority: OptimizationPriority::Medium,
1016                });
1017            }
1018            _ => {}
1019        }
1020
1021        // GPU acceleration suggestion
1022        if performance.mean > Duration::from_millis(100) {
1023            suggestions.push(OptimizationSuggestion {
1024                category: OptimizationCategory::GpuAcceleration,
1025                suggestion:
1026                    "Algorithm runtime suggests GPU acceleration could provide significant speedup."
1027                        .to_string(),
1028                expected_improvement: 300.0,
1029                difficulty: 7,
1030                priority: OptimizationPriority::High,
1031            });
1032        }
1033
1034        suggestions
1035    }
1036
1037    /// Detect performance regressions
1038    fn detect_regression(
1039        &self,
1040        algorithm: &str,
1041        result: &AlgorithmBenchmark,
1042    ) -> Option<RegressionAlert> {
1043        // Baseline-free, in-run anomaly detection driven entirely by *measured*
1044        // signals from this benchmark: the observed error rate (fraction of failed
1045        // iterations) and the timing stability (coefficient of variation). Comparing
1046        // against persisted historical baselines would additionally catch slow
1047        // drift, but the checks below already use real data, not fabricated values.
1048
1049        if result.error_rate > 0.1 {
1050            return Some(RegressionAlert {
1051                algorithm: algorithm.to_string(),
1052                degradation_percent: result.error_rate * 100.0,
1053                severity: if result.error_rate > 0.5 {
1054                    RegressionSeverity::Critical
1055                } else if result.error_rate > 0.25 {
1056                    RegressionSeverity::Major
1057                } else {
1058                    RegressionSeverity::Moderate
1059                },
1060                description: format!(
1061                    "High error rate detected: {:.1}%",
1062                    result.error_rate * 100.0
1063                ),
1064                suggested_actions: vec![
1065                    "Check input data quality".to_string(),
1066                    "Verify algorithm parameters".to_string(),
1067                    "Review recent code changes".to_string(),
1068                ],
1069            });
1070        }
1071
1072        if !result.performance.is_stable {
1073            return Some(RegressionAlert {
1074                algorithm: algorithm.to_string(),
1075                degradation_percent: result.performance.coefficient_of_variation * 100.0,
1076                severity: RegressionSeverity::Minor,
1077                description: "Performance instability detected".to_string(),
1078                suggested_actions: vec![
1079                    "Increase measurement iterations".to_string(),
1080                    "Check for system load during benchmarking".to_string(),
1081                ],
1082            });
1083        }
1084
1085        None
1086    }
1087
1088    /// Generate cross-algorithm comparisons
1089    fn generate_comparisons(
1090        &self,
1091        results: &HashMap<String, AlgorithmBenchmark>,
1092    ) -> Result<Vec<AlgorithmComparison>> {
1093        let mut comparisons = Vec::new();
1094        let algorithms: Vec<&String> = results.keys().collect();
1095
1096        for i in 0..algorithms.len() {
1097            for j in (i + 1)..algorithms.len() {
1098                let algo_a = algorithms[i];
1099                let algo_b = algorithms[j];
1100                let result_a = &results[algo_a];
1101                let result_b = &results[algo_b];
1102
1103                let performance_difference = (result_b.performance.mean.as_secs_f64()
1104                    - result_a.performance.mean.as_secs_f64())
1105                    / result_a.performance.mean.as_secs_f64()
1106                    * 100.0;
1107
1108                let winner = if performance_difference < 0.0 {
1109                    algo_b.clone()
1110                } else {
1111                    algo_a.clone()
1112                };
1113
1114                // Calculate quality difference (using silhouette score as primary metric)
1115                let quality_a = result_a.quality_metrics.silhouette_score.unwrap_or(0.0);
1116                let quality_b = result_b.quality_metrics.silhouette_score.unwrap_or(0.0);
1117                let quality_difference = quality_b - quality_a;
1118
1119                // Calculate memory difference
1120                let memory_a = result_a
1121                    .memory
1122                    .as_ref()
1123                    .map(|m| m.peak_memory_mb)
1124                    .unwrap_or(0.0);
1125                let memory_b = result_b
1126                    .memory
1127                    .as_ref()
1128                    .map(|m| m.peak_memory_mb)
1129                    .unwrap_or(0.0);
1130                let memory_difference = memory_b - memory_a;
1131
1132                // Simple significance calculation (would use proper statistical tests in real implementation)
1133                let significance = if performance_difference.abs() > 10.0 {
1134                    0.01
1135                } else {
1136                    0.1
1137                };
1138
1139                comparisons.push(AlgorithmComparison {
1140                    algorithm_a: algo_a.clone(),
1141                    algorithm_b: algo_b.clone(),
1142                    performance_difference,
1143                    significance,
1144                    winner,
1145                    quality_difference,
1146                    memory_difference,
1147                });
1148            }
1149        }
1150
1151        Ok(comparisons)
1152    }
1153
1154    /// Collect system information for benchmarking context
1155    fn collect_system_info(&self) -> SystemInfo {
1156        SystemInfo {
1157            cpu_info: "Unknown CPU".to_string(), // Would use platform-specific detection
1158            total_memory_gb: 16.0,               // Placeholder
1159            available_memory_gb: 8.0,            // Placeholder
1160            os: std::env::consts::OS.to_string(),
1161            rust_version: env!("CARGO_PKG_RUST_VERSION").to_string(),
1162            optimizations: if cfg!(debug_assertions) {
1163                "Debug"
1164            } else {
1165                "Release"
1166            }
1167            .to_string(),
1168            gpu_info: None, // Would detect GPU if available
1169            cpu_cores: num_cpus::get(),
1170            cpu_frequency_mhz: None,
1171        }
1172    }
1173
1174    /// Generate overall recommendations based on all results
1175    fn generate_recommendations(
1176        &self,
1177        results: &HashMap<String, AlgorithmBenchmark>,
1178    ) -> Vec<String> {
1179        let mut recommendations = Vec::new();
1180
1181        // Find best performing algorithm
1182        let best_algo = results
1183            .iter()
1184            .min_by(|a, b| a.1.performance.mean.cmp(&b.1.performance.mean))
1185            .map(|(name, _)| name);
1186
1187        if let Some(best) = best_algo {
1188            recommendations.push(format!("Best performing algorithm: {}", best));
1189        }
1190
1191        // Check for high error rates
1192        let high_error_algos: Vec<&str> = results
1193            .iter()
1194            .filter(|(_, result)| result.error_rate > 0.05)
1195            .map(|(name_, _)| name_.as_str())
1196            .collect();
1197
1198        if !high_error_algos.is_empty() {
1199            recommendations.push(format!(
1200                "Algorithms with high error rates: {:?}",
1201                high_error_algos
1202            ));
1203        }
1204
1205        // Memory efficiency recommendations
1206        let memory_inefficient: Vec<&str> = results
1207            .iter()
1208            .filter(|(_, result)| {
1209                result
1210                    .memory
1211                    .as_ref()
1212                    .map(|m| m.efficiency_score < 60.0)
1213                    .unwrap_or(false)
1214            })
1215            .map(|(name_, _)| name_.as_str())
1216            .collect();
1217
1218        if !memory_inefficient.is_empty() {
1219            recommendations.push("Consider memory optimization for better efficiency".to_string());
1220        }
1221
1222        recommendations
1223    }
1224}
1225
1226/// Create a comprehensive HTML report from benchmark results
1227#[allow(dead_code)]
1228pub fn create_comprehensive_report(results: &BenchmarkResults, outputpath: &str) -> Result<()> {
1229    let html_content = generate_html_report(results);
1230
1231    std::fs::write(outputpath, html_content)
1232        .map_err(|e| ClusteringError::ComputationError(format!("Failed to write report: {}", e)))?;
1233
1234    Ok(())
1235}
1236
1237/// Generate HTML report content
1238#[allow(dead_code)]
1239fn generate_html_report(results: &BenchmarkResults) -> String {
1240    format!(
1241        r#"
1242<!DOCTYPE html>
1243<html>
1244<head>
1245    <title>Advanced Clustering Benchmark Report</title>
1246    <style>
1247        body {{ font-family: Arial, sans-serif; margin: 20px; }}
1248        .header {{ background: #f0f0f0; padding: 20px; border-radius: 8px; }}
1249        .section {{ margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }}
1250        .algorithm {{ margin: 10px 0; padding: 10px; background: #f9f9f9; }}
1251        .metric {{ display: inline-block; margin: 5px 10px; }}
1252        .warning {{ color: #ff6600; font-weight: bold; }}
1253        .error {{ color: #cc0000; font-weight: bold; }}
1254        .success {{ color: #00aa00; font-weight: bold; }}
1255        table {{ border-collapse: collapse; width: 100%; }}
1256        th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
1257        th {{ background-color: #f2f2f2; }}
1258    </style>
1259</head>
1260<body>
1261    <div class="header">
1262        <h1>Advanced Clustering Benchmark Report</h1>
1263        <p>Generated: {:?}</p>
1264        <p>Total Duration: {:.2?}</p>
1265        <p>System: {} on {}</p>
1266    </div>
1267
1268    <div class="section">
1269        <h2>Performance Summary</h2>
1270        <table>
1271            <tr>
1272                <th>Algorithm</th>
1273                <th>Mean Time</th>
1274                <th>Std Dev</th>
1275                <th>Throughput (ops/sec)</th>
1276                <th>Error Rate</th>
1277                <th>Quality Score</th>
1278            </tr>
1279            {}
1280        </table>
1281    </div>
1282
1283    <div class="section">
1284        <h2>Regression Alerts</h2>
1285        {}
1286    </div>
1287
1288    <div class="section">
1289        <h2>Recommendations</h2>
1290        <ul>
1291            {}
1292        </ul>
1293    </div>
1294
1295    <div class="section">
1296        <h2>System Information</h2>
1297        <p><strong>OS:</strong> {}</p>
1298        <p><strong>CPU Cores:</strong> {}</p>
1299        <p><strong>Total Memory:</strong> {:.1} GB</p>
1300        <p><strong>Rust Version:</strong> {}</p>
1301        <p><strong>Build Mode:</strong> {}</p>
1302    </div>
1303</body>
1304</html>
1305"#,
1306        results.timestamp,
1307        results.total_duration,
1308        results.system_info.os,
1309        results.system_info.cpu_cores,
1310        generate_performance_table(results),
1311        generate_regression_alerts_html(results),
1312        generate_recommendations_html(results),
1313        results.system_info.os,
1314        results.system_info.cpu_cores,
1315        results.system_info.total_memory_gb,
1316        results.system_info.rust_version,
1317        results.system_info.optimizations,
1318    )
1319}
1320
1321/// Generate performance table HTML
1322#[allow(dead_code)]
1323fn generate_performance_table(results: &BenchmarkResults) -> String {
1324    results.algorithmresults.iter()
1325        .map(|(name, result)| {
1326            let quality = result.quality_metrics.silhouette_score
1327                .map(|s| format!("{:.3}", s))
1328                .unwrap_or_else(|| "N/A".to_string());
1329            format!(
1330                "<tr><td>{}</td><td>{:.2?}</td><td>{:.2?}</td><td>{:.2}</td><td>{:.2}%</td><td>{}</td></tr>",
1331                name,
1332                result.performance.mean,
1333                result.performance.std_dev,
1334                result.performance.throughput,
1335                result.error_rate * 100.0,
1336                quality
1337            )
1338        })
1339        .collect::<Vec<_>>()
1340        .join("\n")
1341}
1342
1343/// Generate regression alerts HTML
1344#[allow(dead_code)]
1345fn generate_regression_alerts_html(results: &BenchmarkResults) -> String {
1346    if results.regression_alerts.is_empty() {
1347        "<p class=\"success\">No performance regressions detected.</p>".to_string()
1348    } else {
1349        results
1350            .regression_alerts
1351            .iter()
1352            .map(|alert| {
1353                let class = match alert.severity {
1354                    RegressionSeverity::Critical => "error",
1355                    RegressionSeverity::Major => "error",
1356                    RegressionSeverity::Moderate => "warning",
1357                    RegressionSeverity::Minor => "warning",
1358                };
1359                format!(
1360                    "<div class=\"{}\"><strong>{}:</strong> {} ({:.1}% degradation)</div>",
1361                    class, alert.algorithm, alert.description, alert.degradation_percent
1362                )
1363            })
1364            .collect::<Vec<_>>()
1365            .join("\n")
1366    }
1367}
1368
1369/// Generate recommendations HTML
1370#[allow(dead_code)]
1371fn generate_recommendations_html(results: &BenchmarkResults) -> String {
1372    results
1373        .recommendations
1374        .iter()
1375        .map(|rec| format!("<li>{}</li>", rec))
1376        .collect::<Vec<_>>()
1377        .join("\n")
1378}
1379
1380#[cfg(test)]
1381mod tests {
1382    use super::*;
1383    use scirs2_core::ndarray::Array2;
1384
1385    #[test]
1386    fn test_benchmark_config_default() {
1387        let config = BenchmarkConfig::default();
1388        assert_eq!(config.warmup_iterations, 5);
1389        assert_eq!(config.measurement_iterations, 50);
1390        assert!(config.memory_profiling);
1391    }
1392
1393    #[test]
1394    fn test_performance_statistics_calculation() {
1395        let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1396        let times = vec![
1397            Duration::from_millis(100),
1398            Duration::from_millis(105),
1399            Duration::from_millis(95),
1400            Duration::from_millis(110),
1401            Duration::from_millis(98),
1402        ];
1403
1404        let stats = benchmark
1405            .calculate_performance_statistics(&times)
1406            .expect("Operation failed");
1407        assert!(stats.mean.as_millis() > 90 && stats.mean.as_millis() < 120);
1408        assert!(stats.throughput > 0.0);
1409        assert!(!stats.is_stable || stats.coefficient_of_variation < 0.1);
1410    }
1411
1412    #[test]
1413    fn test_complexity_estimation() {
1414        let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1415
1416        // Linear growth pattern
1417        let linear_timings = vec![
1418            (100, Duration::from_millis(10)),
1419            (200, Duration::from_millis(20)),
1420            (400, Duration::from_millis(40)),
1421        ];
1422        assert_eq!(
1423            benchmark.estimate_complexity(&linear_timings),
1424            ComplexityClass::Linear
1425        );
1426
1427        // Quadratic growth pattern
1428        let quadratic_timings = vec![
1429            (100, Duration::from_millis(10)),
1430            (200, Duration::from_millis(40)),
1431            (400, Duration::from_millis(160)),
1432        ];
1433        assert_eq!(
1434            benchmark.estimate_complexity(&quadratic_timings),
1435            ComplexityClass::Quadratic
1436        );
1437    }
1438
1439    #[test]
1440    fn test_advanced_benchmark_creation() {
1441        let config = BenchmarkConfig {
1442            warmup_iterations: 2,
1443            measurement_iterations: 5,
1444            ..Default::default()
1445        };
1446
1447        let benchmark = AdvancedBenchmark::new(config.clone());
1448        assert_eq!(benchmark.config.warmup_iterations, 2);
1449        assert_eq!(benchmark.config.measurement_iterations, 5);
1450    }
1451
1452    #[test]
1453    fn test_optimization_suggestions() {
1454        let benchmark = AdvancedBenchmark::new(BenchmarkConfig::default());
1455
1456        let performance = PerformanceStatistics {
1457            mean: Duration::from_millis(1000), // Slow performance
1458            coefficient_of_variation: 0.3,     // High variance
1459            throughput: 0.5,                   // Low throughput
1460            is_stable: false,
1461            ..Default::default()
1462        };
1463
1464        let memory = Some(MemoryProfile {
1465            efficiency_score: 30.0, // Low efficiency
1466            potential_leak: true,
1467            ..Default::default()
1468        });
1469
1470        let quality = QualityMetrics {
1471            silhouette_score: Some(0.2), // Poor quality
1472            n_clusters: 3,
1473            ..Default::default()
1474        };
1475
1476        let suggestions =
1477            benchmark.generate_optimization_suggestions("kmeans", &performance, &memory, &quality);
1478
1479        assert!(!suggestions.is_empty());
1480        assert!(suggestions
1481            .iter()
1482            .any(|s| s.category == OptimizationCategory::MemoryOptimization));
1483        assert!(suggestions
1484            .iter()
1485            .any(|s| s.priority == OptimizationPriority::Critical));
1486    }
1487}
1488
1489// Default implementations for test support
1490impl Default for PerformanceStatistics {
1491    fn default() -> Self {
1492        Self {
1493            mean: Duration::from_millis(100),
1494            std_dev: Duration::from_millis(10),
1495            min: Duration::from_millis(90),
1496            max: Duration::from_millis(120),
1497            median: Duration::from_millis(100),
1498            percentile_95: Duration::from_millis(115),
1499            percentile_99: Duration::from_millis(118),
1500            coefficient_of_variation: 0.1,
1501            confidence_interval: (Duration::from_millis(95), Duration::from_millis(105)),
1502            is_stable: true,
1503            outliers: 0,
1504            throughput: 10.0,
1505        }
1506    }
1507}
1508
1509impl Default for MemoryProfile {
1510    fn default() -> Self {
1511        Self {
1512            peak_memory_mb: 100.0,
1513            average_memory_mb: 80.0,
1514            allocation_rate: 10.0,
1515            deallocation_rate: 9.5,
1516            gc_events: 0,
1517            efficiency_score: 85.0,
1518            potential_leak: false,
1519        }
1520    }
1521}
1522
1523impl Default for QualityMetrics {
1524    fn default() -> Self {
1525        Self {
1526            silhouette_score: Some(0.5),
1527            calinski_harabasz: Some(100.0),
1528            davies_bouldin: Some(1.0),
1529            inertia: Some(50.0),
1530            n_clusters: 3,
1531            convergence_iterations: Some(10),
1532        }
1533    }
1534}