use std::alloc::GlobalAlloc;
use std::alloc::Layout;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
#[derive(Debug, Clone, Copy)]
pub struct AllocationSnapshot {
pub allocations: u64,
pub allocated_bytes: u64,
}
pub struct CountingAllocator<A> {
inner: A,
enabled: AtomicBool,
allocations: AtomicU64,
allocated_bytes: AtomicU64,
}
impl<A> CountingAllocator<A> {
pub const fn new(inner: A) -> Self {
Self {
inner,
enabled: AtomicBool::new(false),
allocations: AtomicU64::new(0),
allocated_bytes: AtomicU64::new(0),
}
}
pub fn measure<T>(&self, operation: impl FnOnce() -> T) -> (T, AllocationSnapshot) {
assert!(
!self.enabled.swap(false, Ordering::SeqCst),
"nested allocation measurement"
);
self.allocations.store(0, Ordering::SeqCst);
self.allocated_bytes.store(0, Ordering::SeqCst);
self.enabled.store(true, Ordering::SeqCst);
let result = operation();
self.enabled.store(false, Ordering::SeqCst);
let snapshot = AllocationSnapshot {
allocations: self.allocations.load(Ordering::SeqCst),
allocated_bytes: self.allocated_bytes.load(Ordering::SeqCst),
};
(result, snapshot)
}
#[inline]
fn record(&self, bytes: usize) {
if self.enabled.load(Ordering::Relaxed) {
self.allocations.fetch_add(1, Ordering::Relaxed);
self.allocated_bytes.fetch_add(bytes as u64, Ordering::Relaxed);
}
}
}
unsafe impl<A: GlobalAlloc> GlobalAlloc for CountingAllocator<A> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.record(layout.size());
unsafe { self.inner.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
self.record(layout.size());
unsafe { self.inner.alloc_zeroed(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.inner.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
self.record(new_size);
unsafe { self.inner.realloc(ptr, layout, new_size) }
}
}