use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use kernel::budget::MemoryBudget;
use kernel::io::{open_file, IoMode};
use kernel::page::PAGE_SIZE;
use kernel::pool::BufferPool;
use kernel::Result;
static LIVE: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
let p = unsafe { System.alloc(l) };
if !p.is_null() {
let now = LIVE.fetch_add(l.size(), Ordering::Relaxed) + l.size();
PEAK.fetch_max(now, Ordering::Relaxed);
}
p
}
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
LIVE.fetch_sub(l.size(), Ordering::Relaxed);
unsafe { System.dealloc(p, l) }
}
}
#[global_allocator]
static ALLOC: Counting = Counting;
fn peak_extra_bytes<T>(f: impl FnOnce() -> T) -> (T, usize) {
let base = LIVE.load(Ordering::SeqCst);
PEAK.store(base, Ordering::SeqCst);
let out = f();
(out, PEAK.load(Ordering::SeqCst).saturating_sub(base))
}
fn pool_in(dir: &std::path::Path, frames: usize) -> BufferPool {
let (file, _) = open_file(&dir.join("data"), IoMode::Buffered).unwrap();
let budget = Arc::new(MemoryBudget::new(frames * PAGE_SIZE + (1 << 20)));
BufferPool::new(file.into(), budget, frames).unwrap()
}
fn rows(n: u64) -> impl Iterator<Item = Result<(Vec<u8>, Vec<u8>, bool)>> {
(0..n).map(|i| Ok((i.to_be_bytes().to_vec(), i.to_le_bytes().to_vec(), false)))
}
#[test]
fn pack_tree_memory_is_flat_in_rows() {
let counts = [200_000u64, 2_000_000, 20_000_000];
let mut peaks = Vec::new();
for n in counts {
let d = tempfile::tempdir().unwrap();
let pool = pool_in(d.path(), 64);
let scratch = d.path().join("scratch");
let _ = pool.allocate().unwrap(); let (root, peak) = peak_extra_bytes(|| {
kernel::bulk::pack_tree(&pool, 1, rows(n), 0.9, &scratch).unwrap()
});
assert!(root > 0, "{n} rows must have produced a root");
eprintln!("rows {n:>10} -> peak {peak} B");
peaks.push(peak);
}
let lo = *peaks.iter().min().unwrap();
let hi = *peaks.iter().max().unwrap();
let delta = hi - lo;
eprintln!(
"peak spread {delta} B across a {}x row range (ratio {:.2}x)",
counts[counts.len() - 1] / counts[0],
hi as f64 / lo as f64,
);
const SLACK: usize = 64 * 1024;
assert!(
delta <= SLACK,
"peak allocation grew with rows: {delta} B of spread across {counts:?} rows \
({peaks:?}), more than the {SLACK} B a row-independent pack is allowed. \
Law 1: no RAM proportional to the store."
);
}