Skip to main content

oxirs_gql/
performance.rs

1//! GraphQL Performance Monitoring and Analytics
2//!
3//! This module provides comprehensive performance monitoring, metrics collection,
4//! and analytics for GraphQL operations.
5
6use crate::ast::{Document, OperationType};
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::cmp::Reverse;
10use std::collections::{HashMap, VecDeque};
11use std::sync::{Arc, RwLock};
12use std::time::{Duration, Instant, SystemTime};
13
14/// Performance metrics for a single GraphQL operation
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct OperationMetrics {
17    pub operation_name: Option<String>,
18    pub operation_type: OperationType,
19    pub query_hash: u64,
20    pub execution_time: Duration,
21    pub parsing_time: Duration,
22    pub validation_time: Duration,
23    pub planning_time: Duration,
24    pub field_count: usize,
25    pub depth: usize,
26    pub complexity_score: usize,
27    pub cache_hit: bool,
28    pub error_count: usize,
29    pub timestamp: SystemTime,
30    pub client_info: ClientInfo,
31}
32
33/// Client information for request tracking
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct ClientInfo {
36    pub ip_address: Option<String>,
37    pub user_agent: Option<String>,
38    pub user_id: Option<String>,
39    pub session_id: Option<String>,
40}
41
42/// Aggregated performance statistics
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct PerformanceStats {
45    pub total_requests: u64,
46    pub total_errors: u64,
47    pub avg_execution_time: Duration,
48    pub p50_execution_time: Duration,
49    pub p95_execution_time: Duration,
50    pub p99_execution_time: Duration,
51    pub cache_hit_ratio: f64,
52    pub queries_per_second: f64,
53    pub error_rate: f64,
54    pub most_expensive_queries: Vec<ExpensiveQuery>,
55    pub slowest_fields: Vec<SlowField>,
56    pub client_stats: HashMap<String, ClientStats>,
57}
58
59/// Information about expensive queries
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct ExpensiveQuery {
62    pub query_hash: u64,
63    pub operation_name: Option<String>,
64    pub avg_execution_time: Duration,
65    pub max_execution_time: Duration,
66    pub execution_count: u64,
67    pub complexity_score: usize,
68}
69
70/// Information about slow fields
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct SlowField {
73    pub field_name: String,
74    pub avg_resolution_time: Duration,
75    pub max_resolution_time: Duration,
76    pub call_count: u64,
77}
78
79/// Per-client statistics
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ClientStats {
82    pub request_count: u64,
83    pub error_count: u64,
84    pub avg_execution_time: Duration,
85    pub last_seen: SystemTime,
86}
87
88/// Real-time performance tracker
89#[derive(Debug)]
90pub struct PerformanceTracker {
91    metrics: Arc<RwLock<VecDeque<OperationMetrics>>>,
92    field_metrics: Arc<RwLock<HashMap<String, FieldMetrics>>>,
93    config: PerformanceConfig,
94    start_time: Instant,
95}
96
97/// Configuration for performance tracking
98#[derive(Debug, Clone)]
99pub struct PerformanceConfig {
100    pub max_stored_metrics: usize,
101    pub enable_detailed_tracking: bool,
102    pub enable_client_tracking: bool,
103    pub enable_field_tracking: bool,
104    pub stats_window: Duration,
105    pub expensive_query_threshold: Duration,
106    pub slow_field_threshold: Duration,
107}
108
109impl Default for PerformanceConfig {
110    fn default() -> Self {
111        Self {
112            max_stored_metrics: 10000,
113            enable_detailed_tracking: true,
114            enable_client_tracking: true,
115            enable_field_tracking: true,
116            stats_window: Duration::from_secs(3600), // 1 hour
117            expensive_query_threshold: Duration::from_millis(1000),
118            slow_field_threshold: Duration::from_millis(100),
119        }
120    }
121}
122
123/// Field-level performance metrics
124#[derive(Debug, Clone)]
125struct FieldMetrics {
126    call_count: u64,
127    total_time: Duration,
128    max_time: Duration,
129    error_count: u64,
130    last_called: Instant,
131}
132
133impl FieldMetrics {
134    fn new() -> Self {
135        Self {
136            call_count: 0,
137            total_time: Duration::from_secs(0),
138            max_time: Duration::from_secs(0),
139            error_count: 0,
140            last_called: Instant::now(),
141        }
142    }
143
144    fn record_call(&mut self, duration: Duration, had_error: bool) {
145        self.call_count += 1;
146        self.total_time += duration;
147        self.max_time = self.max_time.max(duration);
148        if had_error {
149            self.error_count += 1;
150        }
151        self.last_called = Instant::now();
152    }
153
154    fn avg_time(&self) -> Duration {
155        if self.call_count == 0 {
156            Duration::from_secs(0)
157        } else {
158            self.total_time / self.call_count as u32
159        }
160    }
161}
162
163impl PerformanceTracker {
164    /// Create a new performance tracker
165    pub fn new() -> Self {
166        Self::with_config(PerformanceConfig::default())
167    }
168
169    /// Create a new performance tracker with custom configuration
170    pub fn with_config(config: PerformanceConfig) -> Self {
171        Self {
172            metrics: Arc::new(RwLock::new(VecDeque::new())),
173            field_metrics: Arc::new(RwLock::new(HashMap::new())),
174            config,
175            start_time: Instant::now(),
176        }
177    }
178
179    /// Record operation metrics
180    pub fn record_operation(&self, metrics: OperationMetrics) {
181        if let Ok(mut storage) = self.metrics.write() {
182            // Enforce size limit
183            while storage.len() >= self.config.max_stored_metrics {
184                storage.pop_front();
185            }
186            storage.push_back(metrics);
187        }
188    }
189
190    /// Record field resolution metrics
191    pub fn record_field_resolution(&self, field_name: &str, duration: Duration, had_error: bool) {
192        if !self.config.enable_field_tracking {
193            return;
194        }
195
196        if let Ok(mut field_metrics) = self.field_metrics.write() {
197            let metrics = field_metrics
198                .entry(field_name.to_string())
199                .or_insert_with(FieldMetrics::new);
200            metrics.record_call(duration, had_error);
201        }
202    }
203
204    /// Get current performance statistics
205    pub fn get_stats(&self) -> Result<PerformanceStats> {
206        let metrics = self
207            .metrics
208            .read()
209            .map_err(|_| anyhow::anyhow!("Lock poisoned"))?;
210        let field_metrics = self
211            .field_metrics
212            .read()
213            .map_err(|_| anyhow::anyhow!("Lock poisoned"))?;
214
215        let cutoff_time = SystemTime::now() - self.config.stats_window;
216        let recent_metrics: Vec<_> = metrics
217            .iter()
218            .filter(|m| m.timestamp > cutoff_time)
219            .collect();
220
221        if recent_metrics.is_empty() {
222            return Ok(PerformanceStats {
223                total_requests: 0,
224                total_errors: 0,
225                avg_execution_time: Duration::from_secs(0),
226                p50_execution_time: Duration::from_secs(0),
227                p95_execution_time: Duration::from_secs(0),
228                p99_execution_time: Duration::from_secs(0),
229                cache_hit_ratio: 0.0,
230                queries_per_second: 0.0,
231                error_rate: 0.0,
232                most_expensive_queries: Vec::new(),
233                slowest_fields: Vec::new(),
234                client_stats: HashMap::new(),
235            });
236        }
237
238        // Calculate basic stats
239        let total_requests = recent_metrics.len() as u64;
240        let total_errors = recent_metrics.iter().map(|m| m.error_count as u64).sum();
241        let cache_hits = recent_metrics.iter().filter(|m| m.cache_hit).count() as u64;
242
243        // Calculate execution time statistics
244        let mut execution_times: Vec<Duration> =
245            recent_metrics.iter().map(|m| m.execution_time).collect();
246        execution_times.sort();
247
248        let total_nanos = execution_times.iter().map(|d| d.as_nanos()).sum::<u128>()
249            / execution_times.len() as u128;
250        let avg_execution_time = Duration::from_nanos(total_nanos.min(u64::MAX as u128) as u64);
251
252        let p50_execution_time = execution_times[execution_times.len() * 50 / 100];
253        let p95_execution_time = execution_times[execution_times.len() * 95 / 100];
254        let p99_execution_time = execution_times[execution_times.len() * 99 / 100];
255
256        let cache_hit_ratio = if total_requests > 0 {
257            cache_hits as f64 / total_requests as f64
258        } else {
259            0.0
260        };
261
262        let queries_per_second = total_requests as f64 / self.config.stats_window.as_secs() as f64;
263        let error_rate = if total_requests > 0 {
264            total_errors as f64 / total_requests as f64
265        } else {
266            0.0
267        };
268
269        // Find most expensive queries
270        let most_expensive_queries = self.calculate_expensive_queries(&recent_metrics);
271
272        // Find slowest fields
273        let slowest_fields = self.calculate_slowest_fields(&field_metrics);
274
275        // Calculate client stats
276        let client_stats = if self.config.enable_client_tracking {
277            self.calculate_client_stats(&recent_metrics)
278        } else {
279            HashMap::new()
280        };
281
282        Ok(PerformanceStats {
283            total_requests,
284            total_errors,
285            avg_execution_time,
286            p50_execution_time,
287            p95_execution_time,
288            p99_execution_time,
289            cache_hit_ratio,
290            queries_per_second,
291            error_rate,
292            most_expensive_queries,
293            slowest_fields,
294            client_stats,
295        })
296    }
297
298    /// Clear all stored metrics
299    pub fn clear_metrics(&self) {
300        if let Ok(mut metrics) = self.metrics.write() {
301            metrics.clear();
302        }
303        if let Ok(mut field_metrics) = self.field_metrics.write() {
304            field_metrics.clear();
305        }
306    }
307
308    /// Get metrics for a specific time range
309    pub fn get_metrics_in_range(
310        &self,
311        start: SystemTime,
312        end: SystemTime,
313    ) -> Result<Vec<OperationMetrics>> {
314        let metrics = self
315            .metrics
316            .read()
317            .map_err(|_| anyhow::anyhow!("Lock poisoned"))?;
318
319        Ok(metrics
320            .iter()
321            .filter(|m| m.timestamp >= start && m.timestamp <= end)
322            .cloned()
323            .collect())
324    }
325
326    /// Export metrics to JSON
327    pub fn export_metrics_json(&self) -> Result<String> {
328        let stats = self.get_stats()?;
329        serde_json::to_string_pretty(&stats)
330            .map_err(|e| anyhow::anyhow!("JSON serialization failed: {}", e))
331    }
332
333    /// Get uptime
334    pub fn uptime(&self) -> Duration {
335        self.start_time.elapsed()
336    }
337
338    fn calculate_expensive_queries(&self, metrics: &[&OperationMetrics]) -> Vec<ExpensiveQuery> {
339        let mut query_stats: HashMap<u64, (Duration, Duration, u64, usize, Option<String>)> =
340            HashMap::new();
341
342        for metric in metrics {
343            if metric.execution_time >= self.config.expensive_query_threshold {
344                let entry = query_stats.entry(metric.query_hash).or_insert((
345                    Duration::from_secs(0),
346                    Duration::from_secs(0),
347                    0,
348                    metric.complexity_score,
349                    metric.operation_name.clone(),
350                ));
351
352                entry.0 += metric.execution_time; // total time
353                entry.1 = entry.1.max(metric.execution_time); // max time
354                entry.2 += 1; // count
355            }
356        }
357
358        let mut expensive_queries: Vec<ExpensiveQuery> = query_stats
359            .into_iter()
360            .map(
361                |(hash, (total_time, max_time, count, complexity, name))| ExpensiveQuery {
362                    query_hash: hash,
363                    operation_name: name,
364                    avg_execution_time: total_time / count as u32,
365                    max_execution_time: max_time,
366                    execution_count: count,
367                    complexity_score: complexity,
368                },
369            )
370            .collect();
371
372        expensive_queries.sort_by_key(|q| Reverse(q.avg_execution_time));
373        expensive_queries.truncate(10); // Top 10
374        expensive_queries
375    }
376
377    fn calculate_slowest_fields(
378        &self,
379        field_metrics: &HashMap<String, FieldMetrics>,
380    ) -> Vec<SlowField> {
381        let mut slow_fields: Vec<SlowField> = field_metrics
382            .iter()
383            .filter(|(_, metrics)| metrics.avg_time() >= self.config.slow_field_threshold)
384            .map(|(name, metrics)| SlowField {
385                field_name: name.clone(),
386                avg_resolution_time: metrics.avg_time(),
387                max_resolution_time: metrics.max_time,
388                call_count: metrics.call_count,
389            })
390            .collect();
391
392        slow_fields.sort_by_key(|f| Reverse(f.avg_resolution_time));
393        slow_fields.truncate(10); // Top 10
394        slow_fields
395    }
396
397    fn calculate_client_stats(
398        &self,
399        metrics: &[&OperationMetrics],
400    ) -> HashMap<String, ClientStats> {
401        let mut client_stats: HashMap<String, ClientStats> = HashMap::new();
402
403        for metric in metrics {
404            if let Some(ref ip) = metric.client_info.ip_address {
405                let stats = client_stats.entry(ip.clone()).or_insert(ClientStats {
406                    request_count: 0,
407                    error_count: 0,
408                    avg_execution_time: Duration::from_secs(0),
409                    last_seen: metric.timestamp,
410                });
411
412                stats.request_count += 1;
413                stats.error_count += metric.error_count as u64;
414                stats.last_seen = stats.last_seen.max(metric.timestamp);
415
416                // Update running average
417                let total_time = stats.avg_execution_time * (stats.request_count - 1) as u32
418                    + metric.execution_time;
419                stats.avg_execution_time = total_time / stats.request_count as u32;
420            }
421        }
422
423        client_stats
424    }
425}
426
427impl Default for PerformanceTracker {
428    fn default() -> Self {
429        Self::new()
430    }
431}
432
433/// Performance monitoring middleware
434pub struct PerformanceMonitor {
435    tracker: Arc<PerformanceTracker>,
436}
437
438impl PerformanceMonitor {
439    pub fn new(tracker: Arc<PerformanceTracker>) -> Self {
440        Self { tracker }
441    }
442
443    /// Start timing an operation
444    pub fn start_operation(&self) -> OperationTimer {
445        OperationTimer::new(Arc::clone(&self.tracker))
446    }
447
448    /// Get current performance statistics
449    pub fn get_stats(&self) -> Result<PerformanceStats> {
450        self.tracker.get_stats()
451    }
452
453    /// Get the underlying tracker
454    pub fn tracker(&self) -> &Arc<PerformanceTracker> {
455        &self.tracker
456    }
457}
458
459/// Timer for tracking operation execution
460pub struct OperationTimer {
461    tracker: Arc<PerformanceTracker>,
462    start_time: Instant,
463    parsing_time: Option<Duration>,
464    validation_time: Option<Duration>,
465    planning_time: Option<Duration>,
466}
467
468impl OperationTimer {
469    fn new(tracker: Arc<PerformanceTracker>) -> Self {
470        Self {
471            tracker,
472            start_time: Instant::now(),
473            parsing_time: None,
474            validation_time: None,
475            planning_time: None,
476        }
477    }
478
479    /// Mark parsing phase completion
480    pub fn mark_parsing_complete(&mut self) {
481        self.parsing_time = Some(self.start_time.elapsed());
482    }
483
484    /// Mark validation phase completion
485    pub fn mark_validation_complete(&mut self) {
486        self.validation_time =
487            Some(self.start_time.elapsed() - self.parsing_time.unwrap_or_default());
488    }
489
490    /// Mark planning phase completion
491    pub fn mark_planning_complete(&mut self) {
492        let elapsed = self.start_time.elapsed();
493        let previous =
494            self.parsing_time.unwrap_or_default() + self.validation_time.unwrap_or_default();
495        self.planning_time = Some(elapsed - previous);
496    }
497
498    /// Complete the operation timing
499    pub fn complete(
500        &self,
501        document: &Document,
502        complexity: crate::optimizer::QueryComplexity,
503        cache_hit: bool,
504        error_count: usize,
505        client_info: ClientInfo,
506    ) {
507        let total_execution_time = self.start_time.elapsed();
508
509        // Extract operation info
510        let (operation_name, operation_type) =
511            if let Some(crate::ast::Definition::Operation(op)) = document.definitions.first() {
512                (op.name.clone(), op.operation_type.clone())
513            } else {
514                (None, OperationType::Query)
515            };
516
517        let metrics = OperationMetrics {
518            operation_name,
519            operation_type,
520            query_hash: self.calculate_query_hash(document),
521            execution_time: total_execution_time,
522            parsing_time: self.parsing_time.unwrap_or_default(),
523            validation_time: self.validation_time.unwrap_or_default(),
524            planning_time: self.planning_time.unwrap_or_default(),
525            field_count: complexity.field_count,
526            depth: complexity.depth,
527            complexity_score: complexity.complexity_score,
528            cache_hit,
529            error_count,
530            timestamp: SystemTime::now(),
531            client_info,
532        };
533
534        self.tracker.record_operation(metrics);
535    }
536
537    fn calculate_query_hash(&self, document: &Document) -> u64 {
538        use std::collections::hash_map::DefaultHasher;
539        use std::hash::{Hash, Hasher};
540
541        let mut hasher = DefaultHasher::new();
542        format!("{document:?}").hash(&mut hasher);
543        hasher.finish()
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use super::*;
550    use crate::ast::*;
551
552    #[test]
553    fn test_performance_tracker() {
554        let tracker = PerformanceTracker::new();
555
556        let metrics = OperationMetrics {
557            operation_name: Some("TestQuery".to_string()),
558            operation_type: OperationType::Query,
559            query_hash: 12345,
560            execution_time: Duration::from_millis(100),
561            parsing_time: Duration::from_millis(10),
562            validation_time: Duration::from_millis(5),
563            planning_time: Duration::from_millis(15),
564            field_count: 5,
565            depth: 3,
566            complexity_score: 50,
567            cache_hit: false,
568            error_count: 0,
569            timestamp: SystemTime::now(),
570            client_info: ClientInfo::default(),
571        };
572
573        tracker.record_operation(metrics);
574
575        let stats = tracker.get_stats().expect("should succeed");
576        assert_eq!(stats.total_requests, 1);
577        assert_eq!(stats.total_errors, 0);
578    }
579
580    #[test]
581    fn test_field_metrics() {
582        let tracker = PerformanceTracker::new();
583
584        // Record an operation metric to ensure recent_metrics is not empty
585        let operation_metrics = OperationMetrics {
586            operation_name: Some("test_operation".to_string()),
587            operation_type: OperationType::Query,
588            query_hash: 12345,
589            execution_time: Duration::from_millis(100),
590            parsing_time: Duration::from_millis(10),
591            validation_time: Duration::from_millis(5),
592            planning_time: Duration::from_millis(5),
593            field_count: 1,
594            depth: 1,
595            complexity_score: 10,
596            cache_hit: false,
597            error_count: 0,
598            timestamp: SystemTime::now(),
599            client_info: ClientInfo::default(),
600        };
601        tracker.record_operation(operation_metrics);
602
603        tracker.record_field_resolution("test_field", Duration::from_millis(150), false);
604        tracker.record_field_resolution("test_field", Duration::from_millis(200), true);
605
606        let stats = tracker.get_stats().expect("should succeed");
607        assert_eq!(stats.slowest_fields.len(), 1);
608        assert_eq!(stats.slowest_fields[0].field_name, "test_field");
609        assert_eq!(stats.slowest_fields[0].call_count, 2);
610    }
611
612    #[test]
613    fn test_operation_timer() {
614        let tracker = Arc::new(PerformanceTracker::new());
615        let monitor = PerformanceMonitor::new(tracker);
616
617        let mut timer = monitor.start_operation();
618        std::thread::sleep(Duration::from_millis(1));
619        timer.mark_parsing_complete();
620
621        assert!(timer.parsing_time.is_some());
622        assert!(timer.parsing_time.expect("should succeed") > Duration::from_millis(0));
623    }
624}