use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
pub static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
pub static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
pub static BYTES_ALLOCATED: AtomicIsize = AtomicIsize::new(0);
pub static PEAK_BYTES: AtomicUsize = AtomicUsize::new(0);
pub struct TrackingAllocator;
unsafe impl GlobalAlloc for TrackingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
let size = layout.size() as isize;
let current = BYTES_ALLOCATED.fetch_add(size, Ordering::Relaxed) + size;
let peak = PEAK_BYTES.load(Ordering::Relaxed);
if current as usize > peak {
PEAK_BYTES.store(current as usize, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
DEALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
BYTES_ALLOCATED.fetch_sub(layout.size() as isize, Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
let size = layout.size() as isize;
let current = BYTES_ALLOCATED.fetch_add(size, Ordering::Relaxed) + size;
let peak = PEAK_BYTES.load(Ordering::Relaxed);
if current as usize > peak {
PEAK_BYTES.store(current as usize, Ordering::Relaxed);
}
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let old_size = layout.size() as isize;
let diff = new_size as isize - old_size;
BYTES_ALLOCATED.fetch_add(diff, Ordering::Relaxed);
if diff > 0 {
let current = BYTES_ALLOCATED.load(Ordering::Relaxed);
let peak = PEAK_BYTES.load(Ordering::Relaxed);
if current as usize > peak {
PEAK_BYTES.store(current as usize, Ordering::Relaxed);
}
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
pub fn reset_counters() {
ALLOC_COUNT.store(0, Ordering::SeqCst);
DEALLOC_COUNT.store(0, Ordering::SeqCst);
BYTES_ALLOCATED.store(0, Ordering::SeqCst);
PEAK_BYTES.store(0, Ordering::SeqCst);
}
pub fn get_stats() -> AllocationStats {
AllocationStats {
alloc_count: ALLOC_COUNT.load(Ordering::SeqCst),
dealloc_count: DEALLOC_COUNT.load(Ordering::SeqCst),
bytes_allocated: BYTES_ALLOCATED.load(Ordering::SeqCst),
peak_bytes: PEAK_BYTES.load(Ordering::SeqCst),
}
}
#[derive(Debug, Clone, Copy)]
pub struct AllocationStats {
pub alloc_count: usize,
pub dealloc_count: usize,
pub bytes_allocated: isize,
pub peak_bytes: usize,
}
pub fn check_no_leaks() {
let stats = get_stats();
let diff = stats.alloc_count as isize - stats.dealloc_count as isize;
if diff != 0 {
panic!(
"Memory leak detected!\n\
Allocations: {}\n\
Deallocations: {}\n\
Difference: {}\n\
Bytes still allocated: {}",
stats.alloc_count, stats.dealloc_count, diff, stats.bytes_allocated
);
}
if stats.bytes_allocated != 0 {
panic!(
"Memory leak detected!\n\
Bytes still allocated: {}\n\
(alloc_count == dealloc_count but bytes != 0, possible size mismatch)",
stats.bytes_allocated
);
}
}
pub fn check_balanced_with_tolerance(tolerance: usize) {
let stats = get_stats();
let diff = (stats.alloc_count as isize - stats.dealloc_count as isize).unsigned_abs();
if diff > tolerance {
panic!(
"Memory leak detected (beyond tolerance of {})!\n\
Allocations: {}\n\
Deallocations: {}\n\
Difference: {}",
tolerance, stats.alloc_count, stats.dealloc_count, diff
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore = "requires TrackingAllocator to be set as global allocator"]
fn test_tracking_allocator_basic() {
reset_counters();
let v1: Vec<u8> = vec![1, 2, 3, 4];
let v2: Vec<u8> = vec![5, 6, 7, 8];
let stats = get_stats();
assert!(stats.alloc_count > 0);
assert!(stats.bytes_allocated > 0);
drop(v1);
drop(v2);
let stats = get_stats();
assert_eq!(stats.alloc_count, stats.dealloc_count);
}
#[test]
#[ignore = "requires TrackingAllocator to be set as global allocator"]
fn test_reset_counters() {
let _v: Vec<u8> = vec![1, 2, 3, 4];
reset_counters();
let stats = get_stats();
assert_eq!(stats.alloc_count, 0);
assert_eq!(stats.dealloc_count, 0);
}
}