Skip to main content

vtcode_core/core/
performance_profiler.rs

1//! Performance benchmarking and profiling tools for VT Code optimizations
2
3use crate::utils::file_utils::write_file_with_context;
4use anyhow::Result;
5use hashbrown::HashMap;
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10use tokio::sync::RwLock;
11
12/// Performance benchmark results
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct BenchmarkResults {
15    pub test_name: String,
16    pub iterations: u64,
17    pub total_duration: Duration,
18    pub avg_duration_ns: u64,
19    pub min_duration_ns: u64,
20    pub max_duration_ns: u64,
21    pub percentile_95_ns: u64,
22    pub percentile_99_ns: u64,
23    pub throughput_ops_per_sec: f64,
24    pub memory_usage_mb: Option<f64>,
25    pub cpu_usage_percent: Option<f64>,
26}
27
28/// System resource usage metrics
29#[derive(Debug, Clone, Default)]
30pub struct ResourceMetrics {
31    pub memory_used_mb: f64,
32    pub cpu_percent: f64,
33    pub network_bytes_sent: u64,
34    pub network_bytes_received: u64,
35    pub disk_reads: u64,
36    pub disk_writes: u64,
37}
38
39/// Performance profiler for tracking execution metrics
40pub struct PerformanceProfiler {
41    /// Active benchmark sessions
42    sessions: Arc<RwLock<HashMap<String, BenchmarkSession>>>,
43
44    /// System resource monitor
45    resource_monitor: Arc<ResourceMonitor>,
46
47    /// Historical benchmark results
48    history: Arc<RwLock<Vec<BenchmarkResults>>>,
49}
50
51/// Individual benchmark session
52#[derive(Debug)]
53pub struct BenchmarkSession {
54    pub name: String,
55    pub start_time: Instant,
56    pub iterations: u64,
57    pub durations: Vec<Duration>,
58    pub resource_snapshots: Vec<ResourceMetrics>,
59}
60
61/// System resource monitoring
62pub struct ResourceMonitor {
63    /// Current resource usage
64    current_metrics: Arc<RwLock<ResourceMetrics>>,
65
66    /// Monitoring interval
67    monitor_interval: Duration,
68
69    /// Whether monitoring is active
70    is_monitoring: Arc<RwLock<bool>>,
71}
72
73impl PerformanceProfiler {
74    pub fn new() -> Self {
75        Self {
76            sessions: Arc::new(RwLock::new(HashMap::new())),
77            resource_monitor: Arc::new(ResourceMonitor::new(Duration::from_millis(100))),
78            history: Arc::new(RwLock::new(Vec::new())),
79        }
80    }
81
82    /// Start a new benchmark session
83    pub async fn start_benchmark(&self, name: &str) -> Result<()> {
84        let session = BenchmarkSession {
85            name: name.to_string(),
86            start_time: Instant::now(),
87            iterations: 0,
88            durations: Vec::new(),
89            resource_snapshots: Vec::new(),
90        };
91
92        self.sessions
93            .write()
94            .await
95            .insert(name.to_string(), session);
96        self.resource_monitor.start_monitoring().await?;
97
98        Ok(())
99    }
100
101    /// Record a single operation timing
102    pub async fn record_operation(&self, session_name: &str, duration: Duration) -> Result<()> {
103        let mut sessions = self.sessions.write().await;
104        if let Some(session) = sessions.get_mut(session_name) {
105            session.iterations += 1;
106            session.durations.push(duration);
107
108            // Capture resource snapshot every 100 operations
109            if session.iterations % 100 == 0 {
110                let metrics = self.resource_monitor.get_current_metrics().await;
111                session.resource_snapshots.push(metrics);
112            }
113        }
114
115        Ok(())
116    }
117
118    /// End benchmark session and calculate results
119    pub async fn end_benchmark(&self, session_name: &str) -> Result<BenchmarkResults> {
120        let session = {
121            let mut sessions = self.sessions.write().await;
122            sessions
123                .remove(session_name)
124                .ok_or_else(|| anyhow::anyhow!("Benchmark session '{}' not found", session_name))?
125        };
126
127        self.resource_monitor.stop_monitoring().await?;
128
129        let results = self.calculate_results(session).await;
130
131        // Store in history
132        self.history.write().await.push(results.clone());
133
134        Ok(results)
135    }
136
137    /// Calculate benchmark results from session data
138    async fn calculate_results(&self, session: BenchmarkSession) -> BenchmarkResults {
139        let total_duration = session.start_time.elapsed();
140        let mut durations_ns: Vec<u64> = session
141            .durations
142            .iter()
143            .map(|d| d.as_nanos() as u64)
144            .collect();
145
146        durations_ns.sort_unstable();
147
148        let avg_duration_ns = if !durations_ns.is_empty() {
149            durations_ns.iter().sum::<u64>() / durations_ns.len() as u64
150        } else {
151            0
152        };
153
154        let min_duration_ns = durations_ns.first().copied().unwrap_or(0);
155        let max_duration_ns = durations_ns.last().copied().unwrap_or(0);
156
157        let percentile_95_ns = if !durations_ns.is_empty() {
158            let index = (durations_ns.len() as f64 * 0.95) as usize;
159            durations_ns
160                .get(index.min(durations_ns.len() - 1))
161                .copied()
162                .unwrap_or(0)
163        } else {
164            0
165        };
166
167        let percentile_99_ns = if !durations_ns.is_empty() {
168            let index = (durations_ns.len() as f64 * 0.99) as usize;
169            durations_ns
170                .get(index.min(durations_ns.len() - 1))
171                .copied()
172                .unwrap_or(0)
173        } else {
174            0
175        };
176
177        let throughput_ops_per_sec = if total_duration.as_secs_f64() > 0.0 {
178            session.iterations as f64 / total_duration.as_secs_f64()
179        } else {
180            0.0
181        };
182
183        // Calculate average resource usage
184        let avg_memory_mb = if !session.resource_snapshots.is_empty() {
185            Some(
186                session
187                    .resource_snapshots
188                    .iter()
189                    .map(|m| m.memory_used_mb)
190                    .sum::<f64>()
191                    / session.resource_snapshots.len() as f64,
192            )
193        } else {
194            None
195        };
196
197        let avg_cpu_percent = if !session.resource_snapshots.is_empty() {
198            Some(
199                session
200                    .resource_snapshots
201                    .iter()
202                    .map(|m| m.cpu_percent)
203                    .sum::<f64>()
204                    / session.resource_snapshots.len() as f64,
205            )
206        } else {
207            None
208        };
209
210        BenchmarkResults {
211            test_name: session.name,
212            iterations: session.iterations,
213            total_duration,
214            avg_duration_ns,
215            min_duration_ns,
216            max_duration_ns,
217            percentile_95_ns,
218            percentile_99_ns,
219            throughput_ops_per_sec,
220            memory_usage_mb: avg_memory_mb,
221            cpu_usage_percent: avg_cpu_percent,
222        }
223    }
224
225    /// Get all historical benchmark results
226    pub async fn get_history(&self) -> Vec<BenchmarkResults> {
227        self.history.read().await.clone()
228    }
229
230    /// Compare two benchmark results
231    pub fn compare_results(
232        &self,
233        baseline: &BenchmarkResults,
234        current: &BenchmarkResults,
235    ) -> ComparisonReport {
236        let throughput_change = if baseline.throughput_ops_per_sec > 0.0 {
237            ((current.throughput_ops_per_sec - baseline.throughput_ops_per_sec)
238                / baseline.throughput_ops_per_sec)
239                * 100.0
240        } else {
241            0.0
242        };
243
244        let avg_latency_change = if baseline.avg_duration_ns > 0 {
245            ((current.avg_duration_ns as f64 - baseline.avg_duration_ns as f64)
246                / baseline.avg_duration_ns as f64)
247                * 100.0
248        } else {
249            0.0
250        };
251
252        let memory_change = match (baseline.memory_usage_mb, current.memory_usage_mb) {
253            (Some(baseline_mem), Some(current_mem)) => {
254                Some(((current_mem - baseline_mem) / baseline_mem) * 100.0)
255            }
256            _ => None,
257        };
258
259        ComparisonReport {
260            baseline_name: baseline.test_name.clone(),
261            current_name: current.test_name.clone(),
262            throughput_change_percent: throughput_change,
263            avg_latency_change_percent: avg_latency_change,
264            memory_change_percent: memory_change,
265            is_improvement: throughput_change > 0.0 && avg_latency_change < 0.0,
266        }
267    }
268
269    /// Export results to JSON
270    pub async fn export_results(&self, file_path: &str) -> Result<()> {
271        let history = self.get_history().await;
272        let json = serde_json::to_string_pretty(&history)?;
273        write_file_with_context(Path::new(file_path), &json, "benchmark results").await?;
274        Ok(())
275    }
276}
277
278/// Benchmark comparison report
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct ComparisonReport {
281    pub baseline_name: String,
282    pub current_name: String,
283    pub throughput_change_percent: f64,
284    pub avg_latency_change_percent: f64,
285    pub memory_change_percent: Option<f64>,
286    pub is_improvement: bool,
287}
288
289impl ResourceMonitor {
290    pub fn new(monitor_interval: Duration) -> Self {
291        Self {
292            current_metrics: Arc::new(RwLock::new(ResourceMetrics::default())),
293            monitor_interval,
294            is_monitoring: Arc::new(RwLock::new(false)),
295        }
296    }
297
298    /// Start resource monitoring
299    pub async fn start_monitoring(&self) -> Result<()> {
300        let mut is_monitoring = self.is_monitoring.write().await;
301        if *is_monitoring {
302            return Ok(()); // Already monitoring
303        }
304        *is_monitoring = true;
305        drop(is_monitoring);
306
307        let current_metrics = Arc::clone(&self.current_metrics);
308        let is_monitoring_flag = Arc::clone(&self.is_monitoring);
309        let interval = self.monitor_interval;
310
311        tokio::spawn(async move {
312            let mut interval_timer = tokio::time::interval(interval);
313
314            while *is_monitoring_flag.read().await {
315                interval_timer.tick().await;
316
317                let metrics = Self::collect_system_metrics().await;
318                *current_metrics.write().await = metrics;
319            }
320        });
321
322        Ok(())
323    }
324
325    /// Stop resource monitoring
326    pub async fn stop_monitoring(&self) -> Result<()> {
327        *self.is_monitoring.write().await = false;
328        Ok(())
329    }
330
331    /// Get current resource metrics
332    pub async fn get_current_metrics(&self) -> ResourceMetrics {
333        self.current_metrics.read().await.clone()
334    }
335
336    /// Collect system resource metrics
337    async fn collect_system_metrics() -> ResourceMetrics {
338        // This is a simplified implementation
339        // In a real system, you'd use system APIs or libraries like `sysinfo`
340
341        ResourceMetrics {
342            memory_used_mb: Self::get_memory_usage_mb(),
343            cpu_percent: Self::get_cpu_usage_percent(),
344            network_bytes_sent: 0,
345            network_bytes_received: 0,
346            disk_reads: 0,
347            disk_writes: 0,
348        }
349    }
350
351    /// Get current memory usage in MB
352    fn get_memory_usage_mb() -> f64 {
353        // Simplified implementation - would use actual system APIs
354        #[cfg(target_os = "linux")]
355        {
356            if let Ok(contents) = std::fs::read_to_string("/proc/self/status") {
357                for line in contents.lines() {
358                    if line.starts_with("VmRSS:")
359                        && let Some(kb_str) = line.split_whitespace().nth(1)
360                        && let Ok(kb) = kb_str.parse::<f64>()
361                    {
362                        return kb / 1024.0; // Convert KB to MB
363                    }
364                }
365            }
366        }
367
368        // Fallback estimation
369        100.0
370    }
371
372    /// Get current CPU usage percentage
373    fn get_cpu_usage_percent() -> f64 {
374        // Simplified implementation - would use actual system APIs
375        // This would require tracking CPU time over intervals
376        0.0
377    }
378}
379
380/// Macro for easy benchmarking
381#[macro_export]
382macro_rules! benchmark {
383    ($profiler:expr, $name:expr, $code:block) => {{
384        let start = std::time::Instant::now();
385        let result = $code;
386        let duration = start.elapsed();
387        $profiler.record_operation($name, duration).await?;
388        result
389    }};
390}
391
392/// Utility functions for common benchmarking scenarios
393pub struct BenchmarkUtils;
394
395impl BenchmarkUtils {
396    /// Benchmark a function with multiple iterations
397    pub async fn benchmark_function<F, R>(
398        profiler: &PerformanceProfiler,
399        name: &str,
400        iterations: u64,
401        mut func: F,
402    ) -> Result<BenchmarkResults>
403    where
404        F: FnMut() -> R,
405    {
406        profiler.start_benchmark(name).await?;
407
408        for _ in 0..iterations {
409            let start = Instant::now();
410            let _ = func();
411            let duration = start.elapsed();
412            profiler.record_operation(name, duration).await?;
413        }
414
415        profiler.end_benchmark(name).await
416    }
417
418    /// Benchmark an async function with multiple iterations
419    pub async fn benchmark_async_function<F, Fut, R>(
420        profiler: &PerformanceProfiler,
421        name: &str,
422        iterations: u64,
423        mut func: F,
424    ) -> Result<BenchmarkResults>
425    where
426        F: FnMut() -> Fut,
427        Fut: Future<Output = R>,
428    {
429        profiler.start_benchmark(name).await?;
430
431        for _ in 0..iterations {
432            let start = Instant::now();
433            let _ = func().await;
434            let duration = start.elapsed();
435            profiler.record_operation(name, duration).await?;
436        }
437
438        profiler.end_benchmark(name).await
439    }
440
441    /// Run a performance regression test
442    pub async fn regression_test(
443        profiler: &PerformanceProfiler,
444        baseline_name: &str,
445        current_name: &str,
446        max_regression_percent: f64,
447    ) -> Result<bool> {
448        let history = profiler.get_history().await;
449
450        let baseline = history
451            .iter()
452            .find(|r| r.test_name == baseline_name)
453            .ok_or_else(|| anyhow::anyhow!("Baseline '{}' not found", baseline_name))?;
454
455        let current = history
456            .iter()
457            .find(|r| r.test_name == current_name)
458            .ok_or_else(|| anyhow::anyhow!("Current '{}' not found", current_name))?;
459
460        let comparison = profiler.compare_results(baseline, current);
461
462        // Check if performance regressed beyond threshold
463        let regression = comparison.avg_latency_change_percent > max_regression_percent
464            || comparison.throughput_change_percent < -max_regression_percent;
465
466        if regression {
467            tracing::warn!(
468                latency_change_percent = comparison.avg_latency_change_percent,
469                throughput_change_percent = comparison.throughput_change_percent,
470                "Performance regression detected"
471            );
472        }
473
474        Ok(!regression)
475    }
476}
477
478impl Default for PerformanceProfiler {
479    fn default() -> Self {
480        Self::new()
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487    use tokio::time::sleep;
488
489    #[tokio::test]
490    async fn test_benchmark_session() -> Result<()> {
491        let profiler = PerformanceProfiler::new();
492
493        profiler.start_benchmark("test_session").await?;
494
495        // Simulate some operations
496        for i in 0..10 {
497            let duration = Duration::from_millis(10 + i);
498            profiler.record_operation("test_session", duration).await?;
499        }
500
501        let results = profiler.end_benchmark("test_session").await?;
502
503        assert_eq!(results.test_name, "test_session");
504        assert_eq!(results.iterations, 10);
505        assert!(results.avg_duration_ns > 0);
506
507        Ok(())
508    }
509
510    #[tokio::test]
511    async fn test_benchmark_utils() -> Result<()> {
512        let profiler = PerformanceProfiler::new();
513
514        let results = BenchmarkUtils::benchmark_function(&profiler, "test_function", 100, || {
515            // Simulate work
516            std::thread::sleep(Duration::from_micros(100));
517            42
518        })
519        .await?;
520
521        assert_eq!(results.iterations, 100);
522        assert!(results.throughput_ops_per_sec > 0.0);
523
524        Ok(())
525    }
526
527    #[tokio::test]
528    async fn test_async_benchmark() -> Result<()> {
529        let profiler = PerformanceProfiler::new();
530
531        let results = BenchmarkUtils::benchmark_async_function(
532            &profiler,
533            "test_async_function",
534            50,
535            || async {
536                sleep(Duration::from_micros(200)).await;
537                "result"
538            },
539        )
540        .await?;
541
542        assert_eq!(results.iterations, 50);
543        assert!(results.avg_duration_ns > 0);
544
545        Ok(())
546    }
547}