Viewing files at commit 62758990fe31Commit detailsView latest

Browse files
scripts/oakbench/scale_calibration.py 356 lines · 17.6 KB · python
1
"""Quiet-host calibration for the scale lane: evidence verified by recomputation.
2
3
A scale row's ``claim_eligible`` / ``evidence_class`` are DERIVED, never a
4
free flag, and nothing a calibration record *states* is trusted:
5
6
1. ``run_scale_suite.py calibrate`` runs an identical-binary A/A campaign
7
   (git, git_aa, oak_installed, oak_installed_aa) plus host probes (load
8
   average, small-file creation rate, F_FULLFSYNC latency) and writes its rows
9
   to a JSONL file. The record names that file's content sha256.
10
2. Publishing requires that calibration JSONL among publish_gate's inputs,
11
   matched by content sha256. The gate RECOMPUTES the A/A cell count, per-cell
12
   floors (oakbench.cellstats, the P1 machinery), identical-binary false
13
   claims, host load and disk probes from those rows, and checks that the
14
   calibration rows and the published rows share runner identity, host
15
   cpu_count and physical memory, the Oak and Git binary sha256, and
16
   registry-pinned fixtures. The published run must carry its own quiet disk
17
   probes too.
18
19
This detects edited, resealed, copied or invented records (the sealed digest
20
is only an integrity check of the record's own content). It cannot detect a
21
fabricated calibration JSONL with invented timings; that would need a signed
22
runner, which the lane does not have. Do not describe it as forgery-proof.
23
"""
24
25
from __future__ import annotations
26
27
import hashlib
28
import json
29
import os
30
import statistics
31
import time
32
from pathlib import Path
33
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
34
35
SCHEMA_VERSION = 2
36
KIND = "oakbench_quiet_host_calibration"
37
CALIBRATED_EVIDENCE_CLASS = "calibrated_quiet_host"
38
DEVELOPMENT_EVIDENCE_CLASS = "development_only_loaded_host"
39
POLICY_VERSION = "2026-09-29.2"
40
# Quiet-host policy (provisional; re-derive on the dedicated runner and bump
41
# POLICY_VERSION). Load/core 0.1 is ~1.4 runnable threads on a 14-core host.
42
MAX_LOAD_PER_CORE = 0.10
43
MIN_AA_CELLS = 20
44
MIN_RUNS = 5
45
MAX_AA_FLOOR_MEDIAN_PCT = 10.0
46
# Disk contention (load average does not see it; Oak clone/switch are
47
# fsync-bound, fb-549). Measured on the loaded dev host: F_FULLFSYNC median
48
# 3.7 ms, file creation as low as 170 files/s.
49
MAX_FULLFSYNC_MEDIAN_MS = 2.5
50
MIN_CREATE_FILES_PER_S = 2000.0
51
PROBE_OPERATIONS = ("host.disk_fullfsync", "host.file_create_rate")
52
AA_PAIRS = (("git", "git_aa"), ("oak_installed", "oak_installed_aa"))
53
54
55
# ---------------------------------------------------------------------------
56
# Host facts and probes
57
# ---------------------------------------------------------------------------
58
59
60
def host_facts() -> Dict[str, Any]:
61
    try:
62
        mem = int(os.sysconf("SC_PAGE_SIZE")) * int(os.sysconf("SC_PHYS_PAGES"))
63
    except (ValueError, OSError, AttributeError):
64
        mem = None
65
    return {"host_cpu_count": os.cpu_count(), "host_mem_bytes": mem}
66
67
68
def disk_probe(directory: Path, samples: int = 20, files: int = 500) -> Dict[str, Any]:
69
    """F_FULLFSYNC (fsync elsewhere) latency of a 4 KiB write, and small-file creation rate."""
70
    import fcntl
71
    directory = Path(directory)
72
    directory.mkdir(parents=True, exist_ok=True)
73
    full = getattr(fcntl, "F_FULLFSYNC", None)
74
    latencies = []
75
    path = directory / ".oakbench-fsync-probe"
76
    for _ in range(samples):
77
        fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
78
        try:
79
            os.write(fd, os.urandom(4096))
