rez_lsp_server/performance/
profiler.rs1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7use tokio::sync::RwLock;
8
9#[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 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 pub fn elapsed(&self) -> Duration {
36 self.start_time.elapsed()
37 }
38
39 pub fn session_id(&self) -> u64 {
41 self.session_id
42 }
43
44 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 let profiler = self.profiler.clone();
63 tokio::spawn(async move {
64 profiler.record_entry(profile_entry).await;
65 });
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ProfileEntry {
72 pub name: String,
74 pub duration_ms: u64,
76 #[serde(skip, default = "Instant::now")]
78 pub start_time: Instant,
79 pub parent_id: Option<u64>,
81 pub session_id: u64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ProfileSession {
88 pub root_operation: String,
90 pub entries: Vec<ProfileEntry>,
92 pub total_duration_ms: u64,
94 pub start_timestamp: u64,
96}
97
98impl ProfileSession {
99 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct CallTreeNode {
146 pub name: String,
148 pub duration_ms: u64,
150 pub self_time_ms: u64,
152 pub children: Vec<CallTreeNode>,
154}
155
156impl CallTreeNode {
157 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 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 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#[derive(Debug)]
186pub struct Profiler {
187 sessions: Arc<RwLock<HashMap<u64, Vec<ProfileEntry>>>>,
189 completed_sessions: Arc<RwLock<Vec<ProfileSession>>>,
191 next_id: Arc<RwLock<u64>>,
193 max_sessions: usize,
195 enabled: bool,
197}
198
199impl Profiler {
200 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 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 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 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 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 while completed.len() > self.max_sessions {
270 completed.remove(0);
271 }
272 }
273 }
274 }
275
276 pub async fn get_completed_sessions(&self) -> Vec<ProfileSession> {
278 let completed = self.completed_sessions.read().await;
279 completed.clone()
280 }
281
282 pub async fn get_latest_session(&self) -> Option<ProfileSession> {
284 let completed = self.completed_sessions.read().await;
285 completed.last().cloned()
286 }
287
288 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 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 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct ProfilerStats {
353 pub active_sessions: usize,
355 pub completed_sessions: usize,
357 pub total_entries: usize,
359 pub avg_session_duration_ms: f64,
361 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 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 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 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}