use std::sync::atomic::{AtomicU64, Ordering};
use mimalloc_pprof::memory_events::{self, Callbacks, Change, ChangeKind};
#[global_allocator]
static ALLOCATOR: mimalloc_pprof::MiMalloc = mimalloc_pprof::MiMalloc;
#[test]
fn memory_events_end_to_end() {
enabling_is_authoritative_and_reversible();
snapshot_counts_allocations_while_enabled();
tracking_off_records_nothing();
unwrapped_allocations_are_excluded_from_accounting();
callbacks_observe_every_kind();
}
fn enabling_is_authoritative_and_reversible() {
let previous = memory_events::set_enabled(true);
assert!(memory_events::is_enabled());
memory_events::set_enabled(false);
assert!(!memory_events::is_enabled());
memory_events::set_enabled(previous);
}
fn snapshot_counts_allocations_while_enabled() {
let previous = memory_events::set_enabled(true);
let before = memory_events::snapshot().expect("snapshot while enabled");
let held: Vec<Vec<u8>> = (0..64).map(|_| vec![0_u8; 4096]).collect();
std::hint::black_box(&held);
let during = memory_events::snapshot().expect("snapshot while enabled");
assert!(
during.accum_count >= before.accum_count + 64,
"64 allocations should raise accum_count by at least 64: {} -> {}",
before.accum_count,
during.accum_count
);
assert!(
during.accum_bytes >= before.accum_bytes + 64 * 4096,
"accum_bytes should grow by at least the requested bytes: {} -> {}",
before.accum_bytes,
during.accum_bytes
);
assert!(
during.live_bytes >= 64 * 4096,
"live_bytes = {}",
during.live_bytes
);
drop(held);
let after = memory_events::snapshot().expect("snapshot while enabled");
assert!(
after.live_count < during.live_count,
"freeing 64 blocks should lower live_count: {} -> {}",
during.live_count,
after.live_count
);
assert!(after.accum_count >= during.accum_count);
memory_events::set_enabled(previous);
}
fn tracking_off_records_nothing() {
let previous = memory_events::set_enabled(false);
let before = memory_events::snapshot().expect("snapshot while disabled");
let held: Vec<Vec<u8>> = (0..32).map(|_| vec![0_u8; 8192]).collect();
std::hint::black_box(&held);
let after = memory_events::snapshot().expect("snapshot while disabled");
assert_eq!(
before.accum_count, after.accum_count,
"accounting must stop dead while tracking is off"
);
drop(held);
memory_events::set_enabled(previous);
}
fn unwrapped_allocations_are_excluded_from_accounting() {
mimalloc_pprof::scavenger_stop();
let previous = memory_events::set_enabled(true);
let before = memory_events::snapshot().expect("snapshot while enabled");
let p = unsafe { mimalloc_pprof::unwrapped_malloc(64 * 1024, 4096) };
assert!(!p.is_null(), "unwrapped_malloc returned NULL");
let after = memory_events::snapshot().expect("snapshot while enabled");
unsafe { mimalloc_pprof::unwrapped_free(p) };
assert_eq!(before.accum_count, after.accum_count);
assert_eq!(before.accum_bytes, after.accum_bytes);
memory_events::set_enabled(previous);
}
static ALLOCATES: AtomicU64 = AtomicU64::new(0);
static FREES: AtomicU64 = AtomicU64::new(0);
static RESIZES: AtomicU64 = AtomicU64::new(0);
static VIOLATIONS: AtomicU64 = AtomicU64::new(0);
fn record(condition: bool) {
if !condition {
VIOLATIONS.fetch_add(1, Ordering::Relaxed);
}
}
fn on_allocate(change: &Change) {
record(change.kind == ChangeKind::Allocate && change.delta_bytes >= 0);
ALLOCATES.fetch_add(1, Ordering::Relaxed);
}
fn on_free(change: &Change) {
record(change.kind == ChangeKind::Free && change.delta_bytes <= 0 && change.request_size == 0);
FREES.fetch_add(1, Ordering::Relaxed);
}
fn on_resize(change: &Change) {
record(change.kind == ChangeKind::Resize);
RESIZES.fetch_add(1, Ordering::Relaxed);
}
static CALLBACKS: Callbacks = Callbacks {
allocate: Some(on_allocate),
free: Some(on_free),
resize: Some(on_resize),
};
fn callbacks_observe_every_kind() {
let previous = memory_events::set_enabled(true);
assert!(memory_events::set_callbacks(&CALLBACKS));
let mut grown: Vec<u8> = Vec::with_capacity(64);
grown.resize(64, 1);
grown.reserve(64 * 1024); std::hint::black_box(&grown);
drop(grown);
assert!(memory_events::clear_callbacks());
memory_events::set_enabled(previous);
assert_eq!(
VIOLATIONS.load(Ordering::Relaxed),
0,
"a callback saw a change record that contradicts memory-events.h"
);
assert!(
ALLOCATES.load(Ordering::Relaxed) > 0,
"no ALLOCATE callbacks fired"
);
assert!(FREES.load(Ordering::Relaxed) > 0, "no FREE callbacks fired");
assert!(
RESIZES.load(Ordering::Relaxed) > 0,
"no RESIZE callbacks fired"
);
let allocates = ALLOCATES.load(Ordering::Relaxed);
let noise: Vec<u8> = vec![3_u8; 32 * 1024];
std::hint::black_box(&noise);
assert_eq!(
allocates,
ALLOCATES.load(Ordering::Relaxed),
"a callback fired after clear_callbacks"
);
}