diff options
Diffstat (limited to 'crates/ra_prof')
-rw-r--r-- | crates/ra_prof/Cargo.toml | 9 | ||||
-rw-r--r-- | crates/ra_prof/src/lib.rs | 198 |
2 files changed, 207 insertions, 0 deletions
diff --git a/crates/ra_prof/Cargo.toml b/crates/ra_prof/Cargo.toml new file mode 100644 index 000000000..19ce21783 --- /dev/null +++ b/crates/ra_prof/Cargo.toml | |||
@@ -0,0 +1,9 @@ | |||
1 | [package] | ||
2 | edition = "2018" | ||
3 | name = "ra_prof" | ||
4 | version = "0.1.0" | ||
5 | authors = ["rust-analyzer developers"] | ||
6 | publish = false | ||
7 | |||
8 | [dependencies] | ||
9 | lazy_static = "1.3.0" \ No newline at end of file | ||
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 @@ | |||
1 | use std::cell::RefCell; | ||
2 | use std::time::{Duration, Instant}; | ||
3 | use std::mem; | ||
4 | use std::io::{stderr, Write}; | ||
5 | use std::iter::repeat; | ||
6 | use std::collections::{HashSet}; | ||
7 | use std::default::Default; | ||
8 | use std::iter::FromIterator; | ||
9 | use std::sync::RwLock; | ||
10 | use 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 | /// | ||
26 | pub 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 | /// | ||
64 | pub 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 | |||
91 | pub struct Profiler { | ||
92 | desc: Option<String>, | ||
93 | } | ||
94 | |||
95 | pub struct Filter { | ||
96 | depth: usize, | ||
97 | allowed: Vec<String>, | ||
98 | } | ||
99 | |||
100 | impl Filter { | ||
101 | pub fn new(depth: usize, allowed: Vec<String>) -> Filter { | ||
102 | Filter { depth, allowed } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | struct ProfileStack { | ||
107 | starts: Vec<Instant>, | ||
108 | messages: Vec<Message>, | ||
109 | filter_data: FilterData, | ||
110 | } | ||
111 | |||
112 | struct Message { | ||
113 | level: usize, | ||
114 | duration: Duration, | ||
115 | message: String, | ||
116 | } | ||
117 | |||
118 | impl ProfileStack { | ||
119 | fn new() -> ProfileStack { | ||
120 | ProfileStack { starts: Vec::new(), messages: Vec::new(), filter_data: Default::default() } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | #[derive(Default, Clone)] | ||
125 | struct FilterData { | ||
126 | depth: usize, | ||
127 | version: usize, | ||
128 | allowed: HashSet<String>, | ||
129 | } | ||
130 | |||
131 | lazy_static! { | ||
132 | static ref FILTER: RwLock<FilterData> = RwLock::new(Default::default()); | ||
133 | } | ||
134 | |||
135 | thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new())); | ||
136 | |||
137 | impl 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 | |||
160 | fn 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)] | ||
176 | mod 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 | } | ||