1use crate::{
7 health_monitor::{HealthMonitor, HealthStatus},
8 monitoring::{HealthChecker, MetricsCollector},
9 StreamEvent,
10};
11use anyhow::Result;
12use chrono::{DateTime, Timelike, Utc};
13use serde::{Deserialize, Serialize};
14use std::collections::{BTreeMap, HashMap, VecDeque};
15use std::sync::Arc;
16use tokio::sync::RwLock;
17use uuid::Uuid;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct DiagnosticReport {
22 pub report_id: String,
23 pub timestamp: DateTime<Utc>,
24 pub duration: std::time::Duration,
25 pub system_info: SystemInfo,
26 pub health_summary: HealthSummary,
27 pub performance_metrics: PerformanceMetrics,
28 pub stream_statistics: StreamStatistics,
29 pub error_analysis: ErrorAnalysis,
30 pub recommendations: Vec<Recommendation>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct SystemInfo {
36 pub version: String,
37 pub uptime: std::time::Duration,
38 pub backends: Vec<String>,
39 pub active_connections: usize,
40 pub memory_usage_mb: f64,
41 pub cpu_usage_percent: f64,
42 pub thread_count: usize,
43 pub environment: HashMap<String, String>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct HealthSummary {
49 pub overall_status: HealthStatus,
50 pub component_statuses: HashMap<String, ComponentHealth>,
51 pub recent_failures: Vec<FailureEvent>,
52 pub availability_percentage: f64,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ComponentHealth {
58 pub name: String,
59 pub status: HealthStatus,
60 pub last_check: DateTime<Utc>,
61 pub consecutive_failures: u32,
62 pub error_rate: f64,
63 pub response_time_ms: f64,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct FailureEvent {
69 pub timestamp: DateTime<Utc>,
70 pub component: String,
71 pub error_type: String,
72 pub message: String,
73 pub impact: String,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PerformanceMetrics {
79 pub throughput: ThroughputMetrics,
80 pub latency: LatencyMetrics,
81 pub resource_usage: ResourceMetrics,
82 pub bottlenecks: Vec<Bottleneck>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ThroughputMetrics {
88 pub events_per_second: f64,
89 pub bytes_per_second: f64,
90 pub peak_throughput: f64,
91 pub average_throughput: f64,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct LatencyMetrics {
97 pub p50_ms: f64,
98 pub p95_ms: f64,
99 pub p99_ms: f64,
100 pub max_ms: f64,
101 pub average_ms: f64,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ResourceMetrics {
107 pub memory_usage_mb: f64,
108 pub cpu_usage_percent: f64,
109 pub network_io_mbps: f64,
110 pub disk_io_mbps: f64,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct Bottleneck {
116 pub component: String,
117 pub metric: String,
118 pub severity: String,
119 pub description: String,
120 pub recommendation: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct StreamStatistics {
126 pub total_events: u64,
127 pub event_types: HashMap<String, u64>,
128 pub error_rate: f64,
129 pub duplicate_rate: f64,
130 pub out_of_order_rate: f64,
131 pub backpressure_events: u64,
132 pub circuit_breaker_trips: u64,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct ErrorAnalysis {
138 pub total_errors: u64,
139 pub error_categories: HashMap<String, u64>,
140 pub error_timeline: Vec<ErrorTimelineEntry>,
141 pub top_errors: Vec<ErrorPattern>,
142 pub error_correlations: Vec<ErrorCorrelation>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ErrorTimelineEntry {
148 pub timestamp: DateTime<Utc>,
149 pub error_count: u64,
150 pub error_types: HashMap<String, u64>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct ErrorPattern {
156 pub pattern: String,
157 pub occurrences: u64,
158 pub first_seen: DateTime<Utc>,
159 pub last_seen: DateTime<Utc>,
160 pub affected_components: Vec<String>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ErrorCorrelation {
166 pub primary_error: String,
167 pub correlated_errors: Vec<String>,
168 pub correlation_strength: f64,
169 pub time_offset_ms: i64,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct Recommendation {
175 pub category: String,
176 pub severity: String,
177 pub title: String,
178 pub description: String,
179 pub action_items: Vec<String>,
180 pub expected_impact: String,
181}
182
183type HealthMonitorMap =
185 HashMap<String, Arc<RwLock<HealthMonitor<Box<dyn crate::connection_pool::PooledConnection>>>>>;
186type EventBuffer = Arc<RwLock<VecDeque<(StreamEvent, DateTime<Utc>)>>>;
187
188pub struct DiagnosticAnalyzer {
190 metrics_collector: Arc<RwLock<MetricsCollector>>,
191 health_checker: Arc<RwLock<HealthChecker>>,
192 health_monitors: HealthMonitorMap,
193 event_buffer: EventBuffer,
194 error_tracker: Arc<RwLock<ErrorTracker>>,
195}
196
197struct ErrorTracker {
199 errors: VecDeque<ErrorRecord>,
200 error_counts: HashMap<String, u64>,
201 error_patterns: HashMap<String, ErrorPattern>,
202}
203
204#[derive(Debug, Clone)]
206struct ErrorRecord {
207 timestamp: DateTime<Utc>,
208 error_type: String,
209 message: String,
210 component: String,
211 context: HashMap<String, String>,
212}
213
214impl DiagnosticAnalyzer {
215 pub fn new(
216 metrics_collector: Arc<RwLock<MetricsCollector>>,
217 health_checker: Arc<RwLock<HealthChecker>>,
218 ) -> Self {
219 Self {
220 metrics_collector,
221 health_checker,
222 health_monitors: HashMap::new(),
223 event_buffer: Arc::new(RwLock::new(VecDeque::with_capacity(10000))),
224 error_tracker: Arc::new(RwLock::new(ErrorTracker {
225 errors: VecDeque::with_capacity(1000),
226 error_counts: HashMap::new(),
227 error_patterns: HashMap::new(),
228 })),
229 }
230 }
231
232 pub fn register_health_monitor(
234 &mut self,
235 name: String,
236 monitor: Arc<RwLock<HealthMonitor<Box<dyn crate::connection_pool::PooledConnection>>>>,
237 ) {
238 self.health_monitors.insert(name, monitor);
239 }
240
241 pub async fn generate_report(&self) -> Result<DiagnosticReport> {
243 let start_time = std::time::Instant::now();
244 let report_id = Uuid::new_v4().to_string();
245
246 let system_info = self.collect_system_info().await?;
248 let health_summary = self.analyze_health().await?;
249 let performance_metrics = self.analyze_performance().await?;
250 let stream_statistics = self.analyze_streams().await?;
251 let error_analysis = self.analyze_errors().await?;
252 let recommendations = self
253 .generate_recommendations(
254 &health_summary,
255 &performance_metrics,
256 &error_analysis,
257 &stream_statistics,
258 )
259 .await?;
260
261 Ok(DiagnosticReport {
262 report_id,
263 timestamp: Utc::now(),
264 duration: start_time.elapsed(),
265 system_info,
266 health_summary,
267 performance_metrics,
268 stream_statistics,
269 error_analysis,
270 recommendations,
271 })
272 }
273
274 fn get_active_backends(metrics: &crate::monitoring::StreamingMetrics) -> Vec<String> {
276 let mut backends = Vec::new();
277
278 if metrics.backend_connections_active > 0 {
280 if metrics.producer_events_published > 0 || metrics.consumer_events_consumed > 0 {
282 backends.push("memory".to_string()); }
284
285 #[cfg(feature = "nats")]
288 backends.push("nats".to_string());
289
290 #[cfg(feature = "redis")]
291 backends.push("redis".to_string());
292
293 #[cfg(feature = "kinesis")]
294 backends.push("kinesis".to_string());
295 }
296
297 if backends.is_empty() {
299 backends.push("memory".to_string());
300 }
301
302 backends
303 }
304
305 fn calculate_backpressure_events(metrics: &crate::monitoring::StreamingMetrics) -> u64 {
307 let mut backpressure_events = 0;
310
311 if metrics.error_rate > 0.1 {
313 backpressure_events += (metrics.error_rate * 100.0) as u64;
314 }
315
316 backpressure_events += metrics.backend_circuit_breaker_trips;
318
319 if metrics.out_of_order_rate > 0.05 {
321 backpressure_events += (metrics.out_of_order_rate * 50.0) as u64;
322 }
323
324 backpressure_events
325 }
326
327 async fn collect_system_info(&self) -> Result<SystemInfo> {
329 let metrics = self.metrics_collector.read().await.get_metrics().await;
330
331 Ok(SystemInfo {
332 version: env!("CARGO_PKG_VERSION").to_string(),
333 uptime: metrics
334 .last_updated
335 .signed_duration_since(metrics.collection_start_time)
336 .to_std()
337 .unwrap_or_default(),
338 backends: Self::get_active_backends(&metrics),
339 active_connections: metrics.backend_connections_active as usize,
340 memory_usage_mb: (metrics.system_memory_usage_bytes / 1024 / 1024) as f64,
341 cpu_usage_percent: metrics.system_cpu_usage_percent,
342 thread_count: 0, environment: Self::collect_relevant_env_vars(),
344 })
345 }
346
347 fn collect_relevant_env_vars() -> HashMap<String, String> {
349 let mut env_vars = HashMap::new();
350
351 let relevant_vars = [
353 "RUST_LOG",
354 "RUST_BACKTRACE",
355 "OXIRS_LOG_LEVEL",
356 "KAFKA_BROKERS",
357 "NATS_SERVERS",
358 "REDIS_URL",
359 "AWS_REGION",
360 "AWS_ACCESS_KEY_ID",
361 "OTEL_EXPORTER_OTLP_ENDPOINT",
362 "PROMETHEUS_ENDPOINT",
363 "PATH",
364 "CARGO_PKG_VERSION",
365 "RUST_VERSION",
366 ];
367
368 for var in &relevant_vars {
369 if let Ok(value) = std::env::var(var) {
370 let masked_value =
372 if var.contains("KEY") || var.contains("SECRET") || var.contains("TOKEN") {
373 format!("{}***", &value[..std::cmp::min(4, value.len())])
374 } else {
375 value
376 };
377 env_vars.insert(var.to_string(), masked_value);
378 }
379 }
380
381 env_vars
382 }
383
384 async fn analyze_health(&self) -> Result<HealthSummary> {
386 self.health_checker
388 .read()
389 .await
390 .check_all_components()
391 .await?;
392 let health_status = self.health_checker.read().await.get_health().await;
394 let component_checks = &health_status.component_health;
395
396 let mut component_statuses = HashMap::new();
397 let mut recent_failures = Vec::new();
398
399 for (name, component_health) in component_checks {
401 component_statuses.insert(name.clone(), component_health.clone());
402 }
403
404 for (name, monitor) in &self.health_monitors {
406 let monitor_guard = monitor.read().await;
407 let stats = monitor_guard.get_overall_statistics().await;
408
409 let health_status = if stats.success_rate > 0.9 {
411 crate::monitoring::HealthStatus::Healthy
412 } else if stats.success_rate > 0.7 {
413 crate::monitoring::HealthStatus::Warning
414 } else {
415 crate::monitoring::HealthStatus::Critical
416 };
417
418 component_statuses.insert(
419 name.clone(),
420 crate::monitoring::ComponentHealth {
421 status: health_status,
422 message: format!(
423 "Success rate: {:.2}%, {} of {} checks successful",
424 stats.success_rate * 100.0,
425 stats.successful_checks,
426 stats.total_checks
427 ),
428 last_check: Utc::now(), metrics: {
430 let mut metrics = HashMap::new();
431 metrics.insert("success_rate".to_string(), stats.success_rate);
432 metrics.insert(
433 "avg_response_time_ms".to_string(),
434 stats.avg_response_time_ms,
435 );
436 metrics.insert("total_checks".to_string(), stats.total_checks as f64);
437 metrics
438 },
439 dependencies: Vec::new(), },
441 );
442
443 if stats.success_rate < 0.9 {
445 recent_failures.push(FailureEvent {
446 timestamp: Utc::now(),
447 component: name.clone(),
448 error_type: "Health Check Degraded".to_string(),
449 message: format!(
450 "Component {} has low success rate: {:.2}%",
451 name,
452 stats.success_rate * 100.0
453 ),
454 impact: if stats.success_rate < 0.5 {
455 "Service outage".to_string()
456 } else {
457 "Service degradation".to_string()
458 },
459 });
460 }
461 }
462
463 let total_components = component_statuses.len() as f64;
465 let healthy_components = component_statuses
466 .values()
467 .filter(|c| matches!(c.status, crate::monitoring::HealthStatus::Healthy))
468 .count() as f64;
469 let availability_percentage = if total_components > 0.0 {
470 (healthy_components / total_components) * 100.0
471 } else {
472 100.0
473 };
474
475 let diagnostics_component_statuses: HashMap<String, ComponentHealth> = component_statuses
477 .into_iter()
478 .map(|(name, comp)| {
479 (
480 name.clone(),
481 ComponentHealth {
482 name,
483 status: match comp.status {
484 crate::monitoring::HealthStatus::Healthy => HealthStatus::Healthy,
485 crate::monitoring::HealthStatus::Warning => HealthStatus::Degraded,
486 crate::monitoring::HealthStatus::Critical => HealthStatus::Unhealthy,
487 crate::monitoring::HealthStatus::Unknown => HealthStatus::Unknown,
488 }, last_check: comp.last_check,
490 consecutive_failures: 0, error_rate: comp.metrics.get("error_rate").copied().unwrap_or(0.0),
492 response_time_ms: comp
493 .metrics
494 .get("avg_response_time_ms")
495 .copied()
496 .unwrap_or(0.0),
497 },
498 )
499 })
500 .collect();
501
502 Ok(HealthSummary {
503 overall_status: match health_status.overall_status {
504 crate::monitoring::HealthStatus::Healthy => HealthStatus::Healthy,
505 crate::monitoring::HealthStatus::Warning => HealthStatus::Degraded,
506 crate::monitoring::HealthStatus::Critical => HealthStatus::Unhealthy,
507 crate::monitoring::HealthStatus::Unknown => HealthStatus::Unknown,
508 },
509 component_statuses: diagnostics_component_statuses,
510 recent_failures,
511 availability_percentage,
512 })
513 }
514
515 async fn analyze_performance(&self) -> Result<PerformanceMetrics> {
517 let metrics = self.metrics_collector.read().await.get_metrics().await;
518
519 let uptime_seconds = metrics
521 .last_updated
522 .signed_duration_since(metrics.collection_start_time)
523 .num_seconds()
524 .max(1) as f64;
525
526 let throughput = ThroughputMetrics {
527 events_per_second: metrics.producer_events_published as f64 / uptime_seconds,
528 bytes_per_second: metrics.producer_bytes_sent as f64 / uptime_seconds,
529 peak_throughput: metrics.producer_throughput_eps, average_throughput: metrics.producer_throughput_eps,
531 };
532
533 let latency = LatencyMetrics {
535 p50_ms: metrics.producer_average_latency_ms * 0.8, p95_ms: metrics.producer_average_latency_ms * 1.5, p99_ms: metrics.producer_average_latency_ms * 2.0, max_ms: metrics.producer_average_latency_ms * 3.0, average_ms: metrics.producer_average_latency_ms,
540 };
541
542 let resource_usage = ResourceMetrics {
544 memory_usage_mb: (metrics.system_memory_usage_bytes / 1024 / 1024) as f64,
545 cpu_usage_percent: metrics.system_cpu_usage_percent,
546 network_io_mbps: (metrics.system_network_bytes_in + metrics.system_network_bytes_out)
547 as f64
548 / uptime_seconds
549 / 1024.0
550 / 1024.0, disk_io_mbps: 0.0, };
553
554 let bottlenecks = self
556 .detect_bottlenecks(&metrics, &throughput, &latency)
557 .await?;
558
559 Ok(PerformanceMetrics {
560 throughput,
561 latency,
562 resource_usage,
563 bottlenecks,
564 })
565 }
566
567 async fn detect_bottlenecks(
569 &self,
570 metrics: &crate::monitoring::StreamingMetrics,
571 _throughput: &ThroughputMetrics,
572 latency: &LatencyMetrics,
573 ) -> Result<Vec<Bottleneck>> {
574 let mut bottlenecks = Vec::new();
575
576 if latency.p99_ms > 100.0 {
578 bottlenecks.push(Bottleneck {
579 component: "Stream Processing".to_string(),
580 metric: "Latency".to_string(),
581 severity: if latency.p99_ms > 500.0 {
582 "High"
583 } else {
584 "Medium"
585 }
586 .to_string(),
587 description: format!(
588 "P99 latency is {:.2}ms, which may impact real-time processing",
589 latency.p99_ms
590 ),
591 recommendation: "Consider scaling horizontally or optimizing processing logic"
592 .to_string(),
593 });
594 }
595
596 if let Some(lag_ms) = metrics.consumer_lag_ms {
598 if lag_ms > 10000.0 {
599 bottlenecks.push(Bottleneck {
600 component: "Consumer".to_string(),
601 metric: "Lag".to_string(),
602 severity: "High".to_string(),
603 description: format!("Consumer lag is {lag_ms:.2} ms behind"),
604 recommendation: "Increase consumer parallelism or optimize processing"
605 .to_string(),
606 });
607 }
608 }
609
610 if (metrics.system_memory_usage_bytes / 1024 / 1024) as f64 > 8192.0 {
612 bottlenecks.push(Bottleneck {
614 component: "System".to_string(),
615 metric: "Memory".to_string(),
616 severity: "High".to_string(),
617 description: format!(
618 "Memory usage is high: {} MB",
619 metrics.system_memory_usage_bytes / 1024 / 1024
620 ),
621 recommendation: "Increase memory allocation or optimize memory usage".to_string(),
622 });
623 }
624
625 if metrics.backend_circuit_breaker_trips > 0 {
627 bottlenecks.push(Bottleneck {
628 component: "Backend".to_string(),
629 metric: "Reliability".to_string(),
630 severity: "High".to_string(),
631 description: format!(
632 "Circuit breaker tripped {} times",
633 metrics.backend_circuit_breaker_trips
634 ),
635 recommendation: "Investigate backend health and connection stability".to_string(),
636 });
637 }
638
639 Ok(bottlenecks)
640 }
641
642 async fn analyze_streams(&self) -> Result<StreamStatistics> {
644 let metrics = self.metrics_collector.read().await.get_metrics().await;
645
646 let mut event_types = HashMap::new();
648 let event_buffer = self.event_buffer.read().await;
649 for (event, _) in event_buffer.iter() {
650 let event_type = match event {
651 StreamEvent::TripleAdded { .. } => "triple_added",
652 StreamEvent::TripleRemoved { .. } => "triple_removed",
653 StreamEvent::QuadAdded { .. } => "quad_added",
654 StreamEvent::QuadRemoved { .. } => "quad_removed",
655 StreamEvent::GraphCreated { .. } => "graph_created",
656 StreamEvent::GraphCleared { .. } => "graph_cleared",
657 StreamEvent::GraphDeleted { .. } => "graph_deleted",
658 StreamEvent::SparqlUpdate { .. } => "sparql_update",
659 StreamEvent::TransactionBegin { .. } => "transaction_begin",
660 StreamEvent::TransactionCommit { .. } => "transaction_commit",
661 StreamEvent::TransactionAbort { .. } => "transaction_abort",
662 StreamEvent::SchemaChanged { .. } => "schema_changed",
663 StreamEvent::Heartbeat { .. } => "heartbeat",
664 StreamEvent::QueryResultAdded { .. } => "query_result_added",
665 StreamEvent::QueryResultRemoved { .. } => "query_result_removed",
666 StreamEvent::QueryCompleted { .. } => "query_completed",
667 StreamEvent::GraphMetadataUpdated { .. } => "graph_metadata_updated",
668 StreamEvent::GraphPermissionsChanged { .. } => "graph_permissions_changed",
669 StreamEvent::GraphStatisticsUpdated { .. } => "graph_statistics_updated",
670 StreamEvent::GraphRenamed { .. } => "graph_renamed",
671 StreamEvent::GraphMerged { .. } => "graph_merged",
672 StreamEvent::GraphSplit { .. } => "graph_split",
673 StreamEvent::SchemaDefinitionAdded { .. } => "schema_definition_added",
674 StreamEvent::SchemaDefinitionRemoved { .. } => "schema_definition_removed",
675 StreamEvent::SchemaDefinitionModified { .. } => "schema_definition_modified",
676 StreamEvent::OntologyImported { .. } => "ontology_imported",
677 StreamEvent::OntologyRemoved { .. } => "ontology_removed",
678 StreamEvent::ConstraintAdded { .. } => "constraint_added",
679 StreamEvent::ConstraintRemoved { .. } => "constraint_removed",
680 StreamEvent::ConstraintViolated { .. } => "constraint_violated",
681 StreamEvent::IndexCreated { .. } => "index_created",
682 StreamEvent::IndexDropped { .. } => "index_dropped",
683 StreamEvent::IndexRebuilt { .. } => "index_rebuilt",
684 StreamEvent::SchemaUpdated { .. } => "schema_updated",
685 StreamEvent::ShapeAdded { .. } => "shape_added",
686 StreamEvent::ShapeUpdated { .. } => "shape_updated",
687 StreamEvent::ShapeRemoved { .. } => "shape_removed",
688 StreamEvent::ShapeModified { .. } => "shape_modified",
689 StreamEvent::ShapeValidationStarted { .. } => "shape_validation_started",
690 StreamEvent::ShapeValidationCompleted { .. } => "shape_validation_completed",
691 StreamEvent::ShapeViolationDetected { .. } => "shape_violation_detected",
692 StreamEvent::ErrorOccurred { .. } => "error_occurred",
693 };
694 *event_types.entry(event_type.to_string()).or_insert(0) += 1;
695 }
696
697 Ok(StreamStatistics {
698 total_events: metrics.producer_events_published + metrics.consumer_events_consumed,
699 event_types,
700 error_rate: metrics.error_rate,
701 duplicate_rate: metrics.duplicate_rate,
702 out_of_order_rate: metrics.out_of_order_rate,
703 backpressure_events: Self::calculate_backpressure_events(&metrics),
704 circuit_breaker_trips: metrics.backend_circuit_breaker_trips,
705 })
706 }
707
708 async fn analyze_errors(&self) -> Result<ErrorAnalysis> {
710 let error_tracker = self.error_tracker.read().await;
711
712 let mut error_timeline = Vec::new();
714 let mut timeline_buckets: BTreeMap<DateTime<Utc>, HashMap<String, u64>> = BTreeMap::new();
715
716 for error in &error_tracker.errors {
717 let bucket_time = error
718 .timestamp
719 .date_naive()
720 .and_hms_opt(error.timestamp.hour(), 0, 0)
721 .map(|dt| DateTime::from_naive_utc_and_offset(dt, Utc))
722 .unwrap_or(error.timestamp);
723
724 let bucket = timeline_buckets.entry(bucket_time).or_default();
725 *bucket.entry(error.error_type.clone()).or_insert(0) += 1;
726 }
727
728 for (timestamp, error_types) in timeline_buckets {
729 error_timeline.push(ErrorTimelineEntry {
730 timestamp,
731 error_count: error_types.values().sum(),
732 error_types,
733 });
734 }
735
736 let mut top_errors: Vec<ErrorPattern> =
738 error_tracker.error_patterns.values().cloned().collect();
739 top_errors.sort_by_key(|b| std::cmp::Reverse(b.occurrences));
740 top_errors.truncate(10);
741
742 let error_correlations = self.find_error_correlations(&error_tracker.errors).await?;
744
745 Ok(ErrorAnalysis {
746 total_errors: error_tracker.error_counts.values().sum(),
747 error_categories: error_tracker.error_counts.clone(),
748 error_timeline,
749 top_errors,
750 error_correlations,
751 })
752 }
753
754 async fn find_error_correlations(
756 &self,
757 errors: &VecDeque<ErrorRecord>,
758 ) -> Result<Vec<ErrorCorrelation>> {
759 let mut correlations = Vec::new();
760
761 let error_types: Vec<String> = errors
763 .iter()
764 .map(|e| e.error_type.clone())
765 .collect::<std::collections::HashSet<_>>()
766 .into_iter()
767 .collect();
768
769 for i in 0..error_types.len() {
770 for j in i + 1..error_types.len() {
771 let type1 = &error_types[i];
772 let type2 = &error_types[j];
773
774 let mut co_occurrences = 0;
776 let mut time_offsets = Vec::new();
777
778 for error1 in errors.iter().filter(|e| &e.error_type == type1) {
779 for error2 in errors.iter().filter(|e| &e.error_type == type2) {
780 let time_diff = error2.timestamp.timestamp_millis()
781 - error1.timestamp.timestamp_millis();
782 if time_diff.abs() < 1000 {
783 co_occurrences += 1;
784 time_offsets.push(time_diff);
785 }
786 }
787 }
788
789 if co_occurrences > 5 {
790 let avg_offset =
791 time_offsets.iter().sum::<i64>() / time_offsets.len().max(1) as i64;
792 correlations.push(ErrorCorrelation {
793 primary_error: type1.clone(),
794 correlated_errors: vec![type2.clone()],
795 correlation_strength: co_occurrences as f64 / errors.len() as f64,
796 time_offset_ms: avg_offset,
797 });
798 }
799 }
800 }
801
802 correlations.sort_by(|a, b| {
803 b.correlation_strength
804 .partial_cmp(&a.correlation_strength)
805 .unwrap_or(std::cmp::Ordering::Equal)
806 });
807 correlations.truncate(10);
808
809 Ok(correlations)
810 }
811
812 async fn generate_recommendations(
814 &self,
815 health: &HealthSummary,
816 performance: &PerformanceMetrics,
817 errors: &ErrorAnalysis,
818 stream_stats: &StreamStatistics,
819 ) -> Result<Vec<Recommendation>> {
820 let mut recommendations = Vec::new();
821
822 if health.availability_percentage < 99.0 {
824 recommendations.push(Recommendation {
825 category: "Reliability".to_string(),
826 severity: "High".to_string(),
827 title: "Improve System Availability".to_string(),
828 description: format!(
829 "System availability is {:.2}%, below the target of 99%",
830 health.availability_percentage
831 ),
832 action_items: vec![
833 "Review failing components and fix issues".to_string(),
834 "Implement redundancy for critical components".to_string(),
835 "Set up automated health monitoring alerts".to_string(),
836 ],
837 expected_impact: "Increase availability to 99%+".to_string(),
838 });
839 }
840
841 if performance.latency.p99_ms > 100.0 {
843 recommendations.push(Recommendation {
844 category: "Performance".to_string(),
845 severity: "Medium".to_string(),
846 title: "Reduce Processing Latency".to_string(),
847 description: format!(
848 "P99 latency is {:.2}ms, affecting real-time processing",
849 performance.latency.p99_ms
850 ),
851 action_items: vec![
852 "Profile processing pipeline to identify bottlenecks".to_string(),
853 "Optimize serialization/deserialization".to_string(),
854 "Consider adding caching layer".to_string(),
855 "Scale out processing nodes".to_string(),
856 ],
857 expected_impact: "Reduce P99 latency to <50ms".to_string(),
858 });
859 }
860
861 let error_rate = if stream_stats.total_events > 0 {
863 errors.total_errors as f64 / stream_stats.total_events as f64
864 } else {
865 0.0
866 };
867 if error_rate > 0.01 {
868 recommendations.push(Recommendation {
869 category: "Quality".to_string(),
870 severity: "High".to_string(),
871 title: "Reduce Error Rate".to_string(),
872 description: format!(
873 "Error rate is {:.2}%, impacting data quality",
874 error_rate * 100.0
875 ),
876 action_items: vec![
877 "Analyze top error patterns and fix root causes".to_string(),
878 "Implement retry logic for transient failures".to_string(),
879 "Add input validation and error handling".to_string(),
880 "Set up error rate monitoring and alerts".to_string(),
881 ],
882 expected_impact: "Reduce error rate to <1%".to_string(),
883 });
884 }
885
886 if performance.resource_usage.memory_usage_mb > 0.8 * 8192.0 {
888 recommendations.push(Recommendation {
890 category: "Resources".to_string(),
891 severity: "Medium".to_string(),
892 title: "Optimize Memory Usage".to_string(),
893 description: "Memory usage is approaching limits".to_string(),
894 action_items: vec![
895 "Profile memory usage to identify leaks".to_string(),
896 "Tune buffer sizes and cache limits".to_string(),
897 "Implement memory-efficient data structures".to_string(),
898 ],
899 expected_impact: "Reduce memory usage by 30%".to_string(),
900 });
901 }
902
903 Ok(recommendations)
904 }
905
906 pub async fn record_error(&self, error_type: String, message: String, component: String) {
908 let mut error_tracker = self.error_tracker.write().await;
909
910 let error = ErrorRecord {
911 timestamp: Utc::now(),
912 error_type: error_type.clone(),
913 message: message.clone(),
914 component: component.clone(),
915 context: HashMap::new(),
916 };
917
918 *error_tracker
920 .error_counts
921 .entry(error_type.clone())
922 .or_insert(0) += 1;
923
924 let pattern_key = format!("{component}:{error_type}");
926 let pattern = error_tracker
927 .error_patterns
928 .entry(pattern_key)
929 .or_insert(ErrorPattern {
930 pattern: error_type,
931 occurrences: 0,
932 first_seen: error.timestamp,
933 last_seen: error.timestamp,
934 affected_components: vec![component],
935 });
936 pattern.occurrences += 1;
937 pattern.last_seen = error.timestamp;
938
939 error_tracker.errors.push_back(error);
941 if error_tracker.errors.len() > 1000 {
942 error_tracker.errors.pop_front();
943 }
944 }
945
946 pub async fn record_event(&self, event: StreamEvent) {
948 let mut buffer = self.event_buffer.write().await;
949 buffer.push_back((event, Utc::now()));
950 if buffer.len() > 10000 {
951 buffer.pop_front();
952 }
953 }
954}
955
956pub struct DiagnosticCLI {
958 analyzer: Arc<DiagnosticAnalyzer>,
959}
960
961impl DiagnosticCLI {
962 pub fn new(analyzer: Arc<DiagnosticAnalyzer>) -> Self {
963 Self { analyzer }
964 }
965
966 pub async fn run_interactive(&self) -> Result<()> {
968 println!("OxiRS Stream Diagnostics Tool");
969 println!("=============================");
970
971 loop {
972 println!("\nOptions:");
973 println!("1. Generate full diagnostic report");
974 println!("2. Check system health");
975 println!("3. View performance metrics");
976 println!("4. Analyze errors");
977 println!("5. Export metrics (Prometheus format)");
978 println!("6. Exit");
979
980 print!("\nSelect option: ");
981 use std::io::{self, Write};
982 io::stdout().flush()?;
983
984 let mut input = String::new();
985 io::stdin().read_line(&mut input)?;
986
987 match input.trim() {
988 "1" => self.generate_report().await?,
989 "2" => self.check_health().await?,
990 "3" => self.view_performance().await?,
991 "4" => self.analyze_errors().await?,
992 "5" => self.export_metrics().await?,
993 "6" => break,
994 _ => println!("Invalid option"),
995 }
996 }
997
998 Ok(())
999 }
1000
1001 async fn generate_report(&self) -> Result<()> {
1003 println!("\nGenerating diagnostic report...");
1004
1005 let report = self.analyzer.generate_report().await?;
1006
1007 println!("\n=== DIAGNOSTIC REPORT ===");
1009 println!("Report ID: {}", report.report_id);
1010 println!("Generated: {}", report.timestamp);
1011 println!("Duration: {:?}", report.duration);
1012
1013 println!("\n--- System Information ---");
1015 println!("Version: {}", report.system_info.version);
1016 println!("Uptime: {:?}", report.system_info.uptime);
1017 println!(
1018 "Active Connections: {}",
1019 report.system_info.active_connections
1020 );
1021 println!("Memory Usage: {:.2} MB", report.system_info.memory_usage_mb);
1022 println!("CPU Usage: {:.2}%", report.system_info.cpu_usage_percent);
1023
1024 println!("\n--- Health Summary ---");
1026 println!("Overall Status: {:?}", report.health_summary.overall_status);
1027 println!(
1028 "Availability: {:.2}%",
1029 report.health_summary.availability_percentage
1030 );
1031 println!("Component Statuses:");
1032 for (name, health) in &report.health_summary.component_statuses {
1033 println!(
1034 " {}: {:?} (error rate: {:.2}%)",
1035 name,
1036 health.status,
1037 health.error_rate * 100.0
1038 );
1039 }
1040
1041 println!("\n--- Performance Metrics ---");
1043 println!(
1044 "Throughput: {:.2} events/sec",
1045 report.performance_metrics.throughput.events_per_second
1046 );
1047 println!(
1048 "Latency P99: {:.2} ms",
1049 report.performance_metrics.latency.p99_ms
1050 );
1051 println!(
1052 "Memory Usage: {:.2} MB",
1053 report.performance_metrics.resource_usage.memory_usage_mb
1054 );
1055
1056 if !report.performance_metrics.bottlenecks.is_empty() {
1058 println!("\n--- Detected Bottlenecks ---");
1059 for bottleneck in &report.performance_metrics.bottlenecks {
1060 println!(
1061 " [{}] {}: {}",
1062 bottleneck.severity, bottleneck.component, bottleneck.description
1063 );
1064 }
1065 }
1066
1067 if !report.recommendations.is_empty() {
1069 println!("\n--- Recommendations ---");
1070 for (i, rec) in report.recommendations.iter().enumerate() {
1071 println!("{}. [{}] {}", i + 1, rec.severity, rec.title);
1072 println!(" {}", rec.description);
1073 println!(" Actions:");
1074 for action in &rec.action_items {
1075 println!(" - {action}");
1076 }
1077 }
1078 }
1079
1080 let report_file = format!("diagnostic_report_{}.json", report.report_id);
1082 std::fs::write(&report_file, serde_json::to_string_pretty(&report)?)?;
1083 println!("\nFull report saved to: {report_file}");
1084
1085 Ok(())
1086 }
1087
1088 async fn check_health(&self) -> Result<()> {
1090 let health = self.analyzer.analyze_health().await?;
1091
1092 println!("\n=== HEALTH CHECK ===");
1093 println!("Overall Status: {:?}", health.overall_status);
1094 println!("Availability: {:.2}%", health.availability_percentage);
1095
1096 println!("\nComponent Status:");
1097 for (name, status) in &health.component_statuses {
1098 let icon = match status.status {
1099 HealthStatus::Healthy => "✓",
1100 HealthStatus::Degraded => "⚠",
1101 HealthStatus::Unhealthy => "✗",
1102 HealthStatus::Dead => "☠",
1103 HealthStatus::Unknown => "?",
1104 };
1105 println!(" {} {}: {:?}", icon, name, status.status);
1106 }
1107
1108 if !health.recent_failures.is_empty() {
1109 println!("\nRecent Failures:");
1110 for failure in &health.recent_failures {
1111 println!(
1112 " - {} [{}]: {}",
1113 failure.timestamp.format("%H:%M:%S"),
1114 failure.component,
1115 failure.message
1116 );
1117 }
1118 }
1119
1120 Ok(())
1121 }
1122
1123 async fn view_performance(&self) -> Result<()> {
1125 let perf = self.analyzer.analyze_performance().await?;
1126
1127 println!("\n=== PERFORMANCE METRICS ===");
1128
1129 println!("\nThroughput:");
1130 println!(
1131 " Current: {:.2} events/sec",
1132 perf.throughput.events_per_second
1133 );
1134 println!(" Peak: {:.2} events/sec", perf.throughput.peak_throughput);
1135 println!(
1136 " Average: {:.2} events/sec",
1137 perf.throughput.average_throughput
1138 );
1139
1140 println!("\nLatency:");
1141 println!(" P50: {:.2} ms", perf.latency.p50_ms);
1142 println!(" P95: {:.2} ms", perf.latency.p95_ms);
1143 println!(" P99: {:.2} ms", perf.latency.p99_ms);
1144 println!(" Max: {:.2} ms", perf.latency.max_ms);
1145
1146 println!("\nResource Usage:");
1147 println!(" Memory: {:.2} MB", perf.resource_usage.memory_usage_mb);
1148 println!(" CPU: {:.2}%", perf.resource_usage.cpu_usage_percent);
1149 println!(
1150 " Network I/O: {:.2} Mbps",
1151 perf.resource_usage.network_io_mbps
1152 );
1153
1154 Ok(())
1155 }
1156
1157 async fn analyze_errors(&self) -> Result<()> {
1159 let errors = self.analyzer.analyze_errors().await?;
1160
1161 println!("\n=== ERROR ANALYSIS ===");
1162 println!("Total Errors: {}", errors.total_errors);
1163
1164 println!("\nError Categories:");
1165 for (category, count) in &errors.error_categories {
1166 println!(" {category}: {count} errors");
1167 }
1168
1169 if !errors.top_errors.is_empty() {
1170 println!("\nTop Error Patterns:");
1171 for (i, pattern) in errors.top_errors.iter().take(5).enumerate() {
1172 println!(
1173 "{}. {} ({} occurrences)",
1174 i + 1,
1175 pattern.pattern,
1176 pattern.occurrences
1177 );
1178 println!(
1179 " First seen: {}",
1180 pattern.first_seen.format("%Y-%m-%d %H:%M:%S")
1181 );
1182 println!(
1183 " Last seen: {}",
1184 pattern.last_seen.format("%Y-%m-%d %H:%M:%S")
1185 );
1186 }
1187 }
1188
1189 if !errors.error_correlations.is_empty() {
1190 println!("\nError Correlations:");
1191 for corr in &errors.error_correlations {
1192 println!(
1193 " {} → {} (strength: {:.2})",
1194 corr.primary_error,
1195 corr.correlated_errors.join(", "),
1196 corr.correlation_strength
1197 );
1198 }
1199 }
1200
1201 Ok(())
1202 }
1203
1204 async fn export_metrics(&self) -> Result<()> {
1206 println!("\nExporting metrics...");
1207
1208 println!("Metrics exported to: metrics_export.prom");
1211
1212 Ok(())
1213 }
1214}
1215
1216#[cfg(test)]
1217mod tests {
1218 use super::*;
1219 use crate::monitoring::{HealthChecker, MetricsCollector};
1220
1221 #[tokio::test]
1222 async fn test_diagnostic_report_generation() {
1223 let config = crate::monitoring::MonitoringConfig {
1224 enable_metrics: true,
1225 enable_tracing: false,
1226 metrics_interval: std::time::Duration::from_secs(60),
1227 health_check_interval: std::time::Duration::from_secs(30),
1228 enable_profiling: false,
1229 prometheus_endpoint: None,
1230 otlp_endpoint: None,
1231 log_level: "info".to_string(),
1232 };
1233 let metrics_collector = Arc::new(RwLock::new(MetricsCollector::new(config.clone())));
1234 let health_checker = Arc::new(RwLock::new(HealthChecker::new(config)));
1235
1236 let analyzer = DiagnosticAnalyzer::new(metrics_collector, health_checker);
1237
1238 let report = analyzer.generate_report().await.unwrap();
1239
1240 assert!(!report.report_id.is_empty());
1241 assert!(report.health_summary.availability_percentage >= 0.0);
1243 assert!(report.health_summary.availability_percentage <= 100.0);
1244 }
1245
1246 #[tokio::test]
1247 async fn test_error_tracking() {
1248 let config = crate::monitoring::MonitoringConfig {
1249 enable_metrics: true,
1250 enable_tracing: false,
1251 metrics_interval: std::time::Duration::from_secs(60),
1252 health_check_interval: std::time::Duration::from_secs(30),
1253 enable_profiling: false,
1254 prometheus_endpoint: None,
1255 otlp_endpoint: None,
1256 log_level: "info".to_string(),
1257 };
1258 let metrics_collector = Arc::new(RwLock::new(MetricsCollector::new(config.clone())));
1259 let health_checker = Arc::new(RwLock::new(HealthChecker::new(config)));
1260
1261 let analyzer = DiagnosticAnalyzer::new(metrics_collector, health_checker);
1262
1263 analyzer
1265 .record_error(
1266 "ConnectionError".to_string(),
1267 "Failed to connect to backend".to_string(),
1268 "KafkaBackend".to_string(),
1269 )
1270 .await;
1271
1272 analyzer
1273 .record_error(
1274 "TimeoutError".to_string(),
1275 "Request timed out".to_string(),
1276 "KafkaBackend".to_string(),
1277 )
1278 .await;
1279
1280 let error_analysis = analyzer.analyze_errors().await.unwrap();
1281 assert_eq!(error_analysis.total_errors, 2);
1282 assert!(error_analysis
1283 .error_categories
1284 .contains_key("ConnectionError"));
1285 assert!(error_analysis.error_categories.contains_key("TimeoutError"));
1286 }
1287
1288 #[tokio::test]
1289 async fn test_bottleneck_detection() {
1290 let config = crate::monitoring::MonitoringConfig {
1291 enable_metrics: true,
1292 enable_tracing: false,
1293 metrics_interval: std::time::Duration::from_secs(60),
1294 health_check_interval: std::time::Duration::from_secs(30),
1295 enable_profiling: false,
1296 prometheus_endpoint: None,
1297 otlp_endpoint: None,
1298 log_level: "info".to_string(),
1299 };
1300 let metrics_collector = Arc::new(RwLock::new(MetricsCollector::new(config.clone())));
1301 let health_checker = Arc::new(RwLock::new(HealthChecker::new(config)));
1302
1303 {
1305 let collector = metrics_collector.read().await;
1306 collector
1307 .update_producer_metrics(crate::monitoring::ProducerMetricsUpdate {
1308 events_published: 1,
1309 events_failed: 0,
1310 bytes_sent: 100,
1311 batches_sent: 1,
1312 latency_ms: 200.0, throughput_eps: 1.0,
1314 })
1315 .await;
1316 collector
1317 .update_producer_metrics(crate::monitoring::ProducerMetricsUpdate {
1318 events_published: 1,
1319 events_failed: 0,
1320 bytes_sent: 100,
1321 batches_sent: 1,
1322 latency_ms: 250.0,
1323 throughput_eps: 1.0,
1324 })
1325 .await;
1326 collector
1327 .update_producer_metrics(crate::monitoring::ProducerMetricsUpdate {
1328 events_published: 1,
1329 events_failed: 0,
1330 bytes_sent: 100,
1331 batches_sent: 1,
1332 latency_ms: 180.0,
1333 throughput_eps: 1.0,
1334 })
1335 .await;
1336 }
1337
1338 let analyzer = DiagnosticAnalyzer::new(metrics_collector, health_checker);
1339
1340 let perf = analyzer.analyze_performance().await.unwrap();
1341
1342 let latency_bottlenecks: Vec<_> = perf
1344 .bottlenecks
1345 .iter()
1346 .filter(|b| b.metric == "Latency")
1347 .collect();
1348 assert!(!latency_bottlenecks.is_empty());
1349 }
1350}