Skip to content

Commit a175c50

Browse files
ndmitchellfacebook-github-bot
authored andcommitted
Add allocated_summary functions
Summary: Get a list of what is allocated on the heap. Reviewed By: krallin Differential Revision: D30900299 fbshipit-source-id: 6286b1a07ebac7f73d56d7836ced7e3fe0564f8b
1 parent 4211b72 commit a175c50

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

starlark/src/values/layout/arena.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
use crate::values::layout::avalue::{AValue, BlackHole};
3030
use bumpalo::Bump;
3131
use either::Either;
32+
use gazebo::prelude::*;
3233
use std::{
3334
alloc::Layout,
3435
any::TypeId,
3536
cmp,
37+
collections::HashMap,
3638
intrinsics::copy_nonoverlapping,
3739
marker::PhantomData,
3840
mem::{self, MaybeUninit},
@@ -47,9 +49,13 @@ pub(crate) struct Arena {
4749
drop: Bump,
4850
}
4951

52+
#[derive(Hash, PartialEq, Eq, Clone)]
5053
#[repr(transparent)]
5154
pub(crate) struct AValueHeader(DynMetadata<dyn AValue<'static>>);
5255

56+
// Implements Copy so this is fine
57+
impl Dupe for AValueHeader {}
58+
5359
/// How object is represented in arena.
5460
#[repr(C)]
5561
pub(crate) struct AValueRepr<T> {
@@ -95,6 +101,17 @@ impl<'v> Reservation<'v> {
95101
}
96102
}
97103

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+
98115
impl Arena {
99116
pub fn allocated_bytes(&self) -> usize {
100117
self.drop.allocated_bytes() + self.non_drop.allocated_bytes()
@@ -233,6 +250,54 @@ impl Arena {
233250
.iter_allocated_chunks()
234251
.for_each(|chunk| Self::iter_chunk(chunk, &mut f))
235252
}
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+
}
236301
}
237302

238303
impl AValueHeader {
@@ -398,4 +463,14 @@ mod test {
398463
assert_eq!(to_repr(res[0]), "\"test\"");
399464
assert_eq!(to_repr(res[2]), "\"hello\"");
400465
}
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+
}
401476
}

starlark/src/values/layout/heap.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::{
2323
collections::Hashed,
2424
values::{
2525
layout::{
26-
arena::{AValueHeader, Arena, Reservation},
26+
arena::{AValueHeader, Arena, HeapSummary, Reservation},
2727
avalue::{complex, simple, starlark_str, AValue},
2828
constant::constant_string,
2929
value::{FrozenValue, Value},
@@ -126,6 +126,14 @@ impl PartialEq<FrozenHeapRef> for FrozenHeapRef {
126126

127127
impl Eq for FrozenHeapRef {}
128128

129+
impl FrozenHeapRef {
130+
/// Obtain a summary of how much memory is currently allocated by this heap.
131+
/// Doesn't include the heaps it keeps alive by reference.
132+
pub fn allocated_summary(&self) -> HeapSummary {
133+
self.0.arena.allocated_summary()
134+
}
135+
}
136+
129137
impl FrozenHeap {
130138
/// Create a new [`FrozenHeap`].
131139
pub fn new() -> Self {
@@ -183,6 +191,11 @@ impl FrozenHeap {
183191
pub fn alloc_simple(&self, val: impl SimpleValue) -> FrozenValue {
184192
self.alloc_raw(simple(val))
185193
}
194+
195+
/// Obtain a summary of how much memory is currently allocated by this heap.
196+
pub fn allocated_summary(&self) -> HeapSummary {
197+
self.arena.allocated_summary()
198+
}
186199
}
187200

188201
/// Used to `freeze` values by [`ComplexValue::freeze`].
@@ -366,6 +379,11 @@ impl Heap {
366379
f(&tracer);
367380
*arena = tracer.arena;
368381
}
382+
383+
/// Obtain a summary of how much memory is currently allocated by this heap.
384+
pub fn allocated_summary(&self) -> HeapSummary {
385+
self.arena.borrow().allocated_summary()
386+
}
369387
}
370388

371389
/// Used to perform garbage collection by [`Trace::trace`](crate::values::Trace::trace).

0 commit comments

Comments
 (0)