Skip to content

[M0-CORE-08] Budget harness: Histogram/TimeIt/budgetCheck + budgets.json + laige-bench - #9

Merged
offdev merged 2 commits into
masterfrom
m0-core-08-budget-harness
Sep 11, 2026
Merged

offdev merged 2 commits into
masterfrom
m0-core-08-budget-harness

Conversation

@offdev

@offdev offdev commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Scope (M0-CORE-08, per roadmap/M0-foundations.md):

  • laige::Histogram — fixed-capacity rolling window; O(1) allocation-free record(); exact min/mean/p50/p95/p99/max over the stored window via nearest-rank percentiles (rank = ceil(p·n/100), exact integer math); allocation-free stats().
  • laige::TimeIt — steady_clock ms scope timer.
  • loadBudgets / budgetCheck — strict budgets.json schema v1 (ARCH-007) over the M0-CORE-07 bounded JSON parser (no new dependencies); AGENTS §12 report: stable 4-line machine-greppable text with before/after pair and caller context; formatStatsLine is the single source of the stats text.
  • budgets.json (repo root) — all 15 PRD §8.1 budgets as named entries; measured: 0 = not yet measured until their subsystems land (M1+).
  • laige-bench — the canonical command ./build/bin/laige-bench --suite=<name> reserved by docs/getting-started/building.md for this step; M0 suite synthetic (deterministic, allocation-free stand-in workload); --budget=<name> check with exit code 2 on failure (CI-gateable per PRD §8.1).
  • API contract: docs/api/budget_harness.md.

Key design decisions: target == 0 is a hard-zero budget (e.g. sim_heap_allocs: zero steady-state heap allocations per frame), not "not set" — the not-yet-measured marker lives in measured only. An empty histogram is a loud NO_SAMPLES failure (no silent pass — CORE-008). The 1 MiB bound is enforced before the read; unreadable file → IoError, every schema violation → MalformedInput (no new error codes).

Bug fix (M0-CORE-07) found by this step's Verify run: json.cpp parseObjectMembers was missing skipWhitespace before the member key — object documents with ", " between members (the hand-formatted repo-root budgets.json) were rejected. Fixed with a regression test (ConfigJsonValid.ObjectMemberWhitespace, fails pre-fix); the documented grammar was already correct, the implementation did not match it.

Verify (local, 2026-09-11): ctest 16/16, zero warnings under the NFR-8.10 policy, on five Linux trees — GCC 16.2.1 (build static, build-shared, build-asan ASan+UBSan fatal, build-tsan TSan halt_on_error=1) + Clang 22.1.8 (build-clang). budget_harness CTest entry = 22 GTest cases: known percentiles on 1..100, full-ring wrap dropping the oldest, empty → NaN, loud FAIL when a threshold is exceeded, hard-zero-target budget, 13-case schema-rejection corpus against the repo budgets.json, IoError on missing file. The synthetic workload producing percentiles is exercised end-to-end by the laige_bench_smoke entry. include-lint clean (17 files). The MSVC + AppleClang compile proof lands in this PR's CI run (default Linux lane).

Also in this PR (roadmap bookkeeping, per the roadmap agent contract): roadmap/M0-foundations.md M0-CORE-08 checked with Decision/Verify/Size; roadmap/README.md Progress Board M0 11 → 17 (Total 17) and the change log gains this step's line plus five reconstructed lines for M0-CORE-03…07 — the board and change log had drifted (11 vs 16 checked boxes) since M0-CORE-03 landed, so they are brought back in sync in the same PR that checks the next box.

…son + laige-bench

laige::Histogram: fixed-capacity rolling window; O(1) allocation-free record();
exact min/mean/p50/p95/p99/max over the stored window with nearest-rank
percentiles (rank = ceil(p*n/100), exact integer math); stats() is a
cold-path O(n log n) pass with no allocation (pre-allocated scratch).
laige::TimeIt: steady_clock ms scope timer, no allocation.
loadBudgets: file I/O + bounded parse (ADR 0003) + strict budgets.json
schema v1 validation (ARCH-007; unsupported version/fields, bad
name/metric/unit charset, duplicate names, negative or non-finite
numbers -> MalformedInput; unreadable file -> IoError).
budgetCheck: total operation; empty histogram -> loud NO_SAMPLES;
target > 0 -> measured <= target; target == 0 -> hard-zero budget
(sim_heap_allocs), not "not set" (the not-yet-measured marker is the
initial measured: 0). Report = stable 4-line AGENTS §12 text with
before/after pair and caller context; formatStatsLine is the single
source of the stats text.
budgets.json (repo root): all 15 PRD §8.1 budgets as named entries;
measured: 0 = not yet measured until their subsystems land (M1+).
laige-bench: canonical command ./build/bin/laige-bench --suite=<name>
(reserved for this step by docs/getting-started/building.md); M0
suite "synthetic" (deterministic, allocation-free 4096-step LCG +
double stand-in workload); --runs/--warmup, --budget=<name> check
with --budgets path resolution (arg -> LAIGE_BUDGETS_PATH env ->
budgets.json), --report append; exit 0 pass / 1 usage or load failure
/ 2 budget check failure (CI-gateable).
ctest: budget_harness entry (22 GTest cases) + laige_bench_smoke
entry; LAIGE_BUDGETS_PATH wired for both (incl. the TSan tree, where
CTest ENVIRONMENT is last-write-wins).
API contract: docs/api/budget_harness.md.

Bug fix (M0-CORE-07) found by the Verify run of this step: json.cpp
parseObjectMembers was missing skipWhitespace before the member key -
object documents with ", " between members (the hand-formatted
repo-root budgets.json) were rejected. Regression test
ConfigJsonValid.ObjectMemberWhitespace fails pre-fix.

Verified locally: ctest 16/16, zero warnings, on GCC 16.2.1 (static,
shared, ASan+UBSan fatal, TSan halt_on_error=1) and Clang 22.1.8
trees; include-lint clean (17 files). MSVC/AppleClang compile proof
lands in CI.
M0-foundations.md: M0-CORE-08 checked, with Decision (2026-09-11),
Verify (22 GTest cases + laige_bench_smoke; 16/16 ctest on GCC static,
shared, ASan, TSan and Clang trees) and Size notes, including the
record of the M0-CORE-07 parser bug fix found by this step's Verify
run (missing skipWhitespace before the object member key).
README.md: Progress Board M0 11 -> 17 (Total 17), and the change log
gains this step's line (810c251) plus the five reconstructed lines
for M0-CORE-03..07 (0fd41f3, 5c76991+7538053, 4a572af+ba4ffb2, a82de8e,
41938b0) rebuilt from their step records in M0-foundations.md — the
board and change log had drifted (11 vs 16 checked boxes) since
M0-CORE-03 landed.
@offdev
offdev merged commit 1614d09 into master Sep 11, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant