Skip to main content

oxirs_gql/observability/
core.rs

1//! Comprehensive Observability and Monitoring System
2//!
3//! This module provides enterprise-grade observability capabilities including
4//! OpenTelemetry integration, custom metrics, distributed tracing, and real-time
5//! monitoring for GraphQL operations.
6
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, VecDeque};
10use std::sync::Arc;
11use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
12use tokio::sync::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
13use tracing::info;
14
15use crate::performance::OperationMetrics;
16use crate::system_monitor;
17
18/// Comprehensive observability configuration
19#[derive(Debug, Clone)]
20pub struct ObservabilityConfig {
21    pub enable_opentelemetry: bool,
22    pub enable_custom_metrics: bool,
23    pub enable_distributed_tracing: bool,
24    pub enable_real_time_monitoring: bool,
25    pub enable_alerting: bool,
26    pub enable_log_aggregation: bool,
27    pub metrics_collection_interval: Duration,
28    pub trace_sampling_rate: f64,
29    pub alert_thresholds: AlertThresholds,
30    pub retention_period: Duration,
31    pub export_endpoint: Option<String>,
32    pub service_name: String,
33    pub service_version: String,
34    pub environment: String,
35}
36
37impl Default for ObservabilityConfig {
38    fn default() -> Self {
39        Self {
40            enable_opentelemetry: true,
41            enable_custom_metrics: true,
42            enable_distributed_tracing: true,
43            enable_real_time_monitoring: true,
44            enable_alerting: true,
45            enable_log_aggregation: true,
46            metrics_collection_interval: Duration::from_secs(10),
47            trace_sampling_rate: 1.0,
48            alert_thresholds: AlertThresholds::default(),
49            retention_period: Duration::from_secs(86400), // 24 hours
50            export_endpoint: None,
51            service_name: "oxirs-gql".to_string(),
52            service_version: "0.1.0".to_string(),
53            environment: "development".to_string(),
54        }
55    }
56}
57
58/// Alert threshold configuration
59#[derive(Debug, Clone)]
60pub struct AlertThresholds {
61    pub max_response_time_ms: u64,
62    pub max_error_rate: f64,
63    pub max_memory_usage_mb: u64,
64    pub max_cpu_usage_percent: f64,
65    pub min_cache_hit_ratio: f64,
66    pub max_concurrent_requests: usize,
67}
68
69impl Default for AlertThresholds {
70    fn default() -> Self {
71        Self {
72            max_response_time_ms: 5000,
73            max_error_rate: 0.05, // 5%
74            max_memory_usage_mb: 1024,
75            max_cpu_usage_percent: 80.0,
76            min_cache_hit_ratio: 0.8,
77            max_concurrent_requests: 1000,
78        }
79    }
80}
81
82/// Comprehensive observability system
83pub struct ObservabilitySystem {
84    config: ObservabilityConfig,
85    metrics_collector: Arc<AsyncRwLock<MetricsCollector>>,
86    trace_collector: Arc<AsyncRwLock<TraceCollector>>,
87    alert_manager: Arc<AsyncMutex<AlertManager>>,
88    real_time_monitor: Arc<AsyncRwLock<RealTimeMonitor>>,
89}
90
91impl ObservabilitySystem {
92    pub fn new(config: ObservabilityConfig) -> Self {
93        Self {
94            metrics_collector: Arc::new(AsyncRwLock::new(MetricsCollector::new(&config))),
95            trace_collector: Arc::new(AsyncRwLock::new(TraceCollector::new(&config))),
96            alert_manager: Arc::new(AsyncMutex::new(AlertManager::new(&config))),
97            real_time_monitor: Arc::new(AsyncRwLock::new(RealTimeMonitor::new(&config))),
98            config,
99        }
100    }
101
102    /// Record a GraphQL operation
103    pub async fn record_operation(&self, metrics: &OperationMetrics) -> Result<()> {
104        // Record metrics
105        if self.config.enable_custom_metrics {
106            self.metrics_collector
107                .write()
108                .await
109                .record_operation(metrics)?;
110        }
111
112        // Create trace span
113        if self.config.enable_distributed_tracing {
114            self.trace_collector
115                .write()
116                .await
117                .create_operation_span(metrics)?;
118        }
119
120        // Update real-time monitoring
121        if self.config.enable_real_time_monitoring {
122            self.real_time_monitor
123                .write()
124                .await
125                .update_metrics(metrics)
126                .await?;
127        }
128
129        // Check alert conditions
130        if self.config.enable_alerting {
131            self.alert_manager.lock().await.check_alerts(metrics)?;
132        }
133
134        Ok(())
135    }
136
137    /// Get comprehensive observability dashboard
138    pub async fn get_dashboard(&self) -> Result<ObservabilityDashboard> {
139        let metrics = if self.config.enable_custom_metrics {
140            Some(self.metrics_collector.read().await.get_summary())
141        } else {
142            None
143        };
144
145        let traces = if self.config.enable_distributed_tracing {
146            Some(self.trace_collector.read().await.get_recent_traces(100))
147        } else {
148            None
149        };
150
151        let alerts = if self.config.enable_alerting {
152            Some(self.alert_manager.lock().await.get_active_alerts())
153        } else {
154            None
155        };
156
157        let real_time_data = if self.config.enable_real_time_monitoring {
158            Some(self.real_time_monitor.read().await.get_current_state())
159        } else {
160            None
161        };
162
163        Ok(ObservabilityDashboard {
164            service_info: ServiceInfo {
165                name: self.config.service_name.clone(),
166                version: self.config.service_version.clone(),
167                environment: self.config.environment.clone(),
168                uptime: SystemTime::now()
169                    .duration_since(UNIX_EPOCH)
170                    .unwrap_or_default(),
171            },
172            metrics,
173            traces,
174            alerts,
175            real_time_data,
176            health_status: self.calculate_health_status().await,
177        })
178    }
179
180    /// Calculate overall health status
181    async fn calculate_health_status(&self) -> HealthStatus {
182        let mut health_score = 100.0;
183        let mut issues = Vec::new();
184
185        if self.config.enable_alerting {
186            let active_alerts = self.alert_manager.lock().await.get_active_alerts();
187            if !active_alerts.is_empty() {
188                health_score -= active_alerts.len() as f64 * 10.0;
189                issues.push(format!("{} active alerts", active_alerts.len()));
190            }
191        }
192
193        if self.config.enable_real_time_monitoring {
194            let monitor = self.real_time_monitor.read().await;
195            let current_state = monitor.get_current_state();
196
197            if current_state.error_rate > self.config.alert_thresholds.max_error_rate {
198                health_score -= 20.0;
199                issues.push("High error rate detected".to_string());
200            }
201
202            if current_state.avg_response_time.as_millis()
203                > self.config.alert_thresholds.max_response_time_ms as u128
204            {
205                health_score -= 15.0;
206                issues.push("High response time detected".to_string());
207            }
208        }
209
210        let status = if health_score >= 90.0 {
211            HealthStatusLevel::Healthy
212        } else if health_score >= 70.0 {
213            HealthStatusLevel::Warning
214        } else {
215            HealthStatusLevel::Critical
216        };
217
218        HealthStatus {
219            status,
220            score: health_score.max(0.0),
221            issues,
222            last_check: SystemTime::now(),
223        }
224    }
225
226    /// Export metrics to external systems
227    pub async fn export_metrics(&self) -> Result<()> {
228        if let Some(endpoint) = &self.config.export_endpoint {
229            let dashboard = self.get_dashboard().await?;
230            let serialized = serde_json::to_string(&dashboard)?;
231
232            // In a real implementation, this would send to external monitoring systems
233            info!(
234                "Exporting metrics to {}: {} bytes",
235                endpoint,
236                serialized.len()
237            );
238        }
239        Ok(())
240    }
241}
242
243/// Metrics collection system
244pub struct MetricsCollector {
245    operation_metrics: VecDeque<OperationMetrics>,
246    aggregated_metrics: AggregatedMetrics,
247    custom_counters: HashMap<String, u64>,
248    custom_gauges: HashMap<String, f64>,
249    custom_histograms: HashMap<String, Histogram>,
250}
251
252impl MetricsCollector {
253    fn new(_config: &ObservabilityConfig) -> Self {
254        Self {
255            operation_metrics: VecDeque::new(),
256            aggregated_metrics: AggregatedMetrics::new(),
257            custom_counters: HashMap::new(),
258            custom_gauges: HashMap::new(),
259            custom_histograms: HashMap::new(),
260        }
261    }
262
263    fn record_operation(&mut self, metrics: &OperationMetrics) -> Result<()> {
264        self.operation_metrics.push_back(metrics.clone());
265        self.aggregated_metrics.update(metrics);
266
267        // Increment operation counter
268        let counter_key = format!("operations.{:?}", metrics.operation_type);
269        *self.custom_counters.entry(counter_key).or_insert(0) += 1;
270
271        // Update execution time histogram
272        let histogram_key = "execution_time_ms".to_string();
273        let histogram = self
274            .custom_histograms
275            .entry(histogram_key)
276            .or_insert_with(Histogram::new);
277        histogram.record(metrics.execution_time.as_millis() as f64);
278
279        Ok(())
280    }
281
282    fn get_summary(&self) -> MetricsSummary {
283        MetricsSummary {
284            total_operations: self.operation_metrics.len(),
285            aggregated: self.aggregated_metrics.clone(),
286            counters: self.custom_counters.clone(),
287            gauges: self.custom_gauges.clone(),
288            histograms: self
289                .custom_histograms
290                .iter()
291                .map(|(k, v)| (k.clone(), v.summary()))
292                .collect(),
293        }
294    }
295}
296
297/// Distributed tracing system
298#[allow(dead_code)]
299pub struct TraceCollector {
300    active_spans: HashMap<String, TraceSpan>,
301    completed_traces: VecDeque<DistributedTrace>,
302    trace_id_counter: u64,
303}
304
305impl TraceCollector {
306    fn new(_config: &ObservabilityConfig) -> Self {
307        Self {
308            active_spans: HashMap::new(),
309            completed_traces: VecDeque::new(),
310            trace_id_counter: 0,
311        }
312    }
313
314    fn create_operation_span(&mut self, metrics: &OperationMetrics) -> Result<()> {
315        self.trace_id_counter += 1;
316        let trace_id = format!("trace_{}", self.trace_id_counter);
317
318        let span = TraceSpan {
319            trace_id: trace_id.clone(),
320            span_id: format!("span_{}", self.trace_id_counter),
321            parent_span_id: None,
322            operation_name: metrics
323                .operation_name
324                .clone()
325                .unwrap_or_else(|| "anonymous".to_string()),
326            start_time: metrics.timestamp,
327            duration: Some(metrics.execution_time),
328            tags: vec![
329                (
330                    "operation.type".to_string(),
331                    format!("{:?}", metrics.operation_type),
332                ),
333                (
334                    "query.complexity".to_string(),
335                    metrics.complexity_score.to_string(),
336                ),
337                ("query.depth".to_string(), metrics.depth.to_string()),
338                ("cache.hit".to_string(), metrics.cache_hit.to_string()),
339            ],
340            logs: Vec::new(),
341            status: if metrics.error_count > 0 {
342                SpanStatus::Error
343            } else {
344                SpanStatus::Ok
345            },
346        };
347
348        let trace = DistributedTrace {
349            trace_id: trace_id.clone(),
350            spans: vec![span],
351            total_duration: metrics.execution_time,
352            service_count: 1,
353            error_count: metrics.error_count,
354        };
355
356        self.completed_traces.push_back(trace);
357
358        // Keep only recent traces
359        while self.completed_traces.len() > 1000 {
360            self.completed_traces.pop_front();
361        }
362
363        Ok(())
364    }
365
366    fn get_recent_traces(&self, limit: usize) -> Vec<DistributedTrace> {
367        self.completed_traces
368            .iter()
369            .rev()
370            .take(limit)
371            .cloned()
372            .collect()
373    }
374}
375
376/// Alert management system
377pub struct AlertManager {
378    active_alerts: Vec<Alert>,
379    alert_history: VecDeque<Alert>,
380    thresholds: AlertThresholds,
381}
382
383impl AlertManager {
384    fn new(config: &ObservabilityConfig) -> Self {
385        Self {
386            active_alerts: Vec::new(),
387            alert_history: VecDeque::new(),
388            thresholds: config.alert_thresholds.clone(),
389        }
390    }
391
392    fn check_alerts(&mut self, metrics: &OperationMetrics) -> Result<()> {
393        let mut new_alerts = Vec::new();
394
395        // Check response time
396        if metrics.execution_time.as_millis() > self.thresholds.max_response_time_ms as u128 {
397            new_alerts.push(Alert {
398                id: format!(
399                    "response_time_{}",
400                    SystemTime::now()
401                        .duration_since(UNIX_EPOCH)
402                        .expect("SystemTime should be after UNIX_EPOCH")
403                        .as_millis()
404                ),
405                alert_type: AlertType::HighResponseTime,
406                severity: AlertSeverity::Warning,
407                message: format!(
408                    "Operation took {}ms (threshold: {}ms)",
409                    metrics.execution_time.as_millis(),
410                    self.thresholds.max_response_time_ms
411                ),
412                timestamp: SystemTime::now(),
413                resolved: false,
414                metadata: HashMap::new(),
415            });
416        }
417
418        // Check error rate
419        if metrics.error_count > 0 {
420            new_alerts.push(Alert {
421                id: format!(
422                    "error_{}",
423                    SystemTime::now()
424                        .duration_since(UNIX_EPOCH)
425                        .expect("SystemTime should be after UNIX_EPOCH")
426                        .as_millis()
427                ),
428                alert_type: AlertType::HighErrorRate,
429                severity: AlertSeverity::Critical,
430                message: format!("Operation had {} errors", metrics.error_count),
431                timestamp: SystemTime::now(),
432                resolved: false,
433                metadata: HashMap::new(),
434            });
435        }
436
437        for alert in new_alerts {
438            info!("New alert: {} - {}", alert.alert_type, alert.message);
439            self.alert_history.push_back(alert.clone());
440            self.active_alerts.push(alert);
441        }
442
443        Ok(())
444    }
445
446    fn get_active_alerts(&self) -> Vec<Alert> {
447        self.active_alerts.clone()
448    }
449}
450
451/// Real-time monitoring system
452pub struct RealTimeMonitor {
453    current_metrics: RealTimeMetrics,
454    metrics_history: VecDeque<RealTimeMetrics>,
455    update_interval: Duration,
456    last_update: Instant,
457}
458
459impl RealTimeMonitor {
460    fn new(config: &ObservabilityConfig) -> Self {
461        Self {
462            current_metrics: RealTimeMetrics::default(),
463            metrics_history: VecDeque::new(),
464            update_interval: config.metrics_collection_interval,
465            last_update: Instant::now(),
466        }
467    }
468
469    async fn update_metrics(&mut self, operation_metrics: &OperationMetrics) -> Result<()> {
470        self.current_metrics.total_requests += 1;
471        self.current_metrics.total_errors += operation_metrics.error_count as u64;
472
473        // Update running averages
474        let new_response_time = operation_metrics.execution_time;
475        self.current_metrics.avg_response_time = Duration::from_millis(
476            (self.current_metrics.avg_response_time.as_millis() as u64
477                + new_response_time.as_millis() as u64)
478                / 2,
479        );
480
481        // Update error rate
482        self.current_metrics.error_rate =
483            self.current_metrics.total_errors as f64 / self.current_metrics.total_requests as f64;
484
485        // Update cache metrics
486        if operation_metrics.cache_hit {
487            self.current_metrics.cache_hits += 1;
488        }
489        self.current_metrics.cache_hit_ratio =
490            self.current_metrics.cache_hits as f64 / self.current_metrics.total_requests as f64;
491
492        // Update real system metrics
493        self.current_metrics.memory_usage_mb = system_monitor::get_current_memory_usage_mb().await;
494        self.current_metrics.cpu_usage_percent =
495            system_monitor::get_current_cpu_usage_percent().await;
496        self.current_metrics.timestamp = SystemTime::now();
497
498        // Store historical data
499        if self.last_update.elapsed() >= self.update_interval {
500            self.metrics_history.push_back(self.current_metrics.clone());
501
502            // Keep only recent history
503            while self.metrics_history.len() > 100 {
504                self.metrics_history.pop_front();
505            }
506
507            self.last_update = Instant::now();
508        }
509
510        Ok(())
511    }
512
513    fn get_current_state(&self) -> RealTimeMetrics {
514        self.current_metrics.clone()
515    }
516}
517
518// Data structures for observability system
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct ObservabilityDashboard {
522    pub service_info: ServiceInfo,
523    pub metrics: Option<MetricsSummary>,
524    pub traces: Option<Vec<DistributedTrace>>,
525    pub alerts: Option<Vec<Alert>>,
526    pub real_time_data: Option<RealTimeMetrics>,
527    pub health_status: HealthStatus,
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ServiceInfo {
532    pub name: String,
533    pub version: String,
534    pub environment: String,
535    pub uptime: Duration,
536}
537
538#[derive(Debug, Clone, Serialize, Deserialize)]
539pub struct MetricsSummary {
540    pub total_operations: usize,
541    pub aggregated: AggregatedMetrics,
542    pub counters: HashMap<String, u64>,
543    pub gauges: HashMap<String, f64>,
544    pub histograms: HashMap<String, HistogramSummary>,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
548pub struct AggregatedMetrics {
549    pub total_requests: u64,
550    pub total_errors: u64,
551    pub avg_execution_time: Duration,
552    pub min_execution_time: Duration,
553    pub max_execution_time: Duration,
554    pub p50_execution_time: Duration,
555    pub p95_execution_time: Duration,
556    pub p99_execution_time: Duration,
557}
558
559impl AggregatedMetrics {
560    fn new() -> Self {
561        Self {
562            total_requests: 0,
563            total_errors: 0,
564            avg_execution_time: Duration::from_millis(0),
565            min_execution_time: Duration::from_secs(u64::MAX),
566            max_execution_time: Duration::from_millis(0),
567            p50_execution_time: Duration::from_millis(0),
568            p95_execution_time: Duration::from_millis(0),
569            p99_execution_time: Duration::from_millis(0),
570        }
571    }
572
573    fn update(&mut self, metrics: &OperationMetrics) {
574        self.total_requests += 1;
575        self.total_errors += metrics.error_count as u64;
576
577        let exec_time = metrics.execution_time;
578
579        // Update min/max
580        if exec_time < self.min_execution_time {
581            self.min_execution_time = exec_time;
582        }
583        if exec_time > self.max_execution_time {
584            self.max_execution_time = exec_time;
585        }
586
587        // Update running average
588        self.avg_execution_time = Duration::from_millis(
589            (self.avg_execution_time.as_millis() as u64 * (self.total_requests - 1)
590                + exec_time.as_millis() as u64)
591                / self.total_requests,
592        );
593    }
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
597pub struct DistributedTrace {
598    pub trace_id: String,
599    pub spans: Vec<TraceSpan>,
600    pub total_duration: Duration,
601    pub service_count: usize,
602    pub error_count: usize,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct TraceSpan {
607    pub trace_id: String,
608    pub span_id: String,
609    pub parent_span_id: Option<String>,
610    pub operation_name: String,
611    pub start_time: SystemTime,
612    pub duration: Option<Duration>,
613    pub tags: Vec<(String, String)>,
614    pub logs: Vec<SpanLog>,
615    pub status: SpanStatus,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct SpanLog {
620    pub timestamp: SystemTime,
621    pub level: String,
622    pub message: String,
623    pub fields: HashMap<String, String>,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub enum SpanStatus {
628    Ok,
629    Error,
630    Timeout,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct Alert {
635    pub id: String,
636    pub alert_type: AlertType,
637    pub severity: AlertSeverity,
638    pub message: String,
639    pub timestamp: SystemTime,
640    pub resolved: bool,
641    pub metadata: HashMap<String, String>,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize)]
645pub enum AlertType {
646    HighResponseTime,
647    HighErrorRate,
648    HighMemoryUsage,
649    HighCpuUsage,
650    LowCacheHitRatio,
651    TooManyConcurrentRequests,
652}
653
654impl std::fmt::Display for AlertType {
655    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656        match self {
657            AlertType::HighResponseTime => write!(f, "High Response Time"),
658            AlertType::HighErrorRate => write!(f, "High Error Rate"),
659            AlertType::HighMemoryUsage => write!(f, "High Memory Usage"),
660            AlertType::HighCpuUsage => write!(f, "High CPU Usage"),
661            AlertType::LowCacheHitRatio => write!(f, "Low Cache Hit Ratio"),
662            AlertType::TooManyConcurrentRequests => write!(f, "Too Many Concurrent Requests"),
663        }
664    }
665}
666
667#[derive(Debug, Clone, Serialize, Deserialize)]
668pub enum AlertSeverity {
669    Info,
670    Warning,
671    Critical,
672}
673
674#[derive(Debug, Clone, Serialize, Deserialize)]
675pub struct RealTimeMetrics {
676    pub total_requests: u64,
677    pub total_errors: u64,
678    pub error_rate: f64,
679    pub avg_response_time: Duration,
680    pub current_concurrent_requests: usize,
681    pub cache_hits: u64,
682    pub cache_hit_ratio: f64,
683    pub memory_usage_mb: f64,
684    pub cpu_usage_percent: f64,
685    pub timestamp: SystemTime,
686}
687
688impl Default for RealTimeMetrics {
689    fn default() -> Self {
690        Self {
691            total_requests: 0,
692            total_errors: 0,
693            error_rate: 0.0,
694            avg_response_time: Duration::from_millis(0),
695            current_concurrent_requests: 0,
696            cache_hits: 0,
697            cache_hit_ratio: 0.0,
698            memory_usage_mb: 0.0,
699            cpu_usage_percent: 0.0,
700            timestamp: SystemTime::now(),
701        }
702    }
703}
704
705#[derive(Debug, Clone, Serialize, Deserialize)]
706pub struct HealthStatus {
707    pub status: HealthStatusLevel,
708    pub score: f64,
709    pub issues: Vec<String>,
710    pub last_check: SystemTime,
711}
712
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub enum HealthStatusLevel {
715    Healthy,
716    Warning,
717    Critical,
718}
719
720#[derive(Debug, Clone)]
721pub struct Histogram {
722    buckets: Vec<(f64, u64)>,
723    total_count: u64,
724    total_sum: f64,
725}
726
727impl Histogram {
728    fn new() -> Self {
729        Self {
730            buckets: vec![
731                (1.0, 0),
732                (5.0, 0),
733                (10.0, 0),
734                (25.0, 0),
735                (50.0, 0),
736                (100.0, 0),
737                (250.0, 0),
738                (500.0, 0),
739                (1000.0, 0),
740                (2500.0, 0),
741                (5000.0, 0),
742                (10000.0, 0),
743                (f64::INFINITY, 0),
744            ],
745            total_count: 0,
746            total_sum: 0.0,
747        }
748    }
749
750    fn record(&mut self, value: f64) {
751        self.total_count += 1;
752        self.total_sum += value;
753
754        for (threshold, count) in &mut self.buckets {
755            if value <= *threshold {
756                *count += 1;
757            }
758        }
759    }
760
761    fn summary(&self) -> HistogramSummary {
762        HistogramSummary {
763            count: self.total_count,
764            sum: self.total_sum,
765            buckets: self.buckets.clone(),
766        }
767    }
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize)]
771pub struct HistogramSummary {
772    pub count: u64,
773    pub sum: f64,
774    pub buckets: Vec<(f64, u64)>,
775}
776
777#[cfg(test)]
778mod tests {
779    use super::*;
780    use crate::ast::OperationType;
781    use crate::performance::ClientInfo;
782
783    #[tokio::test]
784    async fn test_observability_system_creation() {
785        let config = ObservabilityConfig::default();
786        let system = ObservabilitySystem::new(config);
787
788        let dashboard = system.get_dashboard().await.expect("should succeed");
789        assert_eq!(dashboard.service_info.name, "oxirs-gql");
790    }
791
792    #[tokio::test]
793    async fn test_metrics_collection() {
794        let config = ObservabilityConfig::default();
795        let system = ObservabilitySystem::new(config);
796
797        let metrics = OperationMetrics {
798            operation_name: Some("test_query".to_string()),
799            operation_type: OperationType::Query,
800            query_hash: 12345,
801            execution_time: Duration::from_millis(100),
802            parsing_time: Duration::from_millis(10),
803            validation_time: Duration::from_millis(5),
804            planning_time: Duration::from_millis(15),
805            field_count: 3,
806            depth: 2,
807            complexity_score: 10,
808            cache_hit: true,
809            error_count: 0,
810            timestamp: SystemTime::now(),
811            client_info: ClientInfo::default(),
812        };
813
814        system
815            .record_operation(&metrics)
816            .await
817            .expect("should succeed");
818
819        let dashboard = system.get_dashboard().await.expect("should succeed");
820        assert!(dashboard.metrics.is_some());
821    }
822
823    #[tokio::test]
824    async fn test_alert_generation() {
825        let mut config = ObservabilityConfig::default();
826        config.alert_thresholds.max_response_time_ms = 50; // Low threshold for testing
827
828        let system = ObservabilitySystem::new(config);
829
830        let slow_metrics = OperationMetrics {
831            operation_name: Some("slow_query".to_string()),
832            operation_type: OperationType::Query,
833            query_hash: 67890,
834            execution_time: Duration::from_millis(100), // Exceeds threshold
835            parsing_time: Duration::from_millis(10),
836            validation_time: Duration::from_millis(5),
837            planning_time: Duration::from_millis(15),
838            field_count: 5,
839            depth: 3,
840            complexity_score: 20,
841            cache_hit: false,
842            error_count: 0,
843            timestamp: SystemTime::now(),
844            client_info: ClientInfo::default(),
845        };
846
847        system
848            .record_operation(&slow_metrics)
849            .await
850            .expect("should succeed");
851
852        let dashboard = system.get_dashboard().await.expect("should succeed");
853        if let Some(alerts) = dashboard.alerts {
854            assert!(!alerts.is_empty());
855        }
856    }
857
858    #[tokio::test]
859    async fn test_health_status_calculation() {
860        let config = ObservabilityConfig::default();
861        let system = ObservabilitySystem::new(config);
862
863        let health = system.calculate_health_status().await;
864        assert!(matches!(health.status, HealthStatusLevel::Healthy));
865        assert!(health.score >= 90.0);
866    }
867}