use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicUsize, Ordering};
static CURRENT: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
pub struct Tracking<A>(pub A);
unsafe impl<A: GlobalAlloc> GlobalAlloc for Tracking<A> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let p = unsafe { self.0.alloc(layout) };
if !p.is_null() {
bump(layout.size());
}
p
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
CURRENT.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { self.0.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let p = unsafe { self.0.realloc(ptr, layout, new_size) };
if !p.is_null() {
if new_size >= layout.size() {
bump(new_size - layout.size());
} else {
CURRENT.fetch_sub(layout.size() - new_size, Ordering::Relaxed);
}
}
p
}
}
fn bump(n: usize) {
let now = CURRENT.fetch_add(n, Ordering::Relaxed) + n;
PEAK.fetch_max(now, Ordering::Relaxed);
}
pub fn heap_current() -> u64 {
CURRENT.load(Ordering::Relaxed) as u64
}
pub fn heap_peak() -> u64 {
PEAK.load(Ordering::Relaxed) as u64
}
pub fn reset_heap_peak() {
PEAK.store(CURRENT.load(Ordering::Relaxed), Ordering::Relaxed);
}
#[cfg(unix)]
pub fn rss_highwater() -> u64 {
unsafe {
let mut ru: libc::rusage = std::mem::zeroed();
if libc::getrusage(libc::RUSAGE_SELF, &mut ru) != 0 {
return 0;
}
let v = ru.ru_maxrss as u64;
if cfg!(target_os = "macos") {
v
} else {
v * 1024
}
}
}
#[cfg(windows)]
pub fn rss_highwater() -> u64 {
use windows_sys::Win32::System::ProcessStatus::{
K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS,
};
use windows_sys::Win32::System::Threading::GetCurrentProcess;
unsafe {
let mut pmc: PROCESS_MEMORY_COUNTERS = std::mem::zeroed();
let size = std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
if K32GetProcessMemoryInfo(GetCurrentProcess(), &mut pmc, size) == 0 {
return 0;
}
pmc.PeakWorkingSetSize as u64
}
}
#[cfg(not(any(unix, windows)))]
pub fn rss_highwater() -> u64 {
0
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Phase {
pub time: std::time::Duration,
pub heap_peak: u64,
pub rss_after: u64,
}
pub fn measure<T>(f: impl FnOnce() -> T) -> (T, Phase) {
reset_heap_peak();
let start = std::time::Instant::now();
let out = f();
let phase = Phase {
time: start.elapsed(),
heap_peak: heap_peak(),
rss_after: rss_highwater(),
};
(out, phase)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn works_without_allocator_installed() {
reset_heap_peak();
let (v, p) = measure(|| {
let big: Vec<u8> = vec![7u8; 4 << 20];
std::hint::black_box(&big);
big.len()
});
assert_eq!(v, 4 << 20);
assert!(p.rss_after > 0, "rss should always be readable");
}
#[test]
fn rss_is_plausible() {
assert!(rss_highwater() > 0);
}
#[test]
fn reset_lowers_the_peak() {
{
let _big: Vec<u8> = vec![0u8; 1 << 20];
}
reset_heap_peak();
assert!(heap_peak() >= heap_current());
}
}