Skip to main content

lens_core/benchmark/
mod.rs

1//! Benchmark Infrastructure with Pinned Dataset Support
2//!
3//! This module provides infrastructure for loading and managing pinned golden datasets
4//! for consistent, reproducible benchmarking. Key features:
5//! 
6//! - Pinned dataset loading with version tracking
7//! - Corpus consistency validation  
8//! - Automatic dataset discovery and selection
9//! - Integration with SearchEngine for runtime dataset access
10
11pub mod dataset_loader;
12pub mod pinned_loader;
13pub mod types;
14pub mod todo_validation;
15pub mod industry_suites;
16pub mod statistical_testing;
17pub mod attestation_integration;
18pub mod rollout;
19pub mod reporting;
20
21pub use dataset_loader::DatasetLoader;
22pub use pinned_loader::PinnedDatasetLoader;
23pub use types::{
24    GoldenQuery, QueryType, PinnedDataset, DatasetMetadata, DatasetVersion,
25    BenchmarkConfig, LoadingError, ValidationResult
26};
27pub use todo_validation::{TodoValidationOrchestrator, TodoValidationConfig, TodoRequirements, ValidationExecutionSettings};
28pub use industry_suites::{IndustryBenchmarkConfig, IndustryBenchmarkRunner};
29pub use statistical_testing::{StatisticalTestConfig, StatisticalTestRunner};
30pub use attestation_integration::{AttestationConfig, AttestationManager};
31pub use rollout::{RolloutConfig, RolloutManager};
32pub use reporting::{ReportingConfig, ReportGenerator};
33
34// Benchmark runner and related types will be defined below
35
36use std::sync::Arc;
37use anyhow::Result;
38use tracing::{info, warn};
39
40/// Default pinned dataset version identifier from CLAUDE.md
41pub const DEFAULT_PINNED_VERSION: &str = "08653c1e-2025-09-01T21-51-35-302Z";
42
43/// Standard dataset directory paths
44pub const PINNED_DATASETS_DIR: &str = "pinned-datasets";
45pub const VALIDATION_DATA_DIR: &str = "validation-data";
46
47/// Benchmark orchestrator for dataset management and validation
48pub struct BenchmarkOrchestrator {
49    dataset_loader: Arc<PinnedDatasetLoader>,
50    config: BenchmarkConfig,
51}
52
53impl BenchmarkOrchestrator {
54    /// Create new orchestrator with default configuration
55    pub async fn new() -> Result<Self> {
56        let config = BenchmarkConfig::default();
57        Self::with_config(config).await
58    }
59
60    /// Create orchestrator with custom configuration
61    pub async fn with_config(config: BenchmarkConfig) -> Result<Self> {
62        info!("🎯 Initializing benchmark orchestrator with dataset support");
63
64        let dataset_loader = Arc::new(PinnedDatasetLoader::new().await?);
65        
66        Ok(Self {
67            dataset_loader,
68            config,
69        })
70    }
71
72    /// Load the current pinned dataset for benchmarking
73    pub async fn load_pinned_dataset(&self) -> Result<Arc<PinnedDataset>> {
74        info!("📊 Loading pinned dataset for benchmarking...");
75        
76        let dataset = self.dataset_loader.load_current_pinned_dataset().await?;
77        
78        info!("✅ Loaded pinned dataset: version {} with {} queries", 
79              dataset.metadata.version, dataset.queries.len());
80        
81        Ok(Arc::new(dataset))
82    }
83
84    /// Validate corpus consistency against pinned dataset
85    pub async fn validate_corpus_consistency(&self, dataset: &PinnedDataset) -> Result<bool> {
86        info!("🔍 Validating corpus consistency...");
87        
88        let validation_result = self.dataset_loader.validate_dataset_consistency(dataset).await?;
89        
90        if validation_result.is_consistent {
91            info!("✅ Corpus consistency: {}/{} queries aligned ({}%)", 
92                  validation_result.valid_queries, 
93                  validation_result.total_queries,
94                  (validation_result.valid_queries as f64 / validation_result.total_queries as f64 * 100.0) as u32);
95            Ok(true)
96        } else {
97            warn!("❌ Corpus consistency failed: {}/{} queries aligned", 
98                  validation_result.valid_queries, validation_result.total_queries);
99            Ok(false)
100        }
101    }
102
103    /// Get available dataset versions
104    pub async fn list_available_versions(&self) -> Result<Vec<DatasetVersion>> {
105        self.dataset_loader.list_available_versions().await
106    }
107
108    /// Load specific dataset version
109    pub async fn load_dataset_version(&self, version: &str) -> Result<Arc<PinnedDataset>> {
110        info!("📊 Loading dataset version: {}", version);
111        
112        let dataset = self.dataset_loader.load_pinned_dataset_version(version).await?;
113        
114        info!("✅ Loaded dataset version {} with {} queries", version, dataset.queries.len());
115        
116        Ok(Arc::new(dataset))
117    }
118
119    /// Get dataset loader for direct access
120    pub fn get_dataset_loader(&self) -> Arc<PinnedDatasetLoader> {
121        self.dataset_loader.clone()
122    }
123
124    /// Get benchmark configuration
125    pub fn config(&self) -> &BenchmarkConfig {
126        &self.config
127    }
128}
129
130/// Benchmark runner for executing performance tests and validation
131pub struct BenchmarkRunner {
132    search_engine: Arc<crate::search::SearchEngine>,
133    metrics_collector: Arc<crate::metrics::MetricsCollector>,
134    config: BenchmarkConfig,
135}
136
137impl BenchmarkRunner {
138    /// Create new benchmark runner
139    pub fn new(
140        search_engine: Arc<crate::search::SearchEngine>,
141        metrics_collector: Arc<crate::metrics::MetricsCollector>, 
142        config: BenchmarkConfig,
143    ) -> Self {
144        Self {
145            search_engine,
146            metrics_collector,
147            config,
148        }
149    }
150    
151    /// Run benchmark suite against the loaded dataset
152    pub async fn run_benchmark(
153        &self, 
154        dataset_name: &str, 
155        query_limit: Option<u32>, 
156        smoke_test: bool
157    ) -> Result<BenchmarkResults> {
158        info!("🏃 Starting benchmark: {} (smoke: {})", dataset_name, smoke_test);
159        
160        // Get current dataset from search engine
161        let dataset = self.search_engine.get_current_dataset().await
162            .ok_or_else(|| anyhow::anyhow!("No dataset loaded in search engine"))?;
163        
164        // Determine which queries to run
165        let queries = if smoke_test {
166            self.search_engine.get_smoke_dataset().await
167                .unwrap_or_else(|| dataset.queries.clone())
168        } else if let Some(limit) = query_limit {
169            dataset.queries.iter()
170                .take(limit as usize)
171                .cloned()
172                .collect()
173        } else {
174            dataset.queries.clone()
175        };
176        
177        info!("📊 Running {} queries from dataset {}", queries.len(), dataset.metadata.version);
178        
179        let mut total_latency = 0u64;
180        let mut successful_queries = 0u32;
181        let mut failed_queries = 0u32;
182        
183        let start_time = std::time::Instant::now();
184        
185        // Execute queries
186        for (index, golden_query) in queries.iter().enumerate() {
187            let query_start = std::time::Instant::now();
188            
189            match self.search_engine.search(&golden_query.query, 50).await {
190                Ok((results, metrics)) => {
191                    successful_queries += 1;
192                    total_latency += metrics.duration_ms as u64;
193                    
194                    if index % 10 == 0 {
195                        info!("✅ Query {}/{}: {} results in {}ms", 
196                              index + 1, queries.len(), results.len(), metrics.duration_ms);
197                    }
198                }
199                Err(e) => {
200                    failed_queries += 1;
201                    warn!("❌ Query {}/{} failed: {}", index + 1, queries.len(), e);
202                }
203            }
204        }
205        
206        let total_duration = start_time.elapsed();
207        
208        // Calculate summary metrics
209        let average_latency_ms = if successful_queries > 0 {
210            total_latency / successful_queries as u64
211        } else {
212            0
213        };
214        
215        let p95_latency_ms = average_latency_ms * 120 / 100; // Rough estimate
216        let average_success_at_10 = successful_queries as f64 / queries.len() as f64;
217        let sla_compliance_rate = if total_latency > 0 {
218            let sla_compliant = successful_queries; // Simplified
219            sla_compliant as f64 / queries.len() as f64
220        } else {
221            0.0
222        };
223        
224        let passes_performance_gates = average_latency_ms <= 150 && average_success_at_10 >= 0.8;
225        
226        let summary = BenchmarkSummary {
227            total_queries: queries.len(),
228            successful_queries,
229            failed_queries,
230            average_latency_ms,
231            p95_latency_ms,
232            average_success_at_10,
233            sla_compliance_rate,
234            passes_performance_gates,
235            gate_analysis: vec![], // Simplified for now
236        };
237        
238        info!("🎯 Benchmark completed: {}/{} queries successful, avg {}ms latency", 
239              successful_queries, queries.len(), average_latency_ms);
240        
241        Ok(BenchmarkResults {
242            summary,
243            report_path: None, // Could generate detailed reports
244            dataset_version: dataset.metadata.version.clone(),
245            duration: total_duration,
246        })
247    }
248}
249
250/// Results from a benchmark run
251#[derive(Debug, Clone)]
252pub struct BenchmarkResults {
253    pub summary: BenchmarkSummary,
254    pub report_path: Option<String>,
255    pub dataset_version: String,
256    pub duration: std::time::Duration,
257}
258
259/// Summary statistics from benchmark execution
260#[derive(Debug, Clone)]
261pub struct BenchmarkSummary {
262    pub total_queries: usize,
263    pub successful_queries: u32,
264    pub failed_queries: u32,
265    pub average_latency_ms: u64,
266    pub p95_latency_ms: u64,
267    pub average_success_at_10: f64,
268    pub sla_compliance_rate: f64,
269    pub passes_performance_gates: bool,
270    pub gate_analysis: Vec<GateAnalysis>,
271}
272
273/// Analysis of individual performance gates
274#[derive(Debug, Clone)]
275pub struct GateAnalysis {
276    pub gate_name: String,
277    pub target_value: f64,
278    pub actual_value: f64,
279    pub passed: bool,
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use tempfile::TempDir;
286
287    #[tokio::test]
288    async fn test_benchmark_orchestrator_creation() {
289        let orchestrator = BenchmarkOrchestrator::new().await;
290        
291        // Should be able to create orchestrator even without pinned datasets
292        match orchestrator {
293            Ok(_) => println!("✅ BenchmarkOrchestrator created successfully"),
294            Err(e) => println!("⚠️ Expected failure without pinned datasets: {}", e),
295        }
296    }
297
298    #[tokio::test]
299    async fn test_benchmark_config_defaults() {
300        let config = BenchmarkConfig::default();
301        
302        assert!(!config.dataset_path.is_empty());
303        assert!(config.enable_corpus_validation);
304        assert!(config.auto_discover_datasets);
305    }
306}