29
29
use crate :: values:: layout:: avalue:: { AValue , BlackHole } ;
30
30
use bumpalo:: Bump ;
31
31
use either:: Either ;
32
+ use gazebo:: prelude:: * ;
32
33
use std:: {
33
34
alloc:: Layout ,
34
35
any:: TypeId ,
35
36
cmp,
37
+ collections:: HashMap ,
36
38
intrinsics:: copy_nonoverlapping,
37
39
marker:: PhantomData ,
38
40
mem:: { self , MaybeUninit } ,
@@ -47,9 +49,13 @@ pub(crate) struct Arena {
47
49
drop : Bump ,
48
50
}
49
51
52
+ #[ derive( Hash , PartialEq , Eq , Clone ) ]
50
53
#[ repr( transparent) ]
51
54
pub ( crate ) struct AValueHeader ( DynMetadata < dyn AValue < ' static > > ) ;
52
55
56
+ // Implements Copy so this is fine
57
+ impl Dupe for AValueHeader { }
58
+
53
59
/// How object is represented in arena.
54
60
#[ repr( C ) ]
55
61
pub ( crate ) struct AValueRepr < T > {
@@ -95,6 +101,17 @@ impl<'v> Reservation<'v> {
95
101
}
96
102
}
97
103
104
+ #[ derive( Debug ) ]
105
+ /// Information about the data stored on a heap. Accessible through
106
+ /// the function `allocated_summary` available on [`Heap`](crate::values::Heap)
107
+ /// and [`FrozenHeap`](crate::values::FrozenHeap)
108
+ pub struct HeapSummary {
109
+ /// For each type, give the (number of entries, size of all entries).
110
+ /// The size may be approximate as it includes information from
111
+ /// the approximate [`memory_size`](StarlarkValue::memory_size) function.
112
+ pub summary : HashMap < String , ( usize , usize ) > ,
113
+ }
114
+
98
115
impl Arena {
99
116
pub fn allocated_bytes ( & self ) -> usize {
100
117
self . drop . allocated_bytes ( ) + self . non_drop . allocated_bytes ( )
@@ -233,6 +250,54 @@ impl Arena {
233
250
. iter_allocated_chunks ( )
234
251
. for_each ( |chunk| Self :: iter_chunk ( chunk, & mut f) )
235
252
}
253
+
254
+ // For each Rust-level type (the String) report how many entries there are in the heap, and how much size they consume
255
+ pub fn allocated_summary ( & self ) -> HeapSummary {
256
+ #[ cold] // Try and avoid problematic UB :-(
257
+ #[ inline( never) ]
258
+ fn for_each < ' a > ( bump : & ' a Bump , mut f : impl FnMut ( & ' a AValueHeader ) ) {
259
+ // We have a problem that `iter_allocated_chunks` requires a &mut, and for things like
260
+ // FrozenModule we don't have a mutable Bump. The only reason that the function requires &mut
261
+ // is to make sure new values don't get allocated while you have references to old values,
262
+ // but that's not a problem for us, since we immediately use the values and don't keep them around.
263
+ //
264
+ // We have requested an alternative function in terms of *const, which would be safe,
265
+ // but until that arrives, we cast the & pointer to &mut, accepting a small amount of UB.
266
+ // See https://github.com/fitzgen/bumpalo/issues/121.
267
+ //
268
+ // This might not be safe if the function `f` allocated on the heap,
269
+ // but since this is a local function with a controlled closure, we know that it doesn't.
270
+ #[ allow( clippy:: cast_ref_to_mut) ]
271
+ let bump = unsafe { & mut * ( bump as * const Bump as * mut Bump ) } ;
272
+ bump. iter_allocated_chunks ( )
273
+ . for_each ( |chunk| Arena :: iter_chunk ( chunk, & mut f) )
274
+ }
275
+
276
+ // Record how many times each header occurs
277
+ // We deliberately hash by the AValueHeader for higher performance, less type lookup
278
+ let mut entries: HashMap < AValueHeader , ( & ' static str , ( usize , usize ) ) > = HashMap :: new ( ) ;
279
+ let mut f = |x : & AValueHeader | {
280
+ let v = x. unpack ( ) ;
281
+ let e = entries
282
+ . entry ( x. dupe ( ) )
283
+ . or_insert_with ( || ( v. get_type ( ) , ( 0 , 0 ) ) ) ;
284
+ e. 1 . 0 += 1 ;
285
+ e. 1 . 1 += v. memory_size ( ) + v. extra_memory ( ) ;
286
+ } ;
287
+ for_each ( & self . drop , & mut f) ;
288
+ for_each ( & self . non_drop , & mut f) ;
289
+
290
+ // For a given type, the AValueHeader isn't always unique
291
+ // (if they get compiled in different translation units),
292
+ // so not just a simple map.
293
+ let mut summary = HashMap :: new ( ) ;
294
+ for ( _, ( name, ( count, memory) ) ) in entries {
295
+ let v = summary. entry ( name. to_owned ( ) ) . or_insert ( ( 0 , 0 ) ) ;
296
+ v. 0 += count;
297
+ v. 1 += memory;
298
+ }
299
+ HeapSummary { summary }
300
+ }
236
301
}
237
302
238
303
impl AValueHeader {
@@ -398,4 +463,14 @@ mod test {
398
463
assert_eq ! ( to_repr( res[ 0 ] ) , "\" test\" " ) ;
399
464
assert_eq ! ( to_repr( res[ 2 ] ) , "\" hello\" " ) ;
400
465
}
466
+
467
+ #[ test]
468
+ fn test_allocated_summary ( ) {
469
+ let arena = Arena :: default ( ) ;
470
+ arena. alloc ( mk_str ( "test" ) ) ;
471
+ arena. alloc ( mk_str ( "test" ) ) ;
472
+ let res = arena. allocated_summary ( ) . summary ;
473
+ assert_eq ! ( res. len( ) , 1 ) ;
474
+ assert_eq ! ( res. values( ) . next( ) . unwrap( ) . 0 , 2 ) ;
475
+ }
401
476
}
0 commit comments