Skip to main content

llm_optimizer_processor/analyzer/
performance_analyzer.rs

1//! Performance Analyzer
2//!
3//! Analyzes performance metrics including latency percentiles, throughput,
4//! token usage, and detects performance degradation.
5
6use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use std::collections::HashMap;
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11use tokio::sync::RwLock;
12use tracing::{debug, info, trace, warn};
13use uuid::Uuid;
14
15use super::stats::{
16    calculate_percentiles, mean, percentage_change, std_dev, CircularBuffer,
17    ExponentialMovingAverage,
18};
19use super::traits::{
20    Analyzer, AnalyzerConfig, AnalyzerError, AnalyzerResult, AnalyzerState,
21};
22use super::types::{
23    Action, ActionType, Alert, AlertStatus, AnalysisReport, AnalyzerEvent, AnalyzerStats,
24    Confidence, Evidence, EvidenceType, Impact, ImpactMetric, Insight, InsightCategory,
25    Priority, Recommendation, ReportSummary, RiskLevel, Severity, Threshold, ThresholdOperator,
26};
27
28/// Configuration specific to the Performance Analyzer
29#[derive(Debug, Clone)]
30pub struct PerformanceAnalyzerConfig {
31    /// Base analyzer configuration
32    pub base: AnalyzerConfig,
33
34    /// Latency thresholds (ms)
35    pub latency_warning_ms: u64,
36    pub latency_critical_ms: u64,
37
38    /// Throughput thresholds (requests/sec)
39    pub throughput_low_rps: f64,
40
41    /// Performance degradation threshold (percentage)
42    pub degradation_threshold_pct: f64,
43
44    /// Window size for percentile calculation
45    pub percentile_window_size: usize,
46
47    /// Enable regression detection
48    pub enable_regression_detection: bool,
49}
50
51impl Default for PerformanceAnalyzerConfig {
52    fn default() -> Self {
53        Self {
54            base: AnalyzerConfig::default(),
55            latency_warning_ms: 1000,  // 1 second
56            latency_critical_ms: 5000, // 5 seconds
57            throughput_low_rps: 1.0,
58            degradation_threshold_pct: 20.0,
59            percentile_window_size: 1000,
60            enable_regression_detection: true,
61        }
62    }
63}
64
65/// Performance metrics tracked over time
66#[derive(Debug, Clone)]
67struct PerformanceMetrics {
68    /// Latency samples (ms)
69    latencies: CircularBuffer<f64>,
70
71    /// Token usage per request
72    tokens_per_request: CircularBuffer<u32>,
73
74    /// Request timestamps for throughput calculation
75    request_times: CircularBuffer<DateTime<Utc>>,
76
77    /// Success/failure counts
78    success_count: u64,
79    failure_count: u64,
80
81    /// Exponential moving average of latency
82    latency_ema: ExponentialMovingAverage,
83
84    /// Historical baseline for comparison
85    baseline_p95_ms: Option<f64>,
86    baseline_throughput_rps: Option<f64>,
87}
88
89impl PerformanceMetrics {
90    fn new(window_size: usize) -> Self {
91        Self {
92            latencies: CircularBuffer::new(window_size),
93            tokens_per_request: CircularBuffer::new(window_size),
94            request_times: CircularBuffer::new(window_size),
95            success_count: 0,
96            failure_count: 0,
97            latency_ema: ExponentialMovingAverage::new(0.3),
98            baseline_p95_ms: None,
99            baseline_throughput_rps: None,
100        }
101    }
102
103    fn record_latency(&mut self, latency_ms: f64) {
104        self.latencies.push(latency_ms);
105        self.latency_ema.update(latency_ms);
106    }
107
108    fn record_tokens(&mut self, tokens: u32) {
109        self.tokens_per_request.push(tokens);
110    }
111
112    fn record_request(&mut self, timestamp: DateTime<Utc>, success: bool) {
113        self.request_times.push(timestamp);
114        if success {
115            self.success_count += 1;
116        } else {
117            self.failure_count += 1;
118        }
119    }
120
121    fn calculate_percentiles(&self) -> HashMap<String, f64> {
122        let latencies = self.latencies.to_vec();
123        if latencies.is_empty() {
124            return HashMap::new();
125        }
126
127        let percentiles = calculate_percentiles(latencies, &[50.0, 90.0, 95.0, 99.0, 99.9]);
128
129        percentiles
130            .into_iter()
131            .map(|(p, v)| (format!("p{}", p), v))
132            .collect()
133    }
134
135    fn calculate_throughput(&self) -> f64 {
136        if self.request_times.len() < 2 {
137            return 0.0;
138        }
139
140        let times = self.request_times.as_slice();
141        let first = times.front().unwrap();
142        let last = times.back().unwrap();
143
144        let duration_secs = (*last - *first).num_milliseconds() as f64 / 1000.0;
145        if duration_secs == 0.0 {
146            return 0.0;
147        }
148
149        self.request_times.len() as f64 / duration_secs
150    }
151
152    fn get_success_rate(&self) -> f64 {
153        let total = self.success_count + self.failure_count;
154        if total == 0 {
155            return 1.0;
156        }
157        self.success_count as f64 / total as f64
158    }
159}
160
161/// Performance Analyzer implementation
162pub struct PerformanceAnalyzer {
163    config: PerformanceAnalyzerConfig,
164    state: AnalyzerState,
165    metrics: Arc<RwLock<PerformanceMetrics>>,
166    stats: Arc<RwLock<AnalyzerStats>>,
167    start_time: Instant,
168    last_report_time: Arc<RwLock<Instant>>,
169}
170
171impl PerformanceAnalyzer {
172    /// Create a new Performance Analyzer
173    pub fn new(config: PerformanceAnalyzerConfig) -> Self {
174        let window_size = config.percentile_window_size;
175
176        Self {
177            config,
178            state: AnalyzerState::Initialized,
179            metrics: Arc::new(RwLock::new(PerformanceMetrics::new(window_size))),
180            stats: Arc::new(RwLock::new(AnalyzerStats {
181                analyzer: "performance".to_string(),
182                ..Default::default()
183            })),
184            start_time: Instant::now(),
185            last_report_time: Arc::new(RwLock::new(Instant::now())),
186        }
187    }
188
189    /// Create with default configuration
190    pub fn with_defaults() -> Self {
191        Self::new(PerformanceAnalyzerConfig::default())
192    }
193
194    /// Process a response event
195    async fn process_response(&mut self, event: AnalyzerEvent) -> AnalyzerResult<()> {
196        if let AnalyzerEvent::Response {
197            timestamp,
198            latency_ms,
199            total_tokens,
200            success,
201            ..
202        } = event
203        {
204            let mut metrics = self.metrics.write().await;
205            metrics.record_latency(latency_ms as f64);
206            metrics.record_tokens(total_tokens);
207            metrics.record_request(timestamp, success);
208
209            trace!(
210                "Recorded performance metrics: latency={}ms, tokens={}, success={}",
211                latency_ms,
212                total_tokens,
213                success
214            );
215        }
216
217        Ok(())
218    }
219
220    /// Process a metric event
221    async fn process_metric(&mut self, event: AnalyzerEvent) -> AnalyzerResult<()> {
222        if let AnalyzerEvent::Metric {
223            name,
224            value,
225            timestamp,
226            ..
227        } = event
228        {
229            if name.contains("latency") || name.contains("duration") {
230                let mut metrics = self.metrics.write().await;
231                metrics.record_latency(value);
232                metrics.record_request(timestamp, true);
233            }
234        }
235
236        Ok(())
237    }
238
239    /// Analyze current metrics and generate insights
240    async fn analyze_metrics(&self) -> Vec<Insight> {
241        let metrics = self.metrics.read().await;
242        let mut insights = Vec::new();
243
244        // Check latency thresholds
245        let percentiles = metrics.calculate_percentiles();
246        if let Some(&p95) = percentiles.get("p95.0") {
247            if p95 > self.config.latency_critical_ms as f64 {
248                insights.push(self.create_high_latency_insight(p95, Severity::Critical));
249            } else if p95 > self.config.latency_warning_ms as f64 {
250                insights.push(self.create_high_latency_insight(p95, Severity::Warning));
251            }
252        }
253
254        // Check throughput
255        let throughput = metrics.calculate_throughput();
256        if throughput > 0.0 && throughput < self.config.throughput_low_rps {
257            insights.push(self.create_low_throughput_insight(throughput));
258        }
259
260        // Check success rate
261        let success_rate = metrics.get_success_rate();
262        if success_rate < 0.95 {
263            insights.push(self.create_low_success_rate_insight(success_rate));
264        }
265
266        // Check for performance degradation
267        if self.config.enable_regression_detection {
268            if let Some(baseline_p95) = metrics.baseline_p95_ms {
269                if let Some(&current_p95) = percentiles.get("p95.0") {
270                    let change_pct = percentage_change(baseline_p95, current_p95);
271                    if change_pct > self.config.degradation_threshold_pct {
272                        insights.push(
273                            self.create_degradation_insight(baseline_p95, current_p95, change_pct),
274                        );
275                    }
276                }
277            }
278        }
279
280        insights
281    }
282
283    /// Create insight for high latency
284    fn create_high_latency_insight(&self, p95_latency: f64, severity: Severity) -> Insight {
285        Insight {
286            id: Uuid::new_v4().to_string(),
287            analyzer: "performance".to_string(),
288            timestamp: Utc::now(),
289            severity,
290            confidence: Confidence::new(0.95),
291            title: format!("High P95 Latency: {:.2}ms", p95_latency),
292            description: format!(
293                "The 95th percentile latency is {:.2}ms, which exceeds the {} threshold of {}ms. This indicates that 5% of requests are experiencing significant delays.",
294                p95_latency,
295                if severity == Severity::Critical { "critical" } else { "warning" },
296                if severity == Severity::Critical {
297                    self.config.latency_critical_ms
298                } else {
299                    self.config.latency_warning_ms
300                }
301            ),
302            category: InsightCategory::Performance,
303            evidence: vec![Evidence {
304                evidence_type: EvidenceType::Threshold,
305                description: format!("P95 latency: {:.2}ms", p95_latency),
306                data: serde_json::json!({
307                    "p95_latency_ms": p95_latency,
308                    "threshold_ms": if severity == Severity::Critical {
309                        self.config.latency_critical_ms
310                    } else {
311                        self.config.latency_warning_ms
312                    }
313                }),
314                timestamp: Utc::now(),
315            }],
316            metrics: {
317                let mut m = HashMap::new();
318                m.insert("p95_latency_ms".to_string(), p95_latency);
319                m
320            },
321            tags: vec!["latency".to_string(), "performance".to_string()],
322        }
323    }
324
325    /// Create insight for low throughput
326    fn create_low_throughput_insight(&self, throughput: f64) -> Insight {
327        Insight {
328            id: Uuid::new_v4().to_string(),
329            analyzer: "performance".to_string(),
330            timestamp: Utc::now(),
331            severity: Severity::Warning,
332            confidence: Confidence::new(0.85),
333            title: format!("Low Throughput: {:.2} req/s", throughput),
334            description: format!(
335                "Current throughput is {:.2} requests/second, which is below the expected threshold of {:.2} req/s. This may indicate capacity issues or upstream bottlenecks.",
336                throughput, self.config.throughput_low_rps
337            ),
338            category: InsightCategory::Performance,
339            evidence: vec![Evidence {
340                evidence_type: EvidenceType::Statistical,
341                description: format!("Throughput: {:.2} req/s", throughput),
342                data: serde_json::json!({
343                    "throughput_rps": throughput,
344                    "threshold_rps": self.config.throughput_low_rps
345                }),
346                timestamp: Utc::now(),
347            }],
348            metrics: {
349                let mut m = HashMap::new();
350                m.insert("throughput_rps".to_string(), throughput);
351                m
352            },
353            tags: vec!["throughput".to_string(), "performance".to_string()],
354        }
355    }
356
357    /// Create insight for low success rate
358    fn create_low_success_rate_insight(&self, success_rate: f64) -> Insight {
359        Insight {
360            id: Uuid::new_v4().to_string(),
361            analyzer: "performance".to_string(),
362            timestamp: Utc::now(),
363            severity: if success_rate < 0.90 {
364                Severity::Error
365            } else {
366                Severity::Warning
367            },
368            confidence: Confidence::new(0.90),
369            title: format!("Low Success Rate: {:.1}%", success_rate * 100.0),
370            description: format!(
371                "Request success rate is {:.1}%, which is below the expected 95% threshold. Investigate error causes and consider implementing retry logic or circuit breakers.",
372                success_rate * 100.0
373            ),
374            category: InsightCategory::Quality,
375            evidence: vec![Evidence {
376                evidence_type: EvidenceType::Statistical,
377                description: format!("Success rate: {:.1}%", success_rate * 100.0),
378                data: serde_json::json!({
379                    "success_rate": success_rate,
380                    "threshold": 0.95
381                }),
382                timestamp: Utc::now(),
383            }],
384            metrics: {
385                let mut m = HashMap::new();
386                m.insert("success_rate".to_string(), success_rate);
387                m
388            },
389            tags: vec!["reliability".to_string(), "errors".to_string()],
390        }
391    }
392
393    /// Create insight for performance degradation
394    fn create_degradation_insight(
395        &self,
396        baseline: f64,
397        current: f64,
398        change_pct: f64,
399    ) -> Insight {
400        Insight {
401            id: Uuid::new_v4().to_string(),
402            analyzer: "performance".to_string(),
403            timestamp: Utc::now(),
404            severity: if change_pct > 50.0 {
405                Severity::Critical
406            } else {
407                Severity::Warning
408            },
409            confidence: Confidence::new(0.88),
410            title: format!("Performance Degradation: +{:.1}%", change_pct),
411            description: format!(
412                "P95 latency has increased by {:.1}% from baseline {:.2}ms to current {:.2}ms. This indicates a performance regression that should be investigated.",
413                change_pct, baseline, current
414            ),
415            category: InsightCategory::Performance,
416            evidence: vec![
417                Evidence {
418                    evidence_type: EvidenceType::Comparison,
419                    description: "Baseline vs current comparison".to_string(),
420                    data: serde_json::json!({
421                        "baseline_p95_ms": baseline,
422                        "current_p95_ms": current,
423                        "change_pct": change_pct
424                    }),
425                    timestamp: Utc::now(),
426                },
427            ],
428            metrics: {
429                let mut m = HashMap::new();
430                m.insert("baseline_p95_ms".to_string(), baseline);
431                m.insert("current_p95_ms".to_string(), current);
432                m.insert("degradation_pct".to_string(), change_pct);
433                m
434            },
435            tags: vec![
436                "degradation".to_string(),
437                "regression".to_string(),
438                "performance".to_string(),
439            ],
440        }
441    }
442
443    /// Generate recommendations based on insights
444    fn generate_recommendations(&self, insights: &[Insight]) -> Vec<Recommendation> {
445        let mut recommendations = Vec::new();
446
447        for insight in insights {
448            if insight.title.contains("High") && insight.title.contains("Latency") {
449                recommendations.push(Recommendation {
450                    id: Uuid::new_v4().to_string(),
451                    analyzer: "performance".to_string(),
452                    timestamp: Utc::now(),
453                    priority: if insight.severity == Severity::Critical {
454                        Priority::Urgent
455                    } else {
456                        Priority::High
457                    },
458                    title: "Optimize latency-critical paths".to_string(),
459                    description: "Consider enabling caching, optimizing database queries, or scaling resources to reduce latency.".to_string(),
460                    action: Action {
461                        action_type: ActionType::Optimize,
462                        instructions: "1. Enable response caching for frequently requested prompts\n2. Review and optimize slow database queries\n3. Consider horizontal scaling if CPU/memory is constrained".to_string(),
463                        parameters: HashMap::new(),
464                        estimated_effort: Some(4.0),
465                        risk_level: RiskLevel::Low,
466                    },
467                    expected_impact: Impact {
468                        performance: Some(ImpactMetric {
469                            name: "p95_latency".to_string(),
470                            current: insight.metrics.get("p95_latency_ms").copied().unwrap_or(0.0),
471                            expected: insight.metrics.get("p95_latency_ms").copied().unwrap_or(0.0) * 0.7, // 30% improvement
472                            unit: "ms".to_string(),
473                            improvement_pct: 30.0,
474                        }),
475                        cost: None,
476                        quality: None,
477                        description: "Expected 30% latency reduction".to_string(),
478                    },
479                    confidence: Confidence::new(0.75),
480                    related_insights: vec![insight.id.clone()],
481                    tags: vec!["optimization".to_string(), "latency".to_string()],
482                });
483            }
484
485            if insight.title.contains("Low Throughput") {
486                recommendations.push(Recommendation {
487                    id: Uuid::new_v4().to_string(),
488                    analyzer: "performance".to_string(),
489                    timestamp: Utc::now(),
490                    priority: Priority::Medium,
491                    title: "Scale resources to improve throughput".to_string(),
492                    description: "Current throughput is below target. Consider scaling horizontally or vertically.".to_string(),
493                    action: Action {
494                        action_type: ActionType::Scale,
495                        instructions: "1. Monitor CPU and memory utilization\n2. Scale horizontally by adding more instances\n3. Consider load balancing improvements".to_string(),
496                        parameters: HashMap::new(),
497                        estimated_effort: Some(2.0),
498                        risk_level: RiskLevel::Low,
499                    },
500                    expected_impact: Impact {
501                        performance: Some(ImpactMetric {
502                            name: "throughput".to_string(),
503                            current: insight.metrics.get("throughput_rps").copied().unwrap_or(0.0),
504                            expected: insight.metrics.get("throughput_rps").copied().unwrap_or(0.0) * 2.0, // 2x improvement
505                            unit: "req/s".to_string(),
506                            improvement_pct: 100.0,
507                        }),
508                        cost: None,
509                        quality: None,
510                        description: "Expected 2x throughput increase".to_string(),
511                    },
512                    confidence: Confidence::new(0.80),
513                    related_insights: vec![insight.id.clone()],
514                    tags: vec!["scaling".to_string(), "throughput".to_string()],
515                });
516            }
517        }
518
519        recommendations
520    }
521}
522
523#[async_trait]
524impl Analyzer for PerformanceAnalyzer {
525    fn name(&self) -> &str {
526        "performance"
527    }
528
529    fn config(&self) -> &AnalyzerConfig {
530        &self.config.base
531    }
532
533    fn state(&self) -> AnalyzerState {
534        self.state
535    }
536
537    async fn start(&mut self) -> AnalyzerResult<()> {
538        if self.state != AnalyzerState::Initialized && self.state != AnalyzerState::Stopped {
539            return Err(AnalyzerError::InvalidState(format!(
540                "Cannot start from state: {:?}",
541                self.state
542            )));
543        }
544
545        self.state = AnalyzerState::Starting;
546        info!("Starting Performance Analyzer");
547
548        // Initialization logic here
549
550        self.state = AnalyzerState::Running;
551        info!("Performance Analyzer started successfully");
552
553        Ok(())
554    }
555
556    async fn stop(&mut self) -> AnalyzerResult<()> {
557        if !matches!(
558            self.state,
559            AnalyzerState::Running | AnalyzerState::Draining
560        ) {
561            return Err(AnalyzerError::InvalidState(format!(
562                "Cannot stop from state: {:?}",
563                self.state
564            )));
565        }
566
567        self.state = AnalyzerState::Draining;
568        info!("Stopping Performance Analyzer");
569
570        // Cleanup logic here
571
572        self.state = AnalyzerState::Stopped;
573        info!("Performance Analyzer stopped");
574
575        Ok(())
576    }
577
578    async fn process_event(&mut self, event: AnalyzerEvent) -> AnalyzerResult<()> {
579        if !self.state.can_accept_events() {
580            return Err(AnalyzerError::InvalidState(format!(
581                "Cannot process events in state: {:?}",
582                self.state
583            )));
584        }
585
586        let start = Instant::now();
587
588        let result = match &event {
589            AnalyzerEvent::Response { .. } => self.process_response(event).await,
590            AnalyzerEvent::Metric { .. } => self.process_metric(event).await,
591            _ => Ok(()), // Ignore other event types
592        };
593
594        // Update stats
595        let mut stats = self.stats.write().await;
596        stats.events_processed += 1;
597        let elapsed = start.elapsed().as_micros() as u64;
598        stats.analysis_time_ms += elapsed / 1000;
599        stats.avg_analysis_time_us =
600            (stats.analysis_time_ms * 1000) as f64 / stats.events_processed as f64;
601
602        if result.is_err() {
603            stats.error_count += 1;
604            stats.last_error = result.as_ref().err().map(|e| e.to_string());
605        }
606
607        result
608    }
609
610    async fn generate_report(&self) -> AnalyzerResult<AnalysisReport> {
611        let metrics = self.metrics.read().await;
612        let stats = self.stats.read().await;
613        let last_report = *self.last_report_time.read().await;
614
615        let insights = self.analyze_metrics().await;
616        let recommendations = self.generate_recommendations(&insights);
617        let alerts = Vec::new(); // Alerts would be generated based on thresholds
618
619        let mut percentiles = metrics.calculate_percentiles();
620        let throughput = metrics.calculate_throughput();
621        let success_rate = metrics.get_success_rate();
622
623        let p95_latency = percentiles.get("p95.0").copied().unwrap_or(0.0);
624
625        let mut report_metrics = HashMap::new();
626        for (k, v) in percentiles.drain() {
627            report_metrics.insert(k, v);
628        }
629        report_metrics.insert("throughput_rps".to_string(), throughput);
630        report_metrics.insert("success_rate".to_string(), success_rate);
631
632        let report = AnalysisReport {
633            analyzer: "performance".to_string(),
634            timestamp: Utc::now(),
635            period_start: Utc::now() - chrono::Duration::from_std(last_report.elapsed()).unwrap(),
636            period_end: Utc::now(),
637            summary: ReportSummary {
638                events_processed: stats.events_processed,
639                events_per_second: stats.events_processed as f64
640                    / last_report.elapsed().as_secs_f64(),
641                insights_count: insights.len() as u64,
642                recommendations_count: recommendations.len() as u64,
643                alerts_count: alerts.len() as u64,
644                analysis_duration_ms: stats.analysis_time_ms,
645                highlights: vec![
646                    format!("P95 latency: {:.2}ms", p95_latency),
647                    format!("Throughput: {:.2} req/s", throughput),
648                    format!("Success rate: {:.1}%", success_rate * 100.0),
649                ],
650            },
651            insights,
652            recommendations,
653            alerts,
654            metrics: report_metrics,
655        };
656
657        // Update last report time
658        *self.last_report_time.write().await = Instant::now();
659
660        Ok(report)
661    }
662
663    fn get_stats(&self) -> AnalyzerStats {
664        // This is synchronous, so we can't use .await
665        // In production, you'd want to make this async or use blocking read
666        AnalyzerStats {
667            analyzer: "performance".to_string(),
668            uptime_seconds: self.start_time.elapsed().as_secs(),
669            ..Default::default()
670        }
671    }
672
673    async fn reset(&mut self) -> AnalyzerResult<()> {
674        let mut metrics = self.metrics.write().await;
675        *metrics = PerformanceMetrics::new(self.config.percentile_window_size);
676
677        let mut stats = self.stats.write().await;
678        *stats = AnalyzerStats {
679            analyzer: "performance".to_string(),
680            ..Default::default()
681        };
682
683        info!("Performance Analyzer reset");
684        Ok(())
685    }
686
687    async fn health_check(&self) -> AnalyzerResult<()> {
688        let stats = self.stats.read().await;
689
690        // Check error rate
691        if stats.events_processed > 0 {
692            let error_rate = stats.error_count as f64 / stats.events_processed as f64;
693            if error_rate > 0.1 {
694                return Err(AnalyzerError::InternalError(format!(
695                    "High error rate: {:.2}%",
696                    error_rate * 100.0
697                )));
698            }
699        }
700
701        // Check memory usage
702        if stats.memory_usage_bytes > self.config.base.max_memory_bytes {
703            return Err(AnalyzerError::ResourceExhausted(format!(
704                "Memory usage {} exceeds limit {}",
705                stats.memory_usage_bytes, self.config.base.max_memory_bytes
706            )));
707        }
708
709        Ok(())
710    }
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716
717    fn create_test_response_event(latency_ms: u64, success: bool) -> AnalyzerEvent {
718        AnalyzerEvent::Response {
719            timestamp: Utc::now(),
720            request_id: Uuid::new_v4().to_string(),
721            model: "gpt-4".to_string(),
722            completion_tokens: 100,
723            total_tokens: 200,
724            latency_ms,
725            success,
726            error: if success { None } else { Some("Test error".to_string()) },
727            metadata: HashMap::new(),
728        }
729    }
730
731    #[tokio::test]
732    async fn test_analyzer_lifecycle() {
733        let mut analyzer = PerformanceAnalyzer::with_defaults();
734
735        assert_eq!(analyzer.state(), AnalyzerState::Initialized);
736
737        analyzer.start().await.unwrap();
738        assert_eq!(analyzer.state(), AnalyzerState::Running);
739
740        analyzer.stop().await.unwrap();
741        assert_eq!(analyzer.state(), AnalyzerState::Stopped);
742    }
743
744    #[tokio::test]
745    async fn test_process_response_event() {
746        let mut analyzer = PerformanceAnalyzer::with_defaults();
747        analyzer.start().await.unwrap();
748
749        let event = create_test_response_event(500, true);
750        analyzer.process_event(event).await.unwrap();
751
752        let metrics = analyzer.metrics.read().await;
753        assert_eq!(metrics.latencies.len(), 1);
754        assert_eq!(metrics.success_count, 1);
755    }
756
757    #[tokio::test]
758    async fn test_high_latency_detection() {
759        let mut analyzer = PerformanceAnalyzer::with_defaults();
760        analyzer.start().await.unwrap();
761
762        // Add several high-latency events
763        for _ in 0..100 {
764            let event = create_test_response_event(2000, true); // 2 seconds
765            analyzer.process_event(event).await.unwrap();
766        }
767
768        let insights = analyzer.analyze_metrics().await;
769        assert!(!insights.is_empty());
770        assert!(insights
771            .iter()
772            .any(|i| i.title.contains("High") && i.title.contains("Latency")));
773    }
774
775    #[tokio::test]
776    async fn test_low_success_rate_detection() {
777        let mut analyzer = PerformanceAnalyzer::with_defaults();
778        analyzer.start().await.unwrap();
779
780        // Add events with 80% failure rate
781        for i in 0..100 {
782            let event = create_test_response_event(500, i >= 80);
783            analyzer.process_event(event).await.unwrap();
784        }
785
786        let insights = analyzer.analyze_metrics().await;
787        assert!(insights
788            .iter()
789            .any(|i| i.title.contains("Low Success Rate")));
790    }
791
792    #[tokio::test]
793    async fn test_generate_report() {
794        let mut analyzer = PerformanceAnalyzer::with_defaults();
795        analyzer.start().await.unwrap();
796
797        // Add some test events
798        for _ in 0..10 {
799            let event = create_test_response_event(500, true);
800            analyzer.process_event(event).await.unwrap();
801        }
802
803        let report = analyzer.generate_report().await.unwrap();
804        assert_eq!(report.analyzer, "performance");
805        assert_eq!(report.summary.events_processed, 10);
806        assert!(!report.metrics.is_empty());
807    }
808
809    #[tokio::test]
810    async fn test_reset() {
811        let mut analyzer = PerformanceAnalyzer::with_defaults();
812        analyzer.start().await.unwrap();
813
814        let event = create_test_response_event(500, true);
815        analyzer.process_event(event).await.unwrap();
816
817        analyzer.reset().await.unwrap();
818
819        let metrics = analyzer.metrics.read().await;
820        assert_eq!(metrics.latencies.len(), 0);
821        assert_eq!(metrics.success_count, 0);
822    }
823}