aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_prof/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_prof/src/lib.rs')
-rw-r--r--crates/ra_prof/src/lib.rs398
1 files changed, 8 insertions, 390 deletions
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
index 2d4f68f5e..89df7f04b 100644
--- a/crates/ra_prof/src/lib.rs
+++ b/crates/ra_prof/src/lib.rs
@@ -1,24 +1,17 @@
1//! FIXME: write short doc here 1//! A collection of tools for profiling rust-analyzer.
2 2
3mod memory_usage; 3mod memory_usage;
4#[cfg(feature = "cpu_profiler")] 4#[cfg(feature = "cpu_profiler")]
5mod google_cpu_profiler; 5mod google_cpu_profiler;
6mod hprof;
7mod tree;
6 8
7use std::{ 9use std::cell::RefCell;
8 cell::RefCell,
9 collections::BTreeMap,
10 collections::HashSet,
11 io::{stderr, Write},
12 sync::{
13 atomic::{AtomicBool, Ordering},
14 RwLock,
15 },
16 time::{Duration, Instant},
17};
18
19use once_cell::sync::Lazy;
20 10
21pub use crate::memory_usage::{Bytes, MemoryUsage}; 11pub use crate::{
12 hprof::{init, init_from, profile},
13 memory_usage::{Bytes, MemoryUsage},
14};
22 15
23// We use jemalloc mainly to get heap usage statistics, actual performance 16// We use jemalloc mainly to get heap usage statistics, actual performance
24// difference is not measures. 17// difference is not measures.
@@ -26,298 +19,6 @@ pub use crate::memory_usage::{Bytes, MemoryUsage};
26#[global_allocator] 19#[global_allocator]
27static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; 20static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
28 21
29pub fn init() {
30 set_filter(match std::env::var("RA_PROFILE") {
31 Ok(spec) => Filter::from_spec(&spec),
32 Err(_) => Filter::disabled(),
33 });
34}
35
36/// Set profiling filter. It specifies descriptions allowed to profile.
37/// This is helpful when call stack has too many nested profiling scopes.
38/// Additionally filter can specify maximum depth of profiling scopes nesting.
39///
40/// #Example
41/// ```
42/// use ra_prof::{set_filter, Filter};
43/// let f = Filter::from_spec("profile1|profile2@2");
44/// set_filter(f);
45/// ```
46pub fn set_filter(f: Filter) {
47 PROFILING_ENABLED.store(f.depth > 0, Ordering::SeqCst);
48 let set: HashSet<_> = f.allowed.iter().cloned().collect();
49 let mut old = FILTER.write().unwrap();
50 let filter_data = FilterData {
51 depth: f.depth,
52 allowed: set,
53 longer_than: f.longer_than,
54 version: old.version + 1,
55 };
56 *old = filter_data;
57}
58
59pub type Label = &'static str;
60
61/// This function starts a profiling scope in the current execution stack with a given description.
62/// It returns a Profile structure and measure elapsed time between this method invocation and Profile structure drop.
63/// It supports nested profiling scopes in case when this function invoked multiple times at the execution stack. In this case the profiling information will be nested at the output.
64/// Profiling information is being printed in the stderr.
65///
66/// # Example
67/// ```
68/// use ra_prof::{profile, set_filter, Filter};
69///
70/// let f = Filter::from_spec("profile1|profile2@2");
71/// set_filter(f);
72/// profiling_function1();
73///
74/// fn profiling_function1() {
75/// let _p = profile("profile1");
76/// profiling_function2();
77/// }
78///
79/// fn profiling_function2() {
80/// let _p = profile("profile2");
81/// }
82/// ```
83/// This will print in the stderr the following:
84/// ```text
85/// 0ms - profile
86/// 0ms - profile2
87/// ```
88pub fn profile(label: Label) -> Profiler {
89 assert!(!label.is_empty());
90 if !PROFILING_ENABLED.load(Ordering::Relaxed) {
91 return Profiler { label: None, detail: None };
92 }
93
94 PROFILE_STACK.with(|stack| {
95 let mut stack = stack.borrow_mut();
96 if stack.starts.is_empty() {
97 if let Ok(f) = FILTER.try_read() {
98 if f.version > stack.filter_data.version {
99 stack.filter_data = f.clone();
100 }
101 };
102 }
103 if stack.starts.len() > stack.filter_data.depth {
104 return Profiler { label: None, detail: None };
105 }
106 let allowed = &stack.filter_data.allowed;
107 if stack.starts.is_empty() && !allowed.is_empty() && !allowed.contains(label) {
108 return Profiler { label: None, detail: None };
109 }
110
111 stack.starts.push(Instant::now());
112 Profiler { label: Some(label), detail: None }
113 })
114}
115
116pub struct Profiler {
117 label: Option<Label>,
118 detail: Option<String>,
119}
120
121impl Profiler {
122 pub fn detail(mut self, detail: impl FnOnce() -> String) -> Profiler {
123 if self.label.is_some() {
124 self.detail = Some(detail())
125 }
126 self
127 }
128}
129
130pub struct Filter {
131 depth: usize,
132 allowed: Vec<String>,
133 longer_than: Duration,
134}
135
136impl Filter {
137 // Filtering syntax
138 // env RA_PROFILE=* // dump everything
139 // env RA_PROFILE=foo|bar|baz // enabled only selected entries
140 // env RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms
141 pub fn from_spec(mut spec: &str) -> Filter {
142 let longer_than = if let Some(idx) = spec.rfind('>') {
143 let longer_than = spec[idx + 1..].parse().expect("invalid profile longer_than");
144 spec = &spec[..idx];
145 Duration::from_millis(longer_than)
146 } else {
147 Duration::new(0, 0)
148 };
149
150 let depth = if let Some(idx) = spec.rfind('@') {
151 let depth: usize = spec[idx + 1..].parse().expect("invalid profile depth");
152 spec = &spec[..idx];
153 depth
154 } else {
155 999
156 };
157 let allowed =
158 if spec == "*" { Vec::new() } else { spec.split('|').map(String::from).collect() };
159 Filter::new(depth, allowed, longer_than)
160 }
161
162 pub fn disabled() -> Filter {
163 Filter::new(0, Vec::new(), Duration::new(0, 0))
164 }
165
166 pub fn new(depth: usize, allowed: Vec<String>, longer_than: Duration) -> Filter {
167 Filter { depth, allowed, longer_than }
168 }
169}
170
171struct ProfileStack {
172 starts: Vec<Instant>,
173 messages: Vec<Message>,
174 filter_data: FilterData,
175}
176
177struct Message {
178 level: usize,
179 duration: Duration,
180 label: Label,
181 detail: Option<String>,
182}
183
184impl ProfileStack {
185 fn new() -> ProfileStack {
186 ProfileStack { starts: Vec::new(), messages: Vec::new(), filter_data: Default::default() }
187 }
188}
189
190#[derive(Default, Clone)]
191struct FilterData {
192 depth: usize,
193 version: usize,
194 allowed: HashSet<String>,
195 longer_than: Duration,
196}
197
198static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false);
199
200static FILTER: Lazy<RwLock<FilterData>> = Lazy::new(Default::default);
201
202thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new()));
203
204impl Drop for Profiler {
205 fn drop(&mut self) {
206 match self {
207 Profiler { label: Some(label), detail } => {
208 PROFILE_STACK.with(|stack| {
209 let mut stack = stack.borrow_mut();
210 let start = stack.starts.pop().unwrap();
211 let duration = start.elapsed();
212 let level = stack.starts.len();
213 stack.messages.push(Message { level, duration, label, detail: detail.take() });
214 if level == 0 {
215 let stdout = stderr();
216 let longer_than = stack.filter_data.longer_than;
217 // Convert to millis for comparison to avoid problems with rounding
218 // (otherwise we could print `0ms` despite user's `>0` filter when
219 // `duration` is just a few nanos).
220 if duration.as_millis() > longer_than.as_millis() {
221 print(&stack.messages, longer_than, &mut stdout.lock());
222 }
223 stack.messages.clear();
224 }
225 });
226 }
227 Profiler { label: None, .. } => (),
228 }
229 }
230}
231
232fn print(msgs: &[Message], longer_than: Duration, out: &mut impl Write) {
233 if msgs.is_empty() {
234 return;
235 }
236 let children_map = idx_to_children(msgs);
237 let root_idx = msgs.len() - 1;
238 print_for_idx(root_idx, &children_map, msgs, longer_than, out);
239}
240
241fn print_for_idx(
242 current_idx: usize,
243 children_map: &[Vec<usize>],
244 msgs: &[Message],
245 longer_than: Duration,
246 out: &mut impl Write,
247) {
248 let current = &msgs[current_idx];
249 let current_indent = " ".repeat(current.level);
250 let detail = current.detail.as_ref().map(|it| format!(" @ {}", it)).unwrap_or_default();
251 writeln!(
252 out,
253 "{}{:5}ms - {}{}",
254 current_indent,
255 current.duration.as_millis(),
256 current.label,
257 detail,
258 )
259 .expect("printing profiling info");
260
261 let longer_than_millis = longer_than.as_millis();
262 let children_indices = &children_map[current_idx];
263 let mut accounted_for = Duration::default();
264 let mut short_children = BTreeMap::new(); // Use `BTreeMap` to get deterministic output.
265
266 for child_idx in children_indices.iter() {
267 let child = &msgs[*child_idx];
268 if child.duration.as_millis() > longer_than_millis {
269 print_for_idx(*child_idx, children_map, msgs, longer_than, out);
270 } else {
271 let pair = short_children.entry(child.label).or_insert((Duration::default(), 0));
272 pair.0 += child.duration;
273 pair.1 += 1;
274 }
275 accounted_for += child.duration;
276 }
277
278 for (child_msg, (duration, count)) in short_children.iter() {
279 let millis = duration.as_millis();
280 writeln!(out, " {}{:5}ms - {} ({} calls)", current_indent, millis, child_msg, count)
281 .expect("printing profiling info");
282 }
283
284 let unaccounted_millis = (current.duration - accounted_for).as_millis();
285 if !children_indices.is_empty()
286 && unaccounted_millis > 0
287 && unaccounted_millis > longer_than_millis
288 {
289 writeln!(out, " {}{:5}ms - ???", current_indent, unaccounted_millis)
290 .expect("printing profiling info");
291 }
292}
293
294/// Returns a mapping from an index in the `msgs` to the vector with the indices of its children.
295///
296/// This assumes that the entries in `msgs` are in the order of when the calls to `profile` finish.
297/// In other words, a postorder of the call graph. In particular, the root is the last element of
298/// `msgs`.
299fn idx_to_children(msgs: &[Message]) -> Vec<Vec<usize>> {
300 // Initialize with the index of the root; `msgs` and `ancestors` should be never empty.
301 assert!(!msgs.is_empty());
302 let mut ancestors = vec![msgs.len() - 1];
303 let mut result: Vec<Vec<usize>> = vec![vec![]; msgs.len()];
304 for (idx, msg) in msgs[..msgs.len() - 1].iter().enumerate().rev() {
305 // We need to find the parent of the current message, i.e., the last ancestor that has a
306 // level lower than the current message.
307 while msgs[*ancestors.last().unwrap()].level >= msg.level {
308 ancestors.pop();
309 }
310 result[*ancestors.last().unwrap()].push(idx);
311 ancestors.push(idx);
312 }
313 // Note that above we visited all children from the last to the first one. Let's reverse vectors
314 // to get the more natural order where the first element is the first child.
315 for vec in result.iter_mut() {
316 vec.reverse();
317 }
318 result
319}
320
321/// Prints backtrace to stderr, useful for debugging. 22/// Prints backtrace to stderr, useful for debugging.
322#[cfg(feature = "backtrace")] 23#[cfg(feature = "backtrace")]
323pub fn print_backtrace() { 24pub fn print_backtrace() {
@@ -403,86 +104,3 @@ impl Drop for CpuProfiler {
403pub fn memory_usage() -> MemoryUsage { 104pub fn memory_usage() -> MemoryUsage {
404 MemoryUsage::current() 105 MemoryUsage::current()
405} 106}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn test_basic_profile() {
413 let s = vec!["profile1".to_string(), "profile2".to_string()];
414 let f = Filter::new(2, s, Duration::new(0, 0));
415 set_filter(f);
416 profiling_function1();
417 }
418
419 fn profiling_function1() {
420 let _p = profile("profile1");
421 profiling_function2();
422 }
423
424 fn profiling_function2() {
425 let _p = profile("profile2");
426 }
427
428 #[test]
429 fn test_longer_than() {
430 let mut result = vec![];
431 let msgs = vec![
432 Message { level: 1, duration: Duration::from_nanos(3), label: "bar", detail: None },
433 Message { level: 1, duration: Duration::from_nanos(2), label: "bar", detail: None },
434 Message { level: 0, duration: Duration::from_millis(1), label: "foo", detail: None },
435 ];
436 print(&msgs, Duration::from_millis(0), &mut result);
437 // The calls to `bar` are so short that they'll be rounded to 0ms and should get collapsed
438 // when printing.
439 assert_eq!(
440 std::str::from_utf8(&result).unwrap(),
441 " 1ms - foo\n 0ms - bar (2 calls)\n"
442 );
443 }
444
445 #[test]
446 fn test_unaccounted_for_topmost() {
447 let mut result = vec![];
448 let msgs = vec![
449 Message { level: 1, duration: Duration::from_millis(2), label: "bar", detail: None },
450 Message { level: 0, duration: Duration::from_millis(5), label: "foo", detail: None },
451 ];
452 print(&msgs, Duration::from_millis(0), &mut result);
453 assert_eq!(
454 std::str::from_utf8(&result).unwrap().lines().collect::<Vec<_>>(),
455 vec![
456 " 5ms - foo",
457 " 2ms - bar",
458 " 3ms - ???",
459 // Dummy comment to improve formatting
460 ]
461 );
462 }
463
464 #[test]
465 fn test_unaccounted_for_multiple_levels() {
466 let mut result = vec![];
467 let msgs = vec![
468 Message { level: 2, duration: Duration::from_millis(3), label: "baz", detail: None },
469 Message { level: 1, duration: Duration::from_millis(5), label: "bar", detail: None },
470 Message { level: 2, duration: Duration::from_millis(2), label: "baz", detail: None },
471 Message { level: 1, duration: Duration::from_millis(4), label: "bar", detail: None },
472 Message { level: 0, duration: Duration::from_millis(9), label: "foo", detail: None },
473 ];
474 print(&msgs, Duration::from_millis(0), &mut result);
475 assert_eq!(
476 std::str::from_utf8(&result).unwrap().lines().collect::<Vec<_>>(),
477 vec![
478 " 9ms - foo",
479 " 5ms - bar",
480 " 3ms - baz",
481 " 2ms - ???",
482 " 4ms - bar",
483 " 2ms - baz",
484 " 2ms - ???",
485 ]
486 );
487 }
488}