//! The Chapter 10 file-format budgets: typical-edit bundle write and //! manifest+bootstrap read (Phase 2 worklist F1). //! //! The normative budgets (`spec/core_spec.tex`, Chapter 10 §"File Format //! Performance"): //! //! > Bundle write of a typical edit (one or more operation envelopes appended //! > to the operation-envelope block stream, manifest rewrite, superblock flip) //! > completes within 50 ms at p99 on the reference hardware profile. //! > //! > Bundle read of the manifest and bootstrap chunks (sufficient for first //! > interactive frame) completes within 200 ms at p99 for a 100-page //! > orchestral score. //! //! Both rows run against a real on-disk bundle (`FileStore`, whose flush is a //! genuine `fsync`) in a scratch directory under the build's **target dir** — //! deliberately not `std::env::temp_dir()`, which is commonly tmpfs on Linux, //! where fsync is a near-no-op and the commit budget would be measured against //! RAM. The corpus is moderate: 1,000 generated operation envelopes packed //! into operation blocks plus a canonical `MaterializedState` snapshot. That //! snapshot's manifest placement (`canonical_base`) is suspended for the S28 //! -> P13-S27 interval (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` pin 3c) — see //! `Fixture::base_root`'s doc comment — so it is staged and read back //! directly by `ChunkRef` instead. Both //! are expected to **Pass** today, so a regression fails `cargo bench` loudly. //! The read row's honesty note: the spec sizes its 200 ms against a 100-page //! orchestral score; no such corpus generator exists yet, so this row is the //! moderate-corpus stand-in (recorded in `DECISIONS.md` F1) and the budget is //! asserted with margin to spare. The OS page cache is warm across iterations //! (dropping it needs privileges); each iteration's *process-level* state is //! fresh. //! //! The timed sections: //! //! * **typical_edit_commit** — on an opened bundle restored to the same base //! image (restore + open are un-timed setup): one `commit` staging a small //! envelope block, i.e. block append + manifest rewrite + superblock flip, //! every write fsync'd. This mirrors `bundle_harness`'s commit driver. //! * **open_bootstrap_read** — `FileStore::open` + `Bundle::open` (superblock //! selection + header + manifest decode) + reading the cold-reduction //! snapshot (by direct `ChunkRef`, see above) and every operation block — //! the bytes a first interactive frame needs. //! //! Criterion measures; the budget gate in `main` asserts (see //! `epiphany_testkit::budget` for the Pass/Xfail semantics and the documented //! deviation from Chapter 10's p99-over-1000-iterations conformance //! methodology). Run: `cargo bench -p epiphany-testkit --bench bundle`; //! `EPIPHANY_BENCH_QUICK=1` shrinks sampling for PR CI. use std::fs; use std::path::{Path, PathBuf}; use std::time::Duration; use criterion::{BatchSize, Criterion}; use epiphany_bundle::{ pack_operation_blocks, Bundle, ChunkKind, ChunkRef, CommitContext, DocumentId, FileStore, FileUuid, Manifest, MemStore, SchemaVersion, StagedChunk, }; use epiphany_determinism::CanonicalEncode; use epiphany_ops::{OperationEnvelope, OperationSet}; use epiphany_testkit::budget::{self, Expectation}; use epiphany_testkit::{generators, Rng}; /// Chapter 10: a typical-edit bundle write completes within 50 ms (p99). const COMMIT_BUDGET: Duration = Duration::from_millis(50); /// Chapter 10: manifest + bootstrap read completes within 200 ms (p99). const READ_BUDGET: Duration = Duration::from_millis(200); /// Base-corpus scale: the criterion-5 envelope count, packed into real blocks. const BASE_ENVELOPES: usize = 1_000; /// The typical edit: a handful of envelopes appended as one block. const EDIT_ENVELOPES: usize = 4; /// Everything the two rows measure against, built once from a fixed seed. struct Fixture { /// The committed base image (blocks + the corpus's cold-reduction /// snapshot). base_image: Vec, /// The cold-reduction snapshot's `ChunkRef`. /// /// **Suspended manifest placement** (`CONTRACT_FORMAT_EPOCH_MAJOR1.md` /// pin 3c, amended 2026-08-07): this used to be `manifest.canonical_base` /// — its correct semantic home — but pin 3a's format-epoch matrix refuses /// every base introduction and every base-bearing open during the S28 -> /// P13-S27 interval. The ref is carried here instead and read back /// directly via `Bundle::read_chunk`, exactly as /// `epiphany_testkit::roundtrip::assert_reduction_serialization_stable` /// now does. **Not** re-homed to `acceleration_snapshots` — nothing in /// `epiphany-bundle` verifies that field. base_root: ChunkRef, /// The typical edit, staged (one small operation block). edit: Vec, /// Temp-dir file paths: one per row so commit growth never skews reads. commit_path: PathBuf, read_path: PathBuf, } /// The commit-context closure the harness uses: append the new chunks to the /// previous manifest's `operation_roots`. fn append_roots(ctx: &CommitContext) -> Manifest { let mut manifest = ctx.previous_manifest.clone(); manifest .operation_roots .extend(ctx.new_chunks.iter().copied()); manifest } fn staged_blocks(envelopes: &[OperationEnvelope]) -> Vec { let payloads: Vec> = envelopes.iter().map(|e| e.to_canonical_bytes()).collect(); pack_operation_blocks(&payloads) .into_iter() .map(StagedChunk::operation_block) .collect() } /// Builds the moderate base corpus: 1,000 envelopes committed as operation /// blocks, then their cold reduction committed as a `Snapshot` chunk — staged /// but not wired into the manifest (pin 3c; see `Fixture::base_root`'s doc /// comment). fn build_fixture(dir: &Path) -> Fixture { let mut rng = Rng::new(0x00F1_B0DE_0001); let envelopes = generators::operation_envelopes(&mut rng, BASE_ENVELOPES, 3, 40, 40); let edit_envelopes = generators::operation_envelopes(&mut rng, EDIT_ENVELOPES, 3, 8, 8); let uuid = FileUuid(rng.array16()); let doc = DocumentId(rng.array16()); let mut bundle = Bundle::create( MemStore::new(), uuid, Manifest::empty(doc), epiphany_testkit::production_caps(), ) .expect("create base bundle"); bundle .commit(&staged_blocks(&envelopes), append_roots) .expect("commit base operation blocks"); // The canonical base: the corpus's cold reduction, stored as a snapshot. let mut set = OperationSet::new(); set.accept_all(envelopes.iter().cloned()); let canonical = set.reduce().canonical_bytes(); let snapshot = StagedChunk { kind: ChunkKind::Snapshot, schema_version: SchemaVersion::V0, payload: canonical, }; let mut base_root = None; bundle .commit(&[snapshot], |ctx| { base_root = Some(ctx.new_chunks[0]); ctx.previous_manifest.clone() }) .expect("commit cold-reduction snapshot"); let base_root = base_root.expect("commit ran the build closure"); Fixture { base_image: bundle.into_store().into_bytes(), base_root, edit: staged_blocks(&edit_envelopes), commit_path: dir.join("commit.epb"), read_path: dir.join("read.epb"), } } /// Un-timed setup for the commit row: restore the base image and open it. fn restore_and_open(path: &Path, image: &[u8]) -> Bundle { fs::write(path, image).expect("restore base image"); Bundle::open( FileStore::open(path).expect("open store"), epiphany_testkit::production_caps(), ) .expect("open bundle") } /// The timed commit: block append + manifest rewrite + superblock flip, fsync'd. fn typical_edit_commit(mut bundle: Bundle, edit: &[StagedChunk]) -> u64 { bundle .commit(edit, append_roots) .expect("typical-edit commit"); bundle.generation() } /// The timed read: open (superblock selection + manifest decode) + the /// bootstrap chunks — the cold-reduction snapshot (read directly by its /// `ChunkRef`; see `Fixture::base_root`'s doc comment for why it is not /// `manifest.canonical_base` right now) and every operation block. fn open_bootstrap_read(path: &Path, base_root: &ChunkRef) -> usize { let bundle = Bundle::open( FileStore::open(path).expect("open store"), epiphany_testkit::production_caps(), ) .expect("open bundle"); let manifest = bundle.manifest(); let mut bytes = 0usize; bytes += bundle .read_chunk(base_root) .expect("snapshot chunk reads") .len(); for root in &manifest.operation_roots { for envelope in bundle .read_operation_block(root) .expect("operation block reads") { bytes += envelope.len(); } } bytes } /// The criterion measurement side. fn criterion_measurements(criterion: &mut Criterion, fixture: &Fixture, quick: bool) { let mut group = criterion.benchmark_group("bundle"); group.sample_size(if quick { 10 } else { 30 }); group.measurement_time(Duration::from_secs(if quick { 1 } else { 4 })); group.warm_up_time(Duration::from_millis(if quick { 300 } else { 1000 })); group.bench_function("typical_edit_commit", |b| { b.iter_batched( || restore_and_open(&fixture.commit_path, &fixture.base_image), |bundle| typical_edit_commit(bundle, &fixture.edit), BatchSize::PerIteration, ) }); fs::write(&fixture.read_path, &fixture.base_image).expect("write read-row image"); group.bench_function("open_bootstrap_read", |b| { b.iter(|| open_bootstrap_read(&fixture.read_path, &fixture.base_root)) }); group.finish(); } /// The budget-gate side: both rows are expected to **Pass** today. fn budget_gate(fixture: &Fixture, quick: bool) -> Vec { let commit_median = budget::median_time( if quick { 12 } else { 40 }, || restore_and_open(&fixture.commit_path, &fixture.base_image), |bundle| typical_edit_commit(bundle, &fixture.edit), ); fs::write(&fixture.read_path, &fixture.base_image).expect("write read-row image"); let read_median = budget::median_time( if quick { 8 } else { 25 }, || (), |()| open_bootstrap_read(&fixture.read_path, &fixture.base_root), ); vec![ budget::latency_gate( "bundle/typical_edit_commit", commit_median, COMMIT_BUDGET, Expectation::Pass, ), budget::latency_gate( "bundle/open_bootstrap_read", read_median, READ_BUDGET, Expectation::Pass, ), ] } /// A scratch directory on the **build target's filesystem** (real disk), not /// `temp_dir()`: `/tmp` is commonly tmpfs, where the commit row's fsyncs would /// be free and the 50 ms budget vacuous. Honors `CARGO_TARGET_DIR`. fn scratch_dir() -> PathBuf { let target = std::env::var_os("CARGO_TARGET_DIR") .map(PathBuf::from) .unwrap_or_else(|| { Path::new(env!("CARGO_MANIFEST_DIR")) .join("..") .join("..") .join("target") }); target.join(format!("f1-bundle-bench-{}", std::process::id())) } fn main() { // `cargo bench` passes `--bench`; its absence means criterion's test mode // (`cargo test --benches` / `--all-targets`): run each measurement once, // skip the gate. let bench_mode = std::env::args().any(|arg| arg == "--bench"); let quick = budget::quick_mode(); let dir = scratch_dir(); fs::create_dir_all(&dir).expect("create bench scratch dir"); let fixture = build_fixture(&dir); let mut criterion = Criterion::default().configure_from_args(); criterion_measurements(&mut criterion, &fixture, quick); criterion.final_summary(); let holds = if bench_mode { budget::verdict(&budget_gate(&fixture, quick)) } else { true }; let _ = fs::remove_dir_all(&dir); if !holds { std::process::exit(1); } }