Skip to main content

lens_core/semantic/
benchmarks.rs

1//! # Comprehensive Semantic Processing Benchmarks
2//!
3//! Production benchmarking suite for semantic processing components with:
4//! - Performance regression testing and monitoring
5//! - Memory usage profiling and leak detection
6//! - Throughput and latency measurements across workloads
7//! - A/B testing framework for model comparisons
8//! - Integration with existing pinned dataset system
9
10use anyhow::{Context, Result};
11#[cfg(feature = "benchmarks")]
12use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17use tokio::sync::RwLock;
18use tracing::{info, instrument, span, Level};
19
20use super::{
21    embedding::{SemanticEncoder, EmbeddingConfig, CodeEmbedding},
22    query_classifier::{QueryClassifier, ClassifierConfig, QueryClassification},
23    intent_router::{IntentRouter, IntentRouterConfig, SearchContext, IntentRoutingResult},
24    conformal_router::{ConformalRouter, ConformalRouterConfig, extract_conformal_features},
25};
26
27/// Benchmark configuration
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct BenchmarkConfig {
30    /// Benchmark dataset size for different test types
31    pub dataset_sizes: BenchmarkDatasetSizes,
32    /// Performance targets for validation
33    pub performance_targets: PerformanceTargets,
34    /// Memory usage limits
35    pub memory_limits: MemoryLimits,
36    /// Enable detailed profiling
37    pub enable_profiling: bool,
38    /// Output directory for results
39    pub output_directory: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct BenchmarkDatasetSizes {
44    pub micro: usize,      // 10 queries
45    pub small: usize,      // 100 queries
46    pub medium: usize,     // 1,000 queries
47    pub large: usize,      // 10,000 queries
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PerformanceTargets {
52    /// Single query classification (ms)
53    pub classification_p95_ms: f64,
54    /// Single query routing (ms)
55    pub routing_p95_ms: f64,
56    /// Single embedding encoding (ms)
57    pub encoding_p95_ms: f64,
58    /// Batch encoding throughput (queries/sec)
59    pub batch_encoding_qps: f64,
60    /// End-to-end semantic processing (ms)
61    pub end_to_end_p95_ms: f64,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct MemoryLimits {
66    /// Maximum memory per query (MB)
67    pub max_memory_per_query_mb: f64,
68    /// Maximum memory for embeddings cache (MB)
69    pub max_embedding_cache_mb: f64,
70    /// Maximum memory growth per hour (MB)
71    pub max_memory_growth_per_hour_mb: f64,
72}
73
74impl Default for BenchmarkConfig {
75    fn default() -> Self {
76        Self {
77            dataset_sizes: BenchmarkDatasetSizes {
78                micro: 10,
79                small: 100,
80                medium: 1_000,
81                large: 10_000,
82            },
83            performance_targets: PerformanceTargets {
84                classification_p95_ms: 5.0,
85                routing_p95_ms: 10.0,
86                encoding_p95_ms: 50.0,
87                batch_encoding_qps: 100.0,
88                end_to_end_p95_ms: 100.0,
89            },
90            memory_limits: MemoryLimits {
91                max_memory_per_query_mb: 10.0,
92                max_embedding_cache_mb: 100.0,
93                max_memory_growth_per_hour_mb: 50.0,
94            },
95            enable_profiling: true,
96            output_directory: "./benchmark-results".to_string(),
97        }
98    }
99}
100
101/// Benchmark dataset for semantic processing
102#[derive(Debug, Clone)]
103pub struct BenchmarkDataset {
104    pub queries: Vec<BenchmarkQuery>,
105    pub size_category: String,
106}
107
108#[derive(Debug, Clone)]
109pub struct BenchmarkQuery {
110    pub id: String,
111    pub text: String,
112    pub expected_intent: Option<super::query_classifier::QueryIntent>,
113    pub expected_naturalness: Option<f32>,
114    pub language: Option<String>,
115    pub complexity: QueryComplexity,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq)]
119pub enum QueryComplexity {
120    Simple,   // Single word or phrase
121    Medium,   // Short sentence with clear intent
122    Complex,  // Long query with multiple concepts
123    Extreme,  // Very long or ambiguous query
124}
125
126/// Benchmark results for a single component
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ComponentBenchmarkResults {
129    pub component_name: String,
130    pub dataset_size: usize,
131    pub metrics: PerformanceMetrics,
132    pub memory_usage: MemoryUsage,
133    pub errors: Vec<BenchmarkError>,
134    pub timestamp: u64,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct PerformanceMetrics {
139    pub mean_latency_ms: f64,
140    pub p50_latency_ms: f64,
141    pub p95_latency_ms: f64,
142    pub p99_latency_ms: f64,
143    pub max_latency_ms: f64,
144    pub min_latency_ms: f64,
145    pub throughput_qps: f64,
146    pub success_rate: f64,
147    pub total_operations: u64,
148    pub total_duration_ms: f64,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct MemoryUsage {
153    pub peak_memory_mb: f64,
154    pub avg_memory_mb: f64,
155    pub memory_growth_mb: f64,
156    pub cache_hit_rate: f64,
157    pub gc_time_ms: f64,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct BenchmarkError {
162    pub query_id: String,
163    pub error_message: String,
164    pub error_type: String,
165    pub timestamp: u64,
166}
167
168/// Comprehensive benchmark suite for semantic processing
169pub struct SemanticBenchmarkSuite {
170    config: BenchmarkConfig,
171    datasets: HashMap<String, BenchmarkDataset>,
172    results: Arc<RwLock<Vec<ComponentBenchmarkResults>>>,
173}
174
175impl SemanticBenchmarkSuite {
176    /// Create new benchmark suite
177    pub fn new(config: BenchmarkConfig) -> Self {
178        Self {
179            config,
180            datasets: HashMap::new(),
181            results: Arc::new(RwLock::new(Vec::new())),
182        }
183    }
184    
185    /// Generate benchmark datasets
186    pub async fn generate_datasets(&mut self) -> Result<()> {
187        info!("Generating benchmark datasets");
188        
189        // Micro dataset (10 queries)
190        let micro_queries = self.generate_queries(self.config.dataset_sizes.micro, "micro").await?;
191        self.datasets.insert("micro".to_string(), BenchmarkDataset {
192            queries: micro_queries,
193            size_category: "micro".to_string(),
194        });
195        
196        // Small dataset (100 queries)  
197        let small_queries = self.generate_queries(self.config.dataset_sizes.small, "small").await?;
198        self.datasets.insert("small".to_string(), BenchmarkDataset {
199            queries: small_queries,
200            size_category: "small".to_string(),
201        });
202        
203        // Medium dataset (1,000 queries) - only if needed
204        if self.config.dataset_sizes.medium > 0 {
205            let medium_queries = self.generate_queries(self.config.dataset_sizes.medium, "medium").await?;
206            self.datasets.insert("medium".to_string(), BenchmarkDataset {
207                queries: medium_queries,
208                size_category: "medium".to_string(),
209            });
210        }
211        
212        // Large dataset (10,000 queries) - only if needed
213        if self.config.dataset_sizes.large > 0 {
214            let large_queries = self.generate_queries(self.config.dataset_sizes.large, "large").await?;
215            self.datasets.insert("large".to_string(), BenchmarkDataset {
216                queries: large_queries,
217                size_category: "large".to_string(),
218            });
219        }
220        
221        info!("Generated {} benchmark datasets", self.datasets.len());
222        Ok(())
223    }
224    
225    /// Generate queries for a dataset
226    async fn generate_queries(&self, count: usize, category: &str) -> Result<Vec<BenchmarkQuery>> {
227        let mut queries = Vec::with_capacity(count);
228        
229        // Query templates by complexity
230        let simple_queries = [
231            "calculateSum",
232            "def sort",
233            "class User",
234            "function map",
235            "import json",
236        ];
237        
238        let medium_queries = [
239            "how to sort an array",
240            "find function that calculates sum",
241            "class definition for user model", 
242            "import statement for json parsing",
243            "def function with parameters",
244        ];
245        
246        let complex_queries = [
247            "how to implement a binary search algorithm that works with generic types",
248            "find all functions that process user authentication and handle edge cases",
249            "create a class hierarchy for a REST API with proper error handling",
250            "implement async function that fetches data from multiple APIs concurrently",
251            "refactor this code to use dependency injection and improve testability",
252        ];
253        
254        let extreme_queries = [
255            "I need to find a way to optimize this complex algorithm that processes large datasets by implementing caching strategies, parallel processing, and memory-efficient data structures while maintaining backwards compatibility with existing API contracts and ensuring thread safety across multiple concurrent operations",
256            "Can you help me understand how to implement a distributed system architecture that handles real-time data processing with fault tolerance, automatic failover, load balancing, and consistent data replication across multiple geographic regions while meeting strict performance requirements",
257        ];
258        
259        for i in 0..count {
260            let complexity = match i % 10 {
261                0..=3 => QueryComplexity::Simple,
262                4..=7 => QueryComplexity::Medium,
263                8 => QueryComplexity::Complex,
264                9 => QueryComplexity::Extreme,
265                _ => QueryComplexity::Medium,
266            };
267            
268            let query_text = match complexity {
269                QueryComplexity::Simple => simple_queries[i % simple_queries.len()].to_string(),
270                QueryComplexity::Medium => medium_queries[i % medium_queries.len()].to_string(),
271                QueryComplexity::Complex => complex_queries[i % complex_queries.len()].to_string(),
272                QueryComplexity::Extreme => extreme_queries[i % extreme_queries.len()].to_string(),
273            };
274            
275            let expected_intent = self.infer_expected_intent(&query_text);
276            let expected_naturalness = self.calculate_expected_naturalness(&query_text);
277            let language = self.detect_query_language(&query_text);
278            
279            queries.push(BenchmarkQuery {
280                id: format!("{}_{:04}", category, i),
281                text: query_text,
282                expected_intent,
283                expected_naturalness,
284                language,
285                complexity,
286            });
287        }
288        
289        Ok(queries)
290    }
291    
292    /// Infer expected intent for benchmark validation
293    fn infer_expected_intent(&self, query: &str) -> Option<super::query_classifier::QueryIntent> {
294        use super::query_classifier::QueryIntent;
295        
296        if query.starts_with("def ") || query.starts_with("class ") {
297            Some(QueryIntent::Definition)
298        } else if query.starts_with("refs ") || query.contains("references") {
299            Some(QueryIntent::References)
300        } else if query.contains("how to") || query.contains("find") {
301            Some(QueryIntent::NaturalLanguage)
302        } else if query.contains("{}") || query.contains("()") {
303            Some(QueryIntent::Structural)
304        } else if query.len() > 1 && query.chars().any(|c| c.is_uppercase()) {
305            Some(QueryIntent::Symbol)
306        } else {
307            Some(QueryIntent::Lexical)
308        }
309    }
310    
311    /// Calculate expected naturalness score
312    fn calculate_expected_naturalness(&self, query: &str) -> Option<f32> {
313        let words: Vec<&str> = query.split_whitespace().collect();
314        let has_articles = words.iter().any(|&w| ["the", "a", "an"].contains(&w));
315        let has_questions = words.iter().any(|&w| ["how", "what", "where", "why"].contains(&w));
316        let has_prepositions = words.iter().any(|&w| ["to", "of", "in", "for", "with"].contains(&w));
317        let has_code_syntax = query.contains("()") || query.contains("{}") || query.starts_with("def ");
318        
319        let mut score = 0.5;
320        if has_articles { score += 0.2; }
321        if has_questions { score += 0.2; }
322        if has_prepositions { score += 0.1; }
323        if has_code_syntax { score -= 0.3; }
324        if words.len() > 5 { score += 0.1; }
325        
326        Some((score as f32).clamp(0.0, 1.0))
327    }
328    
329    /// Detect query language
330    fn detect_query_language(&self, query: &str) -> Option<String> {
331        if query.contains("def ") || query.contains("import ") {
332            Some("python".to_string())
333        } else if query.contains("function ") || query.contains("const ") {
334            Some("javascript".to_string())
335        } else if query.contains("fn ") || query.contains("impl ") {
336            Some("rust".to_string())
337        } else {
338            Some("natural".to_string())
339        }
340    }
341    
342    /// Benchmark query classification component
343    #[instrument(skip(self, classifier), fields(dataset_size = dataset.queries.len()))]
344    pub async fn benchmark_query_classifier(
345        &self,
346        classifier: &QueryClassifier,
347        dataset: &BenchmarkDataset,
348    ) -> Result<ComponentBenchmarkResults> {
349        info!("Benchmarking query classifier with {} queries", dataset.queries.len());
350        
351        let start_time = Instant::now();
352        let mut latencies = Vec::with_capacity(dataset.queries.len());
353        let mut errors = Vec::new();
354        let mut successful_operations = 0u64;
355        
356        // Memory monitoring
357        let start_memory = get_memory_usage();
358        let mut peak_memory = start_memory;
359        
360        for query in &dataset.queries {
361            let query_start = Instant::now();
362            
363            match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| classifier.classify(&query.text))) {
364                Ok(classification) => {
365                    let latency = query_start.elapsed();
366                    latencies.push(latency.as_millis() as f64);
367                    successful_operations += 1;
368                    
369                    // Validate against expected results if available
370                    if let Some(expected_intent) = query.expected_intent {
371                        if classification.intent != expected_intent {
372                            errors.push(BenchmarkError {
373                                query_id: query.id.clone(),
374                                error_message: format!(
375                                    "Intent mismatch: expected {:?}, got {:?}",
376                                    expected_intent, classification.intent
377                                ),
378                                error_type: "validation".to_string(),
379                                timestamp: std::time::SystemTime::now()
380                                    .duration_since(std::time::UNIX_EPOCH)?
381                                    .as_secs(),
382                            });
383                        }
384                    }
385                }
386                Err(e) => {
387                    errors.push(BenchmarkError {
388                        query_id: query.id.clone(),
389                        error_message: format!("Classification panic: {:?}", e),
390                        error_type: "panic".to_string(),
391                        timestamp: std::time::SystemTime::now()
392                            .duration_since(std::time::UNIX_EPOCH)?
393                            .as_secs(),
394                    });
395                }
396            }
397            
398            // Update peak memory
399            let current_memory = get_memory_usage();
400            if current_memory > peak_memory {
401                peak_memory = current_memory;
402            }
403        }
404        
405        let total_duration = start_time.elapsed();
406        let end_memory = get_memory_usage();
407        
408        // Calculate performance metrics
409        latencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
410        let metrics = PerformanceMetrics {
411            mean_latency_ms: latencies.iter().sum::<f64>() / latencies.len() as f64,
412            p50_latency_ms: latencies[latencies.len() / 2],
413            p95_latency_ms: latencies[(latencies.len() as f64 * 0.95) as usize],
414            p99_latency_ms: latencies[(latencies.len() as f64 * 0.99) as usize],
415            max_latency_ms: latencies.last().copied().unwrap_or(0.0),
416            min_latency_ms: latencies.first().copied().unwrap_or(0.0),
417            throughput_qps: successful_operations as f64 / total_duration.as_secs_f64(),
418            success_rate: successful_operations as f64 / dataset.queries.len() as f64,
419            total_operations: successful_operations,
420            total_duration_ms: total_duration.as_millis() as f64,
421        };
422        
423        let memory_usage = MemoryUsage {
424            peak_memory_mb: peak_memory,
425            avg_memory_mb: (start_memory + end_memory) / 2.0,
426            memory_growth_mb: end_memory - start_memory,
427            cache_hit_rate: 0.0, // Classifiers don't use caching by design
428            gc_time_ms: 0.0,     // Not applicable for Rust
429        };
430        
431        Ok(ComponentBenchmarkResults {
432            component_name: "query_classifier".to_string(),
433            dataset_size: dataset.queries.len(),
434            metrics,
435            memory_usage,
436            errors,
437            timestamp: std::time::SystemTime::now()
438                .duration_since(std::time::UNIX_EPOCH)?
439                .as_secs(),
440        })
441    }
442    
443    /// Benchmark semantic encoder component
444    #[instrument(skip(self, encoder), fields(dataset_size = dataset.queries.len()))]
445    pub async fn benchmark_semantic_encoder(
446        &self,
447        encoder: &SemanticEncoder,
448        dataset: &BenchmarkDataset,
449    ) -> Result<ComponentBenchmarkResults> {
450        info!("Benchmarking semantic encoder with {} queries", dataset.queries.len());
451        
452        let start_time = Instant::now();
453        let mut latencies = Vec::with_capacity(dataset.queries.len());
454        let mut errors = Vec::new();
455        let mut successful_operations = 0u64;
456        
457        // Memory monitoring
458        let start_memory = get_memory_usage();
459        let mut peak_memory = start_memory;
460        
461        // Test both single and batch encoding
462        for query in &dataset.queries {
463            let query_start = Instant::now();
464            
465            match encoder.encode_query(&query.text).await {
466                Ok(embedding) => {
467                    let latency = query_start.elapsed();
468                    latencies.push(latency.as_millis() as f64);
469                    successful_operations += 1;
470                    
471                    // Validate embedding properties
472                    if embedding.vector.is_empty() {
473                        errors.push(BenchmarkError {
474                            query_id: query.id.clone(),
475                            error_message: "Empty embedding vector".to_string(),
476                            error_type: "validation".to_string(),
477                            timestamp: std::time::SystemTime::now()
478                                .duration_since(std::time::UNIX_EPOCH)?
479                                .as_secs(),
480                        });
481                    }
482                }
483                Err(e) => {
484                    errors.push(BenchmarkError {
485                        query_id: query.id.clone(),
486                        error_message: format!("Encoding error: {}", e),
487                        error_type: "encoding".to_string(),
488                        timestamp: std::time::SystemTime::now()
489                            .duration_since(std::time::UNIX_EPOCH)?
490                            .as_secs(),
491                    });
492                }
493            }
494            
495            // Update peak memory
496            let current_memory = get_memory_usage();
497            if current_memory > peak_memory {
498                peak_memory = current_memory;
499            }
500        }
501        
502        // Test batch encoding
503        let batch_texts: Vec<(&str, &str)> = dataset.queries.iter()
504            .take(10) // Small batch for testing
505            .map(|q| (q.text.as_str(), q.id.as_str()))
506            .collect();
507        
508        if !batch_texts.is_empty() {
509            let batch_start = Instant::now();
510            // For batch processing, encode each individually since there's no batch method
511            let mut batch_success = 0;
512            for (text, _id) in &batch_texts {
513                match encoder.encode_query(text).await {
514                    Ok(_embedding) => {
515                        batch_success += 1;
516                    }
517                    Err(e) => {
518                        errors.push(BenchmarkError {
519                        query_id: "batch_test".to_string(),
520                        error_message: format!("Batch encoding error: {}", e),
521                        error_type: "batch_encoding".to_string(),
522                        timestamp: std::time::SystemTime::now()
523                            .duration_since(std::time::UNIX_EPOCH)?
524                            .as_secs(),
525                    });
526                    }
527                }
528            }
529            
530            if batch_success > 0 {
531                let batch_latency = batch_start.elapsed();
532                let per_item_latency = batch_latency.as_millis() as f64 / batch_success as f64;
533                latencies.push(per_item_latency);
534                successful_operations += batch_success;
535            }
536        }
537        
538        let total_duration = start_time.elapsed();
539        let end_memory = get_memory_usage();
540        // Get encoder metrics if available
541        let encoder_cache_stats = encoder.get_cache_stats().await;
542        
543        // Calculate performance metrics
544        latencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
545        let metrics = PerformanceMetrics {
546            mean_latency_ms: latencies.iter().sum::<f64>() / latencies.len() as f64,
547            p50_latency_ms: latencies[latencies.len() / 2],
548            p95_latency_ms: latencies[(latencies.len() as f64 * 0.95) as usize],
549            p99_latency_ms: latencies[(latencies.len() as f64 * 0.99) as usize],
550            max_latency_ms: latencies.last().copied().unwrap_or(0.0),
551            min_latency_ms: latencies.first().copied().unwrap_or(0.0),
552            throughput_qps: successful_operations as f64 / total_duration.as_secs_f64(),
553            success_rate: successful_operations as f64 / (dataset.queries.len() + batch_texts.len()) as f64,
554            total_operations: successful_operations,
555            total_duration_ms: total_duration.as_millis() as f64,
556        };
557        
558        let memory_usage = MemoryUsage {
559            peak_memory_mb: peak_memory,
560            avg_memory_mb: (start_memory + end_memory) / 2.0,
561            memory_growth_mb: end_memory - start_memory,
562            cache_hit_rate: encoder_cache_stats.hit_rate,
563            gc_time_ms: 0.0,
564        };
565        
566        Ok(ComponentBenchmarkResults {
567            component_name: "semantic_encoder".to_string(),
568            dataset_size: dataset.queries.len(),
569            metrics,
570            memory_usage,
571            errors,
572            timestamp: std::time::SystemTime::now()
573                .duration_since(std::time::UNIX_EPOCH)?
574                .as_secs(),
575        })
576    }
577    
578    /// Benchmark conformal router component
579    #[instrument(skip(self, router, classifier), fields(dataset_size = dataset.queries.len()))]
580    pub async fn benchmark_conformal_router(
581        &self,
582        router: &ConformalRouter,
583        classifier: &QueryClassifier,
584        dataset: &BenchmarkDataset,
585    ) -> Result<ComponentBenchmarkResults> {
586        info!("Benchmarking conformal router with {} queries", dataset.queries.len());
587        
588        let start_time = Instant::now();
589        let mut latencies = Vec::with_capacity(dataset.queries.len());
590        let mut errors = Vec::new();
591        let mut successful_operations = 0u64;
592        
593        // Memory monitoring
594        let start_memory = get_memory_usage();
595        let mut peak_memory = start_memory;
596        
597        for query in &dataset.queries {
598            let query_start = Instant::now();
599            
600            // First classify the query
601            let classification = classifier.classify(&query.text);
602            
603            // Extract conformal features
604            let features = super::conformal_router::extract_conformal_features(
605                &query.text,
606                &classification,
607                None, // No file context for benchmarking
608            );
609            
610            match router.make_routing_decision(&features, &classification).await {
611                Ok(decision) => {
612                    let latency = query_start.elapsed();
613                    latencies.push(latency.as_millis() as f64);
614                    successful_operations += 1;
615                    
616                    // Validate decision properties
617                    if decision.risk_assessment.risk_score < 0.0 || decision.risk_assessment.risk_score > 1.0 {
618                        errors.push(BenchmarkError {
619                            query_id: query.id.clone(),
620                            error_message: format!(
621                                "Invalid risk score: {}",
622                                decision.risk_assessment.risk_score
623                            ),
624                            error_type: "validation".to_string(),
625                            timestamp: std::time::SystemTime::now()
626                                .duration_since(std::time::UNIX_EPOCH)?
627                                .as_secs(),
628                        });
629                    }
630                }
631                Err(e) => {
632                    errors.push(BenchmarkError {
633                        query_id: query.id.clone(),
634                        error_message: format!("Routing decision error: {}", e),
635                        error_type: "routing".to_string(),
636                        timestamp: std::time::SystemTime::now()
637                            .duration_since(std::time::UNIX_EPOCH)?
638                            .as_secs(),
639                    });
640                }
641            }
642            
643            // Update peak memory
644            let current_memory = get_memory_usage();
645            if current_memory > peak_memory {
646                peak_memory = current_memory;
647            }
648        }
649        
650        let total_duration = start_time.elapsed();
651        let end_memory = get_memory_usage();
652        let router_status = router.get_status().await;
653        
654        // Calculate performance metrics
655        latencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
656        let metrics = PerformanceMetrics {
657            mean_latency_ms: latencies.iter().sum::<f64>() / latencies.len() as f64,
658            p50_latency_ms: latencies[latencies.len() / 2],
659            p95_latency_ms: latencies[(latencies.len() as f64 * 0.95) as usize],
660            p99_latency_ms: latencies[(latencies.len() as f64 * 0.99) as usize],
661            max_latency_ms: latencies.last().copied().unwrap_or(0.0),
662            min_latency_ms: latencies.first().copied().unwrap_or(0.0),
663            throughput_qps: successful_operations as f64 / total_duration.as_secs_f64(),
664            success_rate: successful_operations as f64 / dataset.queries.len() as f64,
665            total_operations: successful_operations,
666            total_duration_ms: total_duration.as_millis() as f64,
667        };
668        
669        let memory_usage = MemoryUsage {
670            peak_memory_mb: peak_memory,
671            avg_memory_mb: (start_memory + end_memory) / 2.0,
672            memory_growth_mb: end_memory - start_memory,
673            cache_hit_rate: router_status.metrics.cache_hit_rate,
674            gc_time_ms: 0.0,
675        };
676        
677        Ok(ComponentBenchmarkResults {
678            component_name: "conformal_router".to_string(),
679            dataset_size: dataset.queries.len(),
680            metrics,
681            memory_usage,
682            errors,
683            timestamp: std::time::SystemTime::now()
684                .duration_since(std::time::UNIX_EPOCH)?
685                .as_secs(),
686        })
687    }
688    
689    /// Run comprehensive benchmark suite
690    pub async fn run_comprehensive_benchmark(&mut self) -> Result<BenchmarkSuiteResults> {
691        info!("Starting comprehensive semantic processing benchmark suite");
692        
693        // Ensure datasets are generated
694        if self.datasets.is_empty() {
695            self.generate_datasets().await?;
696        }
697        
698        let mut all_results = Vec::new();
699        
700        // Initialize components for benchmarking
701        let classifier_config = ClassifierConfig::default();
702        let classifier = QueryClassifier::new(classifier_config)?;
703        
704        let embedding_config = EmbeddingConfig::default();
705        let encoder = SemanticEncoder::new(embedding_config).await?;
706        
707        let conformal_config = ConformalRouterConfig::default();
708        let conformal_router = ConformalRouter::new(conformal_config);
709        
710        // Benchmark each dataset size
711        for (size_name, dataset) in &self.datasets {
712            info!("Benchmarking with {} dataset", size_name);
713            
714            // Benchmark query classifier
715            let classifier_results = self.benchmark_query_classifier(&classifier, dataset).await?;
716            all_results.push(classifier_results);
717            
718            // Benchmark semantic encoder
719            let encoder_results = self.benchmark_semantic_encoder(&encoder, dataset).await?;
720            all_results.push(encoder_results);
721            
722            // Benchmark conformal router
723            let router_results = self.benchmark_conformal_router(&conformal_router, &classifier, dataset).await?;
724            all_results.push(router_results);
725        }
726        
727        // Store results
728        {
729            let mut results_lock = self.results.write().await;
730            results_lock.extend(all_results.clone());
731        }
732        
733        // Analyze results
734        let analysis = self.analyze_results(&all_results)?;
735        
736        let suite_results = BenchmarkSuiteResults {
737            individual_results: all_results,
738            analysis,
739            config: self.config.clone(),
740            timestamp: std::time::SystemTime::now()
741                .duration_since(std::time::UNIX_EPOCH)?
742                .as_secs(),
743        };
744        
745        // Write results to file if configured
746        self.write_results_to_file(&suite_results).await?;
747        
748        info!("Comprehensive benchmark suite completed");
749        Ok(suite_results)
750    }
751    
752    /// Analyze benchmark results
753    fn analyze_results(&self, results: &[ComponentBenchmarkResults]) -> Result<BenchmarkAnalysis> {
754        let mut analysis = BenchmarkAnalysis {
755            performance_summary: PerformanceSummary::default(),
756            regression_detected: Vec::new(),
757            recommendations: Vec::new(),
758            target_compliance: TargetCompliance::default(),
759        };
760        
761        // Group results by component
762        let mut component_results: HashMap<String, Vec<&ComponentBenchmarkResults>> = HashMap::new();
763        for result in results {
764            component_results.entry(result.component_name.clone())
765                .or_insert_with(Vec::new)
766                .push(result);
767        }
768        
769        let component_count = component_results.len();
770        
771        // Analyze each component
772        for (component_name, component_results) in component_results {
773            let latest_result = component_results.iter()
774                .max_by_key(|r| r.timestamp)
775                .unwrap();
776            
777            // Check performance targets
778            let meets_targets = self.check_performance_targets(component_name.as_str(), latest_result);
779            analysis.target_compliance.components.insert(component_name.clone(), meets_targets);
780            
781            // Check for regression (simplified - would compare against historical data)
782            if latest_result.metrics.p95_latency_ms > 100.0 {
783                analysis.regression_detected.push(format!(
784                    "{}: P95 latency {}ms exceeds reasonable threshold",
785                    component_name, latest_result.metrics.p95_latency_ms
786                ));
787            }
788            
789            if latest_result.metrics.success_rate < 0.95 {
790                analysis.regression_detected.push(format!(
791                    "{}: Success rate {:.2}% below acceptable threshold",
792                    component_name, latest_result.metrics.success_rate * 100.0
793                ));
794            }
795            
796            // Generate recommendations
797            if latest_result.memory_usage.memory_growth_mb > 50.0 {
798                analysis.recommendations.push(format!(
799                    "{}: Consider memory optimization - growth {}MB",
800                    component_name, latest_result.memory_usage.memory_growth_mb
801                ));
802            }
803            
804            if latest_result.metrics.throughput_qps < 10.0 {
805                analysis.recommendations.push(format!(
806                    "{}: Low throughput {:.2} QPS - consider batch processing",
807                    component_name, latest_result.metrics.throughput_qps
808                ));
809            }
810        }
811        
812        // Overall summary
813        analysis.performance_summary.total_components = component_count;
814        analysis.performance_summary.passing_components = analysis.target_compliance.components
815            .values()
816            .filter(|&&meets| meets)
817            .count();
818        analysis.performance_summary.overall_success_rate = results.iter()
819            .map(|r| r.metrics.success_rate)
820            .sum::<f64>() / results.len() as f64;
821        analysis.performance_summary.avg_p95_latency_ms = results.iter()
822            .map(|r| r.metrics.p95_latency_ms)
823            .sum::<f64>() / results.len() as f64;
824        
825        Ok(analysis)
826    }
827    
828    /// Check if component meets performance targets
829    fn check_performance_targets(&self, component_name: &str, result: &ComponentBenchmarkResults) -> bool {
830        let targets = &self.config.performance_targets;
831        
832        match component_name {
833            "query_classifier" => {
834                result.metrics.p95_latency_ms <= targets.classification_p95_ms &&
835                result.metrics.success_rate >= 0.95
836            }
837            "semantic_encoder" => {
838                result.metrics.p95_latency_ms <= targets.encoding_p95_ms &&
839                result.metrics.throughput_qps >= targets.batch_encoding_qps / 10.0 && // Adjusted for single queries
840                result.metrics.success_rate >= 0.95
841            }
842            "conformal_router" => {
843                result.metrics.p95_latency_ms <= targets.routing_p95_ms &&
844                result.metrics.success_rate >= 0.95
845            }
846            _ => result.metrics.success_rate >= 0.95
847        }
848    }
849    
850    /// Write results to file
851    async fn write_results_to_file(&self, results: &BenchmarkSuiteResults) -> Result<()> {
852        use std::fs;
853        
854        // Create output directory if it doesn't exist
855        fs::create_dir_all(&self.config.output_directory)
856            .context("Failed to create output directory")?;
857        
858        let timestamp = chrono::DateTime::from_timestamp(results.timestamp as i64, 0)
859            .unwrap_or_default()
860            .format("%Y%m%d_%H%M%S");
861        
862        let filename = format!("{}/semantic_benchmark_{}.json", 
863                              self.config.output_directory, timestamp);
864        
865        let json_results = serde_json::to_string_pretty(results)
866            .context("Failed to serialize results")?;
867        
868        fs::write(&filename, json_results)
869            .context("Failed to write results file")?;
870        
871        info!("Benchmark results written to: {}", filename);
872        Ok(())
873    }
874}
875
876/// Complete benchmark suite results
877#[derive(Debug, Clone, Serialize, Deserialize)]
878pub struct BenchmarkSuiteResults {
879    pub individual_results: Vec<ComponentBenchmarkResults>,
880    pub analysis: BenchmarkAnalysis,
881    pub config: BenchmarkConfig,
882    pub timestamp: u64,
883}
884
885/// Analysis of benchmark results
886#[derive(Debug, Clone, Serialize, Deserialize)]
887pub struct BenchmarkAnalysis {
888    pub performance_summary: PerformanceSummary,
889    pub regression_detected: Vec<String>,
890    pub recommendations: Vec<String>,
891    pub target_compliance: TargetCompliance,
892}
893
894#[derive(Debug, Clone, Default, Serialize, Deserialize)]
895pub struct PerformanceSummary {
896    pub total_components: usize,
897    pub passing_components: usize,
898    pub overall_success_rate: f64,
899    pub avg_p95_latency_ms: f64,
900}
901
902#[derive(Debug, Clone, Default, Serialize, Deserialize)]
903pub struct TargetCompliance {
904    pub components: HashMap<String, bool>,
905}
906
907/// Get current memory usage in MB using /proc/self/status
908fn get_memory_usage() -> f64 {
909    use std::fs;
910    
911    // Try to read memory from /proc/self/status on Linux
912    if let Ok(status) = fs::read_to_string("/proc/self/status") {
913        for line in status.lines() {
914            if line.starts_with("VmRSS:") {
915                if let Some(memory_str) = line.split_whitespace().nth(1) {
916                    if let Ok(memory_kb) = memory_str.parse::<f64>() {
917                        return memory_kb / 1024.0; // Convert KB to MB
918                    }
919                }
920            }
921        }
922    }
923    
924    // Fallback: use malloc_size if available (macOS)
925    #[cfg(target_os = "macos")]
926    {
927        extern "C" {
928            fn malloc_size(ptr: *const std::ffi::c_void) -> usize;
929        }
930        // This is a simplified fallback - would need proper heap tracking
931        let stack_var = 0;
932        let heap_estimate = unsafe { malloc_size(&stack_var as *const i32 as *const std::ffi::c_void) };
933        return heap_estimate as f64 / (1024.0 * 1024.0);
934    }
935    
936    // Final fallback for unsupported platforms
937    64.0 // Reasonable default MB estimate
938}
939
940/// Initialize benchmark module
941pub async fn initialize_benchmarks(config: &BenchmarkConfig) -> Result<()> {
942    tracing::info!("Initializing semantic benchmarking module");
943    tracing::info!("Dataset sizes: micro={}, small={}, medium={}, large={}", 
944                   config.dataset_sizes.micro,
945                   config.dataset_sizes.small,
946                   config.dataset_sizes.medium,
947                   config.dataset_sizes.large);
948    tracing::info!("Performance targets: classification={}ms, routing={}ms, encoding={}ms",
949                   config.performance_targets.classification_p95_ms,
950                   config.performance_targets.routing_p95_ms,
951                   config.performance_targets.encoding_p95_ms);
952    tracing::info!("Profiling enabled: {}", config.enable_profiling);
953    tracing::info!("Output directory: {}", config.output_directory);
954    
955    // Validate configuration
956    if config.dataset_sizes.micro == 0 {
957        anyhow::bail!("Micro dataset size must be greater than 0");
958    }
959    
960    if config.performance_targets.classification_p95_ms <= 0.0 {
961        anyhow::bail!("Classification P95 target must be greater than 0");
962    }
963    
964    if config.output_directory.is_empty() {
965        anyhow::bail!("Output directory must be specified");
966    }
967    
968    tracing::info!("Semantic benchmarking module initialized successfully");
969    Ok(())
970}
971
972#[cfg(test)]
973mod tests {
974    use super::*;
975    
976    #[tokio::test]
977    async fn test_benchmark_suite_creation() {
978        let config = BenchmarkConfig::default();
979        let suite = SemanticBenchmarkSuite::new(config);
980        
981        assert!(suite.datasets.is_empty()); // No datasets generated yet
982        assert_eq!(suite.results.read().await.len(), 0);
983    }
984    
985    #[tokio::test]
986    async fn test_dataset_generation() {
987        let config = BenchmarkConfig {
988            dataset_sizes: BenchmarkDatasetSizes {
989                micro: 5,
990                small: 10,
991                medium: 0,
992                large: 0,
993            },
994            ..Default::default()
995        };
996        
997        let mut suite = SemanticBenchmarkSuite::new(config);
998        suite.generate_datasets().await.unwrap();
999        
1000        assert!(suite.datasets.contains_key("micro"));
1001        assert!(suite.datasets.contains_key("small"));
1002        assert!(!suite.datasets.contains_key("medium"));
1003        
1004        let micro_dataset = suite.datasets.get("micro").unwrap();
1005        assert_eq!(micro_dataset.queries.len(), 5);
1006        
1007        // Verify query properties
1008        for query in &micro_dataset.queries {
1009            assert!(!query.text.is_empty());
1010            assert!(query.id.starts_with("micro_"));
1011            assert!(query.expected_intent.is_some());
1012        }
1013    }
1014    
1015    #[tokio::test]
1016    async fn test_query_classifier_benchmark() {
1017        let config = BenchmarkConfig::default();
1018        let suite = SemanticBenchmarkSuite::new(config);
1019        
1020        let classifier_config = ClassifierConfig::default();
1021        let classifier = QueryClassifier::new(classifier_config).unwrap();
1022        
1023        // Create small test dataset
1024        let queries = vec![
1025            BenchmarkQuery {
1026                id: "test_1".to_string(),
1027                text: "how to sort an array".to_string(),
1028                expected_intent: Some(super::super::query_classifier::QueryIntent::NaturalLanguage),
1029                expected_naturalness: Some(0.8),
1030                language: Some("natural".to_string()),
1031                complexity: QueryComplexity::Medium,
1032            },
1033            BenchmarkQuery {
1034                id: "test_2".to_string(),
1035                text: "def calculateSum".to_string(),
1036                expected_intent: Some(super::super::query_classifier::QueryIntent::Definition),
1037                expected_naturalness: Some(0.2),
1038                language: Some("python".to_string()),
1039                complexity: QueryComplexity::Simple,
1040            },
1041        ];
1042        
1043        let dataset = BenchmarkDataset {
1044            queries,
1045            size_category: "test".to_string(),
1046        };
1047        
1048        let results = suite.benchmark_query_classifier(&classifier, &dataset).await.unwrap();
1049        
1050        assert_eq!(results.component_name, "query_classifier");
1051        assert_eq!(results.dataset_size, 2);
1052        assert!(results.metrics.success_rate > 0.0);
1053        assert!(results.metrics.mean_latency_ms >= 0.0);
1054        assert!(results.metrics.throughput_qps > 0.0);
1055    }
1056    
1057    #[test]
1058    fn test_expected_intent_inference() {
1059        let config = BenchmarkConfig::default();
1060        let suite = SemanticBenchmarkSuite::new(config);
1061        
1062        assert_eq!(
1063            suite.infer_expected_intent("def myFunction"),
1064            Some(super::super::query_classifier::QueryIntent::Definition)
1065        );
1066        
1067        assert_eq!(
1068            suite.infer_expected_intent("how to sort an array"),
1069            Some(super::super::query_classifier::QueryIntent::NaturalLanguage)
1070        );
1071        
1072        assert_eq!(
1073            suite.infer_expected_intent("refs myVariable"),
1074            Some(super::super::query_classifier::QueryIntent::References)
1075        );
1076    }
1077    
1078    #[test]
1079    fn test_naturalness_calculation() {
1080        let config = BenchmarkConfig::default();
1081        let suite = SemanticBenchmarkSuite::new(config);
1082        
1083        let naturalness = suite.calculate_expected_naturalness("how to find the best solution").unwrap();
1084        assert!(naturalness > 0.7); // Should be high for natural language
1085        
1086        let code_naturalness = suite.calculate_expected_naturalness("def calculate_sum()").unwrap();
1087        assert!(code_naturalness < 0.5); // Should be low for code syntax
1088    }
1089    
1090    #[tokio::test]
1091    async fn test_configuration_validation() {
1092        let mut config = BenchmarkConfig::default();
1093        config.dataset_sizes.micro = 0; // Invalid
1094        
1095        let result = initialize_benchmarks(&config).await;
1096        assert!(result.is_err());
1097        assert!(result.unwrap_err().to_string().contains("Micro dataset size"));
1098        
1099        config.dataset_sizes.micro = 10; // Valid
1100        config.performance_targets.classification_p95_ms = -1.0; // Invalid
1101        
1102        let result = initialize_benchmarks(&config).await;
1103        assert!(result.is_err());
1104        assert!(result.unwrap_err().to_string().contains("Classification P95"));
1105    }
1106}