use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Allocations {
pub count: u64,
pub bytes: u64,
pub freed: u64,
}
impl Allocations {
#[must_use]
pub fn since(self, earlier: Self) -> Self {
Self {
count: self.count.saturating_sub(earlier.count),
bytes: self.bytes.saturating_sub(earlier.bytes),
freed: self.freed.saturating_sub(earlier.freed),
}
}
#[must_use]
pub fn retained(self) -> i64 {
i64::try_from(self.bytes).unwrap_or(i64::MAX)
- i64::try_from(self.freed).unwrap_or(i64::MAX)
}
}
impl std::ops::AddAssign for Allocations {
fn add_assign(&mut self, other: Self) {
self.count += other.count;
self.bytes += other.bytes;
self.freed += other.freed;
}
}
static INSTALLED: AtomicBool = AtomicBool::new(false);
thread_local! {
static COUNT: Cell<u64> = const { Cell::new(0) };
static BYTES: Cell<u64> = const { Cell::new(0) };
static FREED: Cell<u64> = const { Cell::new(0) };
}
#[must_use]
pub fn allocations() -> Option<Allocations> {
if !INSTALLED.load(Ordering::Relaxed) {
return None;
}
Some(Allocations {
count: COUNT.try_with(Cell::get).unwrap_or(0),
bytes: BYTES.try_with(Cell::get).unwrap_or(0),
freed: FREED.try_with(Cell::get).unwrap_or(0),
})
}
fn charge(bytes: usize) {
let _ = COUNT.try_with(|c| c.set(c.get() + 1));
let _ = BYTES.try_with(|b| b.set(b.get() + bytes as u64));
}
fn credit(bytes: usize) {
let _ = FREED.try_with(|f| f.set(f.get() + bytes as u64));
}
pub struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
INSTALLED.store(true, Ordering::Relaxed);
charge(layout.size());
unsafe { System.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
INSTALLED.store(true, Ordering::Relaxed);
charge(layout.size());
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
credit(layout.size());
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
charge(new_size - layout.size());
} else {
credit(layout.size() - new_size);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_uncounted_binary_reads_as_unmeasured() {
let _ = vec![1u8; 64];
assert_eq!(allocations(), None);
}
#[test]
fn since_is_the_difference_and_never_underflows() {
let a = Allocations {
count: 3,
bytes: 100,
freed: 40,
};
let b = Allocations {
count: 5,
bytes: 160,
freed: 50,
};
assert_eq!(
b.since(a),
Allocations {
count: 2,
bytes: 60,
freed: 10
}
);
assert_eq!(a.since(b), Allocations::default());
}
}