Skip to main content

rez_lsp_server/performance/
profiler.rs

1//! Performance profiling utilities.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tokio::sync::RwLock;
8
9/// A profiling session that tracks nested operations.
10#[derive(Debug)]
11pub struct ProfilerGuard {
12    name: String,
13    start_time: Instant,
14    profiler: Arc<Profiler>,
15    parent_id: Option<u64>,
16    session_id: u64,
17}
18
19impl ProfilerGuard {
20    /// Create a new profiler guard.
21    fn new(name: String, profiler: Arc<Profiler>, parent_id: Option<u64>) -> Self {
22        let session_id = profiler.next_session_id();
23        let start_time = Instant::now();
24
25        Self {
26            name,
27            start_time,
28            profiler,
29            parent_id,
30            session_id,
31        }
32    }
33
34    /// Get the elapsed time since this guard was created.
35    pub fn elapsed(&self) -> Duration {
36        self.start_time.elapsed()
37    }
38
39    /// Get the session ID.
40    pub fn session_id(&self) -> u64 {
41        self.session_id
42    }
43
44    /// Create a child profiler guard.
45    pub fn child(&self, name: impl Into<String>) -> ProfilerGuard {
46        ProfilerGuard::new(name.into(), self.profiler.clone(), Some(self.session_id))
47    }
48}
49
50impl Drop for ProfilerGuard {
51    fn drop(&mut self) {
52        let duration = self.elapsed();
53        let profile_entry = ProfileEntry {
54            name: self.name.clone(),
55            duration_ms: duration.as_millis() as u64,
56            start_time: self.start_time,
57            parent_id: self.parent_id,
58            session_id: self.session_id,
59        };
60
61        // Record the profile entry asynchronously
62        let profiler = self.profiler.clone();
63        tokio::spawn(async move {
64            profiler.record_entry(profile_entry).await;
65        });
66    }
67}
68
69/// A single profiling entry.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ProfileEntry {
72    /// Name of the operation
73    pub name: String,
74    /// Duration in milliseconds
75    pub duration_ms: u64,
76    /// Start time of the operation (not serialized)
77    #[serde(skip, default = "Instant::now")]
78    pub start_time: Instant,
79    /// Parent session ID (for nested operations)
80    pub parent_id: Option<u64>,
81    /// Unique session ID
82    pub session_id: u64,
83}
84
85/// A profiling session containing multiple entries.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ProfileSession {
88    /// Root operation name
89    pub root_operation: String,
90    /// All entries in this session
91    pub entries: Vec<ProfileEntry>,
92    /// Total duration of the session
93    pub total_duration_ms: u64,
94    /// Session start time
95    pub start_timestamp: u64,
96}
97
98impl ProfileSession {
99    /// Get the call tree for this session.
100    pub fn call_tree(&self) -> CallTreeNode {
101        let mut root_entries: Vec<&ProfileEntry> = self
102            .entries
103            .iter()
104            .filter(|e| e.parent_id.is_none())
105            .collect();
106
107        if root_entries.is_empty() {
108            return CallTreeNode {
109                name: self.root_operation.clone(),
110                duration_ms: self.total_duration_ms,
111                children: Vec::new(),
112                self_time_ms: self.total_duration_ms,
113            };
114        }
115
116        // Sort by start time to get the actual root
117        root_entries.sort_by_key(|e| e.start_time);
118        let root = root_entries[0];
119
120        self.build_call_tree_node(root)
121    }
122
123    fn build_call_tree_node(&self, entry: &ProfileEntry) -> CallTreeNode {
124        let children: Vec<CallTreeNode> = self
125            .entries
126            .iter()
127            .filter(|e| e.parent_id == Some(entry.session_id))
128            .map(|child| self.build_call_tree_node(child))
129            .collect();
130
131        let children_time: u64 = children.iter().map(|c| c.duration_ms).sum();
132        let self_time_ms = entry.duration_ms.saturating_sub(children_time);
133
134        CallTreeNode {
135            name: entry.name.clone(),
136            duration_ms: entry.duration_ms,
137            children,
138            self_time_ms,
139        }
140    }
141}
142
143/// A node in the call tree.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct CallTreeNode {
146    /// Operation name
147    pub name: String,
148    /// Total duration including children
149    pub duration_ms: u64,
150    /// Time spent in this operation only (excluding children)
151    pub self_time_ms: u64,
152    /// Child operations
153    pub children: Vec<CallTreeNode>,
154}
155
156impl CallTreeNode {
157    /// Get the percentage of time spent in this node relative to the total.
158    pub fn percentage(&self, total_duration_ms: u64) -> f64 {
159        if total_duration_ms > 0 {
160            (self.duration_ms as f64 / total_duration_ms as f64) * 100.0
161        } else {
162            0.0
163        }
164    }
165
166    /// Get the self-time percentage.
167    pub fn self_percentage(&self, total_duration_ms: u64) -> f64 {
168        if total_duration_ms > 0 {
169            (self.self_time_ms as f64 / total_duration_ms as f64) * 100.0
170        } else {
171            0.0
172        }
173    }
174
175    /// Find the most expensive child operations.
176    pub fn most_expensive_children(&self, limit: usize) -> Vec<&CallTreeNode> {
177        let mut children: Vec<&CallTreeNode> = self.children.iter().collect();
178        children.sort_by(|a, b| b.duration_ms.cmp(&a.duration_ms));
179        children.truncate(limit);
180        children
181    }
182}
183
184/// Main profiler that manages profiling sessions.
185#[derive(Debug)]
186pub struct Profiler {
187    /// Active profiling sessions
188    sessions: Arc<RwLock<HashMap<u64, Vec<ProfileEntry>>>>,
189    /// Completed sessions
190    completed_sessions: Arc<RwLock<Vec<ProfileSession>>>,
191    /// Next session ID
192    next_id: Arc<RwLock<u64>>,
193    /// Maximum number of completed sessions to keep
194    max_sessions: usize,
195    /// Whether profiling is enabled
196    enabled: bool,
197}
198
199impl Profiler {
200    /// Create a new profiler.
201    pub fn new(max_sessions: usize, enabled: bool) -> Self {
202        Self {
203            sessions: Arc::new(RwLock::new(HashMap::new())),
204            completed_sessions: Arc::new(RwLock::new(Vec::new())),
205            next_id: Arc::new(RwLock::new(1)),
206            max_sessions,
207            enabled,
208        }
209    }
210
211    /// Start profiling an operation.
212    pub fn profile(&self, name: impl Into<String>) -> Option<ProfilerGuard> {
213        if !self.enabled {
214            return None;
215        }
216
217        Some(ProfilerGuard::new(
218            name.into(),
219            Arc::new(self.clone()),
220            None,
221        ))
222    }
223
224    /// Get the next session ID.
225    fn next_session_id(&self) -> u64 {
226        let mut next_id = futures::executor::block_on(self.next_id.write());
227        let id = *next_id;
228        *next_id += 1;
229        id
230    }
231
232    /// Record a profile entry.
233    async fn record_entry(&self, entry: ProfileEntry) {
234        if !self.enabled {
235            return;
236        }
237
238        let mut sessions = self.sessions.write().await;
239        let session_entries = sessions.entry(entry.session_id).or_insert_with(Vec::new);
240        session_entries.push(entry);
241    }
242
243    /// Complete a profiling session.
244    pub async fn complete_session(&self, root_session_id: u64, root_operation: String) {
245        if !self.enabled {
246            return;
247        }
248
249        let mut sessions = self.sessions.write().await;
250        if let Some(entries) = sessions.remove(&root_session_id) {
251            if !entries.is_empty() {
252                let total_duration_ms = entries.iter().map(|e| e.duration_ms).max().unwrap_or(0);
253                let start_timestamp = std::time::SystemTime::now()
254                    .duration_since(std::time::UNIX_EPOCH)
255                    .unwrap_or_default()
256                    .as_secs();
257
258                let session = ProfileSession {
259                    root_operation,
260                    entries,
261                    total_duration_ms,
262                    start_timestamp,
263                };
264
265                let mut completed = self.completed_sessions.write().await;
266                completed.push(session);
267
268                // Keep only the most recent sessions
269                while completed.len() > self.max_sessions {
270                    completed.remove(0);
271                }
272            }
273        }
274    }
275
276    /// Get all completed sessions.
277    pub async fn get_completed_sessions(&self) -> Vec<ProfileSession> {
278        let completed = self.completed_sessions.read().await;
279        completed.clone()
280    }
281
282    /// Get the most recent session.
283    pub async fn get_latest_session(&self) -> Option<ProfileSession> {
284        let completed = self.completed_sessions.read().await;
285        completed.last().cloned()
286    }
287
288    /// Get sessions by operation name.
289    pub async fn get_sessions_by_operation(&self, operation: &str) -> Vec<ProfileSession> {
290        let completed = self.completed_sessions.read().await;
291        completed
292            .iter()
293            .filter(|s| s.root_operation == operation)
294            .cloned()
295            .collect()
296    }
297
298    /// Clear all sessions.
299    pub async fn clear(&self) {
300        let mut sessions = self.sessions.write().await;
301        let mut completed = self.completed_sessions.write().await;
302        sessions.clear();
303        completed.clear();
304    }
305
306    /// Get profiling statistics.
307    pub async fn get_stats(&self) -> ProfilerStats {
308        let sessions = self.sessions.read().await;
309        let completed = self.completed_sessions.read().await;
310
311        let active_sessions = sessions.len();
312        let completed_sessions = completed.len();
313        let total_entries = sessions.values().map(|v| v.len()).sum::<usize>()
314            + completed.iter().map(|s| s.entries.len()).sum::<usize>();
315
316        let avg_session_duration = if !completed.is_empty() {
317            completed.iter().map(|s| s.total_duration_ms).sum::<u64>() as f64
318                / completed.len() as f64
319        } else {
320            0.0
321        };
322
323        ProfilerStats {
324            active_sessions,
325            completed_sessions,
326            total_entries,
327            avg_session_duration_ms: avg_session_duration,
328            enabled: self.enabled,
329        }
330    }
331
332    /// Enable or disable profiling.
333    pub async fn set_enabled(&mut self, enabled: bool) {
334        self.enabled = enabled;
335    }
336}
337
338impl Clone for Profiler {
339    fn clone(&self) -> Self {
340        Self {
341            sessions: self.sessions.clone(),
342            completed_sessions: self.completed_sessions.clone(),
343            next_id: self.next_id.clone(),
344            max_sessions: self.max_sessions,
345            enabled: self.enabled,
346        }
347    }
348}
349
350/// Profiler statistics.
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct ProfilerStats {
353    /// Number of active profiling sessions
354    pub active_sessions: usize,
355    /// Number of completed sessions
356    pub completed_sessions: usize,
357    /// Total number of profile entries
358    pub total_entries: usize,
359    /// Average session duration in milliseconds
360    pub avg_session_duration_ms: f64,
361    /// Whether profiling is enabled
362    pub enabled: bool,
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use tokio::time::{sleep, Duration};
369
370    #[tokio::test]
371    async fn test_profiler_basic() {
372        let profiler = Profiler::new(10, true);
373
374        {
375            let _guard = profiler.profile("test_operation");
376            sleep(Duration::from_millis(10)).await;
377        }
378
379        // Wait a bit for async recording to complete
380        sleep(Duration::from_millis(50)).await;
381
382        let stats = profiler.get_stats().await;
383        assert!(stats.total_entries > 0);
384    }
385
386    #[tokio::test]
387    async fn test_profiler_disabled() {
388        let profiler = Profiler::new(10, false);
389
390        let guard = profiler.profile("test_operation");
391        assert!(guard.is_none());
392    }
393
394    #[tokio::test]
395    async fn test_nested_profiling() {
396        let profiler = Profiler::new(10, true);
397
398        {
399            let parent_guard = profiler.profile("parent_operation").unwrap();
400            {
401                let _child_guard = parent_guard.child("child_operation");
402                sleep(Duration::from_millis(5)).await;
403            }
404            sleep(Duration::from_millis(5)).await;
405        }
406
407        // Wait a bit for async recording to complete
408        sleep(Duration::from_millis(50)).await;
409
410        let stats = profiler.get_stats().await;
411        assert!(stats.total_entries >= 2);
412    }
413
414    #[test]
415    fn test_call_tree_node() {
416        let node = CallTreeNode {
417            name: "test".to_string(),
418            duration_ms: 100,
419            self_time_ms: 60,
420            children: vec![
421                CallTreeNode {
422                    name: "child1".to_string(),
423                    duration_ms: 30,
424                    self_time_ms: 30,
425                    children: Vec::new(),
426                },
427                CallTreeNode {
428                    name: "child2".to_string(),
429                    duration_ms: 10,
430                    self_time_ms: 10,
431                    children: Vec::new(),
432                },
433            ],
434        };
435
436        assert_eq!(node.percentage(200), 50.0);
437        assert_eq!(node.self_percentage(200), 30.0);
438
439        let expensive_children = node.most_expensive_children(1);
440        assert_eq!(expensive_children.len(), 1);
441        assert_eq!(expensive_children[0].name, "child1");
442    }
443
444    #[tokio::test]
445    async fn test_profiler_session_limit() {
446        let profiler = Profiler::new(2, true);
447
448        // Create more sessions than the limit
449        for i in 0..5 {
450            profiler
451                .complete_session(i, format!("operation_{}", i))
452                .await;
453        }
454
455        let sessions = profiler.get_completed_sessions().await;
456        assert!(sessions.len() <= 2);
457    }
458}