ricecoder_lsp/
performance.rs

1//! Performance monitoring and optimization utilities
2//!
3//! This module provides performance tracking, metrics collection, and optimization
4//! utilities for the LSP server.
5
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8use std::time::{Duration, Instant};
9use tracing::{info, warn};
10
11/// Performance metrics for a specific operation
12#[derive(Debug, Clone)]
13pub struct OperationMetrics {
14    /// Operation name
15    pub name: String,
16    /// Total number of operations
17    pub count: u64,
18    /// Total time spent in milliseconds
19    pub total_time_ms: f64,
20    /// Minimum time in milliseconds
21    pub min_time_ms: f64,
22    /// Maximum time in milliseconds
23    pub max_time_ms: f64,
24}
25
26impl OperationMetrics {
27    /// Calculate average time in milliseconds
28    pub fn avg_time_ms(&self) -> f64 {
29        if self.count == 0 {
30            0.0
31        } else {
32            self.total_time_ms / self.count as f64
33        }
34    }
35}
36
37/// Performance tracker for measuring operation times
38pub struct PerformanceTracker {
39    /// Metrics by operation name
40    metrics: Arc<RwLock<HashMap<String, OperationMetrics>>>,
41    /// Performance targets (operation name -> max time in ms)
42    targets: Arc<RwLock<HashMap<String, f64>>>,
43}
44
45impl PerformanceTracker {
46    /// Create a new performance tracker
47    pub fn new() -> Self {
48        Self {
49            metrics: Arc::new(RwLock::new(HashMap::new())),
50            targets: Arc::new(RwLock::new(HashMap::new())),
51        }
52    }
53
54    /// Set performance target for an operation
55    pub fn set_target(&self, operation: String, max_time_ms: f64) {
56        let mut targets = self.targets.write().unwrap();
57        targets.insert(operation, max_time_ms);
58    }
59
60    /// Record operation time
61    pub fn record(&self, operation: String, duration: Duration) {
62        let time_ms = duration.as_secs_f64() * 1000.0;
63
64        let mut metrics = self.metrics.write().unwrap();
65        let entry = metrics
66            .entry(operation.clone())
67            .or_insert_with(|| OperationMetrics {
68                name: operation.clone(),
69                count: 0,
70                total_time_ms: 0.0,
71                min_time_ms: f64::MAX,
72                max_time_ms: 0.0,
73            });
74
75        entry.count += 1;
76        entry.total_time_ms += time_ms;
77        entry.min_time_ms = entry.min_time_ms.min(time_ms);
78        entry.max_time_ms = entry.max_time_ms.max(time_ms);
79
80        // Check if target was exceeded
81        let targets = self.targets.read().unwrap();
82        if let Some(&target) = targets.get(&operation) {
83            if time_ms > target {
84                warn!(
85                    "Performance target exceeded for {}: {:.2}ms > {:.2}ms",
86                    operation, time_ms, target
87                );
88            }
89        }
90    }
91
92    /// Get metrics for an operation
93    pub fn get_metrics(&self, operation: &str) -> Option<OperationMetrics> {
94        let metrics = self.metrics.read().unwrap();
95        metrics.get(operation).cloned()
96    }
97
98    /// Get all metrics
99    pub fn all_metrics(&self) -> Vec<OperationMetrics> {
100        let metrics = self.metrics.read().unwrap();
101        metrics.values().cloned().collect()
102    }
103
104    /// Clear all metrics
105    pub fn clear(&self) {
106        let mut metrics = self.metrics.write().unwrap();
107        metrics.clear();
108    }
109
110    /// Print performance report
111    pub fn print_report(&self) {
112        let metrics = self.metrics.read().unwrap();
113
114        if metrics.is_empty() {
115            info!("No performance metrics recorded");
116            return;
117        }
118
119        info!("=== Performance Report ===");
120        for (_, metric) in metrics.iter() {
121            info!(
122                "{}: count={}, avg={:.2}ms, min={:.2}ms, max={:.2}ms",
123                metric.name,
124                metric.count,
125                metric.avg_time_ms(),
126                metric.min_time_ms,
127                metric.max_time_ms
128            );
129        }
130    }
131}
132
133impl Default for PerformanceTracker {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139/// Timer for measuring operation duration
140pub struct Timer {
141    start: Instant,
142    operation: String,
143    tracker: Arc<PerformanceTracker>,
144}
145
146impl Timer {
147    /// Create a new timer
148    pub fn new(operation: String, tracker: Arc<PerformanceTracker>) -> Self {
149        Self {
150            start: Instant::now(),
151            operation,
152            tracker,
153        }
154    }
155
156    /// Stop the timer and record the duration
157    pub fn stop(self) {
158        let duration = self.start.elapsed();
159        let operation = self.operation.clone();
160        self.tracker.record(operation, duration);
161    }
162}
163
164impl Drop for Timer {
165    fn drop(&mut self) {
166        let duration = self.start.elapsed();
167        let operation = self.operation.clone();
168        self.tracker.record(operation, duration);
169    }
170}
171
172/// Performance analyzer for identifying bottlenecks
173pub struct PerformanceAnalyzer {
174    tracker: Arc<PerformanceTracker>,
175}
176
177impl PerformanceAnalyzer {
178    /// Create a new performance analyzer
179    pub fn new(tracker: Arc<PerformanceTracker>) -> Self {
180        Self { tracker }
181    }
182
183    /// Identify slow operations (operations exceeding their targets)
184    pub fn identify_slow_operations(&self) -> Vec<(String, f64, f64)> {
185        let metrics = self.tracker.metrics.read().unwrap();
186        let targets = self.tracker.targets.read().unwrap();
187
188        let mut slow_ops = Vec::new();
189
190        for (name, metric) in metrics.iter() {
191            if let Some(&target) = targets.get(name) {
192                let avg = metric.avg_time_ms();
193                if avg > target {
194                    slow_ops.push((name.clone(), avg, target));
195                }
196            }
197        }
198
199        slow_ops.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
200        slow_ops
201    }
202
203    /// Get operations sorted by average time (slowest first)
204    pub fn slowest_operations(&self, limit: usize) -> Vec<OperationMetrics> {
205        let mut metrics = self.tracker.all_metrics();
206        metrics.sort_by(|a, b| b.avg_time_ms().partial_cmp(&a.avg_time_ms()).unwrap());
207        metrics.into_iter().take(limit).collect()
208    }
209
210    /// Get operations sorted by total time (most time spent)
211    pub fn most_time_spent(&self, limit: usize) -> Vec<OperationMetrics> {
212        let mut metrics = self.tracker.all_metrics();
213        metrics.sort_by(|a, b| b.total_time_ms.partial_cmp(&a.total_time_ms).unwrap());
214        metrics.into_iter().take(limit).collect()
215    }
216}
217
218/// Performance optimization recommendations
219#[derive(Debug, Clone)]
220pub struct OptimizationRecommendation {
221    /// Operation name
222    pub operation: String,
223    /// Current average time in milliseconds
224    pub current_time_ms: f64,
225    /// Target time in milliseconds
226    pub target_time_ms: f64,
227    /// Recommendation message
228    pub recommendation: String,
229}
230
231impl OptimizationRecommendation {
232    /// Calculate improvement needed as percentage
233    pub fn improvement_needed(&self) -> f64 {
234        if self.target_time_ms == 0.0 {
235            0.0
236        } else {
237            ((self.current_time_ms - self.target_time_ms) / self.target_time_ms) * 100.0
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use std::thread;
246
247    #[test]
248    fn test_performance_tracker_record() {
249        let tracker = PerformanceTracker::new();
250        let duration = Duration::from_millis(100);
251
252        tracker.record("test_op".to_string(), duration);
253
254        let metrics = tracker.get_metrics("test_op").unwrap();
255        assert_eq!(metrics.count, 1);
256        assert!(metrics.total_time_ms >= 100.0);
257    }
258
259    #[test]
260    fn test_performance_tracker_multiple_records() {
261        let tracker = PerformanceTracker::new();
262
263        tracker.record("test_op".to_string(), Duration::from_millis(100));
264        tracker.record("test_op".to_string(), Duration::from_millis(200));
265        tracker.record("test_op".to_string(), Duration::from_millis(150));
266
267        let metrics = tracker.get_metrics("test_op").unwrap();
268        assert_eq!(metrics.count, 3);
269        assert!(metrics.avg_time_ms() >= 150.0);
270        assert!(metrics.min_time_ms >= 100.0);
271        assert!(metrics.max_time_ms >= 200.0);
272    }
273
274    #[test]
275    fn test_timer_auto_record() {
276        let tracker = Arc::new(PerformanceTracker::new());
277
278        {
279            let _timer = Timer::new("test_op".to_string(), tracker.clone());
280            thread::sleep(Duration::from_millis(50));
281        }
282
283        let metrics = tracker.get_metrics("test_op").unwrap();
284        assert_eq!(metrics.count, 1);
285        assert!(metrics.total_time_ms >= 50.0);
286    }
287
288    #[test]
289    fn test_performance_target() {
290        let tracker = PerformanceTracker::new();
291        tracker.set_target("test_op".to_string(), 100.0);
292
293        tracker.record("test_op".to_string(), Duration::from_millis(50));
294
295        let metrics = tracker.get_metrics("test_op").unwrap();
296        assert_eq!(metrics.count, 1);
297    }
298
299    #[test]
300    fn test_performance_analyzer_slowest() {
301        let tracker = Arc::new(PerformanceTracker::new());
302
303        tracker.record("op1".to_string(), Duration::from_millis(100));
304        tracker.record("op2".to_string(), Duration::from_millis(200));
305        tracker.record("op3".to_string(), Duration::from_millis(150));
306
307        let analyzer = PerformanceAnalyzer::new(tracker);
308        let slowest = analyzer.slowest_operations(2);
309
310        assert_eq!(slowest.len(), 2);
311        assert_eq!(slowest[0].name, "op2");
312        assert_eq!(slowest[1].name, "op3");
313    }
314
315    #[test]
316    fn test_optimization_recommendation() {
317        let rec = OptimizationRecommendation {
318            operation: "test_op".to_string(),
319            current_time_ms: 200.0,
320            target_time_ms: 100.0,
321            recommendation: "Optimize parsing".to_string(),
322        };
323
324        assert_eq!(rec.improvement_needed(), 100.0);
325    }
326}