aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_prof/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_prof/src')
-rw-r--r--crates/ra_prof/src/lib.rs198
1 files changed, 198 insertions, 0 deletions
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs
new file mode 100644
index 000000000..1cc8e361d
--- /dev/null
+++ b/crates/ra_prof/src/lib.rs
@@ -0,0 +1,198 @@
1use std::cell::RefCell;
2use std::time::{Duration, Instant};
3use std::mem;
4use std::io::{stderr, Write};
5use std::iter::repeat;
6use std::collections::{HashSet};
7use std::default::Default;
8use std::iter::FromIterator;
9use std::sync::RwLock;
10use lazy_static::lazy_static;
11
12/// Set profiling filter. It specifies descriptions allowed to profile.
13/// This is helpful when call stack has too many nested profiling scopes.
14/// Additionally filter can specify maximum depth of profiling scopes nesting.
15///
16/// #Example
17/// ```
18/// use ra_prof::set_filter;
19/// use ra_prof::Filter;
20/// let max_depth = 2;
21/// let allowed = vec!["profile1".to_string(), "profile2".to_string()];
22/// let f = Filter::new( max_depth, allowed );
23/// set_filter(f);
24/// ```
25///
26pub fn set_filter(f: Filter) {
27 let set = HashSet::from_iter(f.allowed.iter().cloned());
28 let mut old = FILTER.write().unwrap();
29 let filter_data = FilterData { depth: f.depth, allowed: set, version: old.version + 1 };
30 *old = filter_data;
31}
32
33/// This function starts a profiling scope in the current execution stack with a given description.
34/// It returns a Profile structure and measure elapsed time between this method invocation and Profile structure drop.
35/// 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.
36/// Profiling information is being printed in the stderr.
37///
38/// #Example
39/// ```
40/// use ra_prof::profile;
41/// use ra_prof::set_filter;
42/// use ra_prof::Filter;
43///
44/// let allowed = vec!["profile1".to_string(), "profile2".to_string()];
45/// let f = Filter::new(2, allowed);
46/// set_filter(f);
47/// profiling_function1();
48///
49/// fn profiling_function1() {
50/// let _p = profile("profile1");
51/// profiling_function2();
52/// }
53///
54/// fn profiling_function2() {
55/// let _p = profile("profile2");
56/// }
57/// ```
58/// This will print in the stderr the following:
59/// ```text
60/// 0ms - profile
61/// 0ms - profile2
62/// ```
63///
64pub fn profile(desc: &str) -> Profiler {
65 PROFILE_STACK.with(|stack| {
66 let mut stack = stack.borrow_mut();
67 if stack.starts.len() == 0 {
68 match FILTER.try_read() {
69 Ok(f) => {
70 if f.version > stack.filter_data.version {
71 stack.filter_data = f.clone();
72 }
73 }
74 Err(_) => (),
75 };
76 }
77 let desc_str = desc.to_string();
78 if desc_str.is_empty() {
79 Profiler { desc: None }
80 } else if stack.starts.len() < stack.filter_data.depth
81 && stack.filter_data.allowed.contains(&desc_str)
82 {
83 stack.starts.push(Instant::now());
84 Profiler { desc: Some(desc_str) }
85 } else {
86 Profiler { desc: None }
87 }
88 })
89}
90
91pub struct Profiler {
92 desc: Option<String>,
93}
94
95pub struct Filter {
96 depth: usize,
97 allowed: Vec<String>,
98}
99
100impl Filter {
101 pub fn new(depth: usize, allowed: Vec<String>) -> Filter {
102 Filter { depth, allowed }
103 }
104}
105
106struct ProfileStack {
107 starts: Vec<Instant>,
108 messages: Vec<Message>,
109 filter_data: FilterData,
110}
111
112struct Message {
113 level: usize,
114 duration: Duration,
115 message: String,
116}
117
118impl ProfileStack {
119 fn new() -> ProfileStack {
120 ProfileStack { starts: Vec::new(), messages: Vec::new(), filter_data: Default::default() }
121 }
122}
123
124#[derive(Default, Clone)]
125struct FilterData {
126 depth: usize,
127 version: usize,
128 allowed: HashSet<String>,
129}
130
131lazy_static! {
132 static ref FILTER: RwLock<FilterData> = RwLock::new(Default::default());
133}
134
135thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new()));
136
137impl Drop for Profiler {
138 fn drop(&mut self) {
139 match self {
140 Profiler { desc: Some(desc) } => {
141 PROFILE_STACK.with(|stack| {
142 let mut stack = stack.borrow_mut();
143 let start = stack.starts.pop().unwrap();
144 let duration = start.elapsed();
145 let level = stack.starts.len();
146 let message = mem::replace(desc, String::new());
147 stack.messages.push(Message { level, duration, message });
148 if level == 0 {
149 let stdout = stderr();
150 print(0, &stack.messages, &mut stdout.lock());
151 stack.messages.clear();
152 }
153 });
154 }
155 Profiler { desc: None } => (),
156 }
157 }
158}
159
160fn print(lvl: usize, msgs: &[Message], out: &mut impl Write) {
161 let mut last = 0;
162 let indent = repeat(" ").take(lvl + 1).collect::<String>();
163 for (i, &Message { level: l, duration: dur, message: ref msg }) in msgs.iter().enumerate() {
164 if l != lvl {
165 continue;
166 }
167 writeln!(out, "{} {:6}ms - {}", indent, dur.as_millis(), msg)
168 .expect("printing profiling info to stdout");
169
170 print(lvl + 1, &msgs[last..i], out);
171 last = i;
172 }
173}
174
175#[cfg(test)]
176mod tests {
177
178 use super::profile;
179 use super::set_filter;
180 use super::Filter;
181
182 #[test]
183 fn test_basic_profile() {
184 let s = vec!["profile1".to_string(), "profile2".to_string()];
185 let f = Filter::new(2, s);
186 set_filter(f);
187 profiling_function1();
188 }
189
190 fn profiling_function1() {
191 let _p = profile("profile1");
192 profiling_function2();
193 }
194
195 fn profiling_function2() {
196 let _p = profile("profile2");
197 }
198}