diff options
Diffstat (limited to 'crates/profile/src')
-rw-r--r-- | crates/profile/src/hprof.rs | 87 | ||||
-rw-r--r-- | crates/profile/src/lib.rs | 2 |
2 files changed, 78 insertions, 11 deletions
diff --git a/crates/profile/src/hprof.rs b/crates/profile/src/hprof.rs index 014e906e6..5fdb37206 100644 --- a/crates/profile/src/hprof.rs +++ b/crates/profile/src/hprof.rs | |||
@@ -69,6 +69,20 @@ pub fn span(label: Label) -> ProfileSpan { | |||
69 | } | 69 | } |
70 | } | 70 | } |
71 | 71 | ||
72 | #[inline] | ||
73 | pub fn heartbeat_span() -> HeartbeatSpan { | ||
74 | let enabled = PROFILING_ENABLED.load(Ordering::Relaxed); | ||
75 | HeartbeatSpan::new(enabled) | ||
76 | } | ||
77 | |||
78 | #[inline] | ||
79 | pub fn heartbeat() { | ||
80 | let enabled = PROFILING_ENABLED.load(Ordering::Relaxed); | ||
81 | if enabled { | ||
82 | with_profile_stack(|it| it.heartbeat(1)); | ||
83 | } | ||
84 | } | ||
85 | |||
72 | pub struct ProfileSpan(Option<ProfilerImpl>); | 86 | pub struct ProfileSpan(Option<ProfilerImpl>); |
73 | 87 | ||
74 | struct ProfilerImpl { | 88 | struct ProfilerImpl { |
@@ -92,6 +106,28 @@ impl Drop for ProfilerImpl { | |||
92 | } | 106 | } |
93 | } | 107 | } |
94 | 108 | ||
109 | pub struct HeartbeatSpan { | ||
110 | enabled: bool, | ||
111 | } | ||
112 | |||
113 | impl HeartbeatSpan { | ||
114 | #[inline] | ||
115 | pub fn new(enabled: bool) -> Self { | ||
116 | if enabled { | ||
117 | with_profile_stack(|it| it.heartbeats(true)) | ||
118 | } | ||
119 | Self { enabled } | ||
120 | } | ||
121 | } | ||
122 | |||
123 | impl Drop for HeartbeatSpan { | ||
124 | fn drop(&mut self) { | ||
125 | if self.enabled { | ||
126 | with_profile_stack(|it| it.heartbeats(false)) | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
95 | static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false); | 131 | static PROFILING_ENABLED: AtomicBool = AtomicBool::new(false); |
96 | static FILTER: Lazy<RwLock<Filter>> = Lazy::new(Default::default); | 132 | static FILTER: Lazy<RwLock<Filter>> = Lazy::new(Default::default); |
97 | 133 | ||
@@ -105,6 +141,7 @@ struct Filter { | |||
105 | depth: usize, | 141 | depth: usize, |
106 | allowed: HashSet<String>, | 142 | allowed: HashSet<String>, |
107 | longer_than: Duration, | 143 | longer_than: Duration, |
144 | heartbeat_longer_than: Duration, | ||
108 | version: usize, | 145 | version: usize, |
109 | } | 146 | } |
110 | 147 | ||
@@ -121,6 +158,7 @@ impl Filter { | |||
121 | } else { | 158 | } else { |
122 | Duration::new(0, 0) | 159 | Duration::new(0, 0) |
123 | }; | 160 | }; |
161 | let heartbeat_longer_than = longer_than; | ||
124 | 162 | ||
125 | let depth = if let Some(idx) = spec.rfind('@') { | 163 | let depth = if let Some(idx) = spec.rfind('@') { |
126 | let depth: usize = spec[idx + 1..].parse().expect("invalid profile depth"); | 164 | let depth: usize = spec[idx + 1..].parse().expect("invalid profile depth"); |
@@ -131,7 +169,7 @@ impl Filter { | |||
131 | }; | 169 | }; |
132 | let allowed = | 170 | let allowed = |
133 | if spec == "*" { HashSet::new() } else { spec.split('|').map(String::from).collect() }; | 171 | if spec == "*" { HashSet::new() } else { spec.split('|').map(String::from).collect() }; |
134 | Filter { depth, allowed, longer_than, version: 0 } | 172 | Filter { depth, allowed, longer_than, heartbeat_longer_than, version: 0 } |
135 | } | 173 | } |
136 | 174 | ||
137 | fn install(mut self) { | 175 | fn install(mut self) { |
@@ -143,9 +181,15 @@ impl Filter { | |||
143 | } | 181 | } |
144 | 182 | ||
145 | struct ProfileStack { | 183 | struct ProfileStack { |
146 | starts: Vec<Instant>, | 184 | frames: Vec<Frame>, |
147 | filter: Filter, | 185 | filter: Filter, |
148 | messages: Tree<Message>, | 186 | messages: Tree<Message>, |
187 | heartbeats: bool, | ||
188 | } | ||
189 | |||
190 | struct Frame { | ||
191 | t: Instant, | ||
192 | heartbeats: u32, | ||
149 | } | 193 | } |
150 | 194 | ||
151 | #[derive(Default)] | 195 | #[derive(Default)] |
@@ -157,35 +201,49 @@ struct Message { | |||
157 | 201 | ||
158 | impl ProfileStack { | 202 | impl ProfileStack { |
159 | fn new() -> ProfileStack { | 203 | fn new() -> ProfileStack { |
160 | ProfileStack { starts: Vec::new(), messages: Tree::default(), filter: Default::default() } | 204 | ProfileStack { |
205 | frames: Vec::new(), | ||
206 | messages: Tree::default(), | ||
207 | filter: Default::default(), | ||
208 | heartbeats: false, | ||
209 | } | ||
161 | } | 210 | } |
162 | 211 | ||
163 | fn push(&mut self, label: Label) -> bool { | 212 | fn push(&mut self, label: Label) -> bool { |
164 | if self.starts.is_empty() { | 213 | if self.frames.is_empty() { |
165 | if let Ok(f) = FILTER.try_read() { | 214 | if let Ok(f) = FILTER.try_read() { |
166 | if f.version > self.filter.version { | 215 | if f.version > self.filter.version { |
167 | self.filter = f.clone(); | 216 | self.filter = f.clone(); |
168 | } | 217 | } |
169 | }; | 218 | }; |
170 | } | 219 | } |
171 | if self.starts.len() > self.filter.depth { | 220 | if self.frames.len() > self.filter.depth { |
172 | return false; | 221 | return false; |
173 | } | 222 | } |
174 | let allowed = &self.filter.allowed; | 223 | let allowed = &self.filter.allowed; |
175 | if self.starts.is_empty() && !allowed.is_empty() && !allowed.contains(label) { | 224 | if self.frames.is_empty() && !allowed.is_empty() && !allowed.contains(label) { |
176 | return false; | 225 | return false; |
177 | } | 226 | } |
178 | 227 | ||
179 | self.starts.push(Instant::now()); | 228 | self.frames.push(Frame { t: Instant::now(), heartbeats: 0 }); |
180 | self.messages.start(); | 229 | self.messages.start(); |
181 | true | 230 | true |
182 | } | 231 | } |
183 | 232 | ||
184 | fn pop(&mut self, label: Label, detail: Option<String>) { | 233 | fn pop(&mut self, label: Label, detail: Option<String>) { |
185 | let start = self.starts.pop().unwrap(); | 234 | let frame = self.frames.pop().unwrap(); |
186 | let duration = start.elapsed(); | 235 | let duration = frame.t.elapsed(); |
236 | |||
237 | if self.heartbeats { | ||
238 | self.heartbeat(frame.heartbeats); | ||
239 | let avg_span = duration / (frame.heartbeats + 1); | ||
240 | if avg_span > self.filter.heartbeat_longer_than { | ||
241 | eprintln!("Too few heartbeats {} ({}/{:?})?", label, frame.heartbeats, duration) | ||
242 | } | ||
243 | } | ||
244 | |||
187 | self.messages.finish(Message { duration, label, detail }); | 245 | self.messages.finish(Message { duration, label, detail }); |
188 | if self.starts.is_empty() { | 246 | if self.frames.is_empty() { |
189 | let longer_than = self.filter.longer_than; | 247 | let longer_than = self.filter.longer_than; |
190 | // Convert to millis for comparison to avoid problems with rounding | 248 | // Convert to millis for comparison to avoid problems with rounding |
191 | // (otherwise we could print `0ms` despite user's `>0` filter when | 249 | // (otherwise we could print `0ms` despite user's `>0` filter when |
@@ -198,6 +256,15 @@ impl ProfileStack { | |||
198 | self.messages.clear(); | 256 | self.messages.clear(); |
199 | } | 257 | } |
200 | } | 258 | } |
259 | |||
260 | fn heartbeats(&mut self, yes: bool) { | ||
261 | self.heartbeats = yes; | ||
262 | } | ||
263 | fn heartbeat(&mut self, n: u32) { | ||
264 | if let Some(frame) = self.frames.last_mut() { | ||
265 | frame.heartbeats += n; | ||
266 | } | ||
267 | } | ||
201 | } | 268 | } |
202 | 269 | ||
203 | fn print( | 270 | fn print( |
diff --git a/crates/profile/src/lib.rs b/crates/profile/src/lib.rs index 79dba47d5..9ca6341db 100644 --- a/crates/profile/src/lib.rs +++ b/crates/profile/src/lib.rs | |||
@@ -10,7 +10,7 @@ mod tree; | |||
10 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
11 | 11 | ||
12 | pub use crate::{ | 12 | pub use crate::{ |
13 | hprof::{init, init_from, span}, | 13 | hprof::{heartbeat, heartbeat_span, init, init_from, span}, |
14 | memory_usage::{Bytes, MemoryUsage}, | 14 | memory_usage::{Bytes, MemoryUsage}, |
15 | stop_watch::{StopWatch, StopWatchSpan}, | 15 | stop_watch::{StopWatch, StopWatchSpan}, |
16 | }; | 16 | }; |