80
            start = time.perf_counter()
81
            if full is not None:
82
                fcntl.fcntl(fd, full)
83
            else:
84
                os.fsync(fd)
85
            latencies.append((time.perf_counter() - start) * 1000.0)
86
        finally:
87
            os.close(fd)
88
    path.unlink()
89
    tree = directory / ".oakbench-create-probe"
90
    tree.mkdir(exist_ok=True)
91
    start = time.perf_counter()
92
    for index in range(files):
93
        with open(tree / f"f{index}", "wb") as handle:
94
            handle.write(b"x" * 64)
95
    elapsed = time.perf_counter() - start
96
    for index in range(files):
97
        (tree / f"f{index}").unlink()
98
    tree.rmdir()
99
    return {"fullfsync_ms": [round(v, 4) for v in latencies],
100
            "fullfsync_kind": "F_FULLFSYNC" if full is not None else "fsync",
101
            "create_files": files, "create_elapsed_s": round(elapsed, 6)}
102
103
104
def probe_rows(probe: Dict[str, Any], base: Dict[str, Any], phase: str) -> List[Dict[str, Any]]:
105
    """Diagnostic rows carrying probe samples (so the gate can recompute them)."""
106
    common = {**base, "subject": "harness", "subject_kind": "harness", "command": [], "returncode": 0,
107
              "diagnostic": True, "probe_phase": phase, "measurement_source": "harness_phase_wall_clock",
108
              "evidence_class": DEVELOPMENT_EVIDENCE_CLASS, "claim_eligible": False,
109
              "claim_ineligible_reasons": ["host probe (diagnostic)"], **host_facts()}
110
    rows = [dict(common, operation="host.disk_fullfsync", run=index, elapsed_ms=value,
111
                 fsync_kind=probe["fullfsync_kind"]) for index, value in enumerate(probe["fullfsync_ms"])]
112
    rate = probe["create_files"] / probe["create_elapsed_s"] if probe["create_elapsed_s"] > 0 else 0.0
113
    rows.append(dict(common, operation="host.file_create_rate", run=0,
114
                     elapsed_ms=round(probe["create_elapsed_s"] * 1000.0, 3), files=probe["create_files"],
115
                     files_per_s=round(rate, 1)))
116
    return rows
117
118
119
def probe_verdict(rows: Iterable[Dict[str, Any]]) -> Tuple[List[str], Dict[str, Any]]:
120
    rows = list(rows)
121
    fsyncs = [r["elapsed_ms"] for r in rows if r.get("operation") == "host.disk_fullfsync"
122
              and isinstance(r.get("elapsed_ms"), (int, float))]
123
    rates = [r.get("files_per_s") for r in rows if r.get("operation") == "host.file_create_rate"]
124
    reasons, facts = [], {}
125
    if len(fsyncs) < 10 or not rates:
126
        return ["disk probes missing"], facts
127
    facts["fullfsync_median_ms"] = statistics.median(fsyncs)
128
    facts["create_files_per_s_min"] = min(r for r in rates if isinstance(r, (int, float)))
129
    if facts["fullfsync_median_ms"] > MAX_FULLFSYNC_MEDIAN_MS:
130
        reasons.append(f"disk contended: F_FULLFSYNC median {facts['fullfsync_median_ms']:.2f} ms > "
131
                       f"{MAX_FULLFSYNC_MEDIAN_MS} ms")
132
    if facts["create_files_per_s_min"] < MIN_CREATE_FILES_PER_S:
133
        reasons.append(f"disk contended: {facts['create_files_per_s_min']:.0f} files/s < {MIN_CREATE_FILES_PER_S:.0f}")
134
    return reasons, facts
135
136
137
# ---------------------------------------------------------------------------
138
# Records
139
# ---------------------------------------------------------------------------
140
141
142
def file_sha256(path: Path) -> str:
143
    digest = hashlib.sha256()
144
    with Path(path).open("rb") as handle:
145
        for chunk in iter(lambda: handle.read(1 << 20), b""):
146
            digest.update(chunk)
147
    return digest.hexdigest()
