diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-03 10:09:11 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-03 10:09:11 +0100 |
commit | c6c88070c4f25cd3710f03b7461cb277de8d3cc5 (patch) | |
tree | cfe6ec2fb43dcfb9a7f1c5698aaac0d17b2bf78a /crates/ra_prof/src | |
parent | b8e58d4a2f317fe300f13416858f33e860138c4d (diff) | |
parent | b74449e9952846a8ea66c3507e52c24348d6dbc9 (diff) |
Merge #1068
1068: profiling crate first draft r=matklad a=pasa
I've made this first draft for #961
Could you look at it? Is this something what you are looking for?
It has lack of tests. I can't figure out how to test stderr output in rust right now. Do you have some clues?
Additionally I'm thinking about to implement procedural macros to annotate methods with this profiler. Will it be helpful?
Co-authored-by: Sergey Parilin <[email protected]>
Diffstat (limited to 'crates/ra_prof/src')
-rw-r--r-- | crates/ra_prof/src/lib.rs | 153 |
1 files changed, 153 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..abddff960 --- /dev/null +++ b/crates/ra_prof/src/lib.rs | |||
@@ -0,0 +1,153 @@ | |||
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 | pub fn set_filter(f: Filter) { | ||
13 | let set = HashSet::from_iter(f.allowed.iter().cloned()); | ||
14 | let mut old = FILTER.write().unwrap(); | ||
15 | let filter_data = FilterData { depth: f.depth, allowed: set, version: old.version + 1 }; | ||
16 | *old = filter_data; | ||
17 | } | ||
18 | |||
19 | pub fn profile(desc: &str) -> Profiler { | ||
20 | PROFILE_STACK.with(|stack| { | ||
21 | let mut stack = stack.borrow_mut(); | ||
22 | if stack.starts.len() == 0 { | ||
23 | match FILTER.try_read() { | ||
24 | Ok(f) => { | ||
25 | if f.version > stack.filter_data.version { | ||
26 | stack.filter_data = f.clone(); | ||
27 | } | ||
28 | } | ||
29 | Err(_) => (), | ||
30 | }; | ||
31 | } | ||
32 | let desc_str = desc.to_string(); | ||
33 | if desc_str.is_empty() { | ||
34 | Profiler { desc: None } | ||
35 | } else if stack.starts.len() < stack.filter_data.depth | ||
36 | && stack.filter_data.allowed.contains(&desc_str) | ||
37 | { | ||
38 | stack.starts.push(Instant::now()); | ||
39 | Profiler { desc: Some(desc_str) } | ||
40 | } else { | ||
41 | Profiler { desc: None } | ||
42 | } | ||
43 | }) | ||
44 | } | ||
45 | |||
46 | pub struct Profiler { | ||
47 | desc: Option<String>, | ||
48 | } | ||
49 | |||
50 | pub struct Filter { | ||
51 | depth: usize, | ||
52 | allowed: Vec<String>, | ||
53 | } | ||
54 | |||
55 | impl Filter { | ||
56 | pub fn new(depth: usize, allowed: Vec<String>) -> Filter { | ||
57 | Filter { depth, allowed } | ||
58 | } | ||
59 | } | ||
60 | |||
61 | struct ProfileStack { | ||
62 | starts: Vec<Instant>, | ||
63 | messages: Vec<Message>, | ||
64 | filter_data: FilterData, | ||
65 | } | ||
66 | |||
67 | struct Message { | ||
68 | level: usize, | ||
69 | duration: Duration, | ||
70 | message: String, | ||
71 | } | ||
72 | |||
73 | impl ProfileStack { | ||
74 | fn new() -> ProfileStack { | ||
75 | ProfileStack { starts: Vec::new(), messages: Vec::new(), filter_data: Default::default() } | ||
76 | } | ||
77 | } | ||
78 | |||
79 | #[derive(Default, Clone)] | ||
80 | struct FilterData { | ||
81 | depth: usize, | ||
82 | version: usize, | ||
83 | allowed: HashSet<String>, | ||
84 | } | ||
85 | |||
86 | lazy_static! { | ||
87 | static ref FILTER: RwLock<FilterData> = RwLock::new(Default::default()); | ||
88 | } | ||
89 | |||
90 | thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new())); | ||
91 | |||
92 | impl Drop for Profiler { | ||
93 | fn drop(&mut self) { | ||
94 | match self { | ||
95 | Profiler { desc: Some(desc) } => { | ||
96 | PROFILE_STACK.with(|stack| { | ||
97 | let mut stack = stack.borrow_mut(); | ||
98 | let start = stack.starts.pop().unwrap(); | ||
99 | let duration = start.elapsed(); | ||
100 | let level = stack.starts.len(); | ||
101 | let message = mem::replace(desc, String::new()); | ||
102 | stack.messages.push(Message { level, duration, message }); | ||
103 | if level == 0 { | ||
104 | let stdout = stderr(); | ||
105 | print(0, &stack.messages, &mut stdout.lock()); | ||
106 | stack.messages.clear(); | ||
107 | } | ||
108 | }); | ||
109 | } | ||
110 | Profiler { desc: None } => (), | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn print(lvl: usize, msgs: &[Message], out: &mut impl Write) { | ||
116 | let mut last = 0; | ||
117 | let indent = repeat(" ").take(lvl + 1).collect::<String>(); | ||
118 | for (i, &Message { level: l, duration: dur, message: ref msg }) in msgs.iter().enumerate() { | ||
119 | if l != lvl { | ||
120 | continue; | ||
121 | } | ||
122 | writeln!(out, "{} {:6}ms - {}", indent, dur.as_millis(), msg) | ||
123 | .expect("printing profiling info to stdout"); | ||
124 | |||
125 | print(lvl + 1, &msgs[last..i], out); | ||
126 | last = i; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | #[cfg(test)] | ||
131 | mod tests { | ||
132 | |||
133 | use super::profile; | ||
134 | use super::set_filter; | ||
135 | use super::Filter; | ||
136 | |||
137 | #[test] | ||
138 | fn test_basic_profile() { | ||
139 | let s = vec!["profile1".to_string(), "profile2".to_string()]; | ||
140 | let f = Filter { depth: 2, allowed: s }; | ||
141 | set_filter(f); | ||
142 | profiling_function1(); | ||
143 | } | ||
144 | |||
145 | fn profiling_function1() { | ||
146 | let _p = profile("profile1"); | ||
147 | profiling_function2(); | ||
148 | } | ||
149 | |||
150 | fn profiling_function2() { | ||
151 | let _p = profile("profile2"); | ||
152 | } | ||
153 | } | ||