Files
Aura/crates/aura-bench/src/surfaces/ingest.rs
T
claude 400105e2e2 feat(bench): CLI driver, fixed-cost surface, committed baselines + docs
Tasks 7-8 of the bench-harness plan plus the orchestrator's sizing pass —
this completes the phase-1 harness (closes #251).

Driver: run [--surface|--quick|--reps|--out] / pin over five surfaces,
report-only comparison (drift NOTICE at >=10%, never a failure), fingerprint
mismatch exits 1 with a baseline-vs-measured block, infra errors exit 2,
debug builds refuse to measure. The Bench project-facts line and the crate
README (discipline: quiet box, warmup+median, deliberate re-pins) land with
it.

Workload sizing, measured then corrected: the release engine outran the
plan's guesses by ~100x, leaving sub-second child walls where spawn jitter
reaches the 10% threshold and flaps the report. Full-size workloads now land
in whole seconds — engine 10M bars (~0.7s), ingest 24 months x 20 fresh
drains (~0.4s), campaigns on a 24-month archive with a ~22-month window and
a 5x5 member grid (sweep ~1.4s, heavy ~5.5s), fixed-cost as in-rep medians
over spawn batches (20x help, 10x run). Quick mode keeps the small
E2E-fixture shape.

The campaign fingerprint folds all three realization layers — sweep winner,
gate survivor ordinals, bootstrap trade counts (pooled or per-survivor sum)
— so a wrong-result regression in any stage fails the bench, not just a
wrong sweep winner. Baselines pinned on this host (commit-stamped), verified
by a follow-up run: five fingerprints OK at <=2% drift under load.

Verification: cargo test --workspace 1379 green (bench tests all quick-mode;
the release-binary E2Es are #[ignore]d behind -- --ignored per the
suite-wallclock discipline), clippy -D warnings clean, doc build clean.
2026-07-17 18:10:00 +02:00

87 lines
3.4 KiB
Rust

//! Ingest throughput: bars/s draining one field column out of a synthetic
//! 48-byte M1 zip archive through the public ingest window API. A FRESH
//! `DataServer` per repetition, so the measured drain pays zip inflate +
//! record parse instead of the server's in-memory cache; the discarded warmup
//! rep leaves the OS page cache warm and consistent.
use super::{RepOutcome, Sizing};
use crate::synth::{write_symbol_archive, ScratchDir};
use aura_ingest::{open_columns, DataServer, M1Field};
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::Instant;
pub const SYMBOL: &str = "BENCHI";
pub const FULL_MONTHS: ((i32, u32), (i32, u32)) = ((2024, 1), (2025, 12));
pub const QUICK_MONTHS: ((i32, u32), (i32, u32)) = ((2024, 1), (2024, 2));
/// The archive is written once per surface run (setup, unmeasured) and shared
/// by every repetition. `passes` fresh drains per repetition lift the measured
/// wall out of the millisecond regime, where ordinary jitter would reach the
/// 10% NOTICE threshold and flap the report.
pub struct IngestFixture {
pub dir: ScratchDir,
pub passes: u32,
}
pub const FULL_PASSES: u32 = 20;
pub fn setup(sizing: Sizing) -> Result<IngestFixture, String> {
let dir = ScratchDir::new("ingest").map_err(|e| format!("scratch dir: {e}"))?;
let (from, to) = if sizing.quick { QUICK_MONTHS } else { FULL_MONTHS };
write_symbol_archive(&dir.0, SYMBOL, from, to);
Ok(IngestFixture { dir, passes: if sizing.quick { 1 } else { FULL_PASSES } })
}
/// One measured drain: a FRESH `DataServer`, so the pass pays zip inflate +
/// parse instead of hitting the in-memory FileCache.
fn drain_once(dir: &std::path::Path) -> Result<(u64, u64), String> {
let server = Arc::new(DataServer::new(dir));
let mut srcs = open_columns(&server, SYMBOL, None, None, &[M1Field::Close])
.ok_or("bench archive must be present for the close column")?;
let mut src = srcs.pop().ok_or("one source for one field")?;
let mut n: u64 = 0;
let mut xor: u64 = 0;
while let Some((_, v)) = src.next() {
n += 1;
xor ^= v.as_f64().to_bits();
}
if n == 0 {
return Err("the synthetic archive must yield bars".to_string());
}
Ok((n, xor))
}
pub fn rep(fixture: &IngestFixture) -> Result<RepOutcome, String> {
let t = Instant::now();
let (n, xor) = drain_once(&fixture.dir.0)?;
for _ in 1..fixture.passes {
let again = drain_once(&fixture.dir.0)?;
if again != (n, xor) {
return Err(format!("ingest passes disagree: {:?} vs {:?}", (n, xor), again));
}
}
let wall = t.elapsed().as_secs_f64();
let total = n * u64::from(fixture.passes);
let mut metrics = BTreeMap::new();
metrics.insert("wall_s".to_string(), wall);
metrics.insert("bars_per_s".to_string(), total as f64 / wall);
Ok(RepOutcome { metrics, fingerprint: format!("n={n} xor={xor:016x}") })
}
#[cfg(test)]
mod tests {
use super::*;
/// Determinism smoke: two drains of the same archive fold to the same
/// fingerprint, and a fresh server per rep really re-reads the bytes.
#[test]
fn ingest_surface_fingerprint_is_deterministic_in_quick_mode() {
let fixture = setup(Sizing { quick: true }).expect("fixture writes");
let a = rep(&fixture).expect("first drain");
let b = rep(&fixture).expect("second drain");
assert_eq!(a.fingerprint, b.fingerprint);
assert!(a.fingerprint.starts_with("n="), "shape: {}", a.fingerprint);
}
}