diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_prof/Cargo.toml | 9 | ||||
-rw-r--r-- | crates/ra_prof/src/lib.rs | 161 |
2 files changed, 170 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..55ad45fb7 --- /dev/null +++ b/crates/ra_prof/src/lib.rs | |||
@@ -0,0 +1,161 @@ | |||
1 | extern crate lazy_static; | ||
2 | |||
3 | use std::cell::RefCell; | ||
4 | use std::time; | ||
5 | use std::fmt; | ||
6 | use std::mem; | ||
7 | use std::io::{stderr, StderrLock, Write}; | ||
8 | use std::iter::repeat; | ||
9 | use std::collections::{HashSet}; | ||
10 | use std::default::Default; | ||
11 | use std::iter::FromIterator; | ||
12 | use std::sync::RwLock; | ||
13 | |||
14 | use lazy_static::lazy_static; | ||
15 | |||
16 | type Message = (usize, u64, String); | ||
17 | |||
18 | pub struct Profiler { | ||
19 | desc: String, | ||
20 | } | ||
21 | |||
22 | pub struct Filter { | ||
23 | depth: usize, | ||
24 | allowed: Vec<String>, | ||
25 | } | ||
26 | |||
27 | struct ProfileStack { | ||
28 | starts: Vec<time::Instant>, | ||
29 | messages: Vec<Message>, | ||
30 | filter_data: FilterData, | ||
31 | } | ||
32 | |||
33 | impl ProfileStack { | ||
34 | fn new() -> ProfileStack { | ||
35 | ProfileStack { starts: Vec::new(), messages: Vec::new(), filter_data: Default::default() } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | #[derive(Default)] | ||
40 | struct FilterData { | ||
41 | depth: usize, | ||
42 | version: usize, | ||
43 | allowed: HashSet<String>, | ||
44 | } | ||
45 | |||
46 | impl Clone for FilterData { | ||
47 | fn clone(&self) -> FilterData { | ||
48 | let set = HashSet::from_iter(self.allowed.iter().cloned()); | ||
49 | FilterData { depth: self.depth, allowed: set, version: self.version } | ||
50 | } | ||
51 | } | ||
52 | |||
53 | lazy_static! { | ||
54 | static ref FILTER: RwLock<FilterData> = RwLock::new(Default::default()); | ||
55 | } | ||
56 | |||
57 | thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new())); | ||
58 | |||
59 | pub fn set_filter(f: Filter) { | ||
60 | let mut old = FILTER.write().unwrap(); | ||
61 | let set = HashSet::from_iter(f.allowed.iter().cloned()); | ||
62 | let filter_data = FilterData { depth: f.depth, allowed: set, version: old.version + 1 }; | ||
63 | *old = filter_data; | ||
64 | } | ||
65 | |||
66 | pub fn profile<T: fmt::Display>(desc: T) -> Profiler { | ||
67 | PROFILE_STACK.with(|stack| { | ||
68 | let mut stack = stack.borrow_mut(); | ||
69 | if stack.starts.len() == 0 { | ||
70 | match FILTER.try_read() { | ||
71 | Ok(f) => { | ||
72 | if f.version > stack.filter_data.version { | ||
73 | stack.filter_data = f.clone(); | ||
74 | } | ||
75 | } | ||
76 | Err(_) => (), | ||
77 | }; | ||
78 | } | ||
79 | let desc_str = desc.to_string(); | ||
80 | if desc_str.is_empty() { | ||
81 | Profiler { desc: desc_str } | ||
82 | } else if stack.starts.len() < stack.filter_data.depth | ||
83 | && stack.filter_data.allowed.contains(&desc_str) | ||
84 | { | ||
85 | stack.starts.push(time::Instant::now()); | ||
86 | Profiler { desc: desc_str } | ||
87 | } else { | ||
88 | Profiler { desc: String::new() } | ||
89 | } | ||
90 | }) | ||
91 | } | ||
92 | |||
93 | impl Drop for Profiler { | ||
94 | fn drop(&mut self) { | ||
95 | if self.desc.is_empty() { | ||
96 | return; | ||
97 | } | ||
98 | PROFILE_STACK.with(|stack| { | ||
99 | let mut stack = stack.borrow_mut(); | ||
100 | let start = stack.starts.pop().unwrap(); | ||
101 | let duration = start.elapsed(); | ||
102 | let duration_ms = duration.as_secs() * 1000 + u64::from(duration.subsec_millis()); | ||
103 | let stack_len = stack.starts.len(); | ||
104 | let msg = (stack_len, duration_ms, mem::replace(&mut self.desc, String::new())); | ||
105 | stack.messages.push(msg); | ||
106 | if stack_len == 0 { | ||
107 | let stdout = stderr(); | ||
108 | print(0, &stack.messages, 1, &mut stdout.lock()); | ||
109 | stack.messages.clear(); | ||
110 | } | ||
111 | }); | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn print(lvl: usize, msgs: &[Message], enabled: usize, stdout: &mut StderrLock<'_>) { | ||
116 | if lvl > enabled { | ||
117 | return; | ||
118 | } | ||
119 | let mut last = 0; | ||
120 | for (i, &(l, time, ref msg)) in msgs.iter().enumerate() { | ||
121 | if l != lvl { | ||
122 | continue; | ||
123 | } | ||
124 | writeln!( | ||
125 | stdout, | ||
126 | "{} {:6}ms - {}", | ||
127 | repeat(" ").take(lvl + 1).collect::<String>(), | ||
128 | time, | ||
129 | msg | ||
130 | ) | ||
131 | .expect("printing profiling info to stdout"); | ||
132 | |||
133 | print(lvl + 1, &msgs[last..i], enabled, stdout); | ||
134 | last = i; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | #[cfg(test)] | ||
139 | mod tests { | ||
140 | |||
141 | use super::profile; | ||
142 | use super::set_filter; | ||
143 | use super::Filter; | ||
144 | |||
145 | #[test] | ||
146 | fn test_basic_profile() { | ||
147 | let s = vec!["profile1".to_string(), "profile2".to_string()]; | ||
148 | let f = Filter { depth: 2, allowed: s }; | ||
149 | set_filter(f); | ||
150 | profiling_function1(); | ||
151 | } | ||
152 | |||
153 | fn profiling_function1() { | ||
154 | let _p = profile("profile1"); | ||
155 | profiling_function2(); | ||
156 | } | ||
157 | |||
158 | fn profiling_function2() { | ||
159 | let _p = profile("profile2"); | ||
160 | } | ||
161 | } | ||