148
149
150
def record_digest(record: Dict[str, Any]) -> str:
151
    body = {key: value for key, value in record.items() if key != "record_sha256"}
152
    return hashlib.sha256(json.dumps(body, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
153
154
155
def seal(record: Dict[str, Any]) -> Dict[str, Any]:
156
    sealed = dict(record)
157
    sealed.update(schema_version=SCHEMA_VERSION, kind=KIND, policy_version=POLICY_VERSION)
158
    sealed["record_sha256"] = record_digest(sealed)
159
    return sealed
160
161
162
def load_rows(path: Path) -> List[Dict[str, Any]]:
163
    return [json.loads(line) for line in Path(path).read_text().splitlines() if line.strip()]
164
165
166
def _pin_problems(rows: Iterable[Dict[str, Any]], registry: Optional[Dict[str, Any]]) -> List[str]:
167
    if registry is None:
168
        return []
169
    pins = {entry.get("spec_sha256"): entry.get("refs_sha256") for entry in registry.values()
170
            if isinstance(entry, dict) and entry.get("spec_sha256")}
171
    bad = set()
172
    for row in rows:
173
        if row.get("subject_kind") == "harness" or row.get("diagnostic"):
174
            continue
175
        spec, refs = row.get("fixture_spec_sha256"), row.get("fixture_manifest_sha256")
176
        if not spec or pins.get(spec) is None or pins.get(spec) != refs:
177
            bad.add(str(row.get("fixture_id")))
178
    return [f"fixture not registry-pinned or pin mismatch: {sorted(bad)[:3]}"] if bad else []
179
180
181
def campaign_identity(rows: Sequence[Dict[str, Any]]) -> Tuple[Dict[str, Any], List[str]]:
182
    """Runner/host/binary identity shared by every row of a run (or problems)."""
183
    reasons: List[str] = []
184
    identity: Dict[str, Any] = {}
185
    for key in ("runner_id", "host_cpu_count", "host_mem_bytes"):
186
        values = {row.get(key) for row in rows}
187
        if len(values) != 1 or None in values:
188
            reasons.append(f"rows disagree on or lack {key}: {sorted(map(str, values))[:3]}")
189
        else:
190
            identity[key] = values.pop()
191
    for kind in ("git", "oak"):
192
        shas = {row.get("binary_sha256") for row in rows if row.get("subject_kind") == kind
193
                and not row.get("diagnostic")}
194
        shas.discard(None)
195
        if len(shas) > 1:
196
            reasons.append(f"{kind} rows name several binaries")
197
        identity[f"{kind}_binary_sha256"] = shas.pop() if len(shas) == 1 else None
198
    return identity, reasons
199
200
201
def recompute(rows: Sequence[Dict[str, Any]], registry: Optional[Dict[str, Any]] = None
202
              ) -> Tuple[Dict[str, Any], List[str]]:
203
    """Recompute every calibration number from the campaign's own rows."""
204
    from oakbench import cellstats
205
    identity, reasons = campaign_identity(rows)
206
    measured = [r for r in rows if r.get("profile") == "scale" and r.get("returncode") == 0
207
                and not r.get("diagnostic") and isinstance(r.get("elapsed_ms"), (int, float))]
208
    value = lambda r: r.get("elapsed_ms")  # noqa: E731
209
    floors: List[float] = []
210
    false_claims = 0
211
    for arm, control in AA_PAIRS:
212
        runs = {r.get("run") for r in measured if r.get("subject") == arm}
213
        if len(runs) < MIN_RUNS:
214
            reasons.append(f"{arm}: fewer than {MIN_RUNS} runs")
215
            continue
216
        cell = cellstats.cell_floors(measured, arm, control, value)
217
        floors.extend(cell.values())
218
        verdicts = cellstats.classify_cells(measured, control, arm, cell, value_of=value)
219
        false_claims += sum(1 for item in verdicts.values() if item.get("verdict") in ("regression", "improvement"))
220
    cpus = identity.get("host_cpu_count")
221
    loads = [r["environment_sample"]["loadavg"][0] for r in rows
222
             if isinstance((r.get("environment_sample") or {}).get("loadavg"), list)]
223
    load_per_core = max(loads) / cpus if loads and isinstance(cpus, int) and cpus > 0 else None
224
    disk_reasons, disk = probe_verdict(rows)
225
    facts = {**identity, "aa_cells": len(floors),
226
             "aa_floor_median_pct": round(statistics.median(floors), 3) if floors else None,
227
             "aa_false_claims": false_claims,
228
             "loadavg_per_core_max": round(load_per_core, 4) if load_per_core is not None else None, **disk}
229
    if len(floors) < MIN_AA_CELLS:
230
        reasons.append(f"too few A/A cells ({len(floors)} < {MIN_AA_CELLS})")
231
    if facts["aa_floor_median_pct"] is None or facts["aa_floor_median_pct"] > MAX_AA_FLOOR_MEDIAN_PCT:
232
        reasons.append(f"A/A floor median {facts['aa_floor_median_pct']}% > {MAX_AA_FLOOR_MEDIAN_PCT}%")
233
    if false_claims:
234
        reasons.append(f"identical-binary A/A produced {false_claims} claim(s)")
235
    if load_per_core is None or load_per_core > MAX_LOAD_PER_CORE:
236
        reasons.append(f"host not quiet: load/core {load_per_core} > {MAX_LOAD_PER_CORE}")
237
    reasons += disk_reasons
238
    reasons += _pin_problems(rows, registry)
239
    return facts, reasons
240
241
242
def verify_campaign(record: Any, calibration_rows: Optional[Sequence[Dict[str, Any]]],
243
                    calibration_sha256: Optional[str], registry: Optional[Dict[str, Any]] = None
244
                    ) -> Tuple[Dict[str, Any], List[str]]:
245
    """Validate a record against its recomputed campaign; returns (facts, reasons)."""
246
    if not isinstance(record, dict):
247
        return {}, ["calibration record missing or not an object"]
248
    reasons = []
249
    if record.get("schema_version") != SCHEMA_VERSION or record.get("kind") != KIND:
250
        reasons.append("wrong schema_version/kind")
251
    if record.get("record_sha256") != record_digest(record):
252
        reasons.append("record_sha256 does not match content (edited record)")
253
    if calibration_rows is None:
254
        return {}, reasons + ["calibration campaign rows not provided (need the JSONL named by the record)"]
255
    if not calibration_sha256 or calibration_sha256 != record.get("calibration_rows_sha256"):
256
        return {}, reasons + ["calibration JSONL content sha256 does not match the record"]
257
    facts, problems = recompute(calibration_rows, registry)
258
    reasons += problems
259
    for key in ("runner_id", "host_cpu_count", "host_mem_bytes", "oak_binary_sha256", "git_binary_sha256",
260
                "aa_cells", "aa_false_claims"):
261
        if record.get(key) != facts.get(key):
262
            reasons.append(f"record {key} {record.get(key)!r} != recomputed {facts.get(key)!r}")
263
    return facts, reasons
264
265
266
def row_problems(row: Dict[str, Any], facts: Dict[str, Any]) -> List[str]:
267
    """A published row must come from the calibrated runner, host and binaries, on a quiet host."""
268
    reasons = []
269
    for key in ("runner_id", "host_cpu_count", "host_mem_bytes"):
270
        if row.get(key) != facts.get(key):
271
            reasons.append(f"row {key} {row.get(key)!r} != calibration {facts.get(key)!r}")
272
    kind = row.get("subject_kind")
273
    if kind in ("git", "oak") and row.get("binary_sha256") != facts.get(f"{kind}_binary_sha256"):
274
        reasons.append(f"row {kind} binary differs from the calibrated binary")
275
    sample = row.get("environment_sample") or {}
276
    load = sample.get("loadavg") if isinstance(sample, dict) else None
277
    cpus = facts.get("host_cpu_count")
278
    if not isinstance(load, list) or not load or not isinstance(cpus, int) or load[0] / cpus > MAX_LOAD_PER_CORE:
279
        reasons.append("row host load/core not quiet")
280
    if row.get("fixture_pin_status") != "verified":
281
        reasons.append("row fixture not verified against its registry pin")
282
    if row.get("returncode") != 0:
283
        reasons.append("row is not a successful measurement")
284
    return reasons
285
286
287
def stamp(row: Dict[str, Any], record: Optional[Dict[str, Any]], facts: Optional[Dict[str, Any]],
288
          campaign_reasons: Sequence[str]) -> Dict[str, Any]:
289
    """Derive evidence_class / claim_eligible for one row of a run."""
290
    if record is None:
291
        reasons = ["no calibration record"]
292
    else:
293
        reasons = list(campaign_reasons) or row_problems(row, facts or {})
294
    if reasons:
295
        row["evidence_class"] = DEVELOPMENT_EVIDENCE_CLASS
296
        row["claim_eligible"] = False
297
        row["claim_ineligible_reasons"] = reasons[:5]
298
        for key in ("calibration_record", "calibration_record_sha256"):
299
            row.pop(key, None)
300
    else:
301
        row["evidence_class"] = CALIBRATED_EVIDENCE_CLASS
302
        row["claim_eligible"] = True
303
        row["claim_ineligible_reasons"] = []
304
        row["calibration_record"] = record
305
        row["calibration_record_sha256"] = record["record_sha256"]
306
    return row
307
308
309
def gate(scale_rows: Sequence[Tuple[str, Dict[str, Any]]], inputs: Dict[str, List[Dict[str, Any]]],
310
         input_sha256: Dict[str, str], registry: Optional[Dict[str, Any]]) -> List[Tuple[str, Any, Any, Any, str]]:
311
    """publish_gate: every publishable scale row is re-verified by recomputation.
312
313
    ``inputs``/``input_sha256`` map each input path to its rows and content
314
    sha256; the calibration JSONL must be one of them. Returns refusals.
315
    """
316
    by_sha = {sha: path for path, sha in input_sha256.items()}
317
    # Inputs that are the calibration campaigns referenced by rows are evidence
318
    # FOR the gate, not measurements being published.
319
    campaign_paths = {by_sha.get((row.get("calibration_record") or {}).get("calibration_rows_sha256"))
320
                      for _, row in scale_rows if isinstance(row.get("calibration_record"), dict)}
321
    campaign_paths.discard(None)
322
    verified: Dict[str, Tuple[Dict[str, Any], List[str]]] = {}
323
    run_probe_cache: Dict[Tuple[str, str], List[str]] = {}
324
    refused = []
325
    for path, row in scale_rows:
326
        if path in campaign_paths:
327
            continue
328
        if row.get("diagnostic") or row.get("returncode") == 77 or row.get("subject_kind") == "harness":
329
            continue
330
        where = (path, row.get("scenario"), row.get("operation"), row.get("subject"))
331
        if row.get("evidence_class") != CALIBRATED_EVIDENCE_CLASS or row.get("claim_eligible") is not True:
332
            refused.append((*where, f"evidence_class {row.get('evidence_class')!r} is not calibrated"))
333
            continue
334
        record = row.get("calibration_record")
335
        if not isinstance(record, dict) or row.get("calibration_record_sha256") != record.get("record_sha256"):
336
            refused.append((*where, "calibration record missing or its digest does not match the row"))
337
            continue
338
        key = record.get("record_sha256")
339
        if key not in verified:
340
            cal_sha = record.get("calibration_rows_sha256")
341
            cal_path = by_sha.get(cal_sha)
342
            verified[key] = verify_campaign(record, inputs.get(cal_path) if cal_path else None,
343
                                            cal_sha if cal_path else None, registry)
344
        facts, reasons = verified[key]
345
        if reasons:
346
            refused.append((*where, reasons[0]))
347
            continue
348
        problems = row_problems(row, facts)
349
        probe_key = (path, str(row.get("bench_id")))
350
        if probe_key not in run_probe_cache:
351
            run_rows = [r for r in inputs.get(path, []) if r.get("bench_id") == row.get("bench_id")]
352
            run_probe_cache[probe_key] = probe_verdict(run_rows)[0]
353
        problems += [f"published run: {reason}" for reason in run_probe_cache[probe_key]]
354
        if problems:
355
            refused.append((*where, problems[0]))
356
    return refused