1use 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#[derive(Debug, Clone)]
30pub struct PerformanceAnalyzerConfig {
31 pub base: AnalyzerConfig,
33
34 pub latency_warning_ms: u64,
36 pub latency_critical_ms: u64,
37
38 pub throughput_low_rps: f64,
40
41 pub degradation_threshold_pct: f64,
43
44 pub percentile_window_size: usize,
46
47 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, latency_critical_ms: 5000, 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#[derive(Debug, Clone)]
67struct PerformanceMetrics {
68 latencies: CircularBuffer<f64>,
70
71 tokens_per_request: CircularBuffer<u32>,
73
74 request_times: CircularBuffer<DateTime<Utc>>,
76
77 success_count: u64,
79 failure_count: u64,
80
81 latency_ema: ExponentialMovingAverage,
83
84 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
161pub 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 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 pub fn with_defaults() -> Self {
191 Self::new(PerformanceAnalyzerConfig::default())
192 }
193
194 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 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 async fn analyze_metrics(&self) -> Vec<Insight> {
241 let metrics = self.metrics.read().await;
242 let mut insights = Vec::new();
243
244 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 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 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 if self.config.enable_regression_detection {
268 if let Some(baseline_p95) = metrics.baseline_p95_ms {
269 if let Some(¤t_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 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 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 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 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 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, 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, 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 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 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(()), };
593
594 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(); 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 *self.last_report_time.write().await = Instant::now();
659
660 Ok(report)
661 }
662
663 fn get_stats(&self) -> AnalyzerStats {
664 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 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 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 for _ in 0..100 {
764 let event = create_test_response_event(2000, true); 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 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 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}