use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
struct Probe;
#[global_allocator]
static GLOBAL: Probe = Probe;
#[derive(Clone, Copy)]
struct State {
armed: bool,
count: u64,
fail_at: u64,
failed: bool,
live: i64,
peak: i64,
}
const IDLE: State = State {
armed: false,
count: 0,
fail_at: u64::MAX,
failed: false,
live: 0,
peak: 0,
};
thread_local! {
static STATE: Cell<State> = const { Cell::new(IDLE) };
}
fn on_alloc(old: usize, new: usize) -> bool {
STATE
.try_with(|st| {
let mut s = st.get();
if !s.armed {
return true;
}
let n = s.count;
s.count += 1;
if n == s.fail_at {
s.failed = true;
st.set(s);
return false;
}
s.peak = s.peak.max(s.live + new as i64);
s.live += new as i64 - old as i64;
st.set(s);
true
})
.unwrap_or(true)
}
fn on_dealloc(size: usize) {
let _ = STATE.try_with(|st| {
let mut s = st.get();
if s.armed {
s.live -= size as i64;
st.set(s);
}
});
}
unsafe impl GlobalAlloc for Probe {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
if !on_alloc(0, l.size()) {
return std::ptr::null_mut();
}
unsafe { System.alloc(l) }
}
unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
if !on_alloc(0, l.size()) {
return std::ptr::null_mut();
}
unsafe { System.alloc_zeroed(l) }
}
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
on_dealloc(l.size());
unsafe { System.dealloc(p, l) }
}
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
if !on_alloc(l.size(), new) {
return std::ptr::null_mut();
}
unsafe { System.realloc(p, l, new) }
}
}
#[derive(Clone, Copy, Debug)]
pub struct Measure {
pub allocs: u64,
pub peak: i64,
pub net: i64,
pub failed: bool,
}
pub fn measure<R>(fail_at: Option<u64>, f: impl FnOnce() -> R) -> (R, Measure) {
STATE.with(|st| {
st.set(State {
armed: true,
fail_at: fail_at.unwrap_or(u64::MAX),
..IDLE
})
});
let r = f();
let s = STATE.with(|st| {
let s = st.get();
st.set(IDLE);
s
});
(
r,
Measure {
allocs: s.count,
peak: s.peak,
net: s.live,
failed: s.failed,
},
)
}