vtcode_core/core/agent/
performance.rs

1//! Performance optimization and caching systems for the coding agent
2//!
3//! This module implements Research-preview performance features including:
4//! - Intelligent caching with LRU eviction
5//! - Parallel processing for large codebases
6//! - Memory-efficient data structures
7//! - Response time optimization
8
9use crate::tools::tree_sitter::CodeAnalysis;
10use anyhow::Result;
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, VecDeque};
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16use tokio::sync::RwLock;
17
18/// Intelligent caching system with LRU eviction
19pub struct IntelligentCache<T> {
20    cache: Arc<RwLock<HashMap<String, CacheEntry<T>>>>,
21    access_order: Arc<RwLock<VecDeque<String>>>,
22    max_size: usize,
23    ttl: Duration,
24}
25
26#[derive(Debug, Clone)]
27struct CacheEntry<T> {
28    data: T,
29    timestamp: Instant,
30    access_count: usize,
31    size_estimate: usize,
32}
33
34impl<T> IntelligentCache<T> {
35    pub fn new(max_size: usize, ttl: Duration) -> Self {
36        Self {
37            cache: Arc::new(RwLock::new(HashMap::new())),
38            access_order: Arc::new(RwLock::new(VecDeque::new())),
39            max_size,
40            ttl,
41        }
42    }
43
44    pub async fn get(&self, key: &str) -> Option<T>
45    where
46        T: Clone,
47    {
48        let mut cache = self.cache.write().await;
49        let mut access_order = self.access_order.write().await;
50
51        if let Some(entry) = cache.get_mut(key) {
52            // Check if entry has expired
53            if entry.timestamp.elapsed() > self.ttl {
54                cache.remove(key);
55                access_order.retain(|k| k != key);
56                return None;
57            }
58
59            // Update access statistics
60            entry.access_count += 1;
61
62            // Move to front of access order
63            access_order.retain(|k| k != key);
64            access_order.push_front(key.to_string());
65
66            Some(entry.data.clone())
67        } else {
68            None
69        }
70    }
71
72    pub async fn put(&self, key: String, value: T, size_estimate: usize) {
73        let mut cache = self.cache.write().await;
74        let mut access_order = self.access_order.write().await;
75
76        // Remove existing entry if present
77        if cache.contains_key(&key) {
78            cache.remove(&key);
79            access_order.retain(|k| k != &key);
80        }
81
82        // Evict entries if cache is full
83        while cache.len() >= self.max_size {
84            if let Some(evict_key) = access_order.pop_back() {
85                cache.remove(&evict_key);
86            }
87        }
88
89        // Add new entry
90        let entry = CacheEntry {
91            data: value,
92            timestamp: Instant::now(),
93            access_count: 1,
94            size_estimate,
95        };
96
97        cache.insert(key.clone(), entry);
98        access_order.push_front(key);
99    }
100
101    pub async fn clear(&self) {
102        let mut cache = self.cache.write().await;
103        let mut access_order = self.access_order.write().await;
104        cache.clear();
105        access_order.clear();
106    }
107
108    pub async fn stats(&self) -> CacheStats {
109        let cache = self.cache.read().await;
110        let _access_order = self.access_order.read().await;
111
112        let total_entries = cache.len();
113        let total_accesses: usize = cache.values().map(|e| e.access_count).sum();
114        let total_size: usize = cache.values().map(|e| e.size_estimate).sum();
115        let avg_access_count = if total_entries > 0 {
116            total_accesses as f64 / total_entries as f64
117        } else {
118            0.0
119        };
120
121        CacheStats {
122            total_entries,
123            total_accesses,
124            total_size_bytes: total_size,
125            avg_access_count,
126            hit_rate: 0.0, // Would need to track hits/misses separately
127        }
128    }
129}
130
131/// Cache statistics
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct CacheStats {
134    pub total_entries: usize,
135    pub total_accesses: usize,
136    pub total_size_bytes: usize,
137    pub avg_access_count: f64,
138    pub hit_rate: f64,
139}
140
141/// Parallel processing engine for large codebases
142pub struct ParallelProcessor {
143    max_concurrent_tasks: usize,
144}
145
146impl ParallelProcessor {
147    pub fn new(max_concurrent_tasks: usize) -> Self {
148        Self {
149            max_concurrent_tasks,
150        }
151    }
152
153    /// Process multiple files in parallel
154    pub async fn process_files<F, Fut, T>(
155        &self,
156        files: Vec<PathBuf>,
157        processor: F,
158    ) -> Result<Vec<T>>
159    where
160        F: Fn(PathBuf) -> Fut + Send + Sync + Clone,
161        Fut: std::future::Future<Output = Result<T>> + Send,
162        T: Send,
163    {
164        use futures::stream::{self, StreamExt};
165
166        let results: Vec<Result<T>> = stream::iter(files)
167            .map(|file| {
168                let processor = processor.clone();
169                async move { processor(file).await }
170            })
171            .buffer_unordered(self.max_concurrent_tasks)
172            .collect()
173            .await;
174
175        // Collect successful results, propagating first error
176        let mut successful_results = Vec::new();
177        for result in results {
178            successful_results.push(result?);
179        }
180
181        Ok(successful_results)
182    }
183
184    /// Process files with priority-based scheduling
185    pub async fn process_with_priority<F, Fut, T, P>(
186        &self,
187        files_with_priority: Vec<(PathBuf, P)>,
188        processor: F,
189    ) -> Result<Vec<T>>
190    where
191        F: Fn(PathBuf) -> Fut + Send + Sync + Clone,
192        Fut: std::future::Future<Output = Result<T>> + Send,
193        T: Send,
194        P: Ord + Send,
195    {
196        // Sort by priority (highest first)
197        let mut sorted_files: Vec<_> = files_with_priority;
198        sorted_files.sort_by(|a, b| b.1.cmp(&a.1));
199
200        let files: Vec<PathBuf> = sorted_files.into_iter().map(|(file, _)| file).collect();
201
202        self.process_files(files, processor).await
203    }
204}
205
206/// Memory-efficient code analysis storage
207pub struct MemoryEfficientStorage {
208    analyses: Arc<RwLock<HashMap<String, CompressedAnalysis>>>,
209    max_memory_mb: usize,
210    current_memory_usage: Arc<RwLock<usize>>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
214struct CompressedAnalysis {
215    symbols: Vec<u8>,      // Compressed symbol data
216    dependencies: Vec<u8>, // Compressed dependency data
217    metrics: Vec<u8>,      // Compressed metrics data
218    original_size: usize,
219    compressed_size: usize,
220}
221
222impl MemoryEfficientStorage {
223    pub fn new(max_memory_mb: usize) -> Self {
224        Self {
225            analyses: Arc::new(RwLock::new(HashMap::new())),
226            max_memory_mb: max_memory_mb * 1024 * 1024, // Convert to bytes
227            current_memory_usage: Arc::new(RwLock::new(0)),
228        }
229    }
230
231    pub async fn store_analysis(&self, file_path: &Path, analysis: CodeAnalysis) -> Result<()> {
232        let compressed = self.compress_analysis(analysis).await?;
233        let key = file_path.to_string_lossy().to_string();
234
235        let mut analyses = self.analyses.write().await;
236        let mut memory_usage = self.current_memory_usage.write().await;
237
238        // Remove old entry if exists
239        if let Some(old_entry) = analyses.remove(&key) {
240            *memory_usage = memory_usage.saturating_sub(old_entry.compressed_size);
241        }
242
243        // Evict entries if needed
244        while *memory_usage + compressed.compressed_size > self.max_memory_mb {
245            if let Some((_, entry)) = analyses.iter().next() {
246                let entry_size = entry.compressed_size;
247                analyses.retain(|_, e| e.compressed_size != entry_size);
248                *memory_usage = memory_usage.saturating_sub(entry_size);
249                break;
250            }
251        }
252
253        analyses.insert(key, compressed.clone());
254        *memory_usage += compressed.compressed_size;
255
256        Ok(())
257    }
258
259    pub async fn get_analysis(&self, file_path: &Path) -> Result<Option<CodeAnalysis>> {
260        let analyses = self.analyses.read().await;
261        let key = file_path.to_string_lossy().to_string();
262
263        if let Some(compressed) = analyses.get(&key) {
264            let analysis = self.decompress_analysis(compressed).await?;
265            Ok(Some(analysis))
266        } else {
267            Ok(None)
268        }
269    }
270
271    async fn compress_analysis(&self, analysis: CodeAnalysis) -> Result<CompressedAnalysis> {
272        use flate2::{Compression, write::GzEncoder};
273        use std::io::Write;
274
275        let serialize_and_compress = |data: &serde_json::Value| -> Result<Vec<u8>> {
276            let json = serde_json::to_vec(data)?;
277            let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
278            encoder.write_all(&json)?;
279            Ok(encoder.finish()?)
280        };
281
282        let symbols = serialize_and_compress(&serde_json::to_value(&analysis.symbols)?)?;
283        let dependencies = serialize_and_compress(&serde_json::to_value(&analysis.dependencies)?)?;
284        let metrics = serialize_and_compress(&serde_json::to_value(&analysis.metrics)?)?;
285
286        let original_size = analysis.symbols.len()
287            + analysis.dependencies.len()
288            + std::mem::size_of_val(&analysis.metrics);
289        let compressed_size = symbols.len() + dependencies.len() + metrics.len();
290
291        Ok(CompressedAnalysis {
292            symbols,
293            dependencies,
294            metrics,
295            original_size,
296            compressed_size,
297        })
298    }
299
300    async fn decompress_analysis(&self, compressed: &CompressedAnalysis) -> Result<CodeAnalysis> {
301        use flate2::read::GzDecoder;
302        use std::io::Read;
303
304        let decompress_and_deserialize = |data: &[u8]| -> Result<serde_json::Value> {
305            let mut decoder = GzDecoder::new(data);
306            let mut decompressed = Vec::new();
307            decoder.read_to_end(&mut decompressed)?;
308            Ok(serde_json::from_slice(&decompressed)?)
309        };
310
311        let symbols: Vec<crate::tools::tree_sitter::languages::SymbolInfo> =
312            decompress_and_deserialize(&compressed.symbols)?
313                .as_array()
314                .unwrap_or(&vec![])
315                .iter()
316                .filter_map(|v| serde_json::from_value(v.clone()).ok())
317                .collect();
318
319        let _dependencies: Vec<String> = decompress_and_deserialize(&compressed.dependencies)?
320            .as_array()
321            .unwrap_or(&vec![])
322            .iter()
323            .filter_map(|v| v.as_str().map(|s| s.to_string()))
324            .collect();
325
326        let metrics: crate::tools::tree_sitter::CodeMetrics =
327            decompress_and_deserialize(&compressed.metrics)?
328                .as_object()
329                .and_then(|obj| serde_json::from_value(serde_json::Value::Object(obj.clone())).ok())
330                .unwrap_or_default();
331
332        Ok(CodeAnalysis {
333            file_path: String::new(), // Would need to be stored separately
334            language: crate::tools::tree_sitter::analyzer::LanguageSupport::Rust, // Default to Rust
335            symbols,
336            dependencies: Vec::new(), // Would need proper deserialization
337            metrics,
338            issues: Vec::new(),
339            complexity: Default::default(),
340            structure: Default::default(),
341        })
342    }
343}
344
345/// Response time optimizer
346pub struct ResponseOptimizer {
347    response_times: Arc<RwLock<HashMap<String, Vec<Duration>>>>,
348    optimization_strategies: Arc<RwLock<HashMap<String, OptimizationStrategy>>>,
349}
350
351#[derive(Debug, Clone)]
352pub enum OptimizationStrategy {
353    CacheFrequentlyAccessed,
354    PrecomputeResults,
355    ParallelProcessing,
356    ReducePayloadSize,
357    StreamResponse,
358}
359
360impl ResponseOptimizer {
361    pub fn new() -> Self {
362        Self {
363            response_times: Arc::new(RwLock::new(HashMap::new())),
364            optimization_strategies: Arc::new(RwLock::new(HashMap::new())),
365        }
366    }
367
368    /// Record response time for a specific operation
369    pub async fn record_response_time(&self, operation: &str, duration: Duration) {
370        let mut response_times = self.response_times.write().await;
371
372        let times = response_times
373            .entry(operation.to_string())
374            .or_insert_with(Vec::new);
375        times.push(duration);
376
377        // Keep only last 100 measurements
378        if times.len() > 100 {
379            times.remove(0);
380        }
381
382        // Analyze and update optimization strategies
383        self.analyze_and_optimize(operation, times).await;
384    }
385
386    /// Get optimized response strategy for an operation
387    pub async fn get_optimization_strategy(&self, operation: &str) -> OptimizationStrategy {
388        let strategies = self.optimization_strategies.read().await;
389
390        strategies
391            .get(operation)
392            .cloned()
393            .unwrap_or(OptimizationStrategy::CacheFrequentlyAccessed)
394    }
395
396    async fn analyze_and_optimize(&self, operation: &str, times: &[Duration]) {
397        if times.len() < 10 {
398            return; // Need more data
399        }
400
401        let avg_time: Duration = times.iter().sum::<Duration>() / times.len() as u32;
402        let recent_avg: Duration = times.iter().rev().take(5).sum::<Duration>() / 5;
403
404        let mut strategies = self.optimization_strategies.write().await;
405
406        let strategy = if avg_time > Duration::from_millis(1000) {
407            // Slow operation - use parallel processing
408            OptimizationStrategy::ParallelProcessing
409        } else if recent_avg > avg_time * 2 {
410            // Performance degrading - precompute results
411            OptimizationStrategy::PrecomputeResults
412        } else if times.len() > 50 {
413            // Frequently called - cache results
414            OptimizationStrategy::CacheFrequentlyAccessed
415        } else {
416            // Default strategy
417            OptimizationStrategy::CacheFrequentlyAccessed
418        };
419
420        strategies.insert(operation.to_string(), strategy);
421    }
422
423    /// Get performance statistics
424    pub async fn get_performance_stats(&self) -> HashMap<String, PerformanceStats> {
425        let response_times = self.response_times.read().await;
426        let mut stats = HashMap::new();
427
428        for (operation, times) in response_times.iter() {
429            if times.is_empty() {
430                continue;
431            }
432
433            let avg_time = times.iter().sum::<Duration>() / times.len() as u32;
434            let min_time = times.iter().min().unwrap();
435            let max_time = times.iter().max().unwrap();
436
437            stats.insert(
438                operation.clone(),
439                PerformanceStats {
440                    operation: operation.clone(),
441                    avg_response_time: avg_time,
442                    min_response_time: *min_time,
443                    max_response_time: *max_time,
444                    total_calls: times.len(),
445                    p95_response_time: self.calculate_percentile(times, 95),
446                },
447            );
448        }
449
450        stats
451    }
452
453    fn calculate_percentile(&self, times: &[Duration], percentile: u8) -> Duration {
454        if times.is_empty() {
455            return Duration::from_millis(0);
456        }
457
458        let mut sorted_times = times.to_vec();
459        sorted_times.sort();
460
461        let index = (percentile as f64 / 100.0 * (sorted_times.len() - 1) as f64) as usize;
462        sorted_times[index]
463    }
464}
465
466/// Performance statistics for operations
467#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct PerformanceStats {
469    pub operation: String,
470    pub avg_response_time: Duration,
471    pub min_response_time: Duration,
472    pub max_response_time: Duration,
473    pub total_calls: usize,
474    pub p95_response_time: Duration,
475}
476
477/// Performance monitoring system
478pub struct PerformanceMonitor {
479    start_time: Instant,
480    operation_counts: Arc<RwLock<HashMap<String, usize>>>,
481    error_counts: Arc<RwLock<HashMap<String, usize>>>,
482    memory_usage: Arc<RwLock<Vec<(Instant, usize)>>>,
483}
484
485impl PerformanceMonitor {
486    pub fn new() -> Self {
487        Self {
488            start_time: Instant::now(),
489            operation_counts: Arc::new(RwLock::new(HashMap::new())),
490            error_counts: Arc::new(RwLock::new(HashMap::new())),
491            memory_usage: Arc::new(RwLock::new(Vec::new())),
492        }
493    }
494
495    /// Record operation execution
496    pub async fn record_operation(&self, operation: &str) {
497        let mut counts = self.operation_counts.write().await;
498        *counts.entry(operation.to_string()).or_insert(0) += 1;
499    }
500
501    /// Record error occurrence
502    pub async fn record_error(&self, operation: &str) {
503        let mut counts = self.error_counts.write().await;
504        *counts.entry(operation.to_string()).or_insert(0) += 1;
505    }
506
507    /// Record memory usage
508    pub async fn record_memory_usage(&self, usage_bytes: usize) {
509        let mut memory_usage = self.memory_usage.write().await;
510        memory_usage.push((Instant::now(), usage_bytes));
511
512        // Keep only last 1000 measurements
513        if memory_usage.len() > 1000 {
514            memory_usage.remove(0);
515        }
516    }
517
518    /// Generate comprehensive performance report
519    pub async fn generate_report(&self) -> PerformanceReport {
520        let operation_counts = self.operation_counts.read().await;
521        let error_counts = self.error_counts.read().await;
522        let memory_usage = self.memory_usage.read().await;
523
524        let total_operations: usize = operation_counts.values().sum();
525        let total_errors: usize = error_counts.values().sum();
526
527        let avg_memory_usage = if !memory_usage.is_empty() {
528            memory_usage.iter().map(|(_, usage)| usage).sum::<usize>() / memory_usage.len()
529        } else {
530            0
531        };
532
533        let uptime = self.start_time.elapsed();
534
535        PerformanceReport {
536            uptime,
537            total_operations,
538            total_errors,
539            error_rate: if total_operations > 0 {
540                total_errors as f64 / total_operations as f64
541            } else {
542                0.0
543            },
544            avg_memory_usage,
545            operations_per_second: total_operations as f64 / uptime.as_secs_f64(),
546            operation_breakdown: operation_counts.clone(),
547            error_breakdown: error_counts.clone(),
548        }
549    }
550}
551
552/// Comprehensive performance report
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct PerformanceReport {
555    pub uptime: Duration,
556    pub total_operations: usize,
557    pub total_errors: usize,
558    pub error_rate: f64,
559    pub avg_memory_usage: usize,
560    pub operations_per_second: f64,
561    pub operation_breakdown: HashMap<String, usize>,
562    pub error_breakdown: HashMap<String, usize>,
563}