allocator: Add optional heap allocation measurement tracking. (#38727)

Add an off-by-default allocator mode that tracks all live allocations
with sizes and associated stack traces. We also track if each allocation
is visited as part of a measuring heap usage in `about:memory`, allowing
us to report on allocations that are not tracked yet. Right now the list
of untracked allocations is dumped to stdout; I have a python script
coming in a separate PR which makes it easier to perform analysis on the
massive output.

Testing: Manually tested with `./mach build -d --features
servo_allocator/allocation-tracking` and visiting about:memory.
Part of: #11559

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-08-19 14:49:27 -04:00 committed by GitHub
parent 2022831e4f
commit f1a9ceed4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 280 additions and 7 deletions

View file

@ -2,13 +2,53 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Selecting the default global allocator for Servo
//! Selecting the default global allocator for Servo, and exposing common
//! allocator introspection APIs for memory profiling.
use std::os::raw::c_void;
#[cfg(not(feature = "allocation-tracking"))]
#[global_allocator]
static ALLOC: Allocator = Allocator;
#[cfg(feature = "allocation-tracking")]
#[global_allocator]
static ALLOC: crate::tracking::AccountingAlloc<Allocator> =
crate::tracking::AccountingAlloc::with_allocator(Allocator);
#[cfg(feature = "allocation-tracking")]
mod tracking;
pub fn dump_unmeasured() {
#[cfg(feature = "allocation-tracking")]
ALLOC.dump_unmeasured_allocations();
}
pub use crate::platform::*;
type EnclosingSizeFn = unsafe extern "C" fn(*const c_void) -> usize;
/// # Safety
/// No restrictions. The passed pointer is never dereferenced.
/// This function is only marked unsafe because the MallocSizeOfOps APIs
/// requires an unsafe function pointer.
#[cfg(feature = "allocation-tracking")]
unsafe extern "C" fn enclosing_size_impl(ptr: *const c_void) -> usize {
let (adjusted, size) = crate::ALLOC.enclosing_size(ptr);
if size != 0 {
crate::ALLOC.note_allocation(adjusted, size);
}
size
}
#[allow(non_upper_case_globals)]
#[cfg(feature = "allocation-tracking")]
pub static enclosing_size: Option<EnclosingSizeFn> = Some(crate::enclosing_size_impl);
#[allow(non_upper_case_globals)]
#[cfg(not(feature = "allocation-tracking"))]
pub static enclosing_size: Option<EnclosingSizeFn> = None;
#[cfg(not(any(windows, feature = "use-system-allocator", target_env = "ohos")))]
mod platform {
use std::os::raw::c_void;
@ -21,7 +61,10 @@ mod platform {
///
/// Passing a non-heap allocated pointer to this function results in undefined behavior.
pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
unsafe { tikv_jemallocator::usable_size(ptr) }
let size = unsafe { tikv_jemallocator::usable_size(ptr) };
#[cfg(feature = "allocation-tracking")]
crate::ALLOC.note_allocation(ptr, size);
size
}
/// Memory allocation APIs compatible with libc
@ -46,12 +89,18 @@ mod platform {
pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
#[cfg(target_vendor = "apple")]
unsafe {
return libc::malloc_size(ptr);
let size = libc::malloc_size(ptr);
#[cfg(feature = "allocation-tracking")]
crate::ALLOC.note_allocation(ptr, size);
size
}
#[cfg(not(target_vendor = "apple"))]
unsafe {
return libc::malloc_usable_size(ptr as *mut _);
let size = libc::malloc_usable_size(ptr as *mut _);
#[cfg(feature = "allocation-tracking")]
crate::ALLOC.note_allocation(ptr, size);
size
}
}
@ -81,7 +130,10 @@ mod platform {
ptr = *(ptr as *const *const c_void).offset(-1)
}
HeapSize(heap, 0, ptr) as usize
let size = HeapSize(heap, 0, ptr) as usize;
#[cfg(feature = "allocation-tracking")]
crate::ALLOC.note_allocation(ptr, size);
size
}
}
}