reasonkit/orchestration/
performance_tracker.rs

1//! # Performance Tracking System
2//!
3//! This module provides comprehensive performance monitoring for long-horizon operations,
4//! tracking tool calls, resource usage, and optimization opportunities across 100+ tool calling sequences.
5
6use std::collections::{HashMap, VecDeque};
7use std::sync::Arc;
8use tokio::sync::Mutex;
9
10use crate::error::Error;
11
12/// Real-time performance tracker for long-horizon operations
13pub struct PerformanceTracker {
14    /// Real-time metrics collector
15    real_time_metrics: Arc<Mutex<RealTimeMetrics>>,
16    /// Performance history for analysis
17    performance_history: Arc<Mutex<VecDeque<PerformanceRecord>>>,
18    /// Resource utilization tracker
19    resource_tracker: Arc<Mutex<ResourceUtilizationTracker>>,
20    /// Optimization analyzer
21    optimizer: Arc<Mutex<PerformanceOptimizer>>,
22    /// Configuration
23    config: PerformanceTrackerConfig,
24}
25
26impl Default for PerformanceTracker {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl PerformanceTracker {
33    pub fn new() -> Self {
34        Self {
35            real_time_metrics: Arc::new(Mutex::new(RealTimeMetrics::new())),
36            performance_history: Arc::new(Mutex::new(VecDeque::new())),
37            resource_tracker: Arc::new(Mutex::new(ResourceUtilizationTracker::new())),
38            optimizer: Arc::new(Mutex::new(PerformanceOptimizer::new())),
39            config: PerformanceTrackerConfig::default(),
40        }
41    }
42
43    /// Record a tool call with performance metrics
44    pub async fn record_tool_call(
45        &self,
46        tool_call_id: u32,
47        duration_ms: u64,
48        cost: f64,
49    ) -> Result<(), Error> {
50        let timestamp = chrono::Utc::now();
51
52        // Update real-time metrics
53        {
54            let mut metrics = self.real_time_metrics.lock().await;
55            metrics.record_tool_call(tool_call_id, duration_ms, cost, timestamp);
56        }
57
58        // Update resource utilization
59        {
60            let mut tracker = self.resource_tracker.lock().await;
61            tracker.record_tool_call(duration_ms).await;
62        }
63
64        // Analyze performance for optimization opportunities
65        {
66            let mut optimizer = self.optimizer.lock().await;
67            optimizer
68                .analyze_tool_call(tool_call_id, duration_ms, cost)
69                .await;
70        }
71
72        tracing::debug!(
73            "Recorded tool call {}: {}ms, ${:.4}",
74            tool_call_id,
75            duration_ms,
76            cost
77        );
78
79        Ok(())
80    }
81
82    /// Record execution of a complex operation
83    pub async fn record_operation(
84        &self,
85        operation_name: &str,
86        tool_calls_used: u32,
87        duration_ms: u64,
88        success: bool,
89        metadata: serde_json::Value,
90    ) -> Result<(), Error> {
91        let timestamp = chrono::Utc::now();
92        let record = PerformanceRecord {
93            id: format!("op_{}_{}", operation_name, timestamp.timestamp()),
94            operation_name: operation_name.to_string(),
95            timestamp,
96            tool_calls_used,
97            duration_ms,
98            success,
99            metadata,
100        };
101
102        // Add to history
103        {
104            let mut history = self.performance_history.lock().await;
105            history.push_back(record.clone());
106
107            // Maintain history limit
108            if history.len() > self.config.max_history_records {
109                history.pop_front();
110            }
111        }
112
113        // Update real-time metrics
114        {
115            let mut metrics = self.real_time_metrics.lock().await;
116            metrics.record_operation(&record);
117        }
118
119        // Analyze operation performance
120        {
121            let mut optimizer = self.optimizer.lock().await;
122            optimizer.analyze_operation(&record).await;
123        }
124
125        tracing::info!(
126            "Recorded operation '{}': {} tool calls, {}ms, success: {}",
127            operation_name,
128            tool_calls_used,
129            duration_ms,
130            success
131        );
132
133        Ok(())
134    }
135
136    /// Get current real-time metrics
137    pub async fn get_real_time_metrics(&self) -> Result<RealTimeMetrics, Error> {
138        let metrics = self.real_time_metrics.lock().await;
139        Ok(metrics.clone())
140    }
141
142    /// Get performance summary for a time window
143    pub async fn get_performance_summary(
144        &self,
145        window_duration: chrono::Duration,
146    ) -> Result<PerformanceSummary, Error> {
147        let now = chrono::Utc::now();
148        let cutoff = now - window_duration;
149
150        let history = self.performance_history.lock().await;
151        let recent_records: Vec<_> = history
152            .iter()
153            .filter(|record| record.timestamp >= cutoff)
154            .cloned()
155            .collect();
156
157        if recent_records.is_empty() {
158            return Ok(PerformanceSummary::empty());
159        }
160
161        let total_tool_calls: u32 = recent_records.iter().map(|r| r.tool_calls_used).sum();
162        let total_duration_ms: u64 = recent_records.iter().map(|r| r.duration_ms).sum();
163        let success_count = recent_records.iter().filter(|r| r.success).count();
164        let total_records = recent_records.len();
165
166        let avg_tool_calls_per_operation = total_tool_calls as f64 / total_records as f64;
167        let avg_duration_per_operation = total_duration_ms as f64 / total_records as f64;
168        let success_rate = success_count as f64 / total_records as f64;
169
170        // Calculate throughput (operations per minute)
171        let minutes = window_duration.num_minutes() as f64;
172        let throughput_per_minute = if minutes > 0.0 {
173            total_records as f64 / minutes
174        } else {
175            0.0
176        };
177
178        // Calculate efficiency score
179        let efficiency_score = self.calculate_efficiency_score(&recent_records);
180
181        // Identify performance bottlenecks
182        let bottlenecks = self.identify_bottlenecks(&recent_records).await;
183
184        Ok(PerformanceSummary {
185            time_window: window_duration,
186            total_operations: total_records,
187            total_tool_calls,
188            total_duration_ms,
189            avg_tool_calls_per_operation,
190            avg_duration_per_operation,
191            success_rate,
192            throughput_per_minute,
193            efficiency_score,
194            bottlenecks,
195            top_performing_operations: self.get_top_performing_operations(&recent_records),
196            recommendations: self
197                .generate_optimization_recommendations(&recent_records)
198                .await,
199        })
200    }
201
202    /// Get resource utilization statistics
203    pub async fn get_resource_utilization(&self) -> Result<ResourceUtilization, Error> {
204        let tracker = self.resource_tracker.lock().await;
205        Ok(tracker.get_utilization_stats())
206    }
207
208    /// Get optimization recommendations
209    pub async fn get_optimization_recommendations(
210        &self,
211    ) -> Result<Vec<OptimizationRecommendation>, Error> {
212        let optimizer = self.optimizer.lock().await;
213        Ok(optimizer.get_recommendations().await)
214    }
215
216    /// Reset performance tracking
217    pub async fn reset(&self) -> Result<(), Error> {
218        {
219            let mut metrics = self.real_time_metrics.lock().await;
220            metrics.reset();
221        }
222
223        {
224            let mut history = self.performance_history.lock().await;
225            history.clear();
226        }
227
228        {
229            let mut tracker = self.resource_tracker.lock().await;
230            tracker.reset();
231        }
232
233        {
234            let mut optimizer = self.optimizer.lock().await;
235            optimizer.reset();
236        }
237
238        tracing::info!("Performance tracker reset");
239        Ok(())
240    }
241
242    /// Calculate efficiency score based on multiple factors
243    fn calculate_efficiency_score(&self, records: &[PerformanceRecord]) -> f64 {
244        if records.is_empty() {
245            return 1.0;
246        }
247
248        let success_rate =
249            records.iter().filter(|r| r.success).count() as f64 / records.len() as f64;
250
251        // Calculate average tool calls efficiency
252        let avg_tool_calls =
253            records.iter().map(|r| r.tool_calls_used).sum::<u32>() as f64 / records.len() as f64;
254        let tool_call_efficiency = (avg_tool_calls / 100.0).min(1.0); // Normalize to 100 tool calls
255
256        // Calculate time efficiency
257        let avg_duration =
258            records.iter().map(|r| r.duration_ms).sum::<u64>() as f64 / records.len() as f64;
259        let time_efficiency = (60000.0 / avg_duration).min(1.0); // Normalize to 1 minute per operation
260
261        // Weighted combination
262        (success_rate * 0.4 + tool_call_efficiency * 0.3 + time_efficiency * 0.3).clamp(0.0, 1.0)
263    }
264
265    /// Identify performance bottlenecks
266    async fn identify_bottlenecks(
267        &self,
268        records: &[PerformanceRecord],
269    ) -> Vec<PerformanceBottleneck> {
270        let mut bottlenecks = Vec::new();
271
272        // Analyze operation durations
273        let avg_duration =
274            records.iter().map(|r| r.duration_ms).sum::<u64>() as f64 / records.len() as f64;
275        let slow_operations: Vec<_> = records
276            .iter()
277            .filter(|r| r.duration_ms as f64 > avg_duration * 2.0)
278            .collect();
279
280        if !slow_operations.is_empty() {
281            bottlenecks.push(PerformanceBottleneck {
282                type_: BottleneckType::SlowOperations,
283                severity: if slow_operations.len() as f64 / records.len() as f64 > 0.3 {
284                    BottleneckSeverity::High
285                } else {
286                    BottleneckSeverity::Medium
287                },
288                description: format!(
289                    "{} operations are taking longer than average ({}ms avg)",
290                    slow_operations.len(),
291                    avg_duration as u64
292                ),
293                affected_operations: slow_operations
294                    .iter()
295                    .map(|r| r.operation_name.clone())
296                    .collect(),
297            });
298        }
299
300        // Analyze tool call counts
301        let avg_tool_calls =
302            records.iter().map(|r| r.tool_calls_used).sum::<u32>() as f64 / records.len() as f64;
303        let high_tool_call_ops: Vec<_> = records
304            .iter()
305            .filter(|r| r.tool_calls_used as f64 > avg_tool_calls * 2.0)
306            .collect();
307
308        if !high_tool_call_ops.is_empty() {
309            bottlenecks.push(PerformanceBottleneck {
310                type_: BottleneckType::ExcessiveToolCalls,
311                severity: if high_tool_call_ops.len() as f64 / records.len() as f64 > 0.2 {
312                    BottleneckSeverity::High
313                } else {
314                    BottleneckSeverity::Low
315                },
316                description: format!(
317                    "{} operations use excessive tool calls ({} avg)",
318                    high_tool_call_ops.len(),
319                    avg_tool_calls
320                ),
321                affected_operations: high_tool_call_ops
322                    .iter()
323                    .map(|r| r.operation_name.clone())
324                    .collect(),
325            });
326        }
327
328        // Analyze failure rates
329        let failure_rate =
330            records.iter().filter(|r| !r.success).count() as f64 / records.len() as f64;
331        if failure_rate > 0.1 {
332            bottlenecks.push(PerformanceBottleneck {
333                type_: BottleneckType::HighFailureRate,
334                severity: if failure_rate > 0.3 {
335                    BottleneckSeverity::Critical
336                } else {
337                    BottleneckSeverity::High
338                },
339                description: format!("High failure rate: {:.1}%", failure_rate * 100.0),
340                affected_operations: records
341                    .iter()
342                    .filter(|r| !r.success)
343                    .map(|r| r.operation_name.clone())
344                    .collect(),
345            });
346        }
347
348        bottlenecks
349    }
350
351    /// Get top performing operations
352    fn get_top_performing_operations(
353        &self,
354        records: &[PerformanceRecord],
355    ) -> Vec<OperationPerformance> {
356        let mut operations: HashMap<String, Vec<&PerformanceRecord>> = HashMap::new();
357
358        // Group records by operation name
359        for record in records {
360            operations
361                .entry(record.operation_name.clone())
362                .or_default()
363                .push(record);
364        }
365
366        // Calculate performance metrics for each operation
367        let mut performance_data = Vec::new();
368        for (op_name, op_records) in operations {
369            let total_calls: u32 = op_records.iter().map(|r| r.tool_calls_used).sum();
370            let total_duration: u64 = op_records.iter().map(|r| r.duration_ms).sum();
371            let success_count = op_records.iter().filter(|r| r.success).count();
372            let total_ops = op_records.len();
373
374            performance_data.push(OperationPerformance {
375                operation_name: op_name,
376                total_executions: total_ops,
377                avg_tool_calls: total_calls as f64 / total_ops as f64,
378                avg_duration_ms: total_duration as f64 / total_ops as f64,
379                success_rate: success_count as f64 / total_ops as f64,
380                efficiency_score: self.calculate_operation_efficiency(&op_records),
381            });
382        }
383
384        // Sort by efficiency score and return top 5
385        performance_data
386            .sort_by(|a, b| b.efficiency_score.partial_cmp(&a.efficiency_score).unwrap());
387        performance_data.into_iter().take(5).collect()
388    }
389
390    /// Calculate efficiency score for a specific operation
391    fn calculate_operation_efficiency(&self, records: &[&PerformanceRecord]) -> f64 {
392        if records.is_empty() {
393            return 0.0;
394        }
395
396        let success_rate =
397            records.iter().filter(|r| r.success).count() as f64 / records.len() as f64;
398        let avg_duration =
399            records.iter().map(|r| r.duration_ms).sum::<u64>() as f64 / records.len() as f64;
400        let avg_tool_calls =
401            records.iter().map(|r| r.tool_calls_used).sum::<u32>() as f64 / records.len() as f64;
402
403        // Efficiency is inverse of duration and tool calls, weighted by success rate
404        let duration_score = (10000.0 / avg_duration).min(1.0);
405        let tool_call_score = (50.0 / avg_tool_calls).min(1.0);
406
407        success_rate * 0.5 + duration_score * 0.3 + tool_call_score * 0.2
408    }
409
410    /// Generate optimization recommendations
411    async fn generate_optimization_recommendations(
412        &self,
413        records: &[PerformanceRecord],
414    ) -> Vec<OptimizationRecommendation> {
415        let mut recommendations = Vec::new();
416
417        // Analyze patterns and generate recommendations
418        let avg_duration =
419            records.iter().map(|r| r.duration_ms).sum::<u64>() as f64 / records.len() as f64;
420        let avg_tool_calls =
421            records.iter().map(|r| r.tool_calls_used).sum::<u32>() as f64 / records.len() as f64;
422
423        if avg_duration > 30000.0 {
424            recommendations.push(OptimizationRecommendation {
425                priority: RecommendationPriority::High,
426                category: RecommendationCategory::Performance,
427                title: "Optimize Operation Duration".to_string(),
428                description: format!(
429                    "Average operation duration is {:.1} seconds. Consider optimizing algorithms or parallelizing operations.",
430                    avg_duration / 1000.0
431                ),
432                estimated_impact: ImpactLevel::High,
433                implementation_effort: ImplementationEffort::Medium,
434            });
435        }
436
437        if avg_tool_calls > 50.0 {
438            recommendations.push(OptimizationRecommendation {
439                priority: RecommendationPriority::Medium,
440                category: RecommendationCategory::ToolEfficiency,
441                title: "Reduce Tool Call Count".to_string(),
442                description: format!(
443                    "Average tool calls per operation is {:.1}. Consider batching operations or caching results.",
444                    avg_tool_calls
445                ),
446                estimated_impact: ImpactLevel::Medium,
447                implementation_effort: ImplementationEffort::Low,
448            });
449        }
450
451        // Check for consistent patterns that could benefit from caching
452        let operation_counts: HashMap<String, usize> = records
453            .iter()
454            .map(|r| (r.operation_name.clone(), 1))
455            .fold(HashMap::new(), |mut acc, (name, count)| {
456                *acc.entry(name).or_insert(0) += count;
457                acc
458            });
459
460        let frequently_used_ops: Vec<_> = operation_counts
461            .iter()
462            .filter(|(_, count)| **count >= 5)
463            .map(|(name, _)| name)
464            .collect();
465
466        if !frequently_used_ops.is_empty() {
467            recommendations.push(OptimizationRecommendation {
468                priority: RecommendationPriority::Medium,
469                category: RecommendationCategory::Caching,
470                title: "Implement Result Caching".to_string(),
471                description: format!(
472                    "Operations {} are executed frequently. Consider implementing result caching to improve performance.",
473                    frequently_used_ops.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", ")
474                ),
475                estimated_impact: ImpactLevel::High,
476                implementation_effort: ImplementationEffort::Low,
477            });
478        }
479
480        recommendations
481    }
482}
483
484/// Real-time performance metrics
485#[derive(Debug, Clone)]
486pub struct RealTimeMetrics {
487    pub current_tool_call_id: u32,
488    pub total_tool_calls_today: u32,
489    pub avg_duration_ms: f64,
490    pub avg_cost_per_call: f64,
491    pub current_throughput_per_minute: f64,
492    pub error_rate: f64,
493    pub last_update: chrono::DateTime<chrono::Utc>,
494    pub rolling_window: VecDeque<PerformanceSample>,
495}
496
497impl Default for RealTimeMetrics {
498    fn default() -> Self {
499        Self::new()
500    }
501}
502
503impl RealTimeMetrics {
504    pub fn new() -> Self {
505        Self {
506            current_tool_call_id: 0,
507            total_tool_calls_today: 0,
508            avg_duration_ms: 0.0,
509            avg_cost_per_call: 0.0,
510            current_throughput_per_minute: 0.0,
511            error_rate: 0.0,
512            last_update: chrono::Utc::now(),
513            rolling_window: VecDeque::new(),
514        }
515    }
516
517    pub fn record_tool_call(
518        &mut self,
519        tool_call_id: u32,
520        duration_ms: u64,
521        cost: f64,
522        timestamp: chrono::DateTime<chrono::Utc>,
523    ) {
524        self.current_tool_call_id = tool_call_id;
525        self.total_tool_calls_today += 1;
526        self.last_update = timestamp;
527
528        // Add to rolling window (last 100 calls)
529        let sample = PerformanceSample {
530            timestamp,
531            duration_ms,
532            cost,
533            success: true, // Would be determined by actual execution
534        };
535
536        self.rolling_window.push_back(sample);
537        if self.rolling_window.len() > 100 {
538            self.rolling_window.pop_front();
539        }
540
541        // Update averages
542        let total_samples = self.rolling_window.len() as f64;
543        self.avg_duration_ms = self
544            .rolling_window
545            .iter()
546            .map(|s| s.duration_ms as f64)
547            .sum::<f64>()
548            / total_samples;
549
550        self.avg_cost_per_call =
551            self.rolling_window.iter().map(|s| s.cost).sum::<f64>() / total_samples;
552
553        // Calculate throughput (calls per minute in rolling window)
554        if let (Some(first), Some(last)) = (self.rolling_window.front(), self.rolling_window.back())
555        {
556            let time_span = (last.timestamp - first.timestamp).num_seconds() as f64;
557            if time_span > 0.0 {
558                self.current_throughput_per_minute = (total_samples * 60.0) / time_span;
559            }
560        }
561    }
562
563    pub fn record_operation(&mut self, record: &PerformanceRecord) {
564        // Update error rate based on success
565        let total_ops = self.rolling_window.len() as f64 + 1.0;
566        let successful_ops = self.rolling_window.iter().filter(|s| s.success).count() as f64
567            + if record.success { 1.0 } else { 0.0 };
568        self.error_rate = 1.0 - (successful_ops / total_ops);
569    }
570
571    pub fn reset(&mut self) {
572        self.current_tool_call_id = 0;
573        self.total_tool_calls_today = 0;
574        self.avg_duration_ms = 0.0;
575        self.avg_cost_per_call = 0.0;
576        self.current_throughput_per_minute = 0.0;
577        self.error_rate = 0.0;
578        self.rolling_window.clear();
579    }
580}
581
582/// Performance sample for rolling window
583#[derive(Debug, Clone)]
584pub struct PerformanceSample {
585    timestamp: chrono::DateTime<chrono::Utc>,
586    duration_ms: u64,
587    cost: f64,
588    success: bool,
589}
590
591/// Resource utilization tracker
592#[derive(Debug)]
593struct ResourceUtilizationTracker {
594    memory_usage_mb: VecDeque<f64>,
595    cpu_usage_percent: VecDeque<f64>,
596    network_io_mb: VecDeque<f64>,
597    disk_io_mb: VecDeque<f64>,
598    peak_memory_mb: f64,
599    peak_cpu_percent: f64,
600}
601
602impl ResourceUtilizationTracker {
603    fn new() -> Self {
604        Self {
605            memory_usage_mb: VecDeque::new(),
606            cpu_usage_percent: VecDeque::new(),
607            network_io_mb: VecDeque::new(),
608            disk_io_mb: VecDeque::new(),
609            peak_memory_mb: 0.0,
610            peak_cpu_percent: 0.0,
611        }
612    }
613
614    async fn record_tool_call(&mut self, duration_ms: u64) {
615        // Simulate resource usage (in real implementation, would get actual system metrics)
616        let simulated_memory = 100.0 + (duration_ms as f64 / 1000.0) * 10.0;
617        let simulated_cpu = 10.0 + (duration_ms as f64 / 1000.0) * 5.0;
618        let simulated_network = duration_ms as f64 / 1000.0 * 2.0;
619        let simulated_disk = duration_ms as f64 / 1000.0 * 1.0;
620
621        self.memory_usage_mb.push_back(simulated_memory);
622        self.cpu_usage_percent.push_back(simulated_cpu);
623        self.network_io_mb.push_back(simulated_network);
624        self.disk_io_mb.push_back(simulated_disk);
625
626        // Maintain rolling window
627        let max_samples = 50;
628        if self.memory_usage_mb.len() > max_samples {
629            self.memory_usage_mb.pop_front();
630            self.cpu_usage_percent.pop_front();
631            self.network_io_mb.pop_front();
632            self.disk_io_mb.pop_front();
633        }
634
635        // Update peaks
636        self.peak_memory_mb = self.peak_memory_mb.max(simulated_memory);
637        self.peak_cpu_percent = self.peak_cpu_percent.max(simulated_cpu);
638    }
639
640    fn get_utilization_stats(&self) -> ResourceUtilization {
641        let avg_memory = if !self.memory_usage_mb.is_empty() {
642            self.memory_usage_mb.iter().sum::<f64>() / self.memory_usage_mb.len() as f64
643        } else {
644            0.0
645        };
646
647        let avg_cpu = if !self.cpu_usage_percent.is_empty() {
648            self.cpu_usage_percent.iter().sum::<f64>() / self.cpu_usage_percent.len() as f64
649        } else {
650            0.0
651        };
652
653        let avg_network = if !self.network_io_mb.is_empty() {
654            self.network_io_mb.iter().sum::<f64>() / self.network_io_mb.len() as f64
655        } else {
656            0.0
657        };
658
659        let avg_disk = if !self.disk_io_mb.is_empty() {
660            self.disk_io_mb.iter().sum::<f64>() / self.disk_io_mb.len() as f64
661        } else {
662            0.0
663        };
664
665        ResourceUtilization {
666            avg_memory_usage_mb: avg_memory,
667            peak_memory_usage_mb: self.peak_memory_mb,
668            avg_cpu_usage_percent: avg_cpu,
669            peak_cpu_usage_percent: self.peak_cpu_percent,
670            avg_network_io_mb_per_call: avg_network,
671            avg_disk_io_mb_per_call: avg_disk,
672            memory_efficiency: if self.peak_memory_mb > 0.0 {
673                avg_memory / self.peak_memory_mb
674            } else {
675                1.0
676            },
677        }
678    }
679
680    fn reset(&mut self) {
681        self.memory_usage_mb.clear();
682        self.cpu_usage_percent.clear();
683        self.network_io_mb.clear();
684        self.disk_io_mb.clear();
685        self.peak_memory_mb = 0.0;
686        self.peak_cpu_percent = 0.0;
687    }
688}
689
690/// Performance optimizer for identifying improvements
691#[derive(Debug)]
692struct PerformanceOptimizer {
693    analysis_cache: HashMap<String, PerformanceAnalysis>,
694    optimization_history: VecDeque<OptimizationAction>,
695}
696
697impl PerformanceOptimizer {
698    fn new() -> Self {
699        Self {
700            analysis_cache: HashMap::new(),
701            optimization_history: VecDeque::new(),
702        }
703    }
704
705    async fn analyze_tool_call(&mut self, _tool_call_id: u32, duration_ms: u64, cost: f64) {
706        // Analyze individual tool call for patterns
707        if duration_ms > 10_000 {
708            // 10 seconds
709            tracing::warn!("Slow tool call detected: {}ms", duration_ms);
710        }
711
712        if cost > 0.01 {
713            // $0.01
714            tracing::info!("High-cost tool call: ${:.4}", cost);
715        }
716    }
717
718    async fn analyze_operation(&mut self, record: &PerformanceRecord) {
719        // Analyze operation patterns
720        let analysis_key = format!(
721            "{}_{}",
722            record.operation_name,
723            record.timestamp.date_naive()
724        );
725
726        let analysis = PerformanceAnalysis {
727            operation_name: record.operation_name.clone(),
728            avg_duration_ms: record.duration_ms as f64,
729            avg_tool_calls: record.tool_calls_used as f64,
730            success_rate: if record.success { 1.0 } else { 0.0 },
731            last_analysis: chrono::Utc::now(),
732        };
733
734        self.analysis_cache.insert(analysis_key, analysis);
735    }
736
737    async fn get_recommendations(&self) -> Vec<OptimizationRecommendation> {
738        // Generate recommendations based on analysis cache
739        let mut recommendations = Vec::new();
740
741        for analysis in self.analysis_cache.values() {
742            if analysis.avg_duration_ms > 30000.0 {
743                recommendations.push(OptimizationRecommendation {
744                    priority: RecommendationPriority::High,
745                    category: RecommendationCategory::Performance,
746                    title: format!("Optimize {} duration", analysis.operation_name),
747                    description: format!(
748                        "Operation '{}' averages {:.1} seconds. Consider optimization.",
749                        analysis.operation_name,
750                        analysis.avg_duration_ms / 1000.0
751                    ),
752                    estimated_impact: ImpactLevel::High,
753                    implementation_effort: ImplementationEffort::Medium,
754                });
755            }
756
757            if analysis.avg_tool_calls > 30.0 {
758                recommendations.push(OptimizationRecommendation {
759                    priority: RecommendationPriority::Medium,
760                    category: RecommendationCategory::ToolEfficiency,
761                    title: format!("Reduce {} tool calls", analysis.operation_name),
762                    description: format!(
763                        "Operation '{}' uses {:.1} tool calls on average.",
764                        analysis.operation_name, analysis.avg_tool_calls
765                    ),
766                    estimated_impact: ImpactLevel::Medium,
767                    implementation_effort: ImplementationEffort::Low,
768                });
769            }
770        }
771
772        recommendations
773    }
774
775    fn reset(&mut self) {
776        self.analysis_cache.clear();
777        self.optimization_history.clear();
778    }
779}
780
781/// Performance record for history tracking
782#[derive(Debug, Clone)]
783pub struct PerformanceRecord {
784    pub id: String,
785    pub operation_name: String,
786    pub timestamp: chrono::DateTime<chrono::Utc>,
787    pub tool_calls_used: u32,
788    pub duration_ms: u64,
789    pub success: bool,
790    pub metadata: serde_json::Value,
791}
792
793/// Performance analysis
794#[derive(Debug, Clone)]
795#[allow(dead_code)]
796struct PerformanceAnalysis {
797    operation_name: String,
798    avg_duration_ms: f64,
799    avg_tool_calls: f64,
800    success_rate: f64,
801    last_analysis: chrono::DateTime<chrono::Utc>,
802}
803
804/// Performance summary
805#[derive(Debug, Clone)]
806pub struct PerformanceSummary {
807    pub time_window: chrono::Duration,
808    pub total_operations: usize,
809    pub total_tool_calls: u32,
810    pub total_duration_ms: u64,
811    pub avg_tool_calls_per_operation: f64,
812    pub avg_duration_per_operation: f64,
813    pub success_rate: f64,
814    pub throughput_per_minute: f64,
815    pub efficiency_score: f64,
816    pub bottlenecks: Vec<PerformanceBottleneck>,
817    pub top_performing_operations: Vec<OperationPerformance>,
818    pub recommendations: Vec<OptimizationRecommendation>,
819}
820
821impl PerformanceSummary {
822    pub fn empty() -> Self {
823        Self {
824            time_window: chrono::Duration::minutes(0),
825            total_operations: 0,
826            total_tool_calls: 0,
827            total_duration_ms: 0,
828            avg_tool_calls_per_operation: 0.0,
829            avg_duration_per_operation: 0.0,
830            success_rate: 1.0,
831            throughput_per_minute: 0.0,
832            efficiency_score: 1.0,
833            bottlenecks: Vec::new(),
834            top_performing_operations: Vec::new(),
835            recommendations: Vec::new(),
836        }
837    }
838}
839
840/// Performance bottleneck
841#[derive(Debug, Clone)]
842pub struct PerformanceBottleneck {
843    pub type_: BottleneckType,
844    pub severity: BottleneckSeverity,
845    pub description: String,
846    pub affected_operations: Vec<String>,
847}
848
849#[derive(Debug, Clone, PartialEq)]
850pub enum BottleneckType {
851    SlowOperations,
852    ExcessiveToolCalls,
853    HighFailureRate,
854    ResourceContention,
855    NetworkLatency,
856}
857
858#[derive(Debug, Clone, PartialEq)]
859pub enum BottleneckSeverity {
860    Low,
861    Medium,
862    High,
863    Critical,
864}
865
866/// Operation performance metrics
867#[derive(Debug, Clone)]
868pub struct OperationPerformance {
869    pub operation_name: String,
870    pub total_executions: usize,
871    pub avg_tool_calls: f64,
872    pub avg_duration_ms: f64,
873    pub success_rate: f64,
874    pub efficiency_score: f64,
875}
876
877/// Optimization recommendation
878#[derive(Debug, Clone)]
879pub struct OptimizationRecommendation {
880    pub priority: RecommendationPriority,
881    pub category: RecommendationCategory,
882    pub title: String,
883    pub description: String,
884    pub estimated_impact: ImpactLevel,
885    pub implementation_effort: ImplementationEffort,
886}
887
888#[derive(Debug, Clone, PartialEq)]
889pub enum RecommendationPriority {
890    Low,
891    Medium,
892    High,
893    Critical,
894}
895
896#[derive(Debug, Clone, PartialEq)]
897pub enum RecommendationCategory {
898    Performance,
899    ToolEfficiency,
900    Caching,
901    ResourceManagement,
902    ErrorHandling,
903}
904
905#[derive(Debug, Clone, PartialEq)]
906pub enum ImpactLevel {
907    Low,
908    Medium,
909    High,
910    VeryHigh,
911}
912
913#[derive(Debug, Clone, PartialEq)]
914pub enum ImplementationEffort {
915    Low,
916    Medium,
917    High,
918}
919
920/// Resource utilization statistics
921#[derive(Debug, Clone)]
922pub struct ResourceUtilization {
923    pub avg_memory_usage_mb: f64,
924    pub peak_memory_usage_mb: f64,
925    pub avg_cpu_usage_percent: f64,
926    pub peak_cpu_usage_percent: f64,
927    pub avg_network_io_mb_per_call: f64,
928    pub avg_disk_io_mb_per_call: f64,
929    pub memory_efficiency: f64,
930}
931
932/// Optimization action
933#[derive(Debug, Clone)]
934#[allow(dead_code)]
935struct OptimizationAction {
936    timestamp: chrono::DateTime<chrono::Utc>,
937    action_type: String,
938    target: String,
939    expected_improvement: f64,
940    actual_improvement: Option<f64>,
941}
942
943/// Configuration for performance tracker
944#[derive(Debug, Clone)]
945pub struct PerformanceTrackerConfig {
946    pub max_history_records: usize,
947    pub rolling_window_size: usize,
948    pub enable_real_time_monitoring: bool,
949    pub enable_resource_tracking: bool,
950    pub enable_optimization: bool,
951    pub analysis_interval_minutes: u32,
952}
953
954impl Default for PerformanceTrackerConfig {
955    fn default() -> Self {
956        Self {
957            max_history_records: 10000,
958            rolling_window_size: 100,
959            enable_real_time_monitoring: true,
960            enable_resource_tracking: true,
961            enable_optimization: true,
962            analysis_interval_minutes: 5,
963        }
964    }
965}
966
967#[cfg(test)]
968mod tests {
969    use super::*;
970
971    #[tokio::test]
972    async fn test_performance_tracker_creation() {
973        let tracker = PerformanceTracker::new();
974        assert!(tracker.record_tool_call(1, 1000, 0.001).await.is_ok());
975    }
976
977    #[tokio::test]
978    async fn test_real_time_metrics() {
979        let mut metrics = RealTimeMetrics::new();
980        metrics.record_tool_call(1, 2000, 0.002, chrono::Utc::now());
981
982        assert_eq!(metrics.total_tool_calls_today, 1);
983        assert_eq!(metrics.avg_duration_ms, 2000.0);
984    }
985
986    #[tokio::test]
987    async fn test_performance_summary() {
988        let tracker = PerformanceTracker::new();
989
990        let _record = PerformanceRecord {
991            id: "test1".to_string(),
992            operation_name: "test_op".to_string(),
993            timestamp: chrono::Utc::now(),
994            tool_calls_used: 10,
995            duration_ms: 5000,
996            success: true,
997            metadata: serde_json::json!({}),
998        };
999
1000        assert!(tracker
1001            .record_operation("test_op", 10, 5000, true, serde_json::json!({}))
1002            .await
1003            .is_ok());
1004    }
1005}