1
1
//! Simple hierarchical profiler
2
+ use once_cell:: sync:: Lazy ;
2
3
use std:: {
3
4
cell:: RefCell ,
4
5
collections:: { BTreeMap , HashSet } ,
@@ -10,7 +11,7 @@ use std::{
10
11
time:: { Duration , Instant } ,
11
12
} ;
12
13
13
- use once_cell :: sync :: Lazy ;
14
+ use crate :: tree :: { Idx , Tree } ;
14
15
15
16
/// Filtering syntax
16
17
/// env RA_PROFILE=* // dump everything
@@ -138,20 +139,20 @@ impl Filter {
138
139
139
140
struct ProfileStack {
140
141
starts : Vec < Instant > ,
141
- messages : Vec < Message > ,
142
142
filter : Filter ,
143
+ messages : Tree < Message > ,
143
144
}
144
145
146
+ #[ derive( Default ) ]
145
147
struct Message {
146
- level : usize ,
147
148
duration : Duration ,
148
149
label : Label ,
149
150
detail : Option < String > ,
150
151
}
151
152
152
153
impl ProfileStack {
153
154
fn new ( ) -> ProfileStack {
154
- ProfileStack { starts : Vec :: new ( ) , messages : Vec :: new ( ) , filter : Default :: default ( ) }
155
+ ProfileStack { starts : Vec :: new ( ) , messages : Tree :: default ( ) , filter : Default :: default ( ) }
155
156
}
156
157
157
158
fn push ( & mut self , label : Label ) -> bool {
@@ -171,73 +172,64 @@ impl ProfileStack {
171
172
}
172
173
173
174
self . starts . push ( Instant :: now ( ) ) ;
175
+ self . messages . start ( ) ;
174
176
true
175
177
}
176
178
177
179
pub fn pop ( & mut self , label : Label , detail : Option < String > ) {
178
180
let start = self . starts . pop ( ) . unwrap ( ) ;
179
181
let duration = start. elapsed ( ) ;
180
182
let level = self . starts . len ( ) ;
181
- self . messages . push ( Message { level , duration, label, detail } ) ;
183
+ self . messages . finish ( Message { duration, label, detail } ) ;
182
184
if level == 0 {
183
185
let longer_than = self . filter . longer_than ;
184
186
// Convert to millis for comparison to avoid problems with rounding
185
187
// (otherwise we could print `0ms` despite user's `>0` filter when
186
188
// `duration` is just a few nanos).
187
189
if duration. as_millis ( ) > longer_than. as_millis ( ) {
188
190
let stderr = stderr ( ) ;
189
- print ( & self . messages , longer_than, & mut stderr. lock ( ) ) ;
191
+ if let Some ( root) = self . messages . root ( ) {
192
+ print ( & self . messages , root, 0 , longer_than, & mut stderr. lock ( ) ) ;
193
+ }
190
194
}
191
195
self . messages . clear ( ) ;
192
196
assert ! ( self . starts. is_empty( ) )
193
197
}
194
198
}
195
199
}
196
200
197
- fn print ( msgs : & [ Message ] , longer_than : Duration , out : & mut impl Write ) {
198
- if msgs. is_empty ( ) {
199
- return ;
200
- }
201
- let children_map = idx_to_children ( msgs) ;
202
- let root_idx = msgs. len ( ) - 1 ;
203
- print_for_idx ( root_idx, & children_map, msgs, longer_than, out) ;
204
- }
205
-
206
- fn print_for_idx (
207
- current_idx : usize ,
208
- children_map : & [ Vec < usize > ] ,
209
- msgs : & [ Message ] ,
201
+ fn print (
202
+ tree : & Tree < Message > ,
203
+ curr : Idx < Message > ,
204
+ level : u32 ,
210
205
longer_than : Duration ,
211
206
out : & mut impl Write ,
212
207
) {
213
- let current = & msgs[ current_idx] ;
214
- let current_indent = " " . repeat ( current. level ) ;
215
- let detail = current. detail . as_ref ( ) . map ( |it| format ! ( " @ {}" , it) ) . unwrap_or_default ( ) ;
208
+ let current_indent = " " . repeat ( level as usize ) ;
209
+ let detail = tree[ curr] . detail . as_ref ( ) . map ( |it| format ! ( " @ {}" , it) ) . unwrap_or_default ( ) ;
216
210
writeln ! (
217
211
out,
218
212
"{}{:5}ms - {}{}" ,
219
213
current_indent,
220
- current . duration. as_millis( ) ,
221
- current . label,
214
+ tree [ curr ] . duration. as_millis( ) ,
215
+ tree [ curr ] . label,
222
216
detail,
223
217
)
224
218
. expect ( "printing profiling info" ) ;
225
219
226
- let longer_than_millis = longer_than. as_millis ( ) ;
227
- let children_indices = & children_map[ current_idx] ;
228
220
let mut accounted_for = Duration :: default ( ) ;
229
221
let mut short_children = BTreeMap :: new ( ) ; // Use `BTreeMap` to get deterministic output.
222
+ for child in tree. children ( curr) {
223
+ accounted_for += tree[ child] . duration ;
230
224
231
- for child_idx in children_indices. iter ( ) {
232
- let child = & msgs[ * child_idx] ;
233
- if child. duration . as_millis ( ) > longer_than_millis {
234
- print_for_idx ( * child_idx, children_map, msgs, longer_than, out) ;
225
+ if tree[ child] . duration . as_millis ( ) > longer_than. as_millis ( ) {
226
+ print ( tree, child, level + 1 , longer_than, out)
235
227
} else {
236
- let pair = short_children. entry ( child. label ) . or_insert ( ( Duration :: default ( ) , 0 ) ) ;
237
- pair. 0 += child. duration ;
238
- pair. 1 += 1 ;
228
+ let ( total_duration, cnt) =
229
+ short_children. entry ( tree[ child] . label ) . or_insert ( ( Duration :: default ( ) , 0 ) ) ;
230
+ * total_duration += tree[ child] . duration ;
231
+ * cnt += 1 ;
239
232
}
240
- accounted_for += child. duration ;
241
233
}
242
234
243
235
for ( child_msg, ( duration, count) ) in short_children. iter ( ) {
@@ -246,122 +238,9 @@ fn print_for_idx(
246
238
. expect ( "printing profiling info" ) ;
247
239
}
248
240
249
- let unaccounted_millis = ( current. duration - accounted_for) . as_millis ( ) ;
250
- if !children_indices. is_empty ( )
251
- && unaccounted_millis > 0
252
- && unaccounted_millis > longer_than_millis
253
- {
254
- writeln ! ( out, " {}{:5}ms - ???" , current_indent, unaccounted_millis)
241
+ let unaccounted = tree[ curr] . duration - accounted_for;
242
+ if tree. children ( curr) . next ( ) . is_some ( ) && unaccounted > longer_than {
243
+ writeln ! ( out, " {}{:5}ms - ???" , current_indent, unaccounted. as_millis( ) )
255
244
. expect ( "printing profiling info" ) ;
256
245
}
257
246
}
258
-
259
- /// Returns a mapping from an index in the `msgs` to the vector with the indices of its children.
260
- ///
261
- /// This assumes that the entries in `msgs` are in the order of when the calls to `profile` finish.
262
- /// In other words, a postorder of the call graph. In particular, the root is the last element of
263
- /// `msgs`.
264
- fn idx_to_children ( msgs : & [ Message ] ) -> Vec < Vec < usize > > {
265
- // Initialize with the index of the root; `msgs` and `ancestors` should be never empty.
266
- assert ! ( !msgs. is_empty( ) ) ;
267
- let mut ancestors = vec ! [ msgs. len( ) - 1 ] ;
268
- let mut result: Vec < Vec < usize > > = vec ! [ vec![ ] ; msgs. len( ) ] ;
269
- for ( idx, msg) in msgs[ ..msgs. len ( ) - 1 ] . iter ( ) . enumerate ( ) . rev ( ) {
270
- // We need to find the parent of the current message, i.e., the last ancestor that has a
271
- // level lower than the current message.
272
- while msgs[ * ancestors. last ( ) . unwrap ( ) ] . level >= msg. level {
273
- ancestors. pop ( ) ;
274
- }
275
- result[ * ancestors. last ( ) . unwrap ( ) ] . push ( idx) ;
276
- ancestors. push ( idx) ;
277
- }
278
- // Note that above we visited all children from the last to the first one. Let's reverse vectors
279
- // to get the more natural order where the first element is the first child.
280
- for vec in result. iter_mut ( ) {
281
- vec. reverse ( ) ;
282
- }
283
- result
284
- }
285
-
286
- #[ cfg( test) ]
287
- mod tests {
288
- use super :: * ;
289
-
290
- #[ test]
291
- fn test_basic_profile ( ) {
292
- let s = vec ! [ "profile1" . to_string( ) , "profile2" . to_string( ) ] ;
293
- let f = Filter :: new ( 2 , s, Duration :: new ( 0 , 0 ) ) ;
294
- set_filter ( f) ;
295
- profiling_function1 ( ) ;
296
- }
297
-
298
- fn profiling_function1 ( ) {
299
- let _p = profile ( "profile1" ) ;
300
- profiling_function2 ( ) ;
301
- }
302
-
303
- fn profiling_function2 ( ) {
304
- let _p = profile ( "profile2" ) ;
305
- }
306
-
307
- #[ test]
308
- fn test_longer_than ( ) {
309
- let mut result = vec ! [ ] ;
310
- let msgs = vec ! [
311
- Message { level: 1 , duration: Duration :: from_nanos( 3 ) , label: "bar" , detail: None } ,
312
- Message { level: 1 , duration: Duration :: from_nanos( 2 ) , label: "bar" , detail: None } ,
313
- Message { level: 0 , duration: Duration :: from_millis( 1 ) , label: "foo" , detail: None } ,
314
- ] ;
315
- print ( & msgs, Duration :: from_millis ( 0 ) , & mut result) ;
316
- // The calls to `bar` are so short that they'll be rounded to 0ms and should get collapsed
317
- // when printing.
318
- assert_eq ! (
319
- std:: str :: from_utf8( & result) . unwrap( ) ,
320
- " 1ms - foo\n 0ms - bar (2 calls)\n "
321
- ) ;
322
- }
323
-
324
- #[ test]
325
- fn test_unaccounted_for_topmost ( ) {
326
- let mut result = vec ! [ ] ;
327
- let msgs = vec ! [
328
- Message { level: 1 , duration: Duration :: from_millis( 2 ) , label: "bar" , detail: None } ,
329
- Message { level: 0 , duration: Duration :: from_millis( 5 ) , label: "foo" , detail: None } ,
330
- ] ;
331
- print ( & msgs, Duration :: from_millis ( 0 ) , & mut result) ;
332
- assert_eq ! (
333
- std:: str :: from_utf8( & result) . unwrap( ) . lines( ) . collect:: <Vec <_>>( ) ,
334
- vec![
335
- " 5ms - foo" ,
336
- " 2ms - bar" ,
337
- " 3ms - ???" ,
338
- // Dummy comment to improve formatting
339
- ]
340
- ) ;
341
- }
342
-
343
- #[ test]
344
- fn test_unaccounted_for_multiple_levels ( ) {
345
- let mut result = vec ! [ ] ;
346
- let msgs = vec ! [
347
- Message { level: 2 , duration: Duration :: from_millis( 3 ) , label: "baz" , detail: None } ,
348
- Message { level: 1 , duration: Duration :: from_millis( 5 ) , label: "bar" , detail: None } ,
349
- Message { level: 2 , duration: Duration :: from_millis( 2 ) , label: "baz" , detail: None } ,
350
- Message { level: 1 , duration: Duration :: from_millis( 4 ) , label: "bar" , detail: None } ,
351
- Message { level: 0 , duration: Duration :: from_millis( 9 ) , label: "foo" , detail: None } ,
352
- ] ;
353
- print ( & msgs, Duration :: from_millis ( 0 ) , & mut result) ;
354
- assert_eq ! (
355
- std:: str :: from_utf8( & result) . unwrap( ) . lines( ) . collect:: <Vec <_>>( ) ,
356
- vec![
357
- " 9ms - foo" ,
358
- " 5ms - bar" ,
359
- " 3ms - baz" ,
360
- " 2ms - ???" ,
361
- " 4ms - bar" ,
362
- " 2ms - baz" ,
363
- " 2ms - ???" ,
364
- ]
365
- ) ;
366
- }
367
- }
0 commit comments