oxirs_vec/
benchmarking.rs

1//! Comprehensive benchmarking framework for vector search systems
2//!
3//! This module provides extensive benchmarking capabilities including:
4//! - ANN-Benchmarks integration
5//! - Performance profiling and analysis
6//! - Quality metrics (recall, precision)
7//! - Scalability testing
8//! - Comparative benchmarks
9
10use crate::{Vector, VectorIndex};
11use anyhow::{anyhow, Result};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::time::{Duration, Instant};
15
16/// Benchmarking configuration
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct BenchmarkConfig {
19    /// Number of warmup runs
20    pub warmup_runs: usize,
21    /// Number of benchmark runs
22    pub benchmark_runs: usize,
23    /// Maximum benchmark duration
24    pub max_duration: Duration,
25    /// Enable memory profiling
26    pub profile_memory: bool,
27    /// Enable detailed timing
28    pub detailed_timing: bool,
29    /// Enable quality metrics
30    pub quality_metrics: bool,
31    /// Random seed for reproducibility
32    pub random_seed: Option<u64>,
33    /// Output format for results
34    pub output_format: BenchmarkOutputFormat,
35}
36
37impl Default for BenchmarkConfig {
38    fn default() -> Self {
39        Self {
40            warmup_runs: 3,
41            benchmark_runs: 10,
42            max_duration: Duration::from_secs(300), // 5 minutes
43            profile_memory: true,
44            detailed_timing: true,
45            quality_metrics: true,
46            random_seed: Some(42),
47            output_format: BenchmarkOutputFormat::Json,
48        }
49    }
50}
51
52/// Benchmark output formats
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum BenchmarkOutputFormat {
55    Json,
56    Csv,
57    Table,
58    AnnBenchmarks,
59}
60
61/// Benchmark suite for comprehensive vector search testing
62pub struct BenchmarkSuite {
63    config: BenchmarkConfig,
64    datasets: Vec<BenchmarkDataset>,
65    algorithms: Vec<Box<dyn VectorIndex>>,
66    results: Vec<BenchmarkResult>,
67}
68
69/// Benchmark dataset
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct BenchmarkDataset {
72    /// Dataset name
73    pub name: String,
74    /// Dataset description
75    pub description: String,
76    /// Training vectors
77    pub train_vectors: Vec<Vector>,
78    /// Query vectors
79    pub query_vectors: Vec<Vector>,
80    /// Ground truth results (for quality metrics)
81    pub ground_truth: Option<Vec<Vec<usize>>>, // For each query, list of nearest neighbor indices
82    /// Dataset metadata
83    pub metadata: HashMap<String, String>,
84}
85
86/// Benchmark test case
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct BenchmarkTestCase {
89    /// Test name
90    pub name: String,
91    /// Test description
92    pub description: String,
93    /// Dataset name
94    pub dataset: String,
95    /// Algorithm name
96    pub algorithm: String,
97    /// Test parameters
98    pub parameters: HashMap<String, serde_json::Value>,
99    /// Number of queries to test
100    pub query_count: usize,
101    /// k value for kNN search
102    pub k: usize,
103}
104
105/// Comprehensive benchmark results
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct BenchmarkResult {
108    /// Test case information
109    pub test_case: BenchmarkTestCase,
110    /// Performance metrics
111    pub performance: PerformanceMetrics,
112    /// Quality metrics
113    pub quality: Option<QualityMetrics>,
114    /// Memory usage statistics
115    pub memory: Option<MemoryMetrics>,
116    /// Scalability metrics
117    pub scalability: Option<ScalabilityMetrics>,
118    /// System information
119    pub system_info: SystemInfo,
120    /// Timestamp
121    pub timestamp: std::time::SystemTime,
122}
123
124/// Performance metrics
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PerformanceMetrics {
127    /// Average query time
128    pub avg_query_time: Duration,
129    /// Median query time
130    pub median_query_time: Duration,
131    /// 95th percentile query time
132    pub p95_query_time: Duration,
133    /// 99th percentile query time
134    pub p99_query_time: Duration,
135    /// Minimum query time
136    pub min_query_time: Duration,
137    /// Maximum query time
138    pub max_query_time: Duration,
139    /// Standard deviation of query times
140    pub std_dev_query_time: Duration,
141    /// Queries per second
142    pub queries_per_second: f64,
143    /// Index build time
144    pub index_build_time: Option<Duration>,
145    /// Index update time (per vector)
146    pub index_update_time: Option<Duration>,
147    /// Throughput (vectors/second)
148    pub throughput: f64,
149}
150
151/// Quality metrics for approximate search algorithms
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct QualityMetrics {
154    /// Recall at k
155    pub recall_at_k: f64,
156    /// Precision at k
157    pub precision_at_k: f64,
158    /// Mean Average Precision (MAP)
159    pub mean_average_precision: f64,
160    /// Normalized Discounted Cumulative Gain (NDCG)
161    pub ndcg_at_k: f64,
162    /// Distance ratio (for approximate vs exact)
163    pub distance_ratio: Option<f64>,
164    /// Relative error
165    pub relative_error: Option<f64>,
166}
167
168/// Memory usage metrics
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct MemoryMetrics {
171    /// Peak memory usage (bytes)
172    pub peak_memory_bytes: usize,
173    /// Average memory usage (bytes)
174    pub avg_memory_bytes: usize,
175    /// Memory per vector (bytes)
176    pub memory_per_vector: f64,
177    /// Index memory overhead
178    pub index_overhead_bytes: usize,
179    /// Memory efficiency ratio
180    pub memory_efficiency: f64,
181}
182
183/// Scalability metrics
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ScalabilityMetrics {
186    /// Performance vs dataset size
187    pub performance_scaling: Vec<(usize, Duration)>, // (dataset_size, avg_query_time)
188    /// Memory vs dataset size
189    pub memory_scaling: Vec<(usize, usize)>, // (dataset_size, memory_bytes)
190    /// Build time vs dataset size
191    pub build_time_scaling: Vec<(usize, Duration)>, // (dataset_size, build_time)
192    /// Concurrent query performance
193    pub concurrency_scaling: Vec<(usize, f64)>, // (thread_count, queries_per_second)
194}
195
196/// System information
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct SystemInfo {
199    /// CPU information
200    pub cpu_info: String,
201    /// Total RAM
202    pub total_ram_gb: f64,
203    /// Available RAM
204    pub available_ram_gb: f64,
205    /// Operating system
206    pub os: String,
207    /// Rust version
208    pub rust_version: String,
209    /// SIMD capabilities
210    pub simd_features: Vec<String>,
211    /// GPU information
212    pub gpu_info: Option<String>,
213}
214
215/// Performance profiler for detailed timing analysis
216pub struct PerformanceProfiler {
217    start_time: Instant,
218    checkpoints: Vec<(String, Instant)>,
219    memory_samples: Vec<(Instant, usize)>,
220}
221
222impl PerformanceProfiler {
223    pub fn new() -> Self {
224        Self {
225            start_time: Instant::now(),
226            checkpoints: Vec::new(),
227            memory_samples: Vec::new(),
228        }
229    }
230
231    /// Record a checkpoint
232    pub fn checkpoint(&mut self, name: &str) {
233        self.checkpoints.push((name.to_string(), Instant::now()));
234    }
235
236    /// Sample memory usage
237    pub fn sample_memory(&mut self) {
238        let memory_usage = self.get_current_memory_usage();
239        self.memory_samples.push((Instant::now(), memory_usage));
240    }
241
242    /// Get timing breakdown
243    pub fn get_timing_breakdown(&self) -> Vec<(String, Duration)> {
244        let mut breakdown = Vec::new();
245        let mut last_time = self.start_time;
246
247        for (name, time) in &self.checkpoints {
248            breakdown.push((name.clone(), time.duration_since(last_time)));
249            last_time = *time;
250        }
251
252        breakdown
253    }
254
255    /// Get memory usage over time
256    pub fn get_memory_profile(&self) -> Vec<(Duration, usize)> {
257        self.memory_samples
258            .iter()
259            .map(|(time, memory)| (time.duration_since(self.start_time), *memory))
260            .collect()
261    }
262
263    fn get_current_memory_usage(&self) -> usize {
264        // Platform-specific memory usage detection
265        #[cfg(target_os = "linux")]
266        {
267            if let Ok(content) = std::fs::read_to_string("/proc/self/status") {
268                for line in content.lines() {
269                    if line.starts_with("VmRSS:") {
270                        if let Some(kb_str) = line.split_whitespace().nth(1) {
271                            if let Ok(kb) = kb_str.parse::<usize>() {
272                                return kb * 1024; // Convert to bytes
273                            }
274                        }
275                    }
276                }
277            }
278        }
279
280        #[cfg(target_os = "macos")]
281        {
282            use std::process::Command;
283            if let Ok(output) = Command::new("ps")
284                .args(["-o", "rss=", "-p"])
285                .arg(std::process::id().to_string())
286                .output()
287            {
288                if let Ok(rss_str) = String::from_utf8(output.stdout) {
289                    if let Ok(rss_kb) = rss_str.trim().parse::<usize>() {
290                        return rss_kb * 1024; // Convert to bytes
291                    }
292                }
293            }
294        }
295
296        // Fallback - return 0 if unable to determine
297        0
298    }
299}
300
301impl Default for PerformanceProfiler {
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307impl BenchmarkSuite {
308    /// Create a new benchmark suite
309    pub fn new(config: BenchmarkConfig) -> Self {
310        Self {
311            config,
312            datasets: Vec::new(),
313            algorithms: Vec::new(),
314            results: Vec::new(),
315        }
316    }
317
318    /// Add a dataset to the benchmark suite
319    pub fn add_dataset(&mut self, dataset: BenchmarkDataset) {
320        self.datasets.push(dataset);
321    }
322
323    /// Add an algorithm to benchmark
324    pub fn add_algorithm(&mut self, algorithm: Box<dyn VectorIndex>) {
325        self.algorithms.push(algorithm);
326    }
327
328    /// Generate synthetic datasets for testing
329    pub fn generate_synthetic_datasets(&mut self) -> Result<()> {
330        // Generate various synthetic datasets with different characteristics
331        self.generate_random_dataset("random_1000", 1000, 128, 100)?;
332        self.generate_random_dataset("random_10000", 10000, 256, 1000)?;
333        self.generate_clustered_dataset("clustered_5000", 5000, 384, 500, 10)?;
334        self.generate_uniform_dataset("uniform_2000", 2000, 512, 200)?;
335
336        Ok(())
337    }
338
339    /// Generate random dataset
340    fn generate_random_dataset(
341        &mut self,
342        name: &str,
343        size: usize,
344        dimensions: usize,
345        query_count: usize,
346    ) -> Result<()> {
347        let mut train_vectors = Vec::new();
348        let mut query_vectors = Vec::new();
349
350        // Generate training vectors
351        for i in 0..size {
352            let vector = crate::utils::random_vector(dimensions, Some(i as u64));
353            train_vectors.push(vector);
354        }
355
356        // Generate query vectors
357        for i in 0..query_count {
358            let vector = crate::utils::random_vector(dimensions, Some((size + i) as u64));
359            query_vectors.push(vector);
360        }
361
362        let dataset = BenchmarkDataset {
363            name: name.to_string(),
364            description: format!("Random dataset with {size} vectors of {dimensions} dimensions"),
365            train_vectors,
366            query_vectors,
367            ground_truth: None,
368            metadata: {
369                let mut meta = HashMap::new();
370                meta.insert("type".to_string(), "random".to_string());
371                meta.insert("size".to_string(), size.to_string());
372                meta.insert("dimensions".to_string(), dimensions.to_string());
373                meta
374            },
375        };
376
377        self.add_dataset(dataset);
378        Ok(())
379    }
380
381    /// Generate clustered dataset
382    fn generate_clustered_dataset(
383        &mut self,
384        name: &str,
385        size: usize,
386        dimensions: usize,
387        query_count: usize,
388        num_clusters: usize,
389    ) -> Result<()> {
390        let mut train_vectors = Vec::new();
391        let mut query_vectors = Vec::new();
392
393        // Generate cluster centers
394        let mut cluster_centers = Vec::new();
395        for i in 0..num_clusters {
396            let center = crate::utils::random_vector(dimensions, Some(i as u64));
397            cluster_centers.push(center);
398        }
399
400        // Generate training vectors around clusters
401        for i in 0..size {
402            let cluster_idx = i % num_clusters;
403            let center = &cluster_centers[cluster_idx];
404            let center_f32 = center.as_f32();
405
406            // Add Gaussian noise around cluster center
407            let mut noisy_vector = center_f32.clone();
408            let noise_scale = 0.1;
409
410            for val in &mut noisy_vector {
411                let noise = self.gaussian_random(0.0, noise_scale, i as u64);
412                *val += noise;
413            }
414
415            train_vectors.push(Vector::new(noisy_vector));
416        }
417
418        // Generate query vectors
419        for i in 0..query_count {
420            let cluster_idx = i % num_clusters;
421            let center = &cluster_centers[cluster_idx];
422            let center_f32 = center.as_f32();
423
424            let mut noisy_vector = center_f32.clone();
425            let noise_scale = 0.05; // Less noise for queries
426
427            for val in &mut noisy_vector {
428                let noise = self.gaussian_random(0.0, noise_scale, (size + i) as u64);
429                *val += noise;
430            }
431
432            query_vectors.push(Vector::new(noisy_vector));
433        }
434
435        let dataset = BenchmarkDataset {
436            name: name.to_string(),
437            description: format!(
438                "Clustered dataset with {size} vectors in {num_clusters} clusters of {dimensions} dimensions"
439            ),
440            train_vectors,
441            query_vectors,
442            ground_truth: None,
443            metadata: {
444                let mut meta = HashMap::new();
445                meta.insert("type".to_string(), "clustered".to_string());
446                meta.insert("size".to_string(), size.to_string());
447                meta.insert("dimensions".to_string(), dimensions.to_string());
448                meta.insert("clusters".to_string(), num_clusters.to_string());
449                meta
450            },
451        };
452
453        self.add_dataset(dataset);
454        Ok(())
455    }
456
457    /// Generate uniform dataset
458    fn generate_uniform_dataset(
459        &mut self,
460        name: &str,
461        size: usize,
462        dimensions: usize,
463        query_count: usize,
464    ) -> Result<()> {
465        let mut train_vectors = Vec::new();
466        let mut query_vectors = Vec::new();
467
468        // Generate uniformly distributed vectors
469        for i in 0..size {
470            let mut values = Vec::with_capacity(dimensions);
471            let mut state = i as u64;
472
473            for _ in 0..dimensions {
474                state = state.wrapping_mul(1103515245).wrapping_add(12345);
475                let normalized = (state as f32) / (u64::MAX as f32);
476                values.push(normalized); // 0 to 1 range
477            }
478
479            train_vectors.push(Vector::new(values));
480        }
481
482        // Generate query vectors
483        for i in 0..query_count {
484            let mut values = Vec::with_capacity(dimensions);
485            let mut state = (size + i) as u64;
486
487            for _ in 0..dimensions {
488                state = state.wrapping_mul(1103515245).wrapping_add(12345);
489                let normalized = (state as f32) / (u64::MAX as f32);
490                values.push(normalized);
491            }
492
493            query_vectors.push(Vector::new(values));
494        }
495
496        let dataset = BenchmarkDataset {
497            name: name.to_string(),
498            description: format!("Uniform dataset with {size} vectors of {dimensions} dimensions"),
499            train_vectors,
500            query_vectors,
501            ground_truth: None,
502            metadata: {
503                let mut meta = HashMap::new();
504                meta.insert("type".to_string(), "uniform".to_string());
505                meta.insert("size".to_string(), size.to_string());
506                meta.insert("dimensions".to_string(), dimensions.to_string());
507                meta
508            },
509        };
510
511        self.add_dataset(dataset);
512        Ok(())
513    }
514
515    /// Simple Gaussian random number generator
516    fn gaussian_random(&self, mean: f32, std_dev: f32, seed: u64) -> f32 {
517        // Box-Muller transform for Gaussian distribution
518        let mut state = seed;
519        state = state.wrapping_mul(1103515245).wrapping_add(12345);
520        let u1 = (state as f32) / (u64::MAX as f32);
521        state = state.wrapping_mul(1103515245).wrapping_add(12345);
522        let u2 = (state as f32) / (u64::MAX as f32);
523
524        let z0 = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos();
525        mean + std_dev * z0
526    }
527
528    /// Run all benchmarks
529    pub fn run_all_benchmarks(&mut self) -> Result<Vec<BenchmarkResult>> {
530        let mut all_results = Vec::new();
531
532        // For each dataset and algorithm combination
533        for dataset in &self.datasets {
534            for (alg_idx, algorithm) in self.algorithms.iter().enumerate() {
535                let test_case = BenchmarkTestCase {
536                    name: format!("{}_{alg_idx}", dataset.name),
537                    description: format!("Benchmark {alg_idx} on {}", dataset.name),
538                    dataset: dataset.name.clone(),
539                    algorithm: format!("algorithm_{alg_idx}"),
540                    parameters: HashMap::new(),
541                    query_count: dataset.query_vectors.len(),
542                    k: 10,
543                };
544
545                let result = self.run_single_benchmark(&test_case, dataset, algorithm.as_ref())?;
546                all_results.push(result);
547            }
548        }
549
550        self.results.extend(all_results.clone());
551        Ok(all_results)
552    }
553
554    /// Run a single benchmark test
555    fn run_single_benchmark(
556        &self,
557        test_case: &BenchmarkTestCase,
558        dataset: &BenchmarkDataset,
559        algorithm: &dyn VectorIndex,
560    ) -> Result<BenchmarkResult> {
561        let mut profiler = PerformanceProfiler::new();
562
563        tracing::info!("Running benchmark: {}", test_case.name);
564        profiler.checkpoint("benchmark_start");
565
566        // Build index
567        profiler.checkpoint("index_build_start");
568        let mut index = self.create_index_copy(algorithm)?;
569
570        for (i, vector) in dataset.train_vectors.iter().enumerate() {
571            index.insert(format!("vec_{i}"), vector.clone())?;
572        }
573        profiler.checkpoint("index_build_end");
574
575        // Warmup runs
576        profiler.checkpoint("warmup_start");
577        for _ in 0..self.config.warmup_runs {
578            for query in dataset.query_vectors.iter().take(10) {
579                let _ = index.search_knn(query, test_case.k)?;
580            }
581        }
582        profiler.checkpoint("warmup_end");
583
584        // Benchmark runs
585        profiler.checkpoint("benchmark_queries_start");
586        let mut query_times = Vec::new();
587
588        for query in &dataset.query_vectors {
589            profiler.sample_memory();
590
591            let start = Instant::now();
592            let _results = index.search_knn(query, test_case.k)?;
593            let query_time = start.elapsed();
594
595            query_times.push(query_time);
596        }
597        profiler.checkpoint("benchmark_queries_end");
598
599        // Calculate performance metrics
600        let performance = self.calculate_performance_metrics(&query_times)?;
601
602        // Calculate quality metrics if ground truth is available
603        let quality = if let Some(ground_truth) = &dataset.ground_truth {
604            Some(self.calculate_quality_metrics(
605                index.as_ref(),
606                &dataset.query_vectors,
607                ground_truth,
608                test_case.k,
609            )?)
610        } else {
611            None
612        };
613
614        // Calculate memory metrics
615        let memory = if self.config.profile_memory {
616            Some(self.calculate_memory_metrics(&profiler, dataset.train_vectors.len())?)
617        } else {
618            None
619        };
620
621        // Get system information
622        let system_info = self.get_system_info();
623
624        Ok(BenchmarkResult {
625            test_case: test_case.clone(),
626            performance,
627            quality,
628            memory,
629            scalability: None, // Would be calculated in scalability tests
630            system_info,
631            timestamp: std::time::SystemTime::now(),
632        })
633    }
634
635    /// Create a copy of the index for benchmarking
636    fn create_index_copy(&self, _algorithm: &dyn VectorIndex) -> Result<Box<dyn VectorIndex>> {
637        // For now, create a simple memory index
638        // In practice, would clone the specific algorithm
639        Ok(Box::new(crate::MemoryVectorIndex::new()))
640    }
641
642    /// Calculate performance metrics from query times
643    fn calculate_performance_metrics(
644        &self,
645        query_times: &[Duration],
646    ) -> Result<PerformanceMetrics> {
647        if query_times.is_empty() {
648            return Err(anyhow!("No query times to analyze"));
649        }
650
651        let mut sorted_times = query_times.to_vec();
652        sorted_times.sort();
653
654        let avg_query_time = Duration::from_nanos(
655            (query_times.iter().map(|d| d.as_nanos()).sum::<u128>() / query_times.len() as u128)
656                .try_into()
657                .unwrap(),
658        );
659
660        let median_query_time = sorted_times[sorted_times.len() / 2];
661        let p95_idx = (sorted_times.len() as f64 * 0.95) as usize;
662        let p99_idx = (sorted_times.len() as f64 * 0.99) as usize;
663        let p95_query_time = sorted_times[p95_idx.min(sorted_times.len() - 1)];
664        let p99_query_time = sorted_times[p99_idx.min(sorted_times.len() - 1)];
665
666        let min_query_time = sorted_times[0];
667        let max_query_time = sorted_times[sorted_times.len() - 1];
668
669        // Calculate standard deviation
670        let mean_nanos = avg_query_time.as_nanos() as f64;
671        let variance = query_times
672            .iter()
673            .map(|d| {
674                let diff = d.as_nanos() as f64 - mean_nanos;
675                diff * diff
676            })
677            .sum::<f64>()
678            / query_times.len() as f64;
679        let std_dev_query_time = Duration::from_nanos(variance.sqrt() as u64);
680
681        let queries_per_second = 1.0 / avg_query_time.as_secs_f64();
682        let throughput =
683            query_times.len() as f64 / query_times.iter().map(|d| d.as_secs_f64()).sum::<f64>();
684
685        Ok(PerformanceMetrics {
686            avg_query_time,
687            median_query_time,
688            p95_query_time,
689            p99_query_time,
690            min_query_time,
691            max_query_time,
692            std_dev_query_time,
693            queries_per_second,
694            index_build_time: None, // Would be calculated from profiler
695            index_update_time: None,
696            throughput,
697        })
698    }
699
700    /// Calculate quality metrics
701    fn calculate_quality_metrics(
702        &self,
703        index: &dyn VectorIndex,
704        queries: &[Vector],
705        ground_truth: &[Vec<usize>],
706        k: usize,
707    ) -> Result<QualityMetrics> {
708        let mut total_recall = 0.0;
709        let mut total_precision = 0.0;
710        let mut total_queries = 0;
711
712        for (query_idx, query) in queries.iter().enumerate() {
713            if query_idx >= ground_truth.len() {
714                break;
715            }
716
717            let results = index.search_knn(query, k)?;
718            let returned_indices: Vec<usize> = results
719                .iter()
720                .filter_map(|(uri, _)| {
721                    // Extract index from URI (assuming format "vec_{index}")
722                    uri.strip_prefix("vec_")
723                        .and_then(|s| s.parse::<usize>().ok())
724                })
725                .collect();
726
727            let true_neighbors = &ground_truth[query_idx];
728            let true_neighbors_k: std::collections::HashSet<usize> =
729                true_neighbors.iter().take(k).copied().collect();
730
731            // Calculate recall: how many true neighbors were found
732            let found_true = returned_indices
733                .iter()
734                .filter(|&idx| true_neighbors_k.contains(idx))
735                .count();
736
737            let recall = found_true as f64 / k.min(true_neighbors.len()) as f64;
738            let precision = found_true as f64 / returned_indices.len() as f64;
739
740            total_recall += recall;
741            total_precision += precision;
742            total_queries += 1;
743        }
744
745        let avg_recall = total_recall / total_queries as f64;
746        let avg_precision = total_precision / total_queries as f64;
747
748        Ok(QualityMetrics {
749            recall_at_k: avg_recall,
750            precision_at_k: avg_precision,
751            mean_average_precision: avg_precision, // Simplified
752            ndcg_at_k: avg_recall,                 // Simplified
753            distance_ratio: None,
754            relative_error: None,
755        })
756    }
757
758    /// Calculate memory metrics
759    fn calculate_memory_metrics(
760        &self,
761        profiler: &PerformanceProfiler,
762        vector_count: usize,
763    ) -> Result<MemoryMetrics> {
764        let memory_profile = profiler.get_memory_profile();
765
766        if memory_profile.is_empty() {
767            return Ok(MemoryMetrics {
768                peak_memory_bytes: 0,
769                avg_memory_bytes: 0,
770                memory_per_vector: 0.0,
771                index_overhead_bytes: 0,
772                memory_efficiency: 0.0,
773            });
774        }
775
776        let peak_memory_bytes = memory_profile
777            .iter()
778            .map(|(_, mem)| *mem)
779            .max()
780            .unwrap_or(0);
781        let avg_memory_bytes =
782            memory_profile.iter().map(|(_, mem)| *mem).sum::<usize>() / memory_profile.len();
783        let memory_per_vector = avg_memory_bytes as f64 / vector_count as f64;
784
785        Ok(MemoryMetrics {
786            peak_memory_bytes,
787            avg_memory_bytes,
788            memory_per_vector,
789            index_overhead_bytes: 0, // Would calculate based on vector size
790            memory_efficiency: 1.0,  // Would calculate as ratio of theoretical to actual memory
791        })
792    }
793
794    /// Get system information
795    fn get_system_info(&self) -> SystemInfo {
796        SystemInfo {
797            cpu_info: self.get_cpu_info(),
798            total_ram_gb: self.get_total_ram_gb(),
799            available_ram_gb: self.get_available_ram_gb(),
800            os: std::env::consts::OS.to_string(),
801            rust_version: self.get_rust_version(),
802            simd_features: self.get_simd_features(),
803            gpu_info: self.get_gpu_info(),
804        }
805    }
806
807    fn get_cpu_info(&self) -> String {
808        #[cfg(target_os = "linux")]
809        {
810            if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
811                for line in content.lines() {
812                    if line.starts_with("model name") {
813                        if let Some(name) = line.split(':').nth(1) {
814                            return name.trim().to_string();
815                        }
816                    }
817                }
818            }
819        }
820
821        #[cfg(target_os = "macos")]
822        {
823            use std::process::Command;
824            if let Ok(output) = Command::new("sysctl")
825                .args(["-n", "machdep.cpu.brand_string"])
826                .output()
827            {
828                if let Ok(cpu_name) = String::from_utf8(output.stdout) {
829                    return cpu_name.trim().to_string();
830                }
831            }
832        }
833
834        "Unknown CPU".to_string()
835    }
836
837    fn get_total_ram_gb(&self) -> f64 {
838        #[cfg(target_os = "linux")]
839        {
840            if let Ok(content) = std::fs::read_to_string("/proc/meminfo") {
841                for line in content.lines() {
842                    if line.starts_with("MemTotal:") {
843                        if let Some(kb_str) = line.split_whitespace().nth(1) {
844                            if let Ok(kb) = kb_str.parse::<u64>() {
845                                return kb as f64 / 1024.0 / 1024.0; // Convert KB to GB
846                            }
847                        }
848                    }
849                }
850            }
851        }
852
853        #[cfg(target_os = "macos")]
854        {
855            use std::process::Command;
856            if let Ok(output) = Command::new("sysctl").args(["-n", "hw.memsize"]).output() {
857                if let Ok(mem_str) = String::from_utf8(output.stdout) {
858                    if let Ok(bytes) = mem_str.trim().parse::<u64>() {
859                        return bytes as f64 / 1024.0 / 1024.0 / 1024.0; // Convert bytes to GB
860                    }
861                }
862            }
863        }
864
865        8.0 // Default fallback
866    }
867
868    fn get_available_ram_gb(&self) -> f64 {
869        // Simplified - would implement proper available memory detection
870        self.get_total_ram_gb() * 0.8
871    }
872
873    fn get_rust_version(&self) -> String {
874        std::env::var("RUSTC_VERSION").unwrap_or_else(|_| "unknown".to_string())
875    }
876
877    fn get_simd_features(&self) -> Vec<String> {
878        let mut features = Vec::new();
879
880        #[cfg(target_arch = "x86_64")]
881        {
882            if is_x86_feature_detected!("sse") {
883                features.push("SSE".to_string());
884            }
885            if is_x86_feature_detected!("sse2") {
886                features.push("SSE2".to_string());
887            }
888            if is_x86_feature_detected!("sse3") {
889                features.push("SSE3".to_string());
890            }
891            if is_x86_feature_detected!("sse4.1") {
892                features.push("SSE4.1".to_string());
893            }
894            if is_x86_feature_detected!("sse4.2") {
895                features.push("SSE4.2".to_string());
896            }
897            if is_x86_feature_detected!("avx") {
898                features.push("AVX".to_string());
899            }
900            if is_x86_feature_detected!("avx2") {
901                features.push("AVX2".to_string());
902            }
903        }
904
905        #[cfg(target_arch = "aarch64")]
906        {
907            features.push("NEON".to_string());
908        }
909
910        features
911    }
912
913    fn get_gpu_info(&self) -> Option<String> {
914        // Would integrate with GPU detection libraries
915        None
916    }
917
918    /// Export results in various formats
919    pub fn export_results(&self, format: BenchmarkOutputFormat) -> Result<String> {
920        match format {
921            BenchmarkOutputFormat::Json => serde_json::to_string_pretty(&self.results)
922                .map_err(|e| anyhow!("Failed to serialize to JSON: {}", e)),
923            BenchmarkOutputFormat::Csv => self.export_csv(),
924            BenchmarkOutputFormat::Table => self.export_table(),
925            BenchmarkOutputFormat::AnnBenchmarks => self.export_ann_benchmarks(),
926        }
927    }
928
929    fn export_csv(&self) -> Result<String> {
930        let mut csv = String::new();
931        csv.push_str(
932            "dataset,algorithm,avg_query_time_ms,queries_per_second,recall_at_k,memory_mb\n",
933        );
934
935        for result in &self.results {
936            csv.push_str(&format!(
937                "{},{},{:.3},{:.2},{:.3},{:.2}\n",
938                result.test_case.dataset,
939                result.test_case.algorithm,
940                result.performance.avg_query_time.as_millis(),
941                result.performance.queries_per_second,
942                result
943                    .quality
944                    .as_ref()
945                    .map(|q| q.recall_at_k)
946                    .unwrap_or(0.0),
947                result
948                    .memory
949                    .as_ref()
950                    .map(|m| m.avg_memory_bytes as f64 / 1024.0 / 1024.0)
951                    .unwrap_or(0.0),
952            ));
953        }
954
955        Ok(csv)
956    }
957
958    fn export_table(&self) -> Result<String> {
959        let mut table = String::new();
960        table.push_str(&format!(
961            "{:<20} {:<15} {:<15} {:<15} {:<10}\n",
962            "Dataset", "Algorithm", "Avg Time (ms)", "QPS", "Recall@K"
963        ));
964        table.push_str(&"-".repeat(80));
965        table.push('\n');
966
967        for result in &self.results {
968            table.push_str(&format!(
969                "{:<20} {:<15} {:<15.3} {:<15.2} {:<10.3}\n",
970                result.test_case.dataset,
971                result.test_case.algorithm,
972                result.performance.avg_query_time.as_millis(),
973                result.performance.queries_per_second,
974                result
975                    .quality
976                    .as_ref()
977                    .map(|q| q.recall_at_k)
978                    .unwrap_or(0.0),
979            ));
980        }
981
982        Ok(table)
983    }
984
985    fn export_ann_benchmarks(&self) -> Result<String> {
986        // Export in ANN-Benchmarks compatible format
987        let mut results = serde_json::Map::new();
988
989        for result in &self.results {
990            let mut entry = serde_json::Map::new();
991            entry.insert(
992                "k".to_string(),
993                serde_json::Value::Number(result.test_case.k.into()),
994            );
995            entry.insert(
996                "recall".to_string(),
997                serde_json::Value::Number(
998                    serde_json::Number::from_f64(
999                        result
1000                            .quality
1001                            .as_ref()
1002                            .map(|q| q.recall_at_k)
1003                            .unwrap_or(0.0),
1004                    )
1005                    .unwrap_or(serde_json::Number::from(0)),
1006                ),
1007            );
1008            entry.insert(
1009                "qps".to_string(),
1010                serde_json::Value::Number(
1011                    serde_json::Number::from_f64(result.performance.queries_per_second)
1012                        .unwrap_or(serde_json::Number::from(0)),
1013                ),
1014            );
1015
1016            let key = format!(
1017                "{}_{}",
1018                result.test_case.dataset, result.test_case.algorithm
1019            );
1020            results.insert(key, serde_json::Value::Object(entry));
1021        }
1022
1023        serde_json::to_string_pretty(&results)
1024            .map_err(|e| anyhow!("Failed to serialize ANN-Benchmarks format: {}", e))
1025    }
1026
1027    /// Run scalability benchmarks
1028    pub fn run_scalability_benchmark(&mut self, dataset_name: &str) -> Result<ScalabilityMetrics> {
1029        let dataset = self
1030            .datasets
1031            .iter()
1032            .find(|d| d.name == dataset_name)
1033            .ok_or_else(|| anyhow!("Dataset not found: {}", dataset_name))?;
1034
1035        let mut performance_scaling = Vec::new();
1036        let mut memory_scaling = Vec::new();
1037        let mut build_time_scaling = Vec::new();
1038
1039        // Test different dataset sizes
1040        let test_sizes = [100, 500, 1000, 2000, 5000];
1041
1042        for &size in &test_sizes {
1043            if size > dataset.train_vectors.len() {
1044                continue;
1045            }
1046
1047            let subset_vectors = &dataset.train_vectors[..size];
1048            let test_queries = &dataset.query_vectors[..10.min(dataset.query_vectors.len())];
1049
1050            // Build index and measure time
1051            let build_start = Instant::now();
1052            let mut index = Box::new(crate::MemoryVectorIndex::new());
1053
1054            for (i, vector) in subset_vectors.iter().enumerate() {
1055                index.insert(format!("vec_{i}"), vector.clone())?;
1056            }
1057            let build_time = build_start.elapsed();
1058
1059            // Measure query performance
1060            let mut query_times = Vec::new();
1061            let memory_start = self.get_current_memory_usage();
1062
1063            for query in test_queries {
1064                let start = Instant::now();
1065                let _ = index.search_knn(query, 10)?;
1066                query_times.push(start.elapsed());
1067            }
1068
1069            let memory_end = self.get_current_memory_usage();
1070            let avg_query_time = Duration::from_nanos(
1071                (query_times.iter().map(|d| d.as_nanos()).sum::<u128>()
1072                    / query_times.len() as u128)
1073                    .try_into()
1074                    .unwrap(),
1075            );
1076
1077            performance_scaling.push((size, avg_query_time));
1078            memory_scaling.push((size, memory_end.saturating_sub(memory_start)));
1079            build_time_scaling.push((size, build_time));
1080        }
1081
1082        // Test concurrency scaling
1083        let mut concurrency_scaling = Vec::new();
1084        let thread_counts = [1, 2, 4, 8];
1085
1086        for &thread_count in &thread_counts {
1087            let qps = self.measure_concurrent_performance(
1088                &dataset.query_vectors[..100.min(dataset.query_vectors.len())],
1089                thread_count,
1090            )?;
1091            concurrency_scaling.push((thread_count, qps));
1092        }
1093
1094        Ok(ScalabilityMetrics {
1095            performance_scaling,
1096            memory_scaling,
1097            build_time_scaling,
1098            concurrency_scaling,
1099        })
1100    }
1101
1102    fn get_current_memory_usage(&self) -> usize {
1103        // Platform-specific memory usage detection (same as in PerformanceProfiler)
1104        0 // Placeholder
1105    }
1106
1107    fn measure_concurrent_performance(
1108        &self,
1109        queries: &[Vector],
1110        thread_count: usize,
1111    ) -> Result<f64> {
1112        use std::sync::Arc;
1113        use std::thread;
1114
1115        let index = Arc::new(crate::MemoryVectorIndex::new());
1116        let queries = Arc::new(queries.to_vec());
1117
1118        let start_time = Instant::now();
1119        let mut handles = Vec::new();
1120
1121        for thread_id in 0..thread_count {
1122            let index = Arc::clone(&index);
1123            let queries = Arc::clone(&queries);
1124
1125            let handle = thread::spawn(move || {
1126                let queries_per_thread = queries.len() / thread_count;
1127                let start_idx = thread_id * queries_per_thread;
1128                let end_idx = if thread_id == thread_count - 1 {
1129                    queries.len()
1130                } else {
1131                    start_idx + queries_per_thread
1132                };
1133
1134                let mut query_count = 0;
1135                for query in &queries[start_idx..end_idx] {
1136                    if index.search_knn(query, 10).is_ok() {
1137                        query_count += 1;
1138                    }
1139                }
1140                query_count
1141            });
1142
1143            handles.push(handle);
1144        }
1145
1146        let total_queries: usize = handles.into_iter().map(|h| h.join().unwrap_or(0)).sum();
1147        let elapsed = start_time.elapsed();
1148
1149        Ok(total_queries as f64 / elapsed.as_secs_f64())
1150    }
1151}
1152
1153/// Benchmark runner for easy execution
1154pub struct BenchmarkRunner;
1155
1156impl BenchmarkRunner {
1157    /// Run standard benchmarks with default configuration
1158    pub fn run_standard_benchmarks() -> Result<Vec<BenchmarkResult>> {
1159        let config = BenchmarkConfig::default();
1160        let mut suite = BenchmarkSuite::new(config);
1161
1162        // Generate synthetic datasets
1163        suite.generate_synthetic_datasets()?;
1164
1165        // Add different algorithms
1166        suite.add_algorithm(Box::new(crate::MemoryVectorIndex::new()));
1167
1168        // Run benchmarks
1169        suite.run_all_benchmarks()
1170    }
1171
1172    /// Run quick benchmarks for CI/testing
1173    pub fn run_quick_benchmarks() -> Result<Vec<BenchmarkResult>> {
1174        let config = BenchmarkConfig {
1175            warmup_runs: 1,
1176            benchmark_runs: 3,
1177            max_duration: Duration::from_secs(30),
1178            ..BenchmarkConfig::default()
1179        };
1180
1181        let mut suite = BenchmarkSuite::new(config);
1182
1183        // Generate smaller datasets for quick testing
1184        suite.generate_random_dataset("quick_test", 100, 64, 10)?;
1185        suite.add_algorithm(Box::new(crate::MemoryVectorIndex::new()));
1186
1187        suite.run_all_benchmarks()
1188    }
1189
1190    /// Run comprehensive benchmarks with quality metrics
1191    pub fn run_comprehensive_benchmarks() -> Result<String> {
1192        let results = Self::run_standard_benchmarks()?;
1193
1194        // Export results in table format
1195        let config = BenchmarkConfig::default();
1196        let suite = BenchmarkSuite {
1197            config,
1198            datasets: Vec::new(),
1199            algorithms: Vec::new(),
1200            results,
1201        };
1202
1203        suite.export_results(BenchmarkOutputFormat::Table)
1204    }
1205}
1206
1207#[cfg(test)]
1208mod tests {
1209    use super::*;
1210
1211    #[test]
1212    fn test_benchmark_suite_creation() {
1213        let config = BenchmarkConfig::default();
1214        let suite = BenchmarkSuite::new(config);
1215        assert_eq!(suite.datasets.len(), 0);
1216        assert_eq!(suite.algorithms.len(), 0);
1217    }
1218
1219    #[test]
1220    fn test_synthetic_dataset_generation() {
1221        let config = BenchmarkConfig::default();
1222        let mut suite = BenchmarkSuite::new(config);
1223
1224        suite.generate_synthetic_datasets().unwrap();
1225        assert!(!suite.datasets.is_empty());
1226
1227        for dataset in &suite.datasets {
1228            assert!(!dataset.train_vectors.is_empty());
1229            assert!(!dataset.query_vectors.is_empty());
1230        }
1231    }
1232
1233    #[test]
1234    fn test_performance_metrics_calculation() {
1235        let config = BenchmarkConfig::default();
1236        let suite = BenchmarkSuite::new(config);
1237
1238        let query_times = vec![
1239            Duration::from_millis(10),
1240            Duration::from_millis(15),
1241            Duration::from_millis(12),
1242            Duration::from_millis(20),
1243            Duration::from_millis(8),
1244        ];
1245
1246        let metrics = suite.calculate_performance_metrics(&query_times).unwrap();
1247
1248        assert!(metrics.avg_query_time.as_millis() > 0);
1249        assert!(metrics.queries_per_second > 0.0);
1250        assert!(metrics.min_query_time <= metrics.median_query_time);
1251        assert!(metrics.median_query_time <= metrics.max_query_time);
1252    }
1253
1254    #[test]
1255    fn test_quick_benchmarks() {
1256        let results = BenchmarkRunner::run_quick_benchmarks();
1257        assert!(results.is_ok());
1258        let results = results.unwrap();
1259        assert!(!results.is_empty());
1260
1261        for result in results {
1262            assert!(result.performance.avg_query_time.as_nanos() > 0);
1263            assert!(result.performance.queries_per_second > 0.0);
1264        }
1265    }
1266
1267    #[test]
1268    fn test_export_formats() {
1269        let config = BenchmarkConfig::default();
1270        let mut suite = BenchmarkSuite::new(config);
1271
1272        // Create a simple test result
1273        let test_case = BenchmarkTestCase {
1274            name: "test".to_string(),
1275            description: "test case".to_string(),
1276            dataset: "test_data".to_string(),
1277            algorithm: "test_alg".to_string(),
1278            parameters: HashMap::new(),
1279            query_count: 10,
1280            k: 5,
1281        };
1282
1283        let result = BenchmarkResult {
1284            test_case,
1285            performance: PerformanceMetrics {
1286                avg_query_time: Duration::from_millis(10),
1287                median_query_time: Duration::from_millis(10),
1288                p95_query_time: Duration::from_millis(15),
1289                p99_query_time: Duration::from_millis(20),
1290                min_query_time: Duration::from_millis(5),
1291                max_query_time: Duration::from_millis(25),
1292                std_dev_query_time: Duration::from_millis(3),
1293                queries_per_second: 100.0,
1294                index_build_time: None,
1295                index_update_time: None,
1296                throughput: 100.0,
1297            },
1298            quality: None,
1299            memory: None,
1300            scalability: None,
1301            system_info: SystemInfo {
1302                cpu_info: "Test CPU".to_string(),
1303                total_ram_gb: 16.0,
1304                available_ram_gb: 12.0,
1305                os: "test".to_string(),
1306                rust_version: "1.70.0".to_string(),
1307                simd_features: vec!["AVX2".to_string()],
1308                gpu_info: None,
1309            },
1310            timestamp: std::time::SystemTime::now(),
1311        };
1312
1313        suite.results.push(result);
1314
1315        // Test different export formats
1316        let json_output = suite.export_results(BenchmarkOutputFormat::Json);
1317        assert!(json_output.is_ok());
1318
1319        let csv_output = suite.export_results(BenchmarkOutputFormat::Csv);
1320        assert!(csv_output.is_ok());
1321
1322        let table_output = suite.export_results(BenchmarkOutputFormat::Table);
1323        assert!(table_output.is_ok());
1324    }
1325
1326    #[test]
1327    fn test_profiler() {
1328        let mut profiler = PerformanceProfiler::new();
1329
1330        profiler.checkpoint("start");
1331        std::thread::sleep(Duration::from_millis(50));
1332        profiler.checkpoint("middle");
1333        std::thread::sleep(Duration::from_millis(100));
1334        profiler.checkpoint("end");
1335
1336        let breakdown = profiler.get_timing_breakdown();
1337        assert_eq!(breakdown.len(), 3);
1338
1339        // Check that timings are reasonable
1340        for (name, duration) in breakdown {
1341            assert!(!name.is_empty());
1342            // Use nanos instead of micros for more reliable timing
1343            assert!(duration.as_nanos() > 0);
1344        }
1345    }
1346}