Skip to main content

lens_core/
metrics.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::sync::Arc;
4use parking_lot::RwLock;
5use anyhow::Result;
6use prometheus::{Registry, Opts, Counter, Histogram, Gauge, register_counter, register_histogram, register_gauge};
7use std::sync::OnceLock;
8
9/// Comprehensive metrics collection for Lens system
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct SystemMetrics {
12    pub search_metrics: SearchMetrics,
13    pub performance_metrics: PerformanceMetrics,
14    pub quality_metrics: QualityMetrics,
15    pub system_health: SystemHealth,
16    pub timestamp: String,
17}
18
19/// Search-specific metrics
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SearchMetrics {
22    pub total_queries: u64,
23    pub successful_queries: u64,
24    pub failed_queries: u64,
25    pub average_results_per_query: f64,
26    pub query_distribution: HashMap<String, u64>,
27    pub language_distribution: HashMap<String, u64>,
28}
29
30/// Performance metrics with SLA tracking
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct PerformanceMetrics {
33    pub latency_p50_ms: f64,
34    pub latency_p95_ms: f64,
35    pub latency_p99_ms: f64,
36    pub throughput_qps: f64,
37    pub memory_usage_mb: f64,
38    pub cpu_utilization_percent: f64,
39    pub cache_hit_rate: f64,
40    pub lsp_latency_ms: f64,
41}
42
43/// Quality metrics for search relevance
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct QualityMetrics {
46    pub success_at_1: f64,
47    pub success_at_10: f64,
48    pub success_at_20: f64,
49    pub ndcg_at_10: f64,
50    pub ndcg_at_50: f64,
51    pub mrr: f64,
52    pub sla_recall_at_50: f64,
53    pub witness_coverage: f64,
54    pub span_accuracy: f64,
55}
56
57/// System health indicators
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SystemHealth {
60    pub status: HealthStatus,
61    pub lsp_servers_active: u32,
62    pub index_size_mb: f64,
63    pub error_rate: f64,
64    pub uptime_seconds: u64,
65    pub last_index_update: String,
66}
67
68/// Health status enumeration
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum HealthStatus {
71    Healthy,
72    Degraded,
73    Unhealthy,
74}
75
76/// Real-time metrics collector
77pub struct MetricsCollector {
78    metrics: Arc<RwLock<SystemMetrics>>,
79    query_latencies: Arc<RwLock<Vec<f64>>>,
80    query_counts: Arc<RwLock<HashMap<String, u64>>>,
81}
82
83impl MetricsCollector {
84    /// Create new metrics collector
85    pub fn new() -> Self {
86        let initial_metrics = SystemMetrics {
87            search_metrics: SearchMetrics {
88                total_queries: 0,
89                successful_queries: 0,
90                failed_queries: 0,
91                average_results_per_query: 0.0,
92                query_distribution: HashMap::new(),
93                language_distribution: HashMap::new(),
94            },
95            performance_metrics: PerformanceMetrics {
96                latency_p50_ms: 0.0,
97                latency_p95_ms: 0.0,
98                latency_p99_ms: 0.0,
99                throughput_qps: 0.0,
100                memory_usage_mb: 0.0,
101                cpu_utilization_percent: 0.0,
102                cache_hit_rate: 0.0,
103                lsp_latency_ms: 0.0,
104            },
105            quality_metrics: QualityMetrics {
106                success_at_1: 0.0,
107                success_at_10: 0.0,
108                success_at_20: 0.0,
109                ndcg_at_10: 0.0,
110                ndcg_at_50: 0.0,
111                mrr: 0.0,
112                sla_recall_at_50: 0.0,
113                witness_coverage: 0.0,
114                span_accuracy: 0.0,
115            },
116            system_health: SystemHealth {
117                status: HealthStatus::Healthy,
118                lsp_servers_active: 0,
119                index_size_mb: 0.0,
120                error_rate: 0.0,
121                uptime_seconds: 0,
122                last_index_update: chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
123            },
124            timestamp: chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
125        };
126
127        Self {
128            metrics: Arc::new(RwLock::new(initial_metrics)),
129            query_latencies: Arc::new(RwLock::new(Vec::new())),
130            query_counts: Arc::new(RwLock::new(HashMap::new())),
131        }
132    }
133
134    /// Record a search query
135    pub fn record_query(&self, query_type: &str, language: Option<&str>, latency_ms: f64, success: bool, result_count: usize) {
136        let mut metrics = self.metrics.write();
137        let mut latencies = self.query_latencies.write();
138        let mut counts = self.query_counts.write();
139
140        // Update basic counters
141        metrics.search_metrics.total_queries += 1;
142        if success {
143            metrics.search_metrics.successful_queries += 1;
144        } else {
145            metrics.search_metrics.failed_queries += 1;
146        }
147
148        // Update latency tracking
149        latencies.push(latency_ms);
150
151        // Update query distribution
152        *metrics.search_metrics.query_distribution.entry(query_type.to_string()).or_insert(0) += 1;
153        *counts.entry(query_type.to_string()).or_insert(0) += 1;
154
155        // Update language distribution
156        if let Some(lang) = language {
157            *metrics.search_metrics.language_distribution.entry(lang.to_string()).or_insert(0) += 1;
158        }
159
160        // Update average results per query (rolling average)
161        let total_results = metrics.search_metrics.average_results_per_query * (metrics.search_metrics.total_queries - 1) as f64;
162        metrics.search_metrics.average_results_per_query = (total_results + result_count as f64) / metrics.search_metrics.total_queries as f64;
163
164        // Update timestamp
165        metrics.timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
166    }
167
168    /// Calculate and update performance percentiles
169    pub fn update_performance_metrics(&self) {
170        let mut metrics = self.metrics.write();
171        let latencies = self.query_latencies.read();
172
173        if latencies.is_empty() {
174            return;
175        }
176
177        let mut sorted_latencies = latencies.clone();
178        sorted_latencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
179
180        // Calculate percentiles
181        let len = sorted_latencies.len();
182        metrics.performance_metrics.latency_p50_ms = sorted_latencies[len * 50 / 100];
183        metrics.performance_metrics.latency_p95_ms = sorted_latencies[len * 95 / 100];
184        metrics.performance_metrics.latency_p99_ms = sorted_latencies[len * 99 / 100];
185
186        // Calculate throughput (queries per second over last measurement period)
187        if metrics.search_metrics.total_queries > 0 {
188            let elapsed_seconds = 60.0; // Simplified - would track actual elapsed time
189            metrics.performance_metrics.throughput_qps = metrics.search_metrics.total_queries as f64 / elapsed_seconds;
190        }
191
192        // Update system resource metrics (would integrate with actual system monitoring)
193        metrics.performance_metrics.memory_usage_mb = Self::get_memory_usage();
194        metrics.performance_metrics.cpu_utilization_percent = Self::get_cpu_usage();
195    }
196
197    /// Update quality metrics from benchmark results
198    pub fn update_quality_metrics(&self, quality_update: QualityMetricsUpdate) {
199        let mut metrics = self.metrics.write();
200        
201        metrics.quality_metrics.success_at_1 = quality_update.success_at_1;
202        metrics.quality_metrics.success_at_10 = quality_update.success_at_10;
203        metrics.quality_metrics.success_at_20 = quality_update.success_at_20;
204        metrics.quality_metrics.ndcg_at_10 = quality_update.ndcg_at_10;
205        metrics.quality_metrics.ndcg_at_50 = quality_update.ndcg_at_50;
206        metrics.quality_metrics.mrr = quality_update.mrr;
207        metrics.quality_metrics.sla_recall_at_50 = quality_update.sla_recall_at_50;
208        metrics.quality_metrics.witness_coverage = quality_update.witness_coverage;
209        metrics.quality_metrics.span_accuracy = quality_update.span_accuracy;
210        
211        metrics.timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
212    }
213
214    /// Update system health status
215    pub fn update_system_health(&self, health_update: SystemHealthUpdate) {
216        let mut metrics = self.metrics.write();
217        
218        metrics.system_health.lsp_servers_active = health_update.lsp_servers_active;
219        metrics.system_health.index_size_mb = health_update.index_size_mb;
220        metrics.system_health.error_rate = health_update.error_rate;
221        
222        // Determine overall health status
223        metrics.system_health.status = if health_update.error_rate > 0.1 {
224            HealthStatus::Unhealthy
225        } else if health_update.error_rate > 0.05 {
226            HealthStatus::Degraded
227        } else {
228            HealthStatus::Healthy
229        };
230        
231        if let Some(last_update) = health_update.last_index_update {
232            metrics.system_health.last_index_update = last_update;
233        }
234    }
235
236    /// Get current metrics snapshot
237    pub fn get_metrics(&self) -> SystemMetrics {
238        self.metrics.read().clone()
239    }
240
241    /// Reset metrics (for benchmark runs)
242    pub fn reset(&self) {
243        let mut metrics = self.metrics.write();
244        let mut latencies = self.query_latencies.write();
245        let mut counts = self.query_counts.write();
246
247        metrics.search_metrics = SearchMetrics {
248            total_queries: 0,
249            successful_queries: 0,
250            failed_queries: 0,
251            average_results_per_query: 0.0,
252            query_distribution: HashMap::new(),
253            language_distribution: HashMap::new(),
254        };
255
256        latencies.clear();
257        counts.clear();
258        
259        metrics.timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
260    }
261
262    /// Generate comprehensive metrics report
263    pub fn generate_report(&self) -> MetricsReport {
264        let metrics = self.get_metrics();
265        self.update_performance_metrics();
266        
267        MetricsReport {
268            metrics,
269            sla_compliance: self.check_sla_compliance(),
270            performance_grade: self.calculate_performance_grade(),
271            recommendations: self.generate_recommendations(),
272        }
273    }
274
275    fn check_sla_compliance(&self) -> SlaCompliance {
276        let metrics = self.metrics.read();
277        
278        SlaCompliance {
279            latency_p95_compliant: metrics.performance_metrics.latency_p95_ms <= 2000.0,
280            success_rate_compliant: metrics.search_metrics.successful_queries as f64 / metrics.search_metrics.total_queries as f64 >= 0.95,
281            availability_compliant: matches!(metrics.system_health.status, HealthStatus::Healthy),
282        }
283    }
284
285    fn calculate_performance_grade(&self) -> PerformanceGrade {
286        let metrics = self.metrics.read();
287        
288        let latency_score = if metrics.performance_metrics.latency_p95_ms <= 150.0 {
289            100.0
290        } else if metrics.performance_metrics.latency_p95_ms <= 500.0 {
291            80.0
292        } else if metrics.performance_metrics.latency_p95_ms <= 1000.0 {
293            60.0
294        } else {
295            40.0
296        };
297
298        let success_rate = if metrics.search_metrics.total_queries > 0 {
299            (metrics.search_metrics.successful_queries as f64 / metrics.search_metrics.total_queries as f64) * 100.0
300        } else {
301            100.0
302        };
303
304        let overall_score = (latency_score + success_rate) / 2.0;
305        
306        if overall_score >= 90.0 {
307            PerformanceGrade::Excellent
308        } else if overall_score >= 80.0 {
309            PerformanceGrade::Good
310        } else if overall_score >= 70.0 {
311            PerformanceGrade::Fair
312        } else {
313            PerformanceGrade::Poor
314        }
315    }
316
317    fn generate_recommendations(&self) -> Vec<String> {
318        let metrics = self.metrics.read();
319        let mut recommendations = Vec::new();
320
321        if metrics.performance_metrics.latency_p95_ms > 500.0 {
322            recommendations.push("Consider optimizing query processing for better latency".to_string());
323        }
324
325        if metrics.performance_metrics.cache_hit_rate < 0.8 {
326            recommendations.push("Improve caching strategy to increase hit rate".to_string());
327        }
328
329        if metrics.search_metrics.failed_queries as f64 / metrics.search_metrics.total_queries as f64 > 0.05 {
330            recommendations.push("Investigate and reduce query failure rate".to_string());
331        }
332
333        recommendations
334    }
335
336    // Placeholder system monitoring functions
337    fn get_memory_usage() -> f64 {
338        // Would integrate with actual system monitoring
339        0.0
340    }
341
342    fn get_cpu_usage() -> f64 {
343        // Would integrate with actual system monitoring
344        0.0
345    }
346}
347
348/// Update structure for quality metrics
349#[derive(Debug, Clone)]
350pub struct QualityMetricsUpdate {
351    pub success_at_1: f64,
352    pub success_at_10: f64,
353    pub success_at_20: f64,
354    pub ndcg_at_10: f64,
355    pub ndcg_at_50: f64,
356    pub mrr: f64,
357    pub sla_recall_at_50: f64,
358    pub witness_coverage: f64,
359    pub span_accuracy: f64,
360}
361
362/// Update structure for system health
363#[derive(Debug, Clone)]
364pub struct SystemHealthUpdate {
365    pub lsp_servers_active: u32,
366    pub index_size_mb: f64,
367    pub error_rate: f64,
368    pub last_index_update: Option<String>,
369}
370
371/// SLA compliance tracking
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct SlaCompliance {
374    pub latency_p95_compliant: bool,
375    pub success_rate_compliant: bool,
376    pub availability_compliant: bool,
377}
378
379/// Performance grade enumeration
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub enum PerformanceGrade {
382    Excellent,
383    Good,
384    Fair,
385    Poor,
386}
387
388/// Comprehensive metrics report
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct MetricsReport {
391    pub metrics: SystemMetrics,
392    pub sla_compliance: SlaCompliance,
393    pub performance_grade: PerformanceGrade,
394    pub recommendations: Vec<String>,
395}
396
397impl Default for MetricsCollector {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403/// SLA metrics for benchmarking compliance
404#[derive(Debug, Clone, Serialize, Deserialize)]
405pub struct SlaMetrics {
406    pub latency_p95_sla_ms: f64,
407    pub success_rate_sla: f64,
408    pub availability_sla: f64,
409    pub current_latency_p95_ms: f64,
410    pub current_success_rate: f64,
411    pub current_availability: f64,
412    pub sla_violations: u64,
413}
414
415impl SlaMetrics {
416    pub fn new(latency_p95_sla_ms: f64, success_rate_sla: f64, availability_sla: f64) -> Self {
417        Self {
418            latency_p95_sla_ms,
419            success_rate_sla,
420            availability_sla,
421            current_latency_p95_ms: 0.0,
422            current_success_rate: 1.0,
423            current_availability: 1.0,
424            sla_violations: 0,
425        }
426    }
427
428    pub fn is_compliant(&self) -> bool {
429        self.current_latency_p95_ms <= self.latency_p95_sla_ms &&
430        self.current_success_rate >= self.success_rate_sla &&
431        self.current_availability >= self.availability_sla
432    }
433}
434
435/// Performance gate for promotion decisions
436#[derive(Debug, Clone)]
437pub struct PerformanceGate {
438    pub name: String,
439    pub threshold: f64,
440    pub current_value: f64,
441    pub comparison: GateComparison,
442    pub passed: bool,
443}
444
445#[derive(Debug, Clone)]
446pub enum GateComparison {
447    LessThan,
448    LessThanOrEqual,
449    GreaterThan,
450    GreaterThanOrEqual,
451    Equal,
452}
453
454impl PerformanceGate {
455    pub fn new(name: String, threshold: f64, comparison: GateComparison) -> Self {
456        Self {
457            name,
458            threshold,
459            current_value: 0.0,
460            comparison,
461            passed: false,
462        }
463    }
464
465    pub fn evaluate(&mut self, value: f64) -> bool {
466        self.current_value = value;
467        self.passed = match self.comparison {
468            GateComparison::LessThan => value < self.threshold,
469            GateComparison::LessThanOrEqual => value <= self.threshold,
470            GateComparison::GreaterThan => value > self.threshold,
471            GateComparison::GreaterThanOrEqual => value >= self.threshold,
472            GateComparison::Equal => (value - self.threshold).abs() < f64::EPSILON,
473        };
474        self.passed
475    }
476}
477
478/// Global Prometheus metrics registry
479static PROMETHEUS_REGISTRY: OnceLock<PrometheusMetrics> = OnceLock::new();
480
481#[cfg(test)]
482use std::sync::Mutex;
483
484#[cfg(test)]
485static TEST_METRICS_LOCK: Mutex<()> = Mutex::new(());
486
487/// Prometheus metrics for monitoring
488pub struct PrometheusMetrics {
489    pub query_counter: Counter,
490    pub query_duration: Histogram,
491    pub search_latency: Histogram,
492    pub memory_usage: Gauge,
493    pub cpu_utilization: Gauge,
494    pub cache_hit_rate: Gauge,
495}
496
497/// Initialize Prometheus metrics for the Lens system
498pub fn init_prometheus_metrics() -> Result<()> {
499    #[cfg(test)]
500    {
501        // In tests, prevent concurrent initialization
502        let _guard = TEST_METRICS_LOCK.lock().unwrap();
503        
504        // If already initialized in tests, just return success to avoid conflicts
505        if PROMETHEUS_REGISTRY.get().is_some() {
506            tracing::debug!("📊 Prometheus metrics already initialized in tests - reusing");
507            return Ok(());
508        }
509    }
510    let metrics = PrometheusMetrics {
511        query_counter: register_counter!(Opts::new(
512            "lens_queries_total",
513            "Total number of search queries processed"
514        ))?,
515        
516        query_duration: register_histogram!(
517            "lens_query_duration_seconds",
518            "Duration of search queries in seconds",
519            prometheus::exponential_buckets(0.01, 2.0, 10)?
520        )?,
521        
522        search_latency: register_histogram!(
523            "lens_search_latency_seconds", 
524            "Search latency in seconds",
525            prometheus::exponential_buckets(0.001, 2.0, 15)?
526        )?,
527        
528        memory_usage: register_gauge!(Opts::new(
529            "lens_memory_usage_bytes",
530            "Memory usage in bytes"
531        ))?,
532        
533        cpu_utilization: register_gauge!(Opts::new(
534            "lens_cpu_utilization_percent",
535            "CPU utilization percentage"
536        ))?,
537        
538        cache_hit_rate: register_gauge!(Opts::new(
539            "lens_cache_hit_rate_percent",
540            "Cache hit rate percentage"
541        ))?,
542    };
543
544    PROMETHEUS_REGISTRY.set(metrics)
545        .map_err(|_| anyhow::anyhow!("Prometheus metrics already initialized"))?;
546    
547    tracing::info!("✅ Prometheus metrics initialized successfully");
548    Ok(())
549}
550
551/// Get the global Prometheus metrics registry
552pub fn get_prometheus_metrics() -> Option<&'static PrometheusMetrics> {
553    PROMETHEUS_REGISTRY.get()
554}
555
556/// Record a search query in Prometheus metrics
557pub fn record_prometheus_query(duration_secs: f64) {
558    if let Some(metrics) = get_prometheus_metrics() {
559        metrics.query_counter.inc();
560        metrics.query_duration.observe(duration_secs);
561        metrics.search_latency.observe(duration_secs);
562    }
563}
564
565/// Update system resource metrics
566pub fn update_prometheus_system_metrics(memory_bytes: f64, cpu_percent: f64, cache_hit_percent: f64) {
567    if let Some(metrics) = get_prometheus_metrics() {
568        metrics.memory_usage.set(memory_bytes);
569        metrics.cpu_utilization.set(cpu_percent);
570        metrics.cache_hit_rate.set(cache_hit_percent);
571    }
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn test_metrics_collector_new() {
580        let collector = MetricsCollector::new();
581        let metrics = collector.get_metrics();
582        
583        assert_eq!(metrics.search_metrics.total_queries, 0);
584        assert_eq!(metrics.search_metrics.successful_queries, 0);
585        assert_eq!(metrics.search_metrics.failed_queries, 0);
586        assert_eq!(metrics.performance_metrics.latency_p50_ms, 0.0);
587        assert!(matches!(metrics.system_health.status, HealthStatus::Healthy));
588    }
589
590    #[test]
591    fn test_metrics_collector_default() {
592        let collector = MetricsCollector::default();
593        let metrics = collector.get_metrics();
594        assert_eq!(metrics.search_metrics.total_queries, 0);
595    }
596
597    #[test]
598    fn test_record_successful_query() {
599        let collector = MetricsCollector::new();
600        
601        collector.record_query("search", Some("rust"), 50.0, true, 10);
602        
603        let metrics = collector.get_metrics();
604        assert_eq!(metrics.search_metrics.total_queries, 1);
605        assert_eq!(metrics.search_metrics.successful_queries, 1);
606        assert_eq!(metrics.search_metrics.failed_queries, 0);
607        assert_eq!(metrics.search_metrics.average_results_per_query, 10.0);
608        
609        // Check query distribution
610        assert_eq!(metrics.search_metrics.query_distribution.get("search"), Some(&1));
611        // Check language distribution
612        assert_eq!(metrics.search_metrics.language_distribution.get("rust"), Some(&1));
613    }
614
615    #[test]
616    fn test_record_failed_query() {
617        let collector = MetricsCollector::new();
618        
619        collector.record_query("search", None, 100.0, false, 0);
620        
621        let metrics = collector.get_metrics();
622        assert_eq!(metrics.search_metrics.total_queries, 1);
623        assert_eq!(metrics.search_metrics.successful_queries, 0);
624        assert_eq!(metrics.search_metrics.failed_queries, 1);
625        assert_eq!(metrics.search_metrics.average_results_per_query, 0.0);
626    }
627
628    #[test]
629    fn test_multiple_queries_average() {
630        let collector = MetricsCollector::new();
631        
632        collector.record_query("search", Some("rust"), 25.0, true, 5);
633        collector.record_query("search", Some("python"), 75.0, true, 15);
634        
635        let metrics = collector.get_metrics();
636        assert_eq!(metrics.search_metrics.total_queries, 2);
637        assert_eq!(metrics.search_metrics.successful_queries, 2);
638        assert_eq!(metrics.search_metrics.average_results_per_query, 10.0); // (5+15)/2
639        
640        assert_eq!(metrics.search_metrics.query_distribution.get("search"), Some(&2));
641        assert_eq!(metrics.search_metrics.language_distribution.get("rust"), Some(&1));
642        assert_eq!(metrics.search_metrics.language_distribution.get("python"), Some(&1));
643    }
644
645    #[test]
646    fn test_update_performance_metrics() {
647        let collector = MetricsCollector::new();
648        
649        // Record some queries with latencies
650        collector.record_query("search", None, 10.0, true, 5);
651        collector.record_query("search", None, 20.0, true, 5);
652        collector.record_query("search", None, 30.0, true, 5);
653        collector.record_query("search", None, 40.0, true, 5);
654        collector.record_query("search", None, 100.0, true, 5);
655        
656        collector.update_performance_metrics();
657        
658        let metrics = collector.get_metrics();
659        assert_eq!(metrics.performance_metrics.latency_p50_ms, 30.0); // median
660        assert_eq!(metrics.performance_metrics.latency_p95_ms, 100.0); // 95th percentile
661        assert_eq!(metrics.performance_metrics.latency_p99_ms, 100.0); // 99th percentile
662        assert!(metrics.performance_metrics.throughput_qps > 0.0);
663    }
664
665    #[test]
666    fn test_update_performance_metrics_empty() {
667        let collector = MetricsCollector::new();
668        
669        // No queries recorded
670        collector.update_performance_metrics();
671        
672        let metrics = collector.get_metrics();
673        assert_eq!(metrics.performance_metrics.latency_p50_ms, 0.0);
674        assert_eq!(metrics.performance_metrics.latency_p95_ms, 0.0);
675        assert_eq!(metrics.performance_metrics.latency_p99_ms, 0.0);
676    }
677
678    #[test]
679    fn test_update_quality_metrics() {
680        let collector = MetricsCollector::new();
681        
682        let quality_update = QualityMetricsUpdate {
683            success_at_1: 0.8,
684            success_at_10: 0.9,
685            success_at_20: 0.95,
686            ndcg_at_10: 0.85,
687            ndcg_at_50: 0.88,
688            mrr: 0.82,
689            sla_recall_at_50: 0.87,
690            witness_coverage: 0.91,
691            span_accuracy: 0.93,
692        };
693        
694        collector.update_quality_metrics(quality_update);
695        
696        let metrics = collector.get_metrics();
697        assert_eq!(metrics.quality_metrics.success_at_1, 0.8);
698        assert_eq!(metrics.quality_metrics.success_at_10, 0.9);
699        assert_eq!(metrics.quality_metrics.ndcg_at_10, 0.85);
700        assert_eq!(metrics.quality_metrics.mrr, 0.82);
701    }
702
703    #[test]
704    fn test_update_system_health_healthy() {
705        let collector = MetricsCollector::new();
706        
707        let health_update = SystemHealthUpdate {
708            lsp_servers_active: 4,
709            index_size_mb: 1024.0,
710            error_rate: 0.01, // Low error rate - should be healthy
711            last_index_update: Some("2023-01-01T00:00:00Z".to_string()),
712        };
713        
714        collector.update_system_health(health_update);
715        
716        let metrics = collector.get_metrics();
717        assert_eq!(metrics.system_health.lsp_servers_active, 4);
718        assert_eq!(metrics.system_health.index_size_mb, 1024.0);
719        assert_eq!(metrics.system_health.error_rate, 0.01);
720        assert!(matches!(metrics.system_health.status, HealthStatus::Healthy));
721        assert_eq!(metrics.system_health.last_index_update, "2023-01-01T00:00:00Z");
722    }
723
724    #[test]
725    fn test_update_system_health_degraded() {
726        let collector = MetricsCollector::new();
727        
728        let health_update = SystemHealthUpdate {
729            lsp_servers_active: 2,
730            index_size_mb: 512.0,
731            error_rate: 0.08, // Medium error rate - should be degraded
732            last_index_update: None,
733        };
734        
735        collector.update_system_health(health_update);
736        
737        let metrics = collector.get_metrics();
738        assert_eq!(metrics.system_health.error_rate, 0.08);
739        assert!(matches!(metrics.system_health.status, HealthStatus::Degraded));
740    }
741
742    #[test]
743    fn test_update_system_health_unhealthy() {
744        let collector = MetricsCollector::new();
745        
746        let health_update = SystemHealthUpdate {
747            lsp_servers_active: 0,
748            index_size_mb: 0.0,
749            error_rate: 0.15, // High error rate - should be unhealthy
750            last_index_update: None,
751        };
752        
753        collector.update_system_health(health_update);
754        
755        let metrics = collector.get_metrics();
756        assert_eq!(metrics.system_health.error_rate, 0.15);
757        assert!(matches!(metrics.system_health.status, HealthStatus::Unhealthy));
758    }
759
760    #[test]
761    fn test_reset_metrics() {
762        let collector = MetricsCollector::new();
763        
764        // Record some data
765        collector.record_query("search", Some("rust"), 50.0, true, 10);
766        collector.record_query("lookup", Some("python"), 75.0, false, 5);
767        
768        let metrics_before = collector.get_metrics();
769        assert_eq!(metrics_before.search_metrics.total_queries, 2);
770        
771        // Reset
772        collector.reset();
773        
774        let metrics_after = collector.get_metrics();
775        assert_eq!(metrics_after.search_metrics.total_queries, 0);
776        assert_eq!(metrics_after.search_metrics.successful_queries, 0);
777        assert_eq!(metrics_after.search_metrics.failed_queries, 0);
778        assert!(metrics_after.search_metrics.query_distribution.is_empty());
779        assert!(metrics_after.search_metrics.language_distribution.is_empty());
780    }
781
782    #[test]
783    fn test_sla_metrics_creation() {
784        let sla_metrics = SlaMetrics::new(150.0, 0.95, 0.99);
785        
786        assert_eq!(sla_metrics.latency_p95_sla_ms, 150.0);
787        assert_eq!(sla_metrics.success_rate_sla, 0.95);
788        assert_eq!(sla_metrics.availability_sla, 0.99);
789        assert_eq!(sla_metrics.current_latency_p95_ms, 0.0);
790        assert_eq!(sla_metrics.current_success_rate, 1.0);
791        assert_eq!(sla_metrics.current_availability, 1.0);
792        assert_eq!(sla_metrics.sla_violations, 0);
793    }
794
795    #[test]
796    fn test_sla_metrics_compliance_success() {
797        let mut sla_metrics = SlaMetrics::new(150.0, 0.95, 0.99);
798        sla_metrics.current_latency_p95_ms = 100.0; // Under SLA
799        sla_metrics.current_success_rate = 0.97; // Above SLA
800        sla_metrics.current_availability = 0.995; // Above SLA
801        
802        assert!(sla_metrics.is_compliant());
803    }
804
805    #[test]
806    fn test_sla_metrics_compliance_failure() {
807        let mut sla_metrics = SlaMetrics::new(150.0, 0.95, 0.99);
808        sla_metrics.current_latency_p95_ms = 200.0; // Over SLA
809        sla_metrics.current_success_rate = 0.90; // Under SLA
810        sla_metrics.current_availability = 0.98; // Under SLA
811        
812        assert!(!sla_metrics.is_compliant());
813    }
814
815    #[test]
816    fn test_performance_gate_less_than() {
817        let mut gate = PerformanceGate::new(
818            "Latency P95".to_string(),
819            150.0,
820            GateComparison::LessThan
821        );
822        
823        assert!(gate.evaluate(100.0)); // 100 < 150
824        assert!(gate.passed);
825        assert_eq!(gate.current_value, 100.0);
826        
827        assert!(!gate.evaluate(200.0)); // 200 >= 150
828        assert!(!gate.passed);
829        assert_eq!(gate.current_value, 200.0);
830    }
831
832    #[test]
833    fn test_performance_gate_greater_than() {
834        let mut gate = PerformanceGate::new(
835            "Success Rate".to_string(),
836            0.95,
837            GateComparison::GreaterThan
838        );
839        
840        assert!(gate.evaluate(0.98)); // 0.98 > 0.95
841        assert!(gate.passed);
842        
843        assert!(!gate.evaluate(0.90)); // 0.90 <= 0.95
844        assert!(!gate.passed);
845    }
846
847    #[test]
848    fn test_performance_gate_equal() {
849        let mut gate = PerformanceGate::new(
850            "Exact Match".to_string(),
851            1.0,
852            GateComparison::Equal
853        );
854        
855        assert!(gate.evaluate(1.0)); // Exactly 1.0
856        assert!(gate.passed);
857        
858        assert!(!gate.evaluate(1.1)); // Not exactly 1.0
859        assert!(!gate.passed);
860    }
861
862    #[test]
863    fn test_performance_gate_less_than_or_equal() {
864        let mut gate = PerformanceGate::new(
865            "Memory Usage".to_string(),
866            100.0,
867            GateComparison::LessThanOrEqual
868        );
869        
870        assert!(gate.evaluate(100.0)); // 100 <= 100
871        assert!(gate.passed);
872        
873        assert!(gate.evaluate(50.0)); // 50 <= 100
874        assert!(gate.passed);
875        
876        assert!(!gate.evaluate(150.0)); // 150 > 100
877        assert!(!gate.passed);
878    }
879
880    #[test]
881    fn test_performance_gate_greater_than_or_equal() {
882        let mut gate = PerformanceGate::new(
883            "Throughput".to_string(),
884            10.0,
885            GateComparison::GreaterThanOrEqual
886        );
887        
888        assert!(gate.evaluate(10.0)); // 10 >= 10
889        assert!(gate.passed);
890        
891        assert!(gate.evaluate(15.0)); // 15 >= 10
892        assert!(gate.passed);
893        
894        assert!(!gate.evaluate(5.0)); // 5 < 10
895        assert!(!gate.passed);
896    }
897
898    #[test]
899    fn test_health_status_variants() {
900        // Test serialization/deserialization would work
901        let healthy = HealthStatus::Healthy;
902        let degraded = HealthStatus::Degraded;
903        let unhealthy = HealthStatus::Unhealthy;
904        
905        // Basic enum variant checks
906        assert!(matches!(healthy, HealthStatus::Healthy));
907        assert!(matches!(degraded, HealthStatus::Degraded));
908        assert!(matches!(unhealthy, HealthStatus::Unhealthy));
909    }
910
911    #[test]
912    fn test_performance_grade_variants() {
913        let excellent = PerformanceGrade::Excellent;
914        let good = PerformanceGrade::Good;
915        let fair = PerformanceGrade::Fair;
916        let poor = PerformanceGrade::Poor;
917        
918        assert!(matches!(excellent, PerformanceGrade::Excellent));
919        assert!(matches!(good, PerformanceGrade::Good));
920        assert!(matches!(fair, PerformanceGrade::Fair));
921        assert!(matches!(poor, PerformanceGrade::Poor));
922    }
923
924    #[test]
925    fn test_system_metrics_timestamp_updates() {
926        let collector = MetricsCollector::new();
927        
928        let initial_metrics = collector.get_metrics();
929        let initial_timestamp = initial_metrics.timestamp.clone();
930        
931        // Wait a moment and record a query
932        std::thread::sleep(std::time::Duration::from_millis(10));
933        collector.record_query("search", None, 50.0, true, 1);
934        
935        let updated_metrics = collector.get_metrics();
936        assert_ne!(initial_timestamp, updated_metrics.timestamp);
937    }
938
939    #[test]
940    fn test_query_distribution_multiple_types() {
941        let collector = MetricsCollector::new();
942        
943        collector.record_query("search", None, 50.0, true, 5);
944        collector.record_query("lookup", None, 30.0, true, 3);
945        collector.record_query("search", None, 70.0, true, 7);
946        collector.record_query("browse", None, 40.0, true, 2);
947        
948        let metrics = collector.get_metrics();
949        
950        assert_eq!(metrics.search_metrics.query_distribution.get("search"), Some(&2));
951        assert_eq!(metrics.search_metrics.query_distribution.get("lookup"), Some(&1));
952        assert_eq!(metrics.search_metrics.query_distribution.get("browse"), Some(&1));
953        assert_eq!(metrics.search_metrics.total_queries, 4);
954        
955        // Average results: (5+3+7+2)/4 = 4.25
956        assert!((metrics.search_metrics.average_results_per_query - 4.25).abs() < f64::EPSILON);
957    }
958
959    #[test]
960    fn test_language_distribution() {
961        let collector = MetricsCollector::new();
962        
963        collector.record_query("search", Some("rust"), 50.0, true, 5);
964        collector.record_query("search", Some("python"), 30.0, true, 3);
965        collector.record_query("search", Some("rust"), 70.0, true, 7);
966        collector.record_query("search", None, 40.0, true, 2); // No language
967        
968        let metrics = collector.get_metrics();
969        
970        assert_eq!(metrics.search_metrics.language_distribution.get("rust"), Some(&2));
971        assert_eq!(metrics.search_metrics.language_distribution.get("python"), Some(&1));
972        // No language queries don't add to language distribution
973        assert_eq!(metrics.search_metrics.language_distribution.len(), 2);
974    }
975}