Skip to main content

oxirs_stream/
diagnostics.rs

1//! # Stream Diagnostics Tools
2//!
3//! Comprehensive diagnostic utilities for troubleshooting and analyzing
4//! streaming operations in production environments.
5
6use 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/// Diagnostic report containing comprehensive system information
20#[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/// System information for diagnostics
34#[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/// Health summary across all components
47#[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/// Component health details
56#[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/// Failure event for tracking issues
67#[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/// Performance metrics for diagnostics
77#[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/// Throughput metrics
86#[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/// Latency metrics
95#[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/// Resource usage metrics
105#[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/// Detected performance bottleneck
114#[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/// Stream-specific statistics
124#[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/// Error analysis for troubleshooting
136#[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/// Error timeline entry
146#[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/// Common error pattern
154#[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/// Error correlation for root cause analysis
164#[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/// Recommendation for system improvement
173#[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
183// Type aliases for complex types
184type HealthMonitorMap =
185    HashMap<String, Arc<RwLock<HealthMonitor<Box<dyn crate::connection_pool::PooledConnection>>>>>;
186type EventBuffer = Arc<RwLock<VecDeque<(StreamEvent, DateTime<Utc>)>>>;
187
188/// Diagnostic analyzer for generating reports
189pub 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
197/// Error tracking for diagnostics
198struct ErrorTracker {
199    errors: VecDeque<ErrorRecord>,
200    error_counts: HashMap<String, u64>,
201    error_patterns: HashMap<String, ErrorPattern>,
202}
203
204/// Error record for tracking
205#[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    /// Register a health monitor for a component
233    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    /// Generate comprehensive diagnostic report
242    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        // Collect all diagnostic data
247        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    /// Get active backends from metrics
275    fn get_active_backends(metrics: &crate::monitoring::StreamingMetrics) -> Vec<String> {
276        let mut backends = Vec::new();
277
278        // Determine active backends based on metrics
279        if metrics.backend_connections_active > 0 {
280            // Check common backend patterns in metrics
281            if metrics.producer_events_published > 0 || metrics.consumer_events_consumed > 0 {
282                backends.push("memory".to_string()); // Always include memory backend
283            }
284
285            // Add other backends based on available feature flags or connections.
286            // (Kafka/Pulsar live in the publish=false adapter crates — Pure Rust Policy v2.)
287            #[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        // Fallback to default backends if none detected
298        if backends.is_empty() {
299            backends.push("memory".to_string());
300        }
301
302        backends
303    }
304
305    /// Calculate backpressure events from metrics
306    fn calculate_backpressure_events(metrics: &crate::monitoring::StreamingMetrics) -> u64 {
307        // Calculate backpressure events based on available metrics
308        // Backpressure typically occurs when error rate is high or processing is slow
309        let mut backpressure_events = 0;
310
311        // High error rate might indicate backpressure
312        if metrics.error_rate > 0.1 {
313            backpressure_events += (metrics.error_rate * 100.0) as u64;
314        }
315
316        // Circuit breaker trips often indicate backpressure scenarios
317        backpressure_events += metrics.backend_circuit_breaker_trips;
318
319        // High out-of-order rate might indicate processing delays
320        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    /// Collect system information
328    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, // Thread count not available in metrics, using placeholder
343            environment: Self::collect_relevant_env_vars(),
344        })
345    }
346
347    /// Collect relevant environment variables for diagnostics
348    fn collect_relevant_env_vars() -> HashMap<String, String> {
349        let mut env_vars = HashMap::new();
350
351        // List of environment variables relevant to streaming operations
352        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                // Mask sensitive values for security
371                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    /// Analyze system health
385    async fn analyze_health(&self) -> Result<HealthSummary> {
386        // First trigger the health check
387        self.health_checker
388            .read()
389            .await
390            .check_all_components()
391            .await?;
392        // Then get the results
393        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        // Analyze component health
400        for (name, component_health) in component_checks {
401            component_statuses.insert(name.clone(), component_health.clone());
402        }
403
404        // Check health monitors
405        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            // Create a basic ComponentHealth from overall statistics
410            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(), // Use current time as we don't have individual check times
429                    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(), // No dependency info available
440                },
441            );
442
443            // Track recent failures based on low success rate
444            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        // Calculate availability
464        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        // Convert monitoring::ComponentHealth to diagnostics::ComponentHealth
476        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                        }, // Convert monitoring::HealthStatus to health_monitor::HealthStatus
489                        last_check: comp.last_check,
490                        consecutive_failures: 0, // Default value, not available in monitoring::ComponentHealth
491                        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    /// Analyze performance metrics
516    async fn analyze_performance(&self) -> Result<PerformanceMetrics> {
517        let metrics = self.metrics_collector.read().await.get_metrics().await;
518
519        // Calculate throughput metrics
520        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, // Use current throughput as peak
530            average_throughput: metrics.producer_throughput_eps,
531        };
532
533        // Calculate latency metrics
534        let latency = LatencyMetrics {
535            p50_ms: metrics.producer_average_latency_ms * 0.8, // Estimate P50 as 80% of average
536            p95_ms: metrics.producer_average_latency_ms * 1.5, // Estimate P95 as 150% of average
537            p99_ms: metrics.producer_average_latency_ms * 2.0, // Estimate P99 as 200% of average
538            max_ms: metrics.producer_average_latency_ms * 3.0, // Estimate max as 300% of average
539            average_ms: metrics.producer_average_latency_ms,
540        };
541
542        // Resource usage
543        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, // Convert to MB/s
551            disk_io_mbps: 0.0, // No disk I/O metrics available in flat structure
552        };
553
554        // Detect bottlenecks
555        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    /// Detect performance bottlenecks
568    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        // Check for high latency
577        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        // Check for consumer lag
597        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        // Check for memory pressure (use available system metrics)
611        if (metrics.system_memory_usage_bytes / 1024 / 1024) as f64 > 8192.0 {
612            // 8GB threshold
613            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        // Check for circuit breaker trips
626        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    /// Analyze stream statistics
643    async fn analyze_streams(&self) -> Result<StreamStatistics> {
644        let metrics = self.metrics_collector.read().await.get_metrics().await;
645
646        // Count event types from buffer
647        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    /// Analyze errors
709    async fn analyze_errors(&self) -> Result<ErrorAnalysis> {
710        let error_tracker = self.error_tracker.read().await;
711
712        // Build error timeline
713        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        // Find top error patterns
737        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        // Analyze error correlations
743        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    /// Find error correlations
755    async fn find_error_correlations(
756        &self,
757        errors: &VecDeque<ErrorRecord>,
758    ) -> Result<Vec<ErrorCorrelation>> {
759        let mut correlations = Vec::new();
760
761        // Simple correlation analysis - find errors that occur together
762        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                // Count co-occurrences within 1 second windows
775                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    /// Generate recommendations
813    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        // Health-based recommendations
823        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        // Performance-based recommendations
842        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        // Error-based recommendations
862        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        // Resource recommendations
887        if performance.resource_usage.memory_usage_mb > 0.8 * 8192.0 {
888            // Assuming 8GB limit
889            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    /// Record an error for analysis
907    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        // Update counts
919        *error_tracker
920            .error_counts
921            .entry(error_type.clone())
922            .or_insert(0) += 1;
923
924        // Update patterns
925        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        // Add to error history
940        error_tracker.errors.push_back(error);
941        if error_tracker.errors.len() > 1000 {
942            error_tracker.errors.pop_front();
943        }
944    }
945
946    /// Record a stream event for analysis
947    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
956/// Diagnostic CLI interface
957pub struct DiagnosticCLI {
958    analyzer: Arc<DiagnosticAnalyzer>,
959}
960
961impl DiagnosticCLI {
962    pub fn new(analyzer: Arc<DiagnosticAnalyzer>) -> Self {
963        Self { analyzer }
964    }
965
966    /// Run interactive diagnostic session
967    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    /// Generate and display diagnostic report
1002    async fn generate_report(&self) -> Result<()> {
1003        println!("\nGenerating diagnostic report...");
1004
1005        let report = self.analyzer.generate_report().await?;
1006
1007        // Display report summary
1008        println!("\n=== DIAGNOSTIC REPORT ===");
1009        println!("Report ID: {}", report.report_id);
1010        println!("Generated: {}", report.timestamp);
1011        println!("Duration: {:?}", report.duration);
1012
1013        // System info
1014        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        // Health summary
1025        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        // Performance
1042        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        // Bottlenecks
1057        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        // Recommendations
1068        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        // Save report to file
1081        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    /// Check system health
1089    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    /// View performance metrics
1124    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    /// Analyze errors
1158    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    /// Export metrics in Prometheus format
1205    async fn export_metrics(&self) -> Result<()> {
1206        println!("\nExporting metrics...");
1207
1208        // This would typically export to a file or endpoint
1209        // For now, just indicate success
1210        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        // Duration is always non-negative by type invariant (u128)
1242        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        // Record some errors
1264        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        // Simulate high latency by updating metrics
1304        {
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, // High latency to trigger bottleneck
1313                    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        // Should detect latency bottleneck (p99 should be > 100ms with high latency values)
1343        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}