diff --git a/laige-api.json b/laige-api.json index f04d23c..dc41f8e 100644 --- a/laige-api.json +++ b/laige-api.json @@ -14,64 +14,64 @@ "src/laige-core/include/laige/sim_math.h" ], "symbols": [ - {"name": "laige::HistogramStats", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 154, "signature": "struct HistogramStats", "summary": "Summary statistics over the samples currently stored in a Histogram (rolling window). When n == 0 the six statistics are NaN (check n; budgetCheck turns an empty histogram into a loud NO_SAMPLES failure).", "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::n", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 155, "signature": "std::uint64_t n", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::min", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 156, "signature": "double min", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::mean", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 157, "signature": "double mean", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::p50", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 158, "signature": "double p50", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::p95", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 159, "signature": "double p95", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::p99", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 160, "signature": "double p99", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::HistogramStats::max", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 161, "signature": "double max", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::Histogram", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 167, "signature": "class Histogram", "summary": "A fixed-capacity, allocation-free sample store (rolling window). See the header preamble for the full contract (window semantics, nearest-rank percentile definition, performance, threading).", "budget": null, "experimental": false}, - {"name": "laige::Histogram::Options", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 169, "signature": "struct Options", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::Histogram::Options::capacity", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 173, "signature": "std::size_t capacity = 0", "summary": "Capacity: the maximum number of samples kept. 0 is legal: every record() is dropped and stats() is always empty (useful as a churn-only counter, and as the loud-failure state for budgetCheck).", "budget": null, "experimental": false}, - {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 178, "signature": "explicit Histogram(Options options) noexcept : capacity_(options.capacity), window_(options.capacity), scratch_(options.capacity), cursor_(0), count_(0), total_(0)", "summary": "Setup path: performs the two backing allocations (window + scratch sort buffer). O(capacity) time and space.", "budget": null, "experimental": false}, - {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 187, "signature": "Histogram(const Histogram&) = default", "summary": "Value semantics: copy is O(capacity) (deep, cold path), move O(1).", "budget": null, "experimental": false}, - {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 188, "signature": "Histogram(Histogram&&) = default", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::Histogram::operator=", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 189, "signature": "Histogram& operator=(const Histogram&) = default", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::Histogram::operator=", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 190, "signature": "Histogram& operator=(Histogram&&) = default", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::Histogram::record", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 196, "signature": "void record(double value) noexcept", "summary": "Record one sample (unit: whatever the caller measures — typically milliseconds from a TimeIt). O(1), no allocation, no lock, noexcept. When the window is full the oldest sample is dropped; totalRecorded() keeps counting, so truncation is observable.", "budget": null, "experimental": false}, - {"name": "laige::Histogram::reset", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 206, "signature": "void reset() noexcept", "summary": "Drop every stored sample (count -> 0). totalRecorded() survives (since-construction churn; per-frame profilers diff it). Idempotent.", "budget": null, "experimental": false}, - {"name": "laige::Histogram::count", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 212, "signature": "[[nodiscard]] std::uint64_t count() const noexcept", "summary": "Samples currently stored in the window (<= capacity).", "budget": null, "experimental": false}, - {"name": "laige::Histogram::totalRecorded", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 215, "signature": "[[nodiscard]] std::uint64_t totalRecorded() const noexcept", "summary": "Samples recorded since construction, including dropped ones (churn).", "budget": null, "experimental": false}, - {"name": "laige::Histogram::stats", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 221, "signature": "[[nodiscard]] HistogramStats stats() const", "summary": "Exact statistics over the stored window (nearest-rank percentiles — see the preamble). Cold path: O(n log n) time, no allocation (sorts the pre-allocated scratch buffer; logically const — scratch_ is a reusable work buffer, not state).", "budget": null, "experimental": false}, - {"name": "laige::TimeIt", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 281, "signature": "class TimeIt", "summary": "A scope timer over std::chrono::steady_clock (monotonic — see the preamble). Milliseconds as a double. No allocation, no lock.", "budget": null, "experimental": false}, - {"name": "laige::TimeIt::TimeIt", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 284, "signature": "TimeIt() noexcept : start_(std::chrono::steady_clock::now())", "summary": "Starts the scope now.", "budget": null, "experimental": false}, - {"name": "laige::TimeIt::reset", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 287, "signature": "void reset() noexcept", "summary": "Restarts the scope (owner thread only).", "budget": null, "experimental": false}, - {"name": "laige::TimeIt::elapsedMs", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 290, "signature": "[[nodiscard]] double elapsedMs() const noexcept", "summary": "Elapsed time in milliseconds since construction/reset.", "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric", "kind": "enum", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "enum class BudgetMetric : std::uint8_t", "summary": "The statistic of a Histogram that a budget entry checks.", "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::Mean", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "Mean", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::Min", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "Min", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::Max", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "Max", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::P50", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "P50", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::P95", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "P95", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetMetric::P99", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 306, "signature": "P99", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 310, "signature": "struct BudgetEntry", "summary": "One named budget from budgets.json (PRD 8.1 row -> entry). See the preamble for the target/measured/unit/metric contract.", "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::name", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 311, "signature": "std::string name", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::metric", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 312, "signature": "BudgetMetric metric", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::unit", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 313, "signature": "std::string unit", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::target", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 314, "signature": "double target", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::measured", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 315, "signature": "double measured", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetEntry::workload", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 316, "signature": "std::string workload", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetTable", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 322, "signature": "class BudgetTable", "summary": "The parsed budgets.json (schema v1). Immutable after loading; safe to read from any thread. Small by design (one row per PRD 8.1 budget): find() is a linear scan on the cold path (no hash map, PERF-006).", "budget": null, "experimental": false}, - {"name": "laige::BudgetTable::BudgetTable", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 324, "signature": "BudgetTable() = default", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetTable::size", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 326, "signature": "[[nodiscard]] std::size_t size() const noexcept", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetTable::entries", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 329, "signature": "[[nodiscard]] const std::vector& entries() const noexcept", "summary": "The entries in file order.", "budget": null, "experimental": false}, - {"name": "laige::BudgetTable::find", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 334, "signature": "[[nodiscard]] const BudgetEntry* find(std::string_view name) const noexcept", "summary": "The entry with the given name, or nullptr when absent.", "budget": null, "experimental": false}, - {"name": "laige::BudgetReportContext", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 349, "signature": "struct BudgetReportContext", "summary": "Caller-supplied context for the AGENTS 12 report fields the harness cannot know (machine/build facts). The caller (benchmark runner / operator) fills these; budgetCheck formats them verbatim into the report (diagnostic text, not engine state). Defaults to all-empty.", "budget": null, "experimental": false}, - {"name": "laige::BudgetReportContext::workload", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 350, "signature": "const char* workload = \"\"", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetReportContext::build", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 351, "signature": "const char* build = \"\"", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetReportContext::machine", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 352, "signature": "const char* machine = \"\"", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetReportContext::warmup", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 353, "signature": "std::uint32_t warmup = 0", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::formatStatsLine", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 362, "signature": "[[nodiscard]] std::string formatStatsLine(const HistogramStats& s)", "summary": "The stable one-line text form of a HistogramStats value: `stats: n= min= mean= p50= p95= p99= max=` (6 significant digits, locale-free; \"nan\" for an empty histogram). The report lines of budgetCheck and the laige-bench tool both use this, so the stats text has one source (LOG-001 stable fields). Cold path: allocates one string.", "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 366, "signature": "struct BudgetCheckResult", "summary": "The outcome of a budget check plus the formatted AGENTS 12 report (stable multi-line text; format in docs/api/budget_harness.md).", "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult::passed", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 367, "signature": "bool passed", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult::measured", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 368, "signature": "double measured", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult::target", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 370, "signature": "double target", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult::before", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 371, "signature": "double before", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::BudgetCheckResult::report", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 372, "signature": "std::string report", "summary": null, "budget": null, "experimental": false}, - {"name": "laige::loadBudgets", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 385, "signature": "[[nodiscard]] Result loadBudgets(std::string_view path)", "summary": "Loads and validates budgets.json (schema v1) from `path`.", "budget": null, "experimental": false}, - {"name": "laige::budgetCheck", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 394, "signature": "[[nodiscard]] BudgetCheckResult budgetCheck(const BudgetEntry& entry, const Histogram& histogram, const BudgetReportContext& context = {})", "summary": "Checks `entry` against `histogram` (semantics in the preamble: NO_SAMPLES / target>0 at-most / target==0 hard zero). before/after are entry.measured / the current measurement (the AGENTS 12 before/after pair). Cold path: O(n log n) (the stats pass) plus report string building (allocates — reporting is never a hot path). Thread-safe on const inputs; the histogram must not be mutated concurrently (CONC-001 single-owner rule).", "budget": null, "experimental": false}, + {"name": "laige::HistogramStats", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 155, "signature": "struct HistogramStats", "summary": "Summary statistics over the samples currently stored in a Histogram (rolling window). When n == 0 the six statistics are NaN (check n; budgetCheck turns an empty histogram into a loud NO_SAMPLES failure).", "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::n", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 156, "signature": "std::uint64_t n", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::min", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 157, "signature": "double min", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::mean", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 158, "signature": "double mean", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::p50", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 159, "signature": "double p50", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::p95", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 160, "signature": "double p95", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::p99", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 161, "signature": "double p99", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::HistogramStats::max", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 162, "signature": "double max", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::Histogram", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 168, "signature": "class Histogram", "summary": "A fixed-capacity, allocation-free sample store (rolling window). See the header preamble for the full contract (window semantics, nearest-rank percentile definition, performance, threading).", "budget": null, "experimental": false}, + {"name": "laige::Histogram::Options", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 170, "signature": "struct Options", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::Histogram::Options::capacity", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 174, "signature": "std::size_t capacity = 0", "summary": "Capacity: the maximum number of samples kept. 0 is legal: every record() is dropped and stats() is always empty (useful as a churn-only counter, and as the loud-failure state for budgetCheck).", "budget": null, "experimental": false}, + {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 179, "signature": "explicit Histogram(Options options) noexcept : capacity_(options.capacity), window_(options.capacity), scratch_(options.capacity), cursor_(0), count_(0), total_(0)", "summary": "Setup path: performs the two backing allocations (window + scratch sort buffer). O(capacity) time and space.", "budget": null, "experimental": false}, + {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 188, "signature": "Histogram(const Histogram&) = default", "summary": "Value semantics: copy is O(capacity) (deep, cold path), move O(1).", "budget": null, "experimental": false}, + {"name": "laige::Histogram::Histogram", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 189, "signature": "Histogram(Histogram&&) = default", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::Histogram::operator=", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 190, "signature": "Histogram& operator=(const Histogram&) = default", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::Histogram::operator=", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 191, "signature": "Histogram& operator=(Histogram&&) = default", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::Histogram::record", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 197, "signature": "void record(double value) noexcept", "summary": "Record one sample (unit: whatever the caller measures — typically milliseconds from a TimeIt). O(1), no allocation, no lock, noexcept. When the window is full the oldest sample is dropped; totalRecorded() keeps counting, so truncation is observable.", "budget": null, "experimental": false}, + {"name": "laige::Histogram::reset", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 207, "signature": "void reset() noexcept", "summary": "Drop every stored sample (count -> 0). totalRecorded() survives (since-construction churn; per-frame profilers diff it). Idempotent.", "budget": null, "experimental": false}, + {"name": "laige::Histogram::count", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 213, "signature": "[[nodiscard]] std::uint64_t count() const noexcept", "summary": "Samples currently stored in the window (<= capacity).", "budget": null, "experimental": false}, + {"name": "laige::Histogram::totalRecorded", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 216, "signature": "[[nodiscard]] std::uint64_t totalRecorded() const noexcept", "summary": "Samples recorded since construction, including dropped ones (churn).", "budget": null, "experimental": false}, + {"name": "laige::Histogram::stats", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 222, "signature": "[[nodiscard]] HistogramStats stats() const", "summary": "Exact statistics over the stored window (nearest-rank percentiles — see the preamble). Cold path: O(n log n) time, no allocation (sorts the pre-allocated scratch buffer; logically const — scratch_ is a reusable work buffer, not state).", "budget": null, "experimental": false}, + {"name": "laige::TimeIt", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 282, "signature": "class TimeIt", "summary": "A scope timer over std::chrono::steady_clock (monotonic — see the preamble). Milliseconds as a double. No allocation, no lock.", "budget": null, "experimental": false}, + {"name": "laige::TimeIt::TimeIt", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 285, "signature": "TimeIt() noexcept : start_(std::chrono::steady_clock::now())", "summary": "Starts the scope now.", "budget": null, "experimental": false}, + {"name": "laige::TimeIt::reset", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 288, "signature": "void reset() noexcept", "summary": "Restarts the scope (owner thread only).", "budget": null, "experimental": false}, + {"name": "laige::TimeIt::elapsedMs", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 291, "signature": "[[nodiscard]] double elapsedMs() const noexcept", "summary": "Elapsed time in milliseconds since construction/reset.", "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric", "kind": "enum", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "enum class BudgetMetric : std::uint8_t", "summary": "The statistic of a Histogram that a budget entry checks.", "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::Mean", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "Mean", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::Min", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "Min", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::Max", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "Max", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::P50", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "P50", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::P95", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "P95", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetMetric::P99", "kind": "enumerator", "header": "src/laige-core/include/laige/budget_harness.h", "line": 307, "signature": "P99", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 311, "signature": "struct BudgetEntry", "summary": "One named budget from budgets.json (PRD 8.1 row -> entry). See the preamble for the target/measured/unit/metric contract.", "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::name", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 312, "signature": "std::string name", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::metric", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 313, "signature": "BudgetMetric metric", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::unit", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 314, "signature": "std::string unit", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::target", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 315, "signature": "double target", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::measured", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 316, "signature": "double measured", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetEntry::workload", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 317, "signature": "std::string workload", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetTable", "kind": "class", "header": "src/laige-core/include/laige/budget_harness.h", "line": 323, "signature": "class BudgetTable", "summary": "The parsed budgets.json (schema v1). Immutable after loading; safe to read from any thread. Small by design (one row per PRD 8.1 budget): find() is a linear scan on the cold path (no hash map, PERF-006).", "budget": null, "experimental": false}, + {"name": "laige::BudgetTable::BudgetTable", "kind": "constructor", "header": "src/laige-core/include/laige/budget_harness.h", "line": 325, "signature": "BudgetTable() = default", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetTable::size", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 327, "signature": "[[nodiscard]] std::size_t size() const noexcept", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetTable::entries", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 330, "signature": "[[nodiscard]] const std::vector& entries() const noexcept", "summary": "The entries in file order.", "budget": null, "experimental": false}, + {"name": "laige::BudgetTable::find", "kind": "method", "header": "src/laige-core/include/laige/budget_harness.h", "line": 335, "signature": "[[nodiscard]] const BudgetEntry* find(std::string_view name) const noexcept", "summary": "The entry with the given name, or nullptr when absent.", "budget": null, "experimental": false}, + {"name": "laige::BudgetReportContext", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 350, "signature": "struct BudgetReportContext", "summary": "Caller-supplied context for the AGENTS 12 report fields the harness cannot know (machine/build facts). The caller (benchmark runner / operator) fills these; budgetCheck formats them verbatim into the report (diagnostic text, not engine state). Defaults to all-empty.", "budget": null, "experimental": false}, + {"name": "laige::BudgetReportContext::workload", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 351, "signature": "const char* workload = \"\"", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetReportContext::build", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 352, "signature": "const char* build = \"\"", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetReportContext::machine", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 353, "signature": "const char* machine = \"\"", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetReportContext::warmup", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 354, "signature": "std::uint32_t warmup = 0", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::formatStatsLine", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 363, "signature": "[[nodiscard]] std::string formatStatsLine(const HistogramStats& s)", "summary": "The stable one-line text form of a HistogramStats value: `stats: n= min= mean= p50= p95= p99= max=` (6 significant digits, locale-free; \"nan\" for an empty histogram). The report lines of budgetCheck and the laige-bench tool both use this, so the stats text has one source (LOG-001 stable fields). Cold path: allocates one string.", "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult", "kind": "struct", "header": "src/laige-core/include/laige/budget_harness.h", "line": 367, "signature": "struct BudgetCheckResult", "summary": "The outcome of a budget check plus the formatted AGENTS 12 report (stable multi-line text; format in docs/api/budget_harness.md).", "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult::passed", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 368, "signature": "bool passed", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult::measured", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 369, "signature": "double measured", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult::target", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 371, "signature": "double target", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult::before", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 372, "signature": "double before", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::BudgetCheckResult::report", "kind": "variable", "header": "src/laige-core/include/laige/budget_harness.h", "line": 373, "signature": "std::string report", "summary": null, "budget": null, "experimental": false}, + {"name": "laige::loadBudgets", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 386, "signature": "[[nodiscard]] Result loadBudgets(std::string_view path)", "summary": "Loads and validates budgets.json (schema v1) from `path`.", "budget": null, "experimental": false}, + {"name": "laige::budgetCheck", "kind": "function", "header": "src/laige-core/include/laige/budget_harness.h", "line": 395, "signature": "[[nodiscard]] BudgetCheckResult budgetCheck(const BudgetEntry& entry, const Histogram& histogram, const BudgetReportContext& context = {})", "summary": "Checks `entry` against `histogram` (semantics in the preamble: NO_SAMPLES / target>0 at-most / target==0 hard zero). before/after are entry.measured / the current measurement (the AGENTS 12 before/after pair). Cold path: O(n log n) (the stats pass) plus report string building (allocates — reporting is never a hot path). Thread-safe on const inputs; the histogram must not be mutated concurrently (CONC-001 single-owner rule).", "budget": null, "experimental": false}, {"name": "laige::core::kMajor", "kind": "variable", "header": "src/laige-core/include/laige/core/version.h", "line": 15, "signature": "inline constexpr int kMajor = 0", "summary": "Semver components of the laige-core module (0 = not yet released).", "budget": null, "experimental": false}, {"name": "laige::core::kMinor", "kind": "variable", "header": "src/laige-core/include/laige/core/version.h", "line": 16, "signature": "inline constexpr int kMinor = 0", "summary": null, "budget": null, "experimental": false}, {"name": "laige::core::kPatch", "kind": "variable", "header": "src/laige-core/include/laige/core/version.h", "line": 17, "signature": "inline constexpr int kPatch = 0", "summary": null, "budget": null, "experimental": false}, diff --git a/src/laige-core/include/laige/budget_harness.h b/src/laige-core/include/laige/budget_harness.h index c210a3e..58bceb8 100644 --- a/src/laige-core/include/laige/budget_harness.h +++ b/src/laige-core/include/laige/budget_harness.h @@ -132,6 +132,7 @@ #pragma once +#include #include #include #include diff --git a/tests/api/CMakeLists.txt b/tests/api/CMakeLists.txt index 6f45908..2e8b049 100644 --- a/tests/api/CMakeLists.txt +++ b/tests/api/CMakeLists.txt @@ -174,7 +174,6 @@ function(laige_add_api_test name expect_exit root mode file) " string(APPEND _problems \"output missing '<${_esc}'>; \")\n" "endif()\n") endforeach() - set(SCANNER "${CMAKE_BINARY_DIR}/bin/laige-api-scanner") set(CMD "${_cmd}") set(EXPECT_EXIT "${expect_exit}") set(OUT_FILE "") @@ -186,6 +185,13 @@ function(laige_add_api_test name expect_exit root mode file) "${CMAKE_CURRENT_BINARY_DIR}/${name}.cmake" @ONLY) add_test(NAME ${name} COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/${name}.cmake") + # The scanner path is carried in the test environment, not in the + # command or baked into the script: $ resolves per + # configuration under multi-config generators (the Windows VS layout + # puts the executable in bin//), while a configure-time path + # only matches the single-config layout. + set_tests_properties(${name} PROPERTIES + ENVIRONMENT "LAIGE_API_SCANNER=$") endfunction() # The scan test also compares the written manifest byte-for-byte against @@ -194,7 +200,6 @@ set(_scan_out "${CMAKE_CURRENT_BINARY_DIR}/fixture-api.json") set(_scan_root "${FX}/api") set(_scan_expected "${CMAKE_CURRENT_BINARY_DIR}/expected-fixture-api.json") set(_cmd "\"--root\" \"${_scan_root}\" \"--out\" \"${_scan_out}\"") -set(SCANNER "${CMAKE_BINARY_DIR}/bin/laige-api-scanner") set(CMD "${_cmd}") set(EXPECT_EXIT "0") set(OUT_FILE "${_scan_out}") @@ -203,6 +208,9 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/expect-api-result.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/api-fixture-scan.cmake" @ONLY) add_test(NAME api-fixture-scan COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_BINARY_DIR}/api-fixture-scan.cmake") +# Same ENVIRONMENT wiring as laige_add_api_test (see its comment). +set_tests_properties(api-fixture-scan PROPERTIES + ENVIRONMENT "LAIGE_API_SCANNER=$") laige_add_api_test(api-check-fresh 0 "${FX}/api" check "${CMAKE_CURRENT_BINARY_DIR}/expected-fixture-api.json" diff --git a/tests/api/expect-api-result.cmake.in b/tests/api/expect-api-result.cmake.in index 3eef20a..53aba6b 100644 --- a/tests/api/expect-api-result.cmake.in +++ b/tests/api/expect-api-result.cmake.in @@ -12,9 +12,22 @@ # The script exits non-zero (failing the CTest test) on any mismatch and # prints the full scanner output for diagnosis. +# The scanner executable arrives through the test's ENVIRONMENT property +# (LAIGE_API_SCANNER = $, set in +# tests/api/CMakeLists.txt): CMake resolves it per configuration under +# multi-config generators (bin//laige-api-scanner.exe on the +# Windows VS generator), where a path baked in at configure time only +# matches the single-config layout. Read it at test time, never at +# configure time ($ENV{...} in a configure-time string expands early). +set(_scanner "$ENV{LAIGE_API_SCANNER}") +if(_scanner STREQUAL "") + message(FATAL_ERROR + "LAIGE_API_SCANNER is not set: the test's ENVIRONMENT property must " + "carry the scanner path (tests/api/CMakeLists.txt)") +endif() set(_cmd @CMD@) execute_process( - COMMAND @SCANNER@ ${_cmd} + COMMAND "${_scanner}" ${_cmd} RESULT_VARIABLE _rc OUTPUT_VARIABLE _out ERROR_VARIABLE _err diff --git a/tests/laige-core/budget_harness_tests.cpp b/tests/laige-core/budget_harness_tests.cpp index 734cb80..b77c027 100644 --- a/tests/laige-core/budget_harness_tests.cpp +++ b/tests/laige-core/budget_harness_tests.cpp @@ -107,10 +107,26 @@ std::string WriteTempJson(const char* name, const std::string content) { // The repo-root budgets.json, wired by CTest (ENVIRONMENT // LAIGE_BUDGETS_PATH); the fallback covers running the binary from the -// source root by hand. -const char* BudgetsFilePath() { +// source root by hand. MSVC deprecates plain getenv (C4996, fatal under +// /WX); getenv_s has the same lookup semantics (CPP-009, pattern: +// logging.cpp). +std::string BudgetsFilePath() { +#if defined(_MSC_VER) + // Largest environment value the suite reads (the budgets file path; + // fits far inside the bound). Named per CORE-005; a value beyond it is + // treated as unset (the documented fallback applies). MSVC-only for the + // same reason as above (CORE-010: no unused symbols under -Werror). + constexpr std::size_t kEnvValueMaxBytes = 4096; + char buf[kEnvValueMaxBytes]; + std::size_t len = 0; + if (getenv_s(&len, buf, sizeof(buf), "LAIGE_BUDGETS_PATH") != 0) + return "budgets.json"; + return (len > 0) ? std::string(buf, len) : std::string("budgets.json"); +#else const char* env = std::getenv("LAIGE_BUDGETS_PATH"); - return (env != nullptr && env[0] != '\0') ? env : "budgets.json"; + return (env != nullptr && env[0] != '\0') ? std::string(env) + : std::string("budgets.json"); +#endif } void ExpectMalformedFile(const std::string& path) { diff --git a/tools/api/laige-api.cpp b/tools/api/laige-api.cpp index 7428946..8b876da 100644 --- a/tools/api/laige-api.cpp +++ b/tools/api/laige-api.cpp @@ -16,7 +16,9 @@ // --root REPO_ROOT Repository root (default: "."). // --out FILE Write the regenerated manifest to FILE. // --check FILE Regenerate in memory and compare with FILE: -// byte-identical -> exit 0; stale -> exit 1 with a +// identical -> exit 0 (CRLF line endings in FILE are +// normalized to LF first - the canonical form is LF, +// see .gitattributes); stale -> exit 1 with a // symbol-level diff; unreadable/invalid FILE -> exit 2. // // Exit codes: 0 = OK · 1 = stale manifest (--check) · 2 = error (usage, @@ -1722,6 +1724,26 @@ int runCheck(const std::string& checkFile, const std::string& manifestText, std::cout << "laige-api: error: " << err << std::endl; return 2; } + // Line-ending normalization: the manifest is canonically LF (the + // serializer emits LF; the repo's .gitattributes pins eol=lf on every + // checkout). CRLF can reach a checked-in copy only as a Windows tooling + // artifact (text-mode CMake file(WRITE) test fixtures, editor saves), so + // normalize CRLF -> LF before the byte compare. A lone \r is NOT + // normalized, so a genuinely corrupted file still fails (CORE-008). + { + std::string norm; + norm.reserve(oldText.size()); + for (size_t i = 0; i < oldText.size(); ++i) { + if (oldText[i] == '\r' && i + 1 < oldText.size() && + oldText[i + 1] == '\n') { + norm += '\n'; // fold the CRLF pair to one LF + ++i; // the paired \n belongs to the fold - skip it + } else { + norm += oldText[i]; + } + } + oldText = std::move(norm); + } if (oldText == manifestText) { std::cout << "laige-api: OK — " << checkFile << " is up to date (" << symbols.size() << " symbol(s) from the " @@ -1828,7 +1850,8 @@ void printUsage(std::ostream& os) { << " --root REPO_ROOT repository root (default: \".\")\n" << " --out FILE write the regenerated manifest to FILE\n" << " --check FILE compare the regenerated manifest with FILE:\n" - << " exit 0 if byte-identical, exit 1 if stale\n" + << " exit 0 if identical (CRLF normalized to LF),\n" + << " exit 1 if stale\n" << "\n" << "Exit codes: 0 = OK · 1 = stale manifest · 2 = error\n"; } diff --git a/tools/bench/laige-bench.cpp b/tools/bench/laige-bench.cpp index 798fe95..e6ed03a 100644 --- a/tools/bench/laige-bench.cpp +++ b/tools/bench/laige-bench.cpp @@ -162,6 +162,41 @@ constexpr char kCompilerId[] = "unknown"; #define LAIGE_BENCH_BUILD_TYPE "unknown" #endif +// --- Platform boundary (CPP-009, pattern: logging.cpp) ---------------------- +// +// MSVC deprecates plain getenv/fopen (C4996, fatal under the engine /WX +// policy, NFR-8.10); the Windows branch uses the CRT's documented +// replacements with the same lookup/open semantics every other supported +// compiler provides. _fsopen(_SH_DENYNO) keeps plain-fopen sharing +// semantics (no _SH_SECURE re-open denial, see logging.cpp). + +#if defined(_MSC_VER) +// Largest environment value this tool reads (a machine description or a +// budgets file path; both fit far inside the bound). Named per CORE-005; +// a value beyond it is treated as unset (the documented fallback applies). +// MSVC-only: getenv_s needs a caller-sized buffer, so the constant has no +// use outside this branch (CORE-010: no unused symbols under -Werror). +constexpr std::size_t kEnvValueMaxBytes = 4096; + +std::string envValue(const char* name) { + char buf[kEnvValueMaxBytes]; + std::size_t len = 0; + if (getenv_s(&len, buf, sizeof(buf), name) != 0) return {}; + return std::string(buf, len); +} +std::FILE* openReportFile(const char* path, const char* mode) { + return ::_fsopen(path, mode, _SH_DENYNO); +} +#else +std::string envValue(const char* name) { + const char* v = std::getenv(name); + return (v != nullptr) ? std::string(v) : std::string(); +} +std::FILE* openReportFile(const char* path, const char* mode) { + return std::fopen(path, mode); +} +#endif + } // namespace int main(int argc, char** argv) { @@ -201,7 +236,7 @@ int main(int argc, char** argv) { // hardware/OS/compiler/build/workload). The operator may set // LAIGE_BENCH_MACHINE for the machine line; the baseline document // records the rest (docs/benchmarks/, M0-EXIT-01). - const char* envMachine = std::getenv("LAIGE_BENCH_MACHINE"); + const std::string envMachine = envValue("LAIGE_BENCH_MACHINE"); const std::string build = std::string(kCompilerId) + ", " + LAIGE_BENCH_BUILD_TYPE; @@ -225,7 +260,7 @@ int main(int argc, char** argv) { output += " build="; output += build; output += " machine="; - output += (envMachine != nullptr ? envMachine : ""); + output += envMachine; // empty when the env var is unset output += " warmup="; output += std::to_string(cfg.warmup); output += "\n"; @@ -234,10 +269,9 @@ int main(int argc, char** argv) { // full AGENTS 12 report (pass/fail + before/after + statistics). std::string budgetsPath = cfg.budgetsPath; if (budgetsPath.empty()) { - const char* envPath = std::getenv("LAIGE_BUDGETS_PATH"); - budgetsPath = (envPath != nullptr && envPath[0] != '\0') - ? std::string(envPath) - : std::string("budgets.json"); + const std::string envPath = envValue("LAIGE_BUDGETS_PATH"); + budgetsPath = + envPath.empty() ? std::string("budgets.json") : envPath; } const laige::Result table = laige::loadBudgets(budgetsPath); @@ -257,7 +291,7 @@ int main(int argc, char** argv) { BudgetReportContext ctx; ctx.workload = entry->workload.c_str(); ctx.build = build.c_str(); - ctx.machine = envMachine != nullptr ? envMachine : ""; + ctx.machine = envMachine.c_str(); // outlives the budgetCheck call below ctx.warmup = static_cast(cfg.warmup); const laige::BudgetCheckResult check = @@ -271,7 +305,7 @@ int main(int argc, char** argv) { std::fflush(stdout); if (!cfg.reportPath.empty()) { - std::FILE* f = std::fopen(cfg.reportPath.c_str(), "a"); + std::FILE* f = openReportFile(cfg.reportPath.c_str(), "a"); if (f == nullptr) { std::fprintf(stderr, "laige-bench: cannot open report file '%s'\n", cfg.reportPath.c_str());