1use crate::{Vector, VectorId};
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::time::{Duration, Instant, SystemTime};
11use tracing::{debug, info, warn};
12
13#[derive(Debug, Clone)]
15pub struct PerformanceInsightsAnalyzer {
16 query_stats: QueryStatistics,
18 vector_stats: VectorStatistics,
20 trends: PerformanceTrends,
22 recommendations: OptimizationRecommendations,
24 metrics_collector: MetricsCollector,
26 alerting_system: AlertingSystem,
28}
29
30#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32pub struct QueryStatistics {
33 pub total_queries: u64,
34 pub average_latency_ms: f64,
35 pub p50_latency_ms: f64,
36 pub p95_latency_ms: f64,
37 pub p99_latency_ms: f64,
38 pub throughput_qps: f64,
39 pub error_rate: f64,
40 pub cache_hit_rate: f64,
41 pub index_efficiency: f64,
42 pub memory_usage_mb: f64,
43 pub cpu_utilization: f64,
44 pub latency_distribution: Vec<LatencyBucket>,
45 pub query_complexity_distribution: HashMap<QueryComplexity, u64>,
46 pub top_slow_queries: Vec<SlowQueryEntry>,
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
51pub struct VectorStatistics {
52 pub total_vectors: u64,
53 pub average_dimension: u32,
54 pub dimension_distribution: HashMap<u32, u64>,
55 pub vector_density: f64,
56 pub sparsity_ratio: f64,
57 pub clustering_coefficient: f64,
58 pub hubness_measure: f64,
59 pub intrinsic_dimensionality: f64,
60 pub noise_estimation: f64,
61 pub quality_score: f64,
62 pub outlier_count: u64,
63 pub similarity_distribution: SimilarityDistribution,
64 pub vector_type_distribution: HashMap<String, u64>,
65}
66
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub struct PerformanceTrends {
70 pub trend_window_hours: u32,
71 pub latency_trend: TrendDirection,
72 pub throughput_trend: TrendDirection,
73 pub error_rate_trend: TrendDirection,
74 pub memory_usage_trend: TrendDirection,
75 pub cache_efficiency_trend: TrendDirection,
76 pub index_performance_trend: TrendDirection,
77 pub historical_data: Vec<PerformanceSnapshot>,
78 pub seasonal_patterns: SeasonalPatterns,
79 pub anomaly_detection: AnomalyDetection,
80}
81
82#[derive(Debug, Clone, Default, Serialize, Deserialize)]
84pub struct OptimizationRecommendations {
85 pub index_recommendations: Vec<IndexRecommendation>,
86 pub caching_recommendations: Vec<CachingRecommendation>,
87 pub query_recommendations: Vec<QueryRecommendation>,
88 pub hardware_recommendations: Vec<HardwareRecommendation>,
89 pub configuration_recommendations: Vec<ConfigurationRecommendation>,
90 pub priority_actions: Vec<PriorityAction>,
91 pub estimated_improvements: ImprovementEstimates,
92}
93
94#[derive(Debug, Clone)]
96pub struct MetricsCollector {
97 start_time: Instant,
98 query_times: Vec<Duration>,
99 query_complexities: Vec<QueryComplexity>,
100 memory_samples: Vec<u64>,
101 cpu_samples: Vec<f64>,
102 cache_metrics: CacheMetrics,
103 error_counts: HashMap<String, u64>,
104 active_queries: u32,
105}
106
107#[derive(Debug, Clone)]
109pub struct AlertingSystem {
110 alert_rules: Vec<AlertRule>,
111 active_alerts: Vec<ActiveAlert>,
112 alert_history: Vec<AlertEvent>,
113 notification_channels: Vec<NotificationChannel>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct LatencyBucket {
120 pub range_ms: (f64, f64),
121 pub count: u64,
122 pub percentage: f64,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
126pub enum QueryComplexity {
127 Simple,
128 Moderate,
129 Complex,
130 HighlyComplex,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SlowQueryEntry {
135 pub query_id: String,
136 pub execution_time_ms: f64,
137 pub timestamp: SystemTime,
138 pub query_type: String,
139 pub vector_count: u64,
140 pub optimization_potential: f64,
141}
142
143#[derive(Debug, Clone, Default, Serialize, Deserialize)]
144pub struct SimilarityDistribution {
145 pub min_similarity: f64,
146 pub max_similarity: f64,
147 pub mean_similarity: f64,
148 pub std_deviation: f64,
149 pub distribution_buckets: Vec<(f64, u64)>,
150 pub skewness: f64,
151 pub kurtosis: f64,
152}
153
154#[derive(Debug, Clone, Default, Serialize, Deserialize)]
155pub enum TrendDirection {
156 Improving,
157 #[default]
158 Stable,
159 Degrading,
160 Volatile,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct PerformanceSnapshot {
165 pub timestamp: SystemTime,
166 pub latency_p99: f64,
167 pub throughput: f64,
168 pub memory_usage: u64,
169 pub cpu_usage: f64,
170 pub cache_hit_rate: f64,
171 pub error_rate: f64,
172}
173
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175pub struct SeasonalPatterns {
176 pub daily_patterns: Vec<(u8, f64)>, pub weekly_patterns: Vec<(u8, f64)>, pub monthly_patterns: Vec<(u8, f64)>, }
180
181#[derive(Debug, Clone, Default, Serialize, Deserialize)]
182pub struct AnomalyDetection {
183 pub anomaly_threshold: f64,
184 pub detected_anomalies: Vec<AnomalyEvent>,
185 pub baseline_performance: f64,
186 pub current_deviation: f64,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct AnomalyEvent {
191 pub timestamp: SystemTime,
192 pub metric: String,
193 pub value: f64,
194 pub expected_value: f64,
195 pub deviation_score: f64,
196 pub severity: AlertSeverity,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct IndexRecommendation {
201 pub recommendation_type: String,
202 pub description: String,
203 pub estimated_improvement: f64,
204 pub implementation_effort: EffortLevel,
205 pub prerequisites: Vec<String>,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct CachingRecommendation {
210 pub cache_type: String,
211 pub recommended_size_mb: u64,
212 pub eviction_policy: String,
213 pub estimated_hit_rate: f64,
214 pub cost_benefit_ratio: f64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct QueryRecommendation {
219 pub query_pattern: String,
220 pub optimization_technique: String,
221 pub expected_speedup: f64,
222 pub applicability: f64,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct HardwareRecommendation {
227 pub component: String,
228 pub current_bottleneck: bool,
229 pub recommended_upgrade: String,
230 pub performance_impact: f64,
231 pub cost_estimate: Option<String>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct ConfigurationRecommendation {
236 pub parameter: String,
237 pub current_value: String,
238 pub recommended_value: String,
239 pub justification: String,
240 pub risk_level: RiskLevel,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PriorityAction {
245 pub action: String,
246 pub priority: Priority,
247 pub estimated_impact: f64,
248 pub implementation_time: Duration,
249 pub dependencies: Vec<String>,
250}
251
252#[derive(Debug, Clone, Default, Serialize, Deserialize)]
253pub struct ImprovementEstimates {
254 pub latency_improvement: f64,
255 pub throughput_improvement: f64,
256 pub memory_savings: f64,
257 pub cost_reduction: f64,
258 pub reliability_improvement: f64,
259}
260
261#[derive(Debug, Clone, Default)]
262pub struct CacheMetrics {
263 pub hits: u64,
264 pub misses: u64,
265 pub evictions: u64,
266 pub size_bytes: u64,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct AlertRule {
271 pub name: String,
272 pub metric: String,
273 pub threshold: f64,
274 pub comparison: ComparisonOperator,
275 pub severity: AlertSeverity,
276 pub enabled: bool,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct ActiveAlert {
281 pub rule_name: String,
282 pub triggered_at: SystemTime,
283 pub current_value: f64,
284 pub threshold: f64,
285 pub severity: AlertSeverity,
286 pub acknowledged: bool,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct AlertEvent {
291 pub timestamp: SystemTime,
292 pub alert_name: String,
293 pub event_type: AlertEventType,
294 pub details: String,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum NotificationChannel {
299 Email(String),
300 Webhook(String),
301 Slack(String),
302 Console,
303}
304
305#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
306pub enum EffortLevel {
307 Low,
308 Medium,
309 High,
310 VeryHigh,
311}
312
313#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
314pub enum RiskLevel {
315 Low,
316 Medium,
317 High,
318 Critical,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
322pub enum Priority {
323 Low,
324 Medium,
325 High,
326 Critical,
327}
328
329#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
330pub enum ComparisonOperator {
331 GreaterThan,
332 LessThan,
333 Equals,
334 GreaterThanOrEqual,
335 LessThanOrEqual,
336}
337
338#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
339pub enum AlertSeverity {
340 Info,
341 Warning,
342 Critical,
343 Emergency,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub enum AlertEventType {
348 Triggered,
349 Resolved,
350 Acknowledged,
351 Escalated,
352}
353
354impl PerformanceInsightsAnalyzer {
355 pub fn new() -> Self {
357 Self {
358 query_stats: QueryStatistics::default(),
359 vector_stats: VectorStatistics::default(),
360 trends: PerformanceTrends::default(),
361 recommendations: OptimizationRecommendations::default(),
362 metrics_collector: MetricsCollector::new(),
363 alerting_system: AlertingSystem::new(),
364 }
365 }
366
367 pub fn record_query(&mut self, duration: Duration, complexity: QueryComplexity, success: bool) {
369 self.metrics_collector
370 .record_query(duration, complexity, success);
371 self.query_stats.total_queries += 1;
372
373 let latency_ms = duration.as_secs_f64() * 1000.0;
374 self.update_latency_statistics(latency_ms);
375
376 if !success {
377 self.query_stats.error_rate = self.calculate_error_rate();
378 }
379
380 self.check_performance_alerts(latency_ms);
381 }
382
383 pub fn analyze_vector_dataset(
385 &mut self,
386 vectors: &[(VectorId, Vector)],
387 ) -> Result<VectorStatistics> {
388 info!("Analyzing vector dataset with {} vectors", vectors.len());
389
390 let mut stats = VectorStatistics {
391 total_vectors: vectors.len() as u64,
392 ..Default::default()
393 };
394
395 if vectors.is_empty() {
396 return Ok(stats);
397 }
398
399 let dimensions: Vec<u32> = vectors.iter().map(|(_, v)| v.dimensions as u32).collect();
401 stats.average_dimension = dimensions.iter().sum::<u32>() / dimensions.len() as u32;
402
403 for &dim in &dimensions {
404 *stats.dimension_distribution.entry(dim).or_insert(0) += 1;
405 }
406
407 let (density, sparsity) = self.calculate_vector_density(vectors);
409 stats.vector_density = density;
410 stats.sparsity_ratio = sparsity;
411
412 stats.clustering_coefficient = self.calculate_clustering_coefficient(vectors);
414
415 stats.hubness_measure = self.calculate_hubness_measure(vectors);
417
418 stats.intrinsic_dimensionality = self.estimate_intrinsic_dimensionality(vectors);
420
421 stats.noise_estimation = self.estimate_noise_level(vectors);
423
424 stats.quality_score = self.calculate_quality_score(&stats);
426
427 self.vector_stats = stats.clone();
428 Ok(stats)
429 }
430
431 pub fn generate_recommendations(&mut self) -> OptimizationRecommendations {
433 let mut recommendations = OptimizationRecommendations::default();
434
435 recommendations.index_recommendations = self.generate_index_recommendations();
437
438 recommendations.caching_recommendations = self.generate_caching_recommendations();
440
441 recommendations.query_recommendations = self.generate_query_recommendations();
443
444 recommendations.hardware_recommendations = self.generate_hardware_recommendations();
446
447 recommendations.configuration_recommendations =
449 self.generate_configuration_recommendations();
450
451 recommendations.priority_actions = self.generate_priority_actions();
453
454 recommendations.estimated_improvements = self.estimate_improvements(&recommendations);
456
457 self.recommendations = recommendations.clone();
458 recommendations
459 }
460
461 pub fn export_performance_report(&self, format: ReportFormat) -> Result<String> {
463 match format {
464 ReportFormat::Json => {
465 let report = PerformanceReport {
466 timestamp: SystemTime::now(),
467 query_stats: self.query_stats.clone(),
468 vector_stats: self.vector_stats.clone(),
469 trends: self.trends.clone(),
470 recommendations: self.recommendations.clone(),
471 alerts: self.alerting_system.get_active_alerts(),
472 };
473 Ok(serde_json::to_string_pretty(&report)?)
474 }
475 ReportFormat::Csv => self.export_csv_report(),
476 ReportFormat::Html => self.export_html_report(),
477 ReportFormat::Prometheus => self.export_prometheus_metrics(),
478 }
479 }
480
481 fn update_latency_statistics(&mut self, latency_ms: f64) {
484 let alpha = 0.1; if self.query_stats.total_queries == 1 {
487 self.query_stats.average_latency_ms = latency_ms;
488 } else {
489 self.query_stats.average_latency_ms =
490 alpha * latency_ms + (1.0 - alpha) * self.query_stats.average_latency_ms;
491 }
492
493 self.metrics_collector
495 .query_times
496 .push(Duration::from_secs_f64(latency_ms / 1000.0));
497 self.update_percentiles();
498 }
499
500 fn update_percentiles(&mut self) {
501 let mut times: Vec<f64> = self
502 .metrics_collector
503 .query_times
504 .iter()
505 .map(|d| d.as_secs_f64() * 1000.0)
506 .collect();
507 times.sort_by(|a, b| a.partial_cmp(b).unwrap());
508
509 if !times.is_empty() {
510 self.query_stats.p50_latency_ms = self.percentile(×, 0.5);
511 self.query_stats.p95_latency_ms = self.percentile(×, 0.95);
512 self.query_stats.p99_latency_ms = self.percentile(×, 0.99);
513 }
514 }
515
516 fn percentile(&self, sorted_data: &[f64], p: f64) -> f64 {
517 if sorted_data.is_empty() {
518 return 0.0;
519 }
520 let index = (p * (sorted_data.len() - 1) as f64).round() as usize;
521 sorted_data[index.min(sorted_data.len() - 1)]
522 }
523
524 fn calculate_error_rate(&self) -> f64 {
525 let total_errors: u64 = self.metrics_collector.error_counts.values().sum();
526 if self.query_stats.total_queries > 0 {
527 total_errors as f64 / self.query_stats.total_queries as f64
528 } else {
529 0.0
530 }
531 }
532
533 fn check_performance_alerts(&mut self, latency_ms: f64) {
534 let mut rules_to_alert = Vec::new();
536 for rule in &self.alerting_system.alert_rules {
537 if rule.enabled && self.evaluate_alert_rule(rule, latency_ms) {
538 rules_to_alert.push(rule.clone());
539 }
540 }
541
542 for rule in rules_to_alert {
544 self.alerting_system.trigger_alert(&rule, latency_ms);
545 }
546 }
547
548 fn evaluate_alert_rule(&self, rule: &AlertRule, value: f64) -> bool {
549 match rule.comparison {
550 ComparisonOperator::GreaterThan => value > rule.threshold,
551 ComparisonOperator::LessThan => value < rule.threshold,
552 ComparisonOperator::Equals => (value - rule.threshold).abs() < f64::EPSILON,
553 ComparisonOperator::GreaterThanOrEqual => value >= rule.threshold,
554 ComparisonOperator::LessThanOrEqual => value <= rule.threshold,
555 }
556 }
557
558 fn calculate_vector_density(&self, vectors: &[(VectorId, Vector)]) -> (f64, f64) {
559 if vectors.is_empty() {
560 return (0.0, 0.0);
561 }
562
563 let mut total_non_zero = 0;
564 let mut total_elements = 0;
565
566 for (_, vector) in vectors {
567 let values = &vector.as_f32();
568 total_elements += values.len();
569 total_non_zero += values.iter().filter(|&&x| x != 0.0).count();
570 }
571
572 let density = total_non_zero as f64 / total_elements as f64;
573 let sparsity = 1.0 - density;
574 (density, sparsity)
575 }
576
577 fn calculate_clustering_coefficient(&self, _vectors: &[(VectorId, Vector)]) -> f64 {
578 0.5 }
582
583 fn calculate_hubness_measure(&self, _vectors: &[(VectorId, Vector)]) -> f64 {
584 0.3 }
588
589 fn estimate_intrinsic_dimensionality(&self, _vectors: &[(VectorId, Vector)]) -> f64 {
590 if self.vector_stats.average_dimension > 0 {
593 self.vector_stats.average_dimension as f64 * 0.7
594 } else {
595 10.0
596 }
597 }
598
599 fn estimate_noise_level(&self, _vectors: &[(VectorId, Vector)]) -> f64 {
600 0.1 }
603
604 fn calculate_quality_score(&self, stats: &VectorStatistics) -> f64 {
605 let density_score = stats.vector_density;
606 let clustering_score = stats.clustering_coefficient;
607 let noise_penalty = 1.0 - stats.noise_estimation;
608
609 (density_score + clustering_score + noise_penalty) / 3.0
610 }
611
612 fn generate_index_recommendations(&self) -> Vec<IndexRecommendation> {
613 let mut recommendations = Vec::new();
614
615 if self.query_stats.average_latency_ms > 10.0 {
616 recommendations.push(IndexRecommendation {
617 recommendation_type: "HNSW Optimization".to_string(),
618 description: "Consider optimizing HNSW parameters (M, efConstruction) for better search performance".to_string(),
619 estimated_improvement: 0.3,
620 implementation_effort: EffortLevel::Medium,
621 prerequisites: vec!["Performance profiling".to_string()],
622 });
623 }
624
625 if self.vector_stats.sparsity_ratio > 0.8 {
626 recommendations.push(IndexRecommendation {
627 recommendation_type: "Sparse Index".to_string(),
628 description:
629 "High sparsity detected - consider using sparse-optimized index structures"
630 .to_string(),
631 estimated_improvement: 0.4,
632 implementation_effort: EffortLevel::High,
633 prerequisites: vec!["Sparse vector support".to_string()],
634 });
635 }
636
637 recommendations
638 }
639
640 fn generate_caching_recommendations(&self) -> Vec<CachingRecommendation> {
641 let mut recommendations = Vec::new();
642
643 if self.query_stats.cache_hit_rate < 0.7 {
644 recommendations.push(CachingRecommendation {
645 cache_type: "Query Result Cache".to_string(),
646 recommended_size_mb: 512,
647 eviction_policy: "LRU".to_string(),
648 estimated_hit_rate: 0.85,
649 cost_benefit_ratio: 2.5,
650 });
651 }
652
653 recommendations
654 }
655
656 fn generate_query_recommendations(&self) -> Vec<QueryRecommendation> {
657 vec![QueryRecommendation {
658 query_pattern: "High-dimensional similarity search".to_string(),
659 optimization_technique: "Dimensionality reduction with PCA".to_string(),
660 expected_speedup: 1.5,
661 applicability: 0.8,
662 }]
663 }
664
665 fn generate_hardware_recommendations(&self) -> Vec<HardwareRecommendation> {
666 let mut recommendations = Vec::new();
667
668 if self.query_stats.memory_usage_mb > 8192.0 {
669 recommendations.push(HardwareRecommendation {
670 component: "Memory".to_string(),
671 current_bottleneck: true,
672 recommended_upgrade: "Increase RAM to 32GB+".to_string(),
673 performance_impact: 0.4,
674 cost_estimate: Some("$200-500".to_string()),
675 });
676 }
677
678 recommendations
679 }
680
681 fn generate_configuration_recommendations(&self) -> Vec<ConfigurationRecommendation> {
682 vec![ConfigurationRecommendation {
683 parameter: "thread_pool_size".to_string(),
684 current_value: "4".to_string(),
685 recommended_value: "8".to_string(),
686 justification: "CPU utilization suggests more threads could improve throughput"
687 .to_string(),
688 risk_level: RiskLevel::Low,
689 }]
690 }
691
692 fn generate_priority_actions(&self) -> Vec<PriorityAction> {
693 let mut actions = Vec::new();
694
695 if self.query_stats.average_latency_ms > 50.0 {
696 actions.push(PriorityAction {
697 action: "Optimize slow queries".to_string(),
698 priority: Priority::High,
699 estimated_impact: 0.6,
700 implementation_time: Duration::from_secs(3600 * 8), dependencies: vec!["Query profiling".to_string()],
702 });
703 }
704
705 actions.sort_by(|a, b| b.priority.cmp(&a.priority));
706 actions
707 }
708
709 fn estimate_improvements(
710 &self,
711 _recommendations: &OptimizationRecommendations,
712 ) -> ImprovementEstimates {
713 ImprovementEstimates {
714 latency_improvement: 0.3,
715 throughput_improvement: 0.25,
716 memory_savings: 0.15,
717 cost_reduction: 0.2,
718 reliability_improvement: 0.1,
719 }
720 }
721
722 fn export_csv_report(&self) -> Result<String> {
723 let mut csv = String::new();
724 csv.push_str("Metric,Value,Unit\n");
725 csv.push_str(&format!(
726 "Total Queries,{},count\n",
727 self.query_stats.total_queries
728 ));
729 csv.push_str(&format!(
730 "Average Latency,{:.2},ms\n",
731 self.query_stats.average_latency_ms
732 ));
733 csv.push_str(&format!(
734 "P99 Latency,{:.2},ms\n",
735 self.query_stats.p99_latency_ms
736 ));
737 csv.push_str(&format!(
738 "Throughput,{:.2},QPS\n",
739 self.query_stats.throughput_qps
740 ));
741 csv.push_str(&format!(
742 "Error Rate,{:.4},ratio\n",
743 self.query_stats.error_rate
744 ));
745 csv.push_str(&format!(
746 "Cache Hit Rate,{:.4},ratio\n",
747 self.query_stats.cache_hit_rate
748 ));
749 Ok(csv)
750 }
751
752 fn export_html_report(&self) -> Result<String> {
753 let html = format!(
754 r#"
755 <!DOCTYPE html>
756 <html>
757 <head><title>OxiRS Vector Search Performance Report</title></head>
758 <body>
759 <h1>Performance Report</h1>
760 <h2>Query Statistics</h2>
761 <p>Total Queries: {}</p>
762 <p>Average Latency: {:.2} ms</p>
763 <p>P99 Latency: {:.2} ms</p>
764 <p>Throughput: {:.2} QPS</p>
765 <p>Error Rate: {:.4}</p>
766 <h2>Vector Statistics</h2>
767 <p>Total Vectors: {}</p>
768 <p>Average Dimension: {}</p>
769 <p>Vector Density: {:.4}</p>
770 <p>Quality Score: {:.4}</p>
771 </body>
772 </html>
773 "#,
774 self.query_stats.total_queries,
775 self.query_stats.average_latency_ms,
776 self.query_stats.p99_latency_ms,
777 self.query_stats.throughput_qps,
778 self.query_stats.error_rate,
779 self.vector_stats.total_vectors,
780 self.vector_stats.average_dimension,
781 self.vector_stats.vector_density,
782 self.vector_stats.quality_score,
783 );
784 Ok(html)
785 }
786
787 fn export_prometheus_metrics(&self) -> Result<String> {
788 let mut metrics = String::new();
789 metrics.push_str(&format!(
790 "oxirs_query_total {}\n",
791 self.query_stats.total_queries
792 ));
793 metrics.push_str(&format!(
794 "oxirs_query_latency_avg {}\n",
795 self.query_stats.average_latency_ms
796 ));
797 metrics.push_str(&format!(
798 "oxirs_query_latency_p99 {}\n",
799 self.query_stats.p99_latency_ms
800 ));
801 metrics.push_str(&format!(
802 "oxirs_query_throughput {}\n",
803 self.query_stats.throughput_qps
804 ));
805 metrics.push_str(&format!(
806 "oxirs_query_error_rate {}\n",
807 self.query_stats.error_rate
808 ));
809 metrics.push_str(&format!(
810 "oxirs_cache_hit_rate {}\n",
811 self.query_stats.cache_hit_rate
812 ));
813 metrics.push_str(&format!(
814 "oxirs_vector_total {}\n",
815 self.vector_stats.total_vectors
816 ));
817 metrics.push_str(&format!(
818 "oxirs_vector_quality_score {}\n",
819 self.vector_stats.quality_score
820 ));
821 Ok(metrics)
822 }
823}
824
825#[derive(Debug, Clone, Serialize, Deserialize)]
826pub struct PerformanceReport {
827 pub timestamp: SystemTime,
828 pub query_stats: QueryStatistics,
829 pub vector_stats: VectorStatistics,
830 pub trends: PerformanceTrends,
831 pub recommendations: OptimizationRecommendations,
832 pub alerts: Vec<ActiveAlert>,
833}
834
835#[derive(Debug, Clone, Copy)]
836pub enum ReportFormat {
837 Json,
838 Csv,
839 Html,
840 Prometheus,
841}
842
843impl Default for MetricsCollector {
844 fn default() -> Self {
845 Self::new()
846 }
847}
848
849impl MetricsCollector {
850 pub fn new() -> Self {
851 Self {
852 start_time: Instant::now(),
853 query_times: Vec::new(),
854 query_complexities: Vec::new(),
855 memory_samples: Vec::new(),
856 cpu_samples: Vec::new(),
857 cache_metrics: CacheMetrics::default(),
858 error_counts: HashMap::new(),
859 active_queries: 0,
860 }
861 }
862
863 pub fn record_query(&mut self, duration: Duration, complexity: QueryComplexity, success: bool) {
864 self.query_times.push(duration);
865 self.query_complexities.push(complexity);
866
867 if !success {
868 *self
869 .error_counts
870 .entry("query_error".to_string())
871 .or_insert(0) += 1;
872 }
873 }
874}
875
876impl Default for AlertingSystem {
877 fn default() -> Self {
878 Self::new()
879 }
880}
881
882impl AlertingSystem {
883 pub fn new() -> Self {
884 Self {
885 alert_rules: Self::default_alert_rules(),
886 active_alerts: Vec::new(),
887 alert_history: Vec::new(),
888 notification_channels: vec![NotificationChannel::Console],
889 }
890 }
891
892 fn default_alert_rules() -> Vec<AlertRule> {
893 vec![
894 AlertRule {
895 name: "High Latency".to_string(),
896 metric: "latency_p99".to_string(),
897 threshold: 100.0,
898 comparison: ComparisonOperator::GreaterThan,
899 severity: AlertSeverity::Warning,
900 enabled: true,
901 },
902 AlertRule {
903 name: "Critical Latency".to_string(),
904 metric: "latency_p99".to_string(),
905 threshold: 1000.0,
906 comparison: ComparisonOperator::GreaterThan,
907 severity: AlertSeverity::Critical,
908 enabled: true,
909 },
910 ]
911 }
912
913 pub fn trigger_alert(&mut self, rule: &AlertRule, value: f64) {
914 let alert = ActiveAlert {
915 rule_name: rule.name.clone(),
916 triggered_at: SystemTime::now(),
917 current_value: value,
918 threshold: rule.threshold,
919 severity: rule.severity,
920 acknowledged: false,
921 };
922
923 self.active_alerts.push(alert.clone());
924
925 let event = AlertEvent {
926 timestamp: SystemTime::now(),
927 alert_name: rule.name.clone(),
928 event_type: AlertEventType::Triggered,
929 details: format!(
930 "Alert triggered: {} = {:.2} > {:.2}",
931 rule.metric, value, rule.threshold
932 ),
933 };
934
935 self.alert_history.push(event);
936 self.send_notifications(&alert);
937 }
938
939 pub fn get_active_alerts(&self) -> Vec<ActiveAlert> {
940 self.active_alerts.clone()
941 }
942
943 fn send_notifications(&self, alert: &ActiveAlert) {
944 for channel in &self.notification_channels {
945 match channel {
946 NotificationChannel::Console => {
947 warn!(
948 "ALERT: {} - {} at {:.2} (threshold: {:.2})",
949 alert.rule_name, alert.severity as u8, alert.current_value, alert.threshold
950 );
951 }
952 NotificationChannel::Email(_) => {
953 debug!(
954 "Would send email notification for alert: {}",
955 alert.rule_name
956 );
957 }
958 NotificationChannel::Webhook(_) => {
959 debug!(
960 "Would send webhook notification for alert: {}",
961 alert.rule_name
962 );
963 }
964 NotificationChannel::Slack(_) => {
965 debug!(
966 "Would send Slack notification for alert: {}",
967 alert.rule_name
968 );
969 }
970 }
971 }
972 }
973}
974
975impl Default for PerformanceInsightsAnalyzer {
976 fn default() -> Self {
977 Self::new()
978 }
979}
980
981#[cfg(test)]
982mod tests {
983 use super::*;
984
985 #[test]
986 fn test_performance_insights_creation() {
987 let analyzer = PerformanceInsightsAnalyzer::new();
988 assert_eq!(analyzer.query_stats.total_queries, 0);
989 assert_eq!(analyzer.vector_stats.total_vectors, 0);
990 }
991
992 #[test]
993 fn test_query_recording() {
994 let mut analyzer = PerformanceInsightsAnalyzer::new();
995 analyzer.record_query(Duration::from_millis(50), QueryComplexity::Simple, true);
996
997 assert_eq!(analyzer.query_stats.total_queries, 1);
998 assert_eq!(analyzer.query_stats.average_latency_ms, 50.0);
999 }
1000
1001 #[test]
1002 fn test_vector_analysis() {
1003 let mut analyzer = PerformanceInsightsAnalyzer::new();
1004 let vectors = vec![
1005 ("vec1".to_string(), Vector::new(vec![1.0, 2.0, 3.0])),
1006 ("vec2".to_string(), Vector::new(vec![4.0, 5.0, 6.0])),
1007 ];
1008
1009 let stats = analyzer.analyze_vector_dataset(&vectors).unwrap();
1010 assert_eq!(stats.total_vectors, 2);
1011 assert_eq!(stats.average_dimension, 3);
1012 }
1013
1014 #[test]
1015 fn test_alert_generation() {
1016 let mut analyzer = PerformanceInsightsAnalyzer::new();
1017 analyzer.record_query(Duration::from_millis(1500), QueryComplexity::Complex, true);
1019
1020 assert!(!analyzer.alerting_system.active_alerts.is_empty());
1021 }
1022
1023 #[test]
1024 fn test_recommendations_generation() {
1025 let mut analyzer = PerformanceInsightsAnalyzer::new();
1026 analyzer.query_stats.average_latency_ms = 100.0;
1028 analyzer.vector_stats.sparsity_ratio = 0.9;
1029
1030 let recommendations = analyzer.generate_recommendations();
1031 assert!(!recommendations.index_recommendations.is_empty());
1032 }
1033
1034 #[test]
1035 fn test_report_export() {
1036 let analyzer = PerformanceInsightsAnalyzer::new();
1037 let json_report = analyzer
1038 .export_performance_report(ReportFormat::Json)
1039 .unwrap();
1040 assert!(!json_report.is_empty());
1041
1042 let csv_report = analyzer
1043 .export_performance_report(ReportFormat::Csv)
1044 .unwrap();
1045 assert!(csv_report.contains("Metric,Value,Unit"));
1046 }
1047}