// SDD-120 §3 items 1-2 — realtime capture bench harness. // // - `bench_capture_alloc_count`: dhat-backed heap-allocation count // across 1000 post-warmup `CaptureState::ingest` calls. Realizes // the SRS-219 clause-a zero-allocation invariant. Local-developer // surface additionally asserts that the post-warmup count is 0 // so a regression hard-fails locally; the CI advisory comparator // in `compare_baseline.rs` carries the same `tolerance = 0` rule // independently. // - `bench_capture_callback_wall_clock`: criterion default-warmup // wall-clock bench of `ingest` on the same synthetic buffer. // // dhat is invasive — it replaces the global allocator for the // whole bench binary, but only this bench binary; production // builds and other benches are unaffected per Cargo's per-bench // compilation model. #[cfg(any(target_os = "ios", target_os = "macos", target_os = "android"))] fn main() {} #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] use chanora_audio::bench_seam::CaptureBenchHandle; #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] use criterion::{black_box, criterion_group, criterion_main, Criterion}; #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] mod common; #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] use common::{synthetic_capture_buffer, FRAME_SAMPLES}; #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] fn bench_capture_alloc_count(c: &mut Criterion) { // Build the dhat profiler in test mode so it is process-local // and does not write a JSON heap-dump file. Held for the // duration of this bench function. let _profiler = dhat::Profiler::builder().testing().build(); let mut handle = CaptureBenchHandle::new(48_000, 1); let buf = synthetic_capture_buffer(FRAME_SAMPLES, 1); // 100-call warm-up to prime first-call allocations (ring // growth, opus encoder lazy state, mono_scratch / pcm_accum // capacity). SDD-120 §3 item 1. for _ in 0..100 { handle.ingest_f32(&buf); } let stats_warm = dhat::HeapStats::get(); let blocks_warm = stats_warm.total_blocks; // 1000 post-warmup calls — measurement window. for _ in 0..1000 { handle.ingest_f32(&buf); } let stats_final = dhat::HeapStats::get(); let blocks_final = stats_final.total_blocks; let delta = blocks_final - blocks_warm; // Local-developer surface: hard-fail on any regression. // The CI advisory comparator carries the same rule with a // markdown 🔴 marker on regression instead of a panic. assert_eq!( delta, 0, "post-warmup heap allocation regression: {} blocks (SRS-219 clause a)", delta ); // Register the metric value with criterion so it appears in // `target/criterion/.../estimates.json` for the §5 emitter to // pick up. We bench a no-op closure here because the actual // measurement is the delta computed above; criterion's // function-time-mean is uninteresting for an alloc-count // metric. The §5 emitter reads the `capture_alloc_count` // metric value out of band via a sidecar file written below. c.bench_function("capture_alloc_count", |b| { b.iter(|| { black_box(delta); }); }); // Sidecar file for §5 emit_baseline: criterion's own // estimates.json carries the no-op closure timing, NOT the // alloc count, so we write the canonical alloc-count value // here and the emitter reads it directly. if let Ok(dir) = std::env::var("CARGO_TARGET_DIR") .map(std::path::PathBuf::from) .or_else(|_| std::env::current_dir().map(|d| d.join("target"))) { let path = dir.join("criterion").join("capture_alloc_count.sidecar"); if let Some(parent) = path.parent() { let _ = std::fs::create_dir_all(parent); } let _ = std::fs::write(&path, format!("{}", delta)); } } #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] fn bench_capture_callback_wall_clock(c: &mut Criterion) { let mut handle = CaptureBenchHandle::new(48_000, 1); let buf = synthetic_capture_buffer(FRAME_SAMPLES, 1); // Pre-warm so first-call allocations do not skew the // measurement window (criterion's default warmup is 3 s which // is more than enough; this loop is belt-and-braces). for _ in 0..100 { handle.ingest_f32(&buf); } c.bench_function("capture_callback_wall_clock", |b| { b.iter(|| { handle.ingest_f32(black_box(&buf)); }); }); } #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] criterion_group!( realtime_capture, bench_capture_alloc_count, bench_capture_callback_wall_clock ); #[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))] criterion_main!(realtime_capture);