use crate::memory::alloc_count;
pub(crate) const WARMUP_TICKS: u64 = 64;
pub(crate) const QUIET_WINDOW_TICKS: u64 = 64;
pub(crate) struct AllocGuard {
tick: u64,
started: Option<u64>,
ended: Option<u64>,
foreign: bool,
since_quiet: u64,
quietest: (u64, u64),
}
impl AllocGuard {
pub(crate) const fn new() -> Self {
Self {
tick: 0,
started: None,
ended: None,
foreign: false,
since_quiet: 0,
quietest: (0, 0),
}
}
pub(crate) fn begin_tick(&mut self) {
let now = alloc_count();
self.foreign = match (self.ended, now) {
(Some(ended), Some(now)) => now > ended,
_ => false,
};
self.started = now;
}
pub(crate) fn end_tick(&mut self) {
let now = alloc_count();
let sampled = match (self.started, now) {
(Some(started), Some(now)) => Some(now.saturating_sub(started)),
_ => None,
};
self.ended = now;
match sampled {
Some(allocated) => self.observe(allocated),
None => self.tick = self.tick.saturating_add(1),
}
}
fn observe(&mut self, allocated: u64) {
let tick = self.tick;
self.tick = self.tick.saturating_add(1);
if tick < WARMUP_TICKS {
return;
}
if allocated == 0 || self.foreign {
self.since_quiet = 0;
return;
}
if self.since_quiet == 0 || allocated < self.quietest.1 {
self.quietest = (tick, allocated);
}
self.since_quiet += 1;
assert!(
self.since_quiet < QUIET_WINDOW_TICKS,
"the headless loop allocates every tick: none of the last \
{QUIET_WINDOW_TICKS} allocated nothing, and the quietest, tick {}, \
allocated {} time(s)",
self.quietest.0,
self.quietest.1
);
}
}
#[cfg(test)]
pub(crate) fn armed() -> bool {
alloc_count().is_some()
}
#[cfg(test)]
mod tests {
use super::*;
crate::install_global_allocator!();
fn warmed() -> AllocGuard {
let mut guard = AllocGuard::new();
for _ in 0..WARMUP_TICKS {
guard.observe(1);
}
guard
}
#[test]
fn the_warmup_is_not_judged() {
let mut guard = AllocGuard::new();
for _ in 0..WARMUP_TICKS {
guard.observe(64);
}
assert_eq!(guard.tick, WARMUP_TICKS);
}
#[test]
fn a_quiet_tick_clears_the_window() {
let mut guard = warmed();
for _ in 0..(QUIET_WINDOW_TICKS * 4) {
for _ in 0..(QUIET_WINDOW_TICKS - 1) {
guard.observe(3);
}
guard.observe(0);
}
assert_eq!(guard.since_quiet, 0);
}
#[test]
#[should_panic(expected = "allocated 2 time(s)")]
fn a_window_of_allocating_ticks_fails_naming_the_quietest() {
let mut guard = warmed();
for i in 0..QUIET_WINDOW_TICKS {
guard.observe(if i == 3 { 2 } else { 9 });
}
}
#[test]
fn another_allocating_thread_abandons_the_window() {
let mut guard = warmed();
for _ in 0..(QUIET_WINDOW_TICKS * 4) {
guard.foreign = true;
guard.observe(7);
}
assert_eq!(guard.since_quiet, 0);
}
#[test]
fn an_unsampled_tick_is_not_judged() {
let mut guard = warmed();
guard.since_quiet = QUIET_WINDOW_TICKS - 1;
guard.end_tick();
assert_eq!(guard.tick, WARMUP_TICKS + 1, "the tick still counts");
assert_eq!(guard.since_quiet, QUIET_WINDOW_TICKS - 1);
}
#[test]
fn the_test_binary_arms_the_guard() {
assert!(armed());
}
}