use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicU64, Ordering};
static ALLOC_COUNT: AtomicU64 = AtomicU64::new(0);
static BYTES_ALLOCATED: AtomicU64 = AtomicU64::new(0);
static BYTES_DEALLOCATED: AtomicU64 = AtomicU64::new(0);
static ALLOC_COUNT_PEAK: AtomicU64 = AtomicU64::new(0);
pub struct CountingAllocator<A: GlobalAlloc>(pub A);
unsafe impl<A: GlobalAlloc> GlobalAlloc for CountingAllocator<A> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
let size = layout.size() as u64;
let prev = BYTES_ALLOCATED.fetch_add(size, Ordering::Relaxed);
let total = prev + size;
let deallocd = BYTES_DEALLOCATED.load(Ordering::Relaxed);
let current = total.saturating_sub(deallocd);
let peak = ALLOC_COUNT_PEAK.load(Ordering::Relaxed);
if current > peak {
ALLOC_COUNT_PEAK.store(current, Ordering::Relaxed);
}
unsafe { self.0.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
BYTES_DEALLOCATED.fetch_add(layout.size() as u64, Ordering::Relaxed);
unsafe {
self.0.dealloc(ptr, layout);
}
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
let size = layout.size() as u64;
BYTES_ALLOCATED.fetch_add(size, Ordering::Relaxed);
unsafe { self.0.alloc_zeroed(layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
let old_size = layout.size() as u64;
let new_size_u64 = new_size as u64;
if new_size_u64 > old_size {
BYTES_ALLOCATED.fetch_add(new_size_u64 - old_size, Ordering::Relaxed);
} else {
BYTES_DEALLOCATED.fetch_add(old_size - new_size_u64, Ordering::Relaxed);
}
unsafe { self.0.realloc(ptr, layout, new_size) }
}
}
impl<A: GlobalAlloc> CountingAllocator<A> {
pub fn reset() {
ALLOC_COUNT.store(0, Ordering::Relaxed);
BYTES_ALLOCATED.store(0, Ordering::Relaxed);
BYTES_DEALLOCATED.store(0, Ordering::Relaxed);
ALLOC_COUNT_PEAK.store(0, Ordering::Relaxed);
}
pub fn alloc_count() -> u64 {
ALLOC_COUNT.load(Ordering::Relaxed)
}
pub fn bytes_allocated() -> u64 {
BYTES_ALLOCATED.load(Ordering::Relaxed)
}
pub fn bytes_deallocated() -> u64 {
BYTES_DEALLOCATED.load(Ordering::Relaxed)
}
pub fn bytes_current() -> i64 {
BYTES_ALLOCATED.load(Ordering::Relaxed) as i64
- BYTES_DEALLOCATED.load(Ordering::Relaxed) as i64
}
pub fn peak_bytes() -> u64 {
ALLOC_COUNT_PEAK.load(Ordering::Relaxed)
}
}
#[derive(Debug, Clone, Default)]
pub struct OpAllocations {
pub alloc_count: u64,
pub bytes_allocated: u64,
}
pub fn track_allocations<F, T>(f: F) -> (T, OpAllocations)
where
F: FnOnce() -> T,
{
let before = (
ALLOC_COUNT.load(Ordering::Relaxed),
BYTES_ALLOCATED.load(Ordering::Relaxed),
);
let result = f();
let after = (
ALLOC_COUNT.load(Ordering::Relaxed),
BYTES_ALLOCATED.load(Ordering::Relaxed),
);
(
result,
OpAllocations {
alloc_count: after.0 - before.0,
bytes_allocated: after.1 - before.1,
},
)
}
#[derive(Debug, Clone)]
pub struct ProfileResult {
pub total_allocations: u64,
pub total_bytes_allocated: u64,
pub bytes_per_frame: u64,
pub allocs_per_frame: u64,
pub bytes_per_keypress: u64,
pub allocs_per_keypress: u64,
pub scroll_allocations: u64,
pub scroll_bytes: u64,
pub parser_allocations: u64,
pub parser_bytes: u64,
pub peak_memory_bytes: u64,
}
impl ProfileResult {
pub fn print(&self) {
println!("=== Allocation Profile ===");
println!(" Total allocations: {}", self.total_allocations);
println!(
" Total bytes allocated: {} ({:.2} KB)",
self.total_bytes_allocated,
self.total_bytes_allocated as f64 / 1024.0
);
println!(" Allocs/frame: {:.2}", self.allocs_per_frame);
println!(" Bytes/frame: {:.2}", self.bytes_per_frame);
println!(" Allocs/keypress: {:.2}", self.allocs_per_keypress);
println!(" Bytes/keypress: {:.2}", self.bytes_per_keypress);
println!(" Scroll allocs: {}", self.scroll_allocations);
println!(" Scroll bytes: {}", self.scroll_bytes);
println!(" Parser allocs: {}", self.parser_allocations);
println!(" Parser bytes: {}", self.parser_bytes);
println!(
" Peak memory: {} ({:.2} KB)",
self.peak_memory_bytes,
self.peak_memory_bytes as f64 / 1024.0
);
}
}
pub fn print_profiling_guide() {
println!("=== Profiling Guide ===");
println!();
println!("To enable allocation counting:");
println!(" 1. Add to src/main.rs:");
println!(" use replay::profiling::CountingAllocator;");
println!(" use std::alloc::System;");
println!(" #[global_allocator]");
println!(" static ALLOC: CountingAllocator<System> = CountingAllocator(System);");
println!();
println!(" 2. Run with --profile flag to see allocation stats");
println!();
println!("External profilers:");
println!(
" macOS Instruments: xcrun xctrace record --template 'Allocations' --launch ./target/release/panasyn"
);
println!(" Tracy: Add tracy-client dependency and TRACY_ENABLE=1");
println!(" heaptrack: heaptrack ./target/release/panasyn");
println!(" dhat: cargo build --features dhat-heap && DHAT=1 ./target/release/panasyn");
}