diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/profile | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/profile')
-rw-r--r-- | crates/profile/Cargo.toml | 27 | ||||
-rw-r--r-- | crates/profile/src/google_cpu_profiler.rs | 39 | ||||
-rw-r--r-- | crates/profile/src/hprof.rs | 240 | ||||
-rw-r--r-- | crates/profile/src/lib.rs | 109 | ||||
-rw-r--r-- | crates/profile/src/memory_usage.rs | 75 | ||||
-rw-r--r-- | crates/profile/src/stop_watch.rs | 86 | ||||
-rw-r--r-- | crates/profile/src/tree.rs | 84 |
7 files changed, 660 insertions, 0 deletions
diff --git a/crates/profile/Cargo.toml b/crates/profile/Cargo.toml new file mode 100644 index 000000000..e271e3a56 --- /dev/null +++ b/crates/profile/Cargo.toml | |||
@@ -0,0 +1,27 @@ | |||
1 | [package] | ||
2 | name = "profile" | ||
3 | version = "0.0.0" | ||
4 | license = "MIT OR Apache-2.0" | ||
5 | authors = ["rust-analyzer developers"] | ||
6 | edition = "2018" | ||
7 | |||
8 | [lib] | ||
9 | doctest = false | ||
10 | |||
11 | [dependencies] | ||
12 | once_cell = "1.3.1" | ||
13 | cfg-if = "0.1.10" | ||
14 | libc = "0.2.73" | ||
15 | backtrace = { version = "0.3.44", optional = true } | ||
16 | |||
17 | arena = { path = "../arena" } | ||
18 | |||
19 | [target.'cfg(target_os = "linux")'.dependencies] | ||
20 | perf-event = "0.4" | ||
21 | |||
22 | [features] | ||
23 | cpu_profiler = [] | ||
24 | |||
25 | # Uncomment to enable for the whole crate graph | ||
26 | # default = [ "backtrace" ] | ||
27 | # default = [ "cpu_profiler" ] | ||
diff --git a/crates/profile/src/google_cpu_profiler.rs b/crates/profile/src/google_cpu_profiler.rs new file mode 100644 index 000000000..db865c65b --- /dev/null +++ b/crates/profile/src/google_cpu_profiler.rs | |||
@@ -0,0 +1,39 @@ | |||
1 | //! https://github.com/gperftools/gperftools | ||
2 | |||
3 | use std::{ | ||
4 | ffi::CString, | ||
5 | os::raw::c_char, | ||
6 | path::Path, | ||
7 | sync::atomic::{AtomicUsize, Ordering}, | ||
8 | }; | ||
9 | |||
10 | #[link(name = "profiler")] | ||
11 | #[allow(non_snake_case)] | ||
12 | extern "C" { | ||
13 | fn ProfilerStart(fname: *const c_char) -> i32; | ||
14 | fn ProfilerStop(); | ||
15 | } | ||
16 | |||
17 | static PROFILER_STATE: AtomicUsize = AtomicUsize::new(OFF); | ||
18 | const OFF: usize = 0; | ||
19 | const ON: usize = 1; | ||
20 | const PENDING: usize = 2; | ||
21 | |||
22 | pub fn start(path: &Path) { | ||
23 | if PROFILER_STATE.compare_and_swap(OFF, PENDING, Ordering::SeqCst) != OFF { | ||
24 | panic!("profiler already started"); | ||
25 | } | ||
26 | let path = CString::new(path.display().to_string()).unwrap(); | ||
27 | if unsafe { ProfilerStart(path.as_ptr()) } == 0 { | ||
28 | panic!("profiler failed to start") | ||
29 | } | ||
30 | assert!(PROFILER_STATE.compare_and_swap(PENDING, ON, Ordering::SeqCst) == PENDING); | ||
31 | } | ||
32 | |||
33 | pub fn stop() { | ||
34 | if PROFILER_STATE.compare_and_swap(ON, PENDING, Ordering::SeqCst) != ON { | ||
35 | panic!("profiler is not started") | ||
36 | } | ||
37 | unsafe { ProfilerStop() }; | ||
38 | assert!(PROFILER_STATE.compare_and_swap(PENDING, OFF, Ordering::SeqCst) == PENDING); | ||
39 | } | ||
diff --git a/crates/profile/src/hprof.rs b/crates/profile/src/hprof.rs new file mode 100644 index 000000000..934cc8e37 --- /dev/null +++ b/crates/profile/src/hprof.rs | |||
@@ -0,0 +1,240 @@ | |||
1 | //! Simple hierarchical profiler | ||
2 | use once_cell::sync::Lazy; | ||
3 | use std::{ | ||
4 | cell::RefCell, | ||
5 | collections::{BTreeMap, HashSet}, | ||
6 | io::{stderr, Write}, | ||
7 | sync::{ | ||
8 | atomic::{AtomicBool, Ordering}, | ||
9 | RwLock, | ||
10 | }, | ||
11 | time::{Duration, Instant}, | ||
12 | }; | ||
13 | |||
14 | use crate::tree::{Idx, Tree}; | ||
15 | |||
16 | /// Filtering syntax | ||
17 | /// env RA_PROFILE=* // dump everything | ||
18 | /// env RA_PROFILE=foo|bar|baz // enabled only selected entries | ||
19 | /// env RA_PROFILE=*@3>10 // dump everything, up to depth 3, if it takes more than 10 ms | ||
20 | pub fn init() { | ||
21 | let spec = std::env::var("RA_PROFILE").unwrap_or_default(); | ||
22 | init_from(&spec); | ||
23 | } | ||
24 | |||
25 | pub fn init_from(spec: &str) { | ||
26 | let filter = if spec.is_empty() { Filter::disabled() } else { Filter::from_spec(spec) }; | ||
27 | filter.install(); | ||
28 | } | ||
29 | |||
30 | pub type Label = &'static str; | ||
31 | |||
32 | /// This function starts a profiling scope in the current execution stack with a given description. | ||
33 | /// It returns a `Profile` struct that measures elapsed time between this method invocation and `Profile` struct drop. | ||
34 | /// It supports nested profiling scopes in case when this function is invoked multiple times at the execution stack. | ||
35 | /// 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 | /// profile::init_from("profile1|profile2@2"); | ||
41 | /// profiling_function1(); | ||
42 | /// | ||
43 | /// fn profiling_function1() { | ||
44 | /// let _p = profile::span("profile1"); | ||
45 | /// profiling_function2(); | ||
46 | /// } | ||
47 | /// | ||
48 | /// fn profiling_function2() { | ||
49 | /// let _p = profile::span("profile2"); | ||
50 | /// } | ||
51 | /// ``` | ||
52 | /// This will print in the stderr the following: | ||
53 | /// ```text | ||
54 | /// 0ms - profile | ||
55 | /// 0ms - profile2 | ||
56 | /// ``` | ||
57 | pub fn span(label: Label) -> ProfileSpan { | ||
58 | assert!(!label.is_empty()); | ||
59 | |||
60 | if PROFILING_ENABLED.load(Ordering::Relaxed) | ||
61 | && PROFILE_STACK.with(|stack| stack.borrow_mut().push(label)) | ||
62 | { | ||
63 | ProfileSpan(Some(ProfilerImpl { label, detail: None })) | ||
64 | } else { | ||
65 | ProfileSpan(None) | ||
66 | } | ||
67 | } | ||
68 | |||
69 | pub struct ProfileSpan(Option<ProfilerImpl>); | ||
70 | |||
71 | struct ProfilerImpl { | ||
72 | label: Label, | ||
73 | detail: Option<String>, | ||
74 | } | ||
75 | |||
76 | impl ProfileSpan { | ||
77 | pub fn detail(mut self, detail: impl FnOnce() -> String) -> ProfileSpan { | ||
78 | if let Some(profiler) = &mut self.0 { | ||
79 | profiler.detail = Some(detail()) | ||
80 | } | ||
81 | self | ||
82 | } | ||
83 | } | ||
84 | |||
85 | impl Drop for ProfilerImpl { | ||
86 | fn drop(&mut self) { | ||
87 | PROFILE_STACK.with(|it| it.borrow_mut().pop(self.label, self.detail.take())); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false); | ||
92 | static FILTER: Lazy<RwLock<Filter>> = Lazy::new(Default::default); | ||
93 | thread_local!(static PROFILE_STACK: RefCell<ProfileStack> = RefCell::new(ProfileStack::new())); | ||
94 | |||
95 | #[derive(Default, Clone, Debug)] | ||
96 | struct Filter { | ||
97 | depth: usize, | ||
98 | allowed: HashSet<String>, | ||
99 | longer_than: Duration, | ||
100 | version: usize, | ||
101 | } | ||
102 | |||
103 | impl Filter { | ||
104 | fn disabled() -> Filter { | ||
105 | Filter::default() | ||
106 | } | ||
107 | |||
108 | fn from_spec(mut spec: &str) -> Filter { | ||
109 | let longer_than = if let Some(idx) = spec.rfind('>') { | ||
110 | let longer_than = spec[idx + 1..].parse().expect("invalid profile longer_than"); | ||
111 | spec = &spec[..idx]; | ||
112 | Duration::from_millis(longer_than) | ||
113 | } else { | ||
114 | Duration::new(0, 0) | ||
115 | }; | ||
116 | |||
117 | let depth = if let Some(idx) = spec.rfind('@') { | ||
118 | let depth: usize = spec[idx + 1..].parse().expect("invalid profile depth"); | ||
119 | spec = &spec[..idx]; | ||
120 | depth | ||
121 | } else { | ||
122 | 999 | ||
123 | }; | ||
124 | let allowed = | ||
125 | if spec == "*" { HashSet::new() } else { spec.split('|').map(String::from).collect() }; | ||
126 | Filter { depth, allowed, longer_than, version: 0 } | ||
127 | } | ||
128 | |||
129 | fn install(mut self) { | ||
130 | PROFILING_ENABLED.store(self.depth > 0, Ordering::SeqCst); | ||
131 | let mut old = FILTER.write().unwrap(); | ||
132 | self.version = old.version + 1; | ||
133 | *old = self; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | struct ProfileStack { | ||
138 | starts: Vec<Instant>, | ||
139 | filter: Filter, | ||
140 | messages: Tree<Message>, | ||
141 | } | ||
142 | |||
143 | #[derive(Default)] | ||
144 | struct Message { | ||
145 | duration: Duration, | ||
146 | label: Label, | ||
147 | detail: Option<String>, | ||
148 | } | ||
149 | |||
150 | impl ProfileStack { | ||
151 | fn new() -> ProfileStack { | ||
152 | ProfileStack { starts: Vec::new(), messages: Tree::default(), filter: Default::default() } | ||
153 | } | ||
154 | |||
155 | fn push(&mut self, label: Label) -> bool { | ||
156 | if self.starts.is_empty() { | ||
157 | if let Ok(f) = FILTER.try_read() { | ||
158 | if f.version > self.filter.version { | ||
159 | self.filter = f.clone(); | ||
160 | } | ||
161 | }; | ||
162 | } | ||
163 | if self.starts.len() > self.filter.depth { | ||
164 | return false; | ||
165 | } | ||
166 | let allowed = &self.filter.allowed; | ||
167 | if self.starts.is_empty() && !allowed.is_empty() && !allowed.contains(label) { | ||
168 | return false; | ||
169 | } | ||
170 | |||
171 | self.starts.push(Instant::now()); | ||
172 | self.messages.start(); | ||
173 | true | ||
174 | } | ||
175 | |||
176 | pub fn pop(&mut self, label: Label, detail: Option<String>) { | ||
177 | let start = self.starts.pop().unwrap(); | ||
178 | let duration = start.elapsed(); | ||
179 | self.messages.finish(Message { duration, label, detail }); | ||
180 | if self.starts.is_empty() { | ||
181 | let longer_than = self.filter.longer_than; | ||
182 | // Convert to millis for comparison to avoid problems with rounding | ||
183 | // (otherwise we could print `0ms` despite user's `>0` filter when | ||
184 | // `duration` is just a few nanos). | ||
185 | if duration.as_millis() > longer_than.as_millis() { | ||
186 | if let Some(root) = self.messages.root() { | ||
187 | print(&self.messages, root, 0, longer_than, &mut stderr().lock()); | ||
188 | } | ||
189 | } | ||
190 | self.messages.clear(); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | fn print( | ||
196 | tree: &Tree<Message>, | ||
197 | curr: Idx<Message>, | ||
198 | level: u32, | ||
199 | longer_than: Duration, | ||
200 | out: &mut impl Write, | ||
201 | ) { | ||
202 | let current_indent = " ".repeat(level as usize); | ||
203 | let detail = tree[curr].detail.as_ref().map(|it| format!(" @ {}", it)).unwrap_or_default(); | ||
204 | writeln!( | ||
205 | out, | ||
206 | "{}{:5}ms - {}{}", | ||
207 | current_indent, | ||
208 | tree[curr].duration.as_millis(), | ||
209 | tree[curr].label, | ||
210 | detail, | ||
211 | ) | ||
212 | .expect("printing profiling info"); | ||
213 | |||
214 | let mut accounted_for = Duration::default(); | ||
215 | let mut short_children = BTreeMap::new(); // Use `BTreeMap` to get deterministic output. | ||
216 | for child in tree.children(curr) { | ||
217 | accounted_for += tree[child].duration; | ||
218 | |||
219 | if tree[child].duration.as_millis() > longer_than.as_millis() { | ||
220 | print(tree, child, level + 1, longer_than, out) | ||
221 | } else { | ||
222 | let (total_duration, cnt) = | ||
223 | short_children.entry(tree[child].label).or_insert((Duration::default(), 0)); | ||
224 | *total_duration += tree[child].duration; | ||
225 | *cnt += 1; | ||
226 | } | ||
227 | } | ||
228 | |||
229 | for (child_msg, (duration, count)) in short_children.iter() { | ||
230 | let millis = duration.as_millis(); | ||
231 | writeln!(out, " {}{:5}ms - {} ({} calls)", current_indent, millis, child_msg, count) | ||
232 | .expect("printing profiling info"); | ||
233 | } | ||
234 | |||
235 | let unaccounted = tree[curr].duration - accounted_for; | ||
236 | if tree.children(curr).next().is_some() && unaccounted > longer_than { | ||
237 | writeln!(out, " {}{:5}ms - ???", current_indent, unaccounted.as_millis()) | ||
238 | .expect("printing profiling info"); | ||
239 | } | ||
240 | } | ||
diff --git a/crates/profile/src/lib.rs b/crates/profile/src/lib.rs new file mode 100644 index 000000000..ab19271c7 --- /dev/null +++ b/crates/profile/src/lib.rs | |||
@@ -0,0 +1,109 @@ | |||
1 | //! A collection of tools for profiling rust-analyzer. | ||
2 | |||
3 | mod stop_watch; | ||
4 | mod memory_usage; | ||
5 | #[cfg(feature = "cpu_profiler")] | ||
6 | mod google_cpu_profiler; | ||
7 | mod hprof; | ||
8 | mod tree; | ||
9 | |||
10 | use std::cell::RefCell; | ||
11 | |||
12 | pub use crate::{ | ||
13 | hprof::{init, init_from, span}, | ||
14 | memory_usage::{Bytes, MemoryUsage}, | ||
15 | stop_watch::{StopWatch, StopWatchSpan}, | ||
16 | }; | ||
17 | |||
18 | /// Prints backtrace to stderr, useful for debugging. | ||
19 | #[cfg(feature = "backtrace")] | ||
20 | pub fn print_backtrace() { | ||
21 | let bt = backtrace::Backtrace::new(); | ||
22 | eprintln!("{:?}", bt); | ||
23 | } | ||
24 | #[cfg(not(feature = "backtrace"))] | ||
25 | pub fn print_backtrace() { | ||
26 | eprintln!( | ||
27 | r#"enable the backtrace feature: | ||
28 | profile = {{ path = "../profile", features = [ "backtrace"] }} | ||
29 | "# | ||
30 | ); | ||
31 | } | ||
32 | |||
33 | thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); | ||
34 | |||
35 | /// Allows to check if the current code is withing some dynamic scope, can be | ||
36 | /// useful during debugging to figure out why a function is called. | ||
37 | pub struct Scope { | ||
38 | prev: bool, | ||
39 | } | ||
40 | |||
41 | impl Scope { | ||
42 | #[must_use] | ||
43 | pub fn enter() -> Scope { | ||
44 | let prev = IN_SCOPE.with(|slot| std::mem::replace(&mut *slot.borrow_mut(), true)); | ||
45 | Scope { prev } | ||
46 | } | ||
47 | pub fn is_active() -> bool { | ||
48 | IN_SCOPE.with(|slot| *slot.borrow()) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | impl Drop for Scope { | ||
53 | fn drop(&mut self) { | ||
54 | IN_SCOPE.with(|slot| *slot.borrow_mut() = self.prev); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | /// A wrapper around google_cpu_profiler. | ||
59 | /// | ||
60 | /// Usage: | ||
61 | /// 1. Install gpref_tools (https://github.com/gperftools/gperftools), probably packaged with your Linux distro. | ||
62 | /// 2. Build with `cpu_profiler` feature. | ||
63 | /// 3. Tun the code, the *raw* output would be in the `./out.profile` file. | ||
64 | /// 4. Install pprof for visualization (https://github.com/google/pprof). | ||
65 | /// 5. Bump sampling frequency to once per ms: `export CPUPROFILE_FREQUENCY=1000` | ||
66 | /// 6. Use something like `pprof -svg target/release/rust-analyzer ./out.profile` to see the results. | ||
67 | /// | ||
68 | /// For example, here's how I run profiling on NixOS: | ||
69 | /// | ||
70 | /// ```bash | ||
71 | /// $ nix-shell -p gperftools --run \ | ||
72 | /// 'cargo run --release -p rust-analyzer -- parse < ~/projects/rustbench/parser.rs > /dev/null' | ||
73 | /// ``` | ||
74 | /// | ||
75 | /// See this diff for how to profile completions: | ||
76 | /// | ||
77 | /// https://github.com/rust-analyzer/rust-analyzer/pull/5306 | ||
78 | #[derive(Debug)] | ||
79 | pub struct CpuSpan { | ||
80 | _private: (), | ||
81 | } | ||
82 | |||
83 | #[must_use] | ||
84 | pub fn cpu_span() -> CpuSpan { | ||
85 | #[cfg(feature = "cpu_profiler")] | ||
86 | { | ||
87 | google_cpu_profiler::start("./out.profile".as_ref()) | ||
88 | } | ||
89 | |||
90 | #[cfg(not(feature = "cpu_profiler"))] | ||
91 | { | ||
92 | eprintln!("cpu_profiler feature is disabled") | ||
93 | } | ||
94 | |||
95 | CpuSpan { _private: () } | ||
96 | } | ||
97 | |||
98 | impl Drop for CpuSpan { | ||
99 | fn drop(&mut self) { | ||
100 | #[cfg(feature = "cpu_profiler")] | ||
101 | { | ||
102 | google_cpu_profiler::stop() | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | |||
107 | pub fn memory_usage() -> MemoryUsage { | ||
108 | MemoryUsage::current() | ||
109 | } | ||
diff --git a/crates/profile/src/memory_usage.rs b/crates/profile/src/memory_usage.rs new file mode 100644 index 000000000..83390212a --- /dev/null +++ b/crates/profile/src/memory_usage.rs | |||
@@ -0,0 +1,75 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | use std::fmt; | ||
3 | |||
4 | use cfg_if::cfg_if; | ||
5 | |||
6 | #[derive(Copy, Clone)] | ||
7 | pub struct MemoryUsage { | ||
8 | pub allocated: Bytes, | ||
9 | } | ||
10 | |||
11 | impl fmt::Display for MemoryUsage { | ||
12 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
13 | write!(fmt, "{}", self.allocated) | ||
14 | } | ||
15 | } | ||
16 | |||
17 | impl std::ops::Sub for MemoryUsage { | ||
18 | type Output = MemoryUsage; | ||
19 | fn sub(self, rhs: MemoryUsage) -> MemoryUsage { | ||
20 | MemoryUsage { allocated: self.allocated - rhs.allocated } | ||
21 | } | ||
22 | } | ||
23 | |||
24 | impl MemoryUsage { | ||
25 | pub fn current() -> MemoryUsage { | ||
26 | cfg_if! { | ||
27 | if #[cfg(all(target_os = "linux", target_env = "gnu"))] { | ||
28 | // Note: This is incredibly slow. | ||
29 | let alloc = unsafe { libc::mallinfo() }.uordblks as isize; | ||
30 | MemoryUsage { allocated: Bytes(alloc) } | ||
31 | } else { | ||
32 | MemoryUsage { allocated: Bytes(0) } | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | |||
38 | #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] | ||
39 | pub struct Bytes(isize); | ||
40 | |||
41 | impl Bytes { | ||
42 | pub fn megabytes(self) -> isize { | ||
43 | self.0 / 1024 / 1024 | ||
44 | } | ||
45 | } | ||
46 | |||
47 | impl fmt::Display for Bytes { | ||
48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
49 | let bytes = self.0; | ||
50 | let mut value = bytes; | ||
51 | let mut suffix = "b"; | ||
52 | if value.abs() > 4096 { | ||
53 | value /= 1024; | ||
54 | suffix = "kb"; | ||
55 | if value.abs() > 4096 { | ||
56 | value /= 1024; | ||
57 | suffix = "mb"; | ||
58 | } | ||
59 | } | ||
60 | f.pad(&format!("{}{}", value, suffix)) | ||
61 | } | ||
62 | } | ||
63 | |||
64 | impl std::ops::AddAssign<usize> for Bytes { | ||
65 | fn add_assign(&mut self, x: usize) { | ||
66 | self.0 += x as isize; | ||
67 | } | ||
68 | } | ||
69 | |||
70 | impl std::ops::Sub for Bytes { | ||
71 | type Output = Bytes; | ||
72 | fn sub(self, rhs: Bytes) -> Bytes { | ||
73 | Bytes(self.0 - rhs.0) | ||
74 | } | ||
75 | } | ||
diff --git a/crates/profile/src/stop_watch.rs b/crates/profile/src/stop_watch.rs new file mode 100644 index 000000000..5e276190e --- /dev/null +++ b/crates/profile/src/stop_watch.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | //! Like `std::time::Instant`, but also measures memory & CPU cycles. | ||
2 | use std::{ | ||
3 | fmt, | ||
4 | time::{Duration, Instant}, | ||
5 | }; | ||
6 | |||
7 | use crate::MemoryUsage; | ||
8 | |||
9 | pub struct StopWatch { | ||
10 | time: Instant, | ||
11 | #[cfg(target_os = "linux")] | ||
12 | counter: Option<perf_event::Counter>, | ||
13 | memory: Option<MemoryUsage>, | ||
14 | } | ||
15 | |||
16 | pub struct StopWatchSpan { | ||
17 | pub time: Duration, | ||
18 | pub instructions: Option<u64>, | ||
19 | pub memory: Option<MemoryUsage>, | ||
20 | } | ||
21 | |||
22 | impl StopWatch { | ||
23 | pub fn start() -> StopWatch { | ||
24 | #[cfg(target_os = "linux")] | ||
25 | let counter = { | ||
26 | let mut counter = perf_event::Builder::new() | ||
27 | .build() | ||
28 | .map_err(|err| eprintln!("Failed to create perf counter: {}", err)) | ||
29 | .ok(); | ||
30 | if let Some(counter) = &mut counter { | ||
31 | if let Err(err) = counter.enable() { | ||
32 | eprintln!("Failed to start perf counter: {}", err) | ||
33 | } | ||
34 | } | ||
35 | counter | ||
36 | }; | ||
37 | let time = Instant::now(); | ||
38 | StopWatch { | ||
39 | time, | ||
40 | #[cfg(target_os = "linux")] | ||
41 | counter, | ||
42 | memory: None, | ||
43 | } | ||
44 | } | ||
45 | pub fn memory(mut self, yes: bool) -> StopWatch { | ||
46 | if yes { | ||
47 | self.memory = Some(MemoryUsage::current()); | ||
48 | } | ||
49 | self | ||
50 | } | ||
51 | pub fn elapsed(&mut self) -> StopWatchSpan { | ||
52 | let time = self.time.elapsed(); | ||
53 | |||
54 | #[cfg(target_os = "linux")] | ||
55 | let instructions = self.counter.as_mut().and_then(|it| { | ||
56 | it.read().map_err(|err| eprintln!("Failed to read perf counter: {}", err)).ok() | ||
57 | }); | ||
58 | #[cfg(not(target_os = "linux"))] | ||
59 | let instructions = None; | ||
60 | |||
61 | let memory = self.memory.map(|it| MemoryUsage::current() - it); | ||
62 | StopWatchSpan { time, instructions, memory } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | impl fmt::Display for StopWatchSpan { | ||
67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
68 | write!(f, "{:.2?}", self.time)?; | ||
69 | if let Some(mut instructions) = self.instructions { | ||
70 | let mut prefix = ""; | ||
71 | if instructions > 10000 { | ||
72 | instructions /= 1000; | ||
73 | prefix = "k" | ||
74 | } | ||
75 | if instructions > 10000 { | ||
76 | instructions /= 1000; | ||
77 | prefix = "m" | ||
78 | } | ||
79 | write!(f, ", {}{}i", instructions, prefix)?; | ||
80 | } | ||
81 | if let Some(memory) = self.memory { | ||
82 | write!(f, ", {}", memory)?; | ||
83 | } | ||
84 | Ok(()) | ||
85 | } | ||
86 | } | ||
diff --git a/crates/profile/src/tree.rs b/crates/profile/src/tree.rs new file mode 100644 index 000000000..096f58511 --- /dev/null +++ b/crates/profile/src/tree.rs | |||
@@ -0,0 +1,84 @@ | |||
1 | //! A simple tree implementation which tries to not allocate all over the place. | ||
2 | use std::ops; | ||
3 | |||
4 | use arena::Arena; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub struct Tree<T> { | ||
8 | nodes: Arena<Node<T>>, | ||
9 | current_path: Vec<(Idx<T>, Option<Idx<T>>)>, | ||
10 | } | ||
11 | |||
12 | pub type Idx<T> = arena::Idx<Node<T>>; | ||
13 | |||
14 | impl<T> Tree<T> { | ||
15 | pub fn start(&mut self) | ||
16 | where | ||
17 | T: Default, | ||
18 | { | ||
19 | let me = self.nodes.alloc(Node::new(T::default())); | ||
20 | if let Some((parent, last_child)) = self.current_path.last_mut() { | ||
21 | let slot = match *last_child { | ||
22 | Some(last_child) => &mut self.nodes[last_child].next_sibling, | ||
23 | None => &mut self.nodes[*parent].first_child, | ||
24 | }; | ||
25 | let prev = slot.replace(me); | ||
26 | assert!(prev.is_none()); | ||
27 | *last_child = Some(me); | ||
28 | } | ||
29 | |||
30 | self.current_path.push((me, None)); | ||
31 | } | ||
32 | |||
33 | pub fn finish(&mut self, data: T) { | ||
34 | let (me, _last_child) = self.current_path.pop().unwrap(); | ||
35 | self.nodes[me].data = data; | ||
36 | } | ||
37 | |||
38 | pub fn root(&self) -> Option<Idx<T>> { | ||
39 | self.nodes.iter().next().map(|(idx, _)| idx) | ||
40 | } | ||
41 | |||
42 | pub fn children(&self, idx: Idx<T>) -> impl Iterator<Item = Idx<T>> + '_ { | ||
43 | NodeIter { nodes: &self.nodes, next: self.nodes[idx].first_child } | ||
44 | } | ||
45 | pub fn clear(&mut self) { | ||
46 | self.nodes.clear(); | ||
47 | self.current_path.clear(); | ||
48 | } | ||
49 | } | ||
50 | |||
51 | impl<T> ops::Index<Idx<T>> for Tree<T> { | ||
52 | type Output = T; | ||
53 | fn index(&self, index: Idx<T>) -> &T { | ||
54 | &self.nodes[index].data | ||
55 | } | ||
56 | } | ||
57 | |||
58 | pub struct Node<T> { | ||
59 | data: T, | ||
60 | first_child: Option<Idx<T>>, | ||
61 | next_sibling: Option<Idx<T>>, | ||
62 | } | ||
63 | |||
64 | impl<T> Node<T> { | ||
65 | fn new(data: T) -> Node<T> { | ||
66 | Node { data, first_child: None, next_sibling: None } | ||
67 | } | ||
68 | } | ||
69 | |||
70 | struct NodeIter<'a, T> { | ||
71 | nodes: &'a Arena<Node<T>>, | ||
72 | next: Option<Idx<T>>, | ||
73 | } | ||
74 | |||
75 | impl<'a, T> Iterator for NodeIter<'a, T> { | ||
76 | type Item = Idx<T>; | ||
77 | |||
78 | fn next(&mut self) -> Option<Idx<T>> { | ||
79 | self.next.map(|next| { | ||
80 | self.next = self.nodes[next].next_sibling; | ||
81 | next | ||
82 | }) | ||
83 | } | ||
84 | } | ||