1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct BenchmarkConfig {
30 pub dataset_sizes: BenchmarkDatasetSizes,
32 pub performance_targets: PerformanceTargets,
34 pub memory_limits: MemoryLimits,
36 pub enable_profiling: bool,
38 pub output_directory: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct BenchmarkDatasetSizes {
44 pub micro: usize, pub small: usize, pub medium: usize, pub large: usize, }
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PerformanceTargets {
52 pub classification_p95_ms: f64,
54 pub routing_p95_ms: f64,
56 pub encoding_p95_ms: f64,
58 pub batch_encoding_qps: f64,
60 pub end_to_end_p95_ms: f64,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct MemoryLimits {
66 pub max_memory_per_query_mb: f64,
68 pub max_embedding_cache_mb: f64,
70 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#[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, Medium, Complex, Extreme, }
125
126#[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
168pub struct SemanticBenchmarkSuite {
170 config: BenchmarkConfig,
171 datasets: HashMap<String, BenchmarkDataset>,
172 results: Arc<RwLock<Vec<ComponentBenchmarkResults>>>,
173}
174
175impl SemanticBenchmarkSuite {
176 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 pub async fn generate_datasets(&mut self) -> Result<()> {
187 info!("Generating benchmark datasets");
188
189 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 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 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 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 async fn generate_queries(&self, count: usize, category: &str) -> Result<Vec<BenchmarkQuery>> {
227 let mut queries = Vec::with_capacity(count);
228
229 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 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 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 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 #[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 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 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 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 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, gc_time_ms: 0.0, };
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 #[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 let start_memory = get_memory_usage();
459 let mut peak_memory = start_memory;
460
461 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 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 let current_memory = get_memory_usage();
497 if current_memory > peak_memory {
498 peak_memory = current_memory;
499 }
500 }
501
502 let batch_texts: Vec<(&str, &str)> = dataset.queries.iter()
504 .take(10) .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 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 let encoder_cache_stats = encoder.get_cache_stats().await;
542
543 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 #[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 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 let classification = classifier.classify(&query.text);
602
603 let features = super::conformal_router::extract_conformal_features(
605 &query.text,
606 &classification,
607 None, );
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 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 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 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 pub async fn run_comprehensive_benchmark(&mut self) -> Result<BenchmarkSuiteResults> {
691 info!("Starting comprehensive semantic processing benchmark suite");
692
693 if self.datasets.is_empty() {
695 self.generate_datasets().await?;
696 }
697
698 let mut all_results = Vec::new();
699
700 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 for (size_name, dataset) in &self.datasets {
712 info!("Benchmarking with {} dataset", size_name);
713
714 let classifier_results = self.benchmark_query_classifier(&classifier, dataset).await?;
716 all_results.push(classifier_results);
717
718 let encoder_results = self.benchmark_semantic_encoder(&encoder, dataset).await?;
720 all_results.push(encoder_results);
721
722 let router_results = self.benchmark_conformal_router(&conformal_router, &classifier, dataset).await?;
724 all_results.push(router_results);
725 }
726
727 {
729 let mut results_lock = self.results.write().await;
730 results_lock.extend(all_results.clone());
731 }
732
733 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 self.write_results_to_file(&suite_results).await?;
747
748 info!("Comprehensive benchmark suite completed");
749 Ok(suite_results)
750 }
751
752 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 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 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 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 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 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 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 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 && 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 async fn write_results_to_file(&self, results: &BenchmarkSuiteResults) -> Result<()> {
852 use std::fs;
853
854 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#[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#[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
907fn get_memory_usage() -> f64 {
909 use std::fs;
910
911 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; }
919 }
920 }
921 }
922 }
923
924 #[cfg(target_os = "macos")]
926 {
927 extern "C" {
928 fn malloc_size(ptr: *const std::ffi::c_void) -> usize;
929 }
930 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 64.0 }
939
940pub 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 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()); 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 for query in µ_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 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); let code_naturalness = suite.calculate_expected_naturalness("def calculate_sum()").unwrap();
1087 assert!(code_naturalness < 0.5); }
1089
1090 #[tokio::test]
1091 async fn test_configuration_validation() {
1092 let mut config = BenchmarkConfig::default();
1093 config.dataset_sizes.micro = 0; 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; config.performance_targets.classification_p95_ms = -1.0; let result = initialize_benchmarks(&config).await;
1103 assert!(result.is_err());
1104 assert!(result.unwrap_err().to_string().contains("Classification P95"));
1105 }
1106}