use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
use serde::{Deserialize, Serialize};
static PROFILER_ACTIVE: AtomicBool = AtomicBool::new(false);
thread_local! {
static ALLOC_COUNT: Cell<u64> = const { Cell::new(0) };
static DEALLOC_COUNT: Cell<u64> = const { Cell::new(0) };
static REALLOC_COUNT: Cell<u64> = const { Cell::new(0) };
static BYTES_ALLOCATED: Cell<u64> = const { Cell::new(0) };
static BYTES_DEALLOCATED: Cell<u64> = const { Cell::new(0) };
}
pub struct AllocProfiler<A = System> {
alloc: A,
}
impl AllocProfiler {
#[inline]
pub const fn system() -> Self {
Self::new(System)
}
}
impl<A> AllocProfiler<A> {
#[inline]
pub const fn new(alloc: A) -> Self {
Self { alloc }
}
}
#[allow(unsafe_code)]
unsafe impl<A: GlobalAlloc> GlobalAlloc for AllocProfiler<A> {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { self.alloc.alloc(layout) };
if !ptr.is_null() {
PROFILER_ACTIVE.store(true, Ordering::Relaxed);
ALLOC_COUNT.with(|c| c.set(c.get() + 1));
BYTES_ALLOCATED.with(|c| c.set(c.get() + layout.size() as u64));
}
ptr
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { self.alloc.alloc_zeroed(layout) };
if !ptr.is_null() {
PROFILER_ACTIVE.store(true, Ordering::Relaxed);
ALLOC_COUNT.with(|c| c.set(c.get() + 1));
BYTES_ALLOCATED.with(|c| c.set(c.get() + layout.size() as u64));
}
ptr
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.alloc.dealloc(ptr, layout) };
DEALLOC_COUNT.with(|c| c.set(c.get() + 1));
BYTES_DEALLOCATED.with(|c| c.set(c.get() + layout.size() as u64));
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_ptr = unsafe { self.alloc.realloc(ptr, layout, new_size) };
if !new_ptr.is_null() {
REALLOC_COUNT.with(|c| c.set(c.get() + 1));
BYTES_DEALLOCATED.with(|c| c.set(c.get() + layout.size() as u64));
BYTES_ALLOCATED.with(|c| c.set(c.get() + new_size as u64));
}
new_ptr
}
}
#[allow(unsafe_code)]
unsafe impl<A: Sync> Sync for AllocProfiler<A> {}
#[derive(Debug, Clone, Copy, Default)]
pub struct AllocSnapshot {
pub allocs: u64,
pub deallocs: u64,
pub reallocs: u64,
pub bytes_allocated: u64,
pub bytes_deallocated: u64,
}
impl AllocSnapshot {
#[inline]
pub fn now() -> Self {
Self {
allocs: ALLOC_COUNT.with(|c| c.get()),
deallocs: DEALLOC_COUNT.with(|c| c.get()),
reallocs: REALLOC_COUNT.with(|c| c.get()),
bytes_allocated: BYTES_ALLOCATED.with(|c| c.get()),
bytes_deallocated: BYTES_DEALLOCATED.with(|c| c.get()),
}
}
#[inline]
pub fn delta(self, before: Self) -> Self {
Self {
allocs: self.allocs.saturating_sub(before.allocs),
deallocs: self.deallocs.saturating_sub(before.deallocs),
reallocs: self.reallocs.saturating_sub(before.reallocs),
bytes_allocated: self.bytes_allocated.saturating_sub(before.bytes_allocated),
bytes_deallocated: self
.bytes_deallocated
.saturating_sub(before.bytes_deallocated),
}
}
}
#[inline]
pub fn is_active() -> bool {
PROFILER_ACTIVE.load(Ordering::Relaxed)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AllocStats {
pub allocs_per_iter: f64,
pub deallocs_per_iter: f64,
pub reallocs_per_iter: f64,
pub bytes_per_iter: f64,
pub bytes_dealloc_per_iter: f64,
}
impl AllocStats {
pub fn from_totals(
total_allocs: u64,
total_deallocs: u64,
total_reallocs: u64,
total_bytes_alloc: u64,
total_bytes_dealloc: u64,
total_iterations: u64,
) -> Self {
let n = total_iterations.max(1) as f64;
Self {
allocs_per_iter: total_allocs as f64 / n,
deallocs_per_iter: total_deallocs as f64 / n,
reallocs_per_iter: total_reallocs as f64 / n,
bytes_per_iter: total_bytes_alloc as f64 / n,
bytes_dealloc_per_iter: total_bytes_dealloc as f64 / n,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snapshot_delta() {
let before = AllocSnapshot {
allocs: 10,
deallocs: 5,
reallocs: 2,
bytes_allocated: 1000,
bytes_deallocated: 500,
};
let after = AllocSnapshot {
allocs: 15,
deallocs: 8,
reallocs: 3,
bytes_allocated: 2000,
bytes_deallocated: 900,
};
let delta = after.delta(before);
assert_eq!(delta.allocs, 5);
assert_eq!(delta.deallocs, 3);
assert_eq!(delta.reallocs, 1);
assert_eq!(delta.bytes_allocated, 1000);
assert_eq!(delta.bytes_deallocated, 400);
}
#[test]
fn alloc_stats_from_totals() {
let stats = AllocStats::from_totals(100, 100, 10, 8000, 8000, 50);
assert!((stats.allocs_per_iter - 2.0).abs() < f64::EPSILON);
assert!((stats.bytes_per_iter - 160.0).abs() < f64::EPSILON);
}
#[test]
fn snapshot_now_returns_something() {
let snap = AllocSnapshot::now();
let _ = snap.allocs;
}
}