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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
70pub enum HealthStatus {
71 Healthy,
72 Degraded,
73 Unhealthy,
74}
75
76pub 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 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 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 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 latencies.push(latency_ms);
150
151 *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 if let Some(lang) = language {
157 *metrics.search_metrics.language_distribution.entry(lang.to_string()).or_insert(0) += 1;
158 }
159
160 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 metrics.timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
166 }
167
168 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 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 if metrics.search_metrics.total_queries > 0 {
188 let elapsed_seconds = 60.0; metrics.performance_metrics.throughput_qps = metrics.search_metrics.total_queries as f64 / elapsed_seconds;
190 }
191
192 metrics.performance_metrics.memory_usage_mb = Self::get_memory_usage();
194 metrics.performance_metrics.cpu_utilization_percent = Self::get_cpu_usage();
195 }
196
197 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 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 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 pub fn get_metrics(&self) -> SystemMetrics {
238 self.metrics.read().clone()
239 }
240
241 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 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 fn get_memory_usage() -> f64 {
338 0.0
340 }
341
342 fn get_cpu_usage() -> f64 {
343 0.0
345 }
346}
347
348#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
381pub enum PerformanceGrade {
382 Excellent,
383 Good,
384 Fair,
385 Poor,
386}
387
388#[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#[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#[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
478static 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
487pub 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
497pub fn init_prometheus_metrics() -> Result<()> {
499 #[cfg(test)]
500 {
501 let _guard = TEST_METRICS_LOCK.lock().unwrap();
503
504 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
551pub fn get_prometheus_metrics() -> Option<&'static PrometheusMetrics> {
553 PROMETHEUS_REGISTRY.get()
554}
555
556pub 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
565pub 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 assert_eq!(metrics.search_metrics.query_distribution.get("search"), Some(&1));
611 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); 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 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); assert_eq!(metrics.performance_metrics.latency_p95_ms, 100.0); assert_eq!(metrics.performance_metrics.latency_p99_ms, 100.0); 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 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, 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, 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, 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 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 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; sla_metrics.current_success_rate = 0.97; sla_metrics.current_availability = 0.995; 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; sla_metrics.current_success_rate = 0.90; sla_metrics.current_availability = 0.98; 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)); assert!(gate.passed);
825 assert_eq!(gate.current_value, 100.0);
826
827 assert!(!gate.evaluate(200.0)); 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)); assert!(gate.passed);
842
843 assert!(!gate.evaluate(0.90)); 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)); assert!(gate.passed);
857
858 assert!(!gate.evaluate(1.1)); 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)); assert!(gate.passed);
872
873 assert!(gate.evaluate(50.0)); assert!(gate.passed);
875
876 assert!(!gate.evaluate(150.0)); 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)); assert!(gate.passed);
890
891 assert!(gate.evaluate(15.0)); assert!(gate.passed);
893
894 assert!(!gate.evaluate(5.0)); assert!(!gate.passed);
896 }
897
898 #[test]
899 fn test_health_status_variants() {
900 let healthy = HealthStatus::Healthy;
902 let degraded = HealthStatus::Degraded;
903 let unhealthy = HealthStatus::Unhealthy;
904
905 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 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 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); 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 assert_eq!(metrics.search_metrics.language_distribution.len(), 2);
974 }
975}