Viewing files at commit b76ab8b42dfdCommit detailsView latest

suites/scale/scalesuite/runner.py 305 lines · 15.6 KB · python
1
"""Interleaved, A/A-controlled execution of the scale op catalog on one tier.
2
3
Schedule: for each run, for each unit (an op or a short dependent op
4
sequence, see ops.UNIT_ORDER), subjects execute in a freshly shuffled
5
(seeded) order, so every subject sees the same drift and host noise. A/A
6
control subjects (``git_aa``, ``oak_installed_aa``: the same binary under a
7
second name) share their kind's workspace and are interleaved exactly like
8
the real arms; every cell gets its own A/A floor (oakbench.cellstats).
9
10
Measurement is serialized with the cross-process measurement lock around
11
the whole timed schedule (preparation runs outside it).
12
13
Every row is development evidence: ``evidence_class`` says so and
14
``claim_eligible`` is false. The calibrated campaign re-runs the same
15
machinery on a quiet host.
16
"""
17
18
from __future__ import annotations
19
20
import json
21
import platform
22
import random
23
import statistics
24
import sys
25
import time
26
from pathlib import Path
27
from typing import Any, Dict, List, Optional, Sequence, Tuple
28
29
from . import ops as ops_mod
30
from .workspace import TRANSPORT_GIT, TRANSPORT_OAK, Prepared
31
32
SCALE_ROW_VERSION = "scale-2026-09-28.1"
33
EVIDENCE_CLASS = "development_only_loaded_host"
34
AA_PAIRS = {"git": ("git", "git_aa"), "oak": ("oak_installed", "oak_installed_aa")}
35
NETWORK_OPS = frozenset({"push.branch", "pull.ff", "pull.diverged", "clone.full"})
36
37
38
def _say(message: str) -> None:
39
    print(message, file=sys.stderr, flush=True)
40
41
42
def _subject_meta(subject: Any, details: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
43
    item = details.get(subject.name, {})
44
    return {"subject_bin": item.get("bin"), "subject_version": item.get("version"),
45
            "binary_sha256": item.get("binary_sha256"), "subject_role": item.get("role"),
46
            "git_shim_measured": item.get("git_shim_measured"), "git_config_profile": item.get("git_config_profile")}
47
48
49
def build_row(*, bench_id: str, prepared: Prepared, subject: Any, op: str, run: int, outcome: ops_mod.Outcome,
50
              details: Dict[str, Dict[str, Any]], env_sample: Dict[str, Any], lock_wait_ms: Optional[float],
51
              order_position: int, runner_fields: Dict[str, Any]) -> Dict[str, Any]:
52
    info = ops_mod.OP_INFO[op]
53
    captures = [item.capture for item in outcome.timed]
54
55
    def total(attr: str) -> Optional[float]:
56
        values = [getattr(c, attr) for c in captures]
57
        if not values or any(v is None for v in values):
58
            return None
59
        return round(sum(values), 3)
60
61
    row: Dict[str, Any] = {
62
        "bench_id": bench_id,
63
        "profile": "scale",
64
        "scale_row_version": SCALE_ROW_VERSION,
65
        "scenario": prepared.tier.tier_id,
66
        "operation": op,
67
        "run": run,
68
        "subject": subject.name,
69
        "subject_kind": subject.kind,
70
        "command": [item.command for item in outcome.timed],
71
        "component_elapsed_ms": {item.label: round(item.capture.elapsed_ms, 3) for item in outcome.timed},
72
        "elapsed_ms": round(sum(c.elapsed_ms for c in captures), 3) if captures else None,
73
        "cpu_user_ms": total("cpu_user_ms"),
74
        "cpu_sys_ms": total("cpu_sys_ms"),
75
        "io_read_blocks": total("io_read_blocks"),
76
        "io_write_blocks": total("io_write_blocks"),
77
        "peak_rss_bytes": max((c.peak_rss_bytes or 0 for c in captures), default=None) if captures else None,
78
        "stdout_bytes": sum(c.stdout_bytes for c in captures) if captures else None,
79
        "timed_out": any(c.timed_out for c in captures),
80
        "measurement_source": "direct_cli_timed_subprocess",
81
        "rusage_source": "os_wait4_child_rusage",
82
        "page_cache_state": "warm",
83
        "cache_mode_requested": "warm",
84
        "evidence_class": EVIDENCE_CLASS,
85
        "claim_eligible": False,
86
        "tperf_refs": list(info.tperf),
87
        "core_yaml_ops": list(info.core_yaml),
88
        "oracle_ok": outcome.oracle_ok,
89
        "oracle_method": outcome.oracle_method,
90
        "oracle_reader": outcome.oracle_reader,
91
        "oracle_timed": False,
92
        "oracle_detail": outcome.oracle_detail,
93
        "semantic_class": outcome.semantic_class,
94
        "transport": (TRANSPORT_GIT if subject.kind == "git" else TRANSPORT_OAK) if op in NETWORK_OPS else None,
95
        "order_position": order_position,
96
        "environment_sample": env_sample,
97
        "measurement_lock_wait_ms": lock_wait_ms,
98
        **_subject_meta(subject, details),
99
        **prepared.fixture_fields(),
100
        **runner_fields,
101
        **outcome.extra,
102
    }
103
    if outcome.skip_reason is not None:
104
        # Skip rows follow the core lane: elapsed 0.0 with skipped: true (never a measurement).
105
        row.update(returncode=77, skipped=True, skip_reason=outcome.skip_reason, elapsed_ms=0.0)
106
        row.pop("measurement_source", None)
107
    elif outcome.failed is not None:
108
        process_rc = captures[-1].returncode if captures and captures[-1].returncode != 0 else None
109
        row.update(returncode=1, process_returncode=process_rc, failure=outcome.failed,
110
                   elapsed_ms=row["elapsed_ms"] if row["elapsed_ms"] is not None else 0.0)
111
    elif outcome.oracle_ok is False:
112
        row.update(returncode=1, process_returncode=captures[-1].returncode if captures else None,
113
                   failure="oracle_failed")
114
    else:
115
        row.update(returncode=0)
116
    return row
117
118
119
def abort_row(base: Dict[str, Any], op: str, after: str) -> Dict[str, Any]:
120
    row = dict(base)
121
    info = ops_mod.OP_INFO[op]
122
    for key in ("oracle_ok", "oracle_method", "oracle_reader", "semantic_class", "failure", "process_returncode",
123
                "component_elapsed_ms", "cpu_user_ms", "cpu_sys_ms", "io_read_blocks", "io_write_blocks",
124
                "peak_rss_bytes", "stdout_bytes", "dirty_applied", "harness_error"):
125
        row.pop(key, None)
126
    row.pop("measurement_source", None)
127
    row.update(operation=op, command=[], elapsed_ms=0.0, returncode=77, skipped=True, oracle_detail={},
128
               skip_reason=f"aborted_after_failed_prerequisite:{after}", tperf_refs=list(info.tperf),
129
               core_yaml_ops=list(info.core_yaml))
130
    return row
131
132
133
def run_schedule(prepared: Prepared, subjects: Sequence[Any], *, runs: int, ops: Sequence[str], bench_id: str,
134
                 seed: int, scratch: Path, details: Dict[str, Dict[str, Any]], runner_fields: Dict[str, Any],
135
                 timeout_s: float, lock_wait_ms: Optional[float], log=None) -> List[Dict[str, Any]]:
136
    scripts = str(Path(__file__).resolve().parents[3] / "scripts")
137
    if scripts not in sys.path:
138
        sys.path.insert(0, scripts)
139
    from oakbench.envwatch import sample_environment
140
    from oakbench.execution import run_timed
141
142
    scratch.mkdir(parents=True, exist_ok=True)
143
    rng = random.Random(seed)
144
    wanted = set(ops)
145
    units = [tuple(op for op in unit if op in wanted) for unit in ops_mod.UNIT_ORDER]
146
    units = [unit for unit in units if unit]
147
    rows: List[Dict[str, Any]] = []
148
    skip_by_tier = set(prepared.tier.skip_ops)
149
    for run in range(1, runs + 1):
150
        started = time.perf_counter()
151
        for unit in units:
152
            order = list(subjects)
153
            rng.shuffle(order)
154
            env_sample = sample_environment()
155
            for position, subject in enumerate(order):
156
                failed_at: Optional[str] = None
157
                for op in unit:
158
                    ctx = ops_mod.Ctx(prepared, subject, run, run_timed, scratch, timeout_s)
159
                    if failed_at is not None:
160
                        base = build_row(bench_id=bench_id, prepared=prepared, subject=subject, op=op, run=run,
161
                                         outcome=ops_mod.Outcome(), details=details, env_sample=env_sample,
162
                                         lock_wait_ms=lock_wait_ms, order_position=position,
163
                                         runner_fields=runner_fields)
164
                        rows.append(abort_row(base, op, failed_at))
165
                        continue
166
                    if op in skip_by_tier:
167
                        outcome = ops_mod.Outcome(skip_reason=f"tier_excludes_op_per_run_cost:{prepared.tier.tier_id}")
168
                    elif subject.kind not in prepared.kinds:
169
                        outcome = ops_mod.Outcome(skip_reason=f"kind_not_prepared:{subject.kind}")
170
                    else:
171
                        outcome = ops_mod.execute(ctx, op)
172
                    row = build_row(bench_id=bench_id, prepared=prepared, subject=subject, op=op, run=run,
173
                                    outcome=outcome, details=details, env_sample=env_sample,
174
                                    lock_wait_ms=lock_wait_ms, order_position=position,
175
                                    runner_fields=runner_fields)
176
                    rows.append(row)
177
                    if row["returncode"] == 1 and len(unit) > 1:
178
                        failed_at = op
179
        (log or _say)(f"[scale] {prepared.tier.tier_id}: run {run}/{runs} done in {time.perf_counter() - started:.1f}s")
180
    return rows
181
182
183
# ---------------------------------------------------------------------------
184
# Statistics: per-cell A/A floors, dev-only oak/git ratio, MDE per cell
185
# ---------------------------------------------------------------------------
186
187
188
def cell_report(rows: List[Dict[str, Any]], subjects: Sequence[str]) -> List[Dict[str, Any]]:
189
    from oakbench import cellstats
190
191
    measured = [r for r in rows if r.get("returncode") == 0 and isinstance(r.get("elapsed_ms"), (int, float))]
192
    floors: Dict[str, Dict[Tuple[str, str], float]] = {}
193
    for kind, (arm, control) in AA_PAIRS.items():
194
        if arm in subjects and control in subjects:
195
            floors[kind] = cellstats.cell_floors(measured, arm, control, lambda r: r.get("elapsed_ms"))
196
    cross: Dict[Tuple[str, str], Dict[str, Any]] = {}
197
    if "git" in subjects and "oak_installed" in subjects:
198
        keys = set(floors.get("git", {})) | set(floors.get("oak", {}))
199
        combined = {key: max(floors.get("git", {}).get(key, 0.0), floors.get("oak", {}).get(key, 0.0))
200
                    for key in keys}
201
        cross = cellstats.classify_cells(measured, "oak_installed", "git", combined)
202
    # Oak-vs-Oak resolution: what an oak changeset comparison can detect here.
203
    self_mde: Dict[Tuple[str, str], Optional[float]] = {}
204
    if "oak" in floors:
205
        aa = cellstats.classify_cells(measured, "oak_installed_aa", "oak_installed", floors["oak"])
206
        self_mde = {key: item.get("mde_pct") for key, item in aa.items()}
207
    cells = sorted({(r["scenario"], r["operation"]) for r in rows})
208
    report = []
209
    for key in cells:
210
        per_subject: Dict[str, Any] = {}
211
        for name in subjects:
212
            values = [r["elapsed_ms"] for r in measured if r["subject"] == name and (r["scenario"], r["operation"]) == key]
213
            fails = [r for r in rows if r["subject"] == name and (r["scenario"], r["operation"]) == key
214
                     and r.get("returncode") == 1]
215
            skips = [r for r in rows if r["subject"] == name and (r["scenario"], r["operation"]) == key
216
                     and r.get("returncode") == 77]
217
            per_subject[name] = {
218
                "n": len(values), "median_ms": round(statistics.median(values), 3) if values else None,
219
                "failures": len(fails), "skips": len(skips),
220
                "skip_reason": skips[0].get("skip_reason") if skips else None,
221
                "failure": (fails[0].get("failure") or "")[:200] if fails else None,
222
                "teardown_errors": sum(1 for r in rows if r["subject"] == name
223
                                       and (r["scenario"], r["operation"]) == key and r.get("teardown_error")),
224
            }
225
        item = cross.get(key, {})
226
        report.append({
227
            "scenario": key[0], "operation": key[1], "subjects": per_subject,
228
            "git_aa_floor_pct": floors.get("git", {}).get(key), "oak_aa_floor_pct": floors.get("oak", {}).get(key),
229
            "oak_vs_git_delta_pct_dev_only": item.get("delta_pct"),
230
            "oak_vs_git_ci_pct_dev_only": [item.get("ci_low_pct"), item.get("ci_high_pct")] if item else None,
231
            "oak_vs_git_mde_pct_80": item.get("mde_pct"),
232
            "oak_vs_oak_mde_pct_80": self_mde.get(key),
233
            "verdict_suppressed": item.get("verdict"),
234
            "claim_eligible": False,
235
        })
236
    return report
237
238
239
def _fmt(value: Any, suffix: str = "") -> str:
240
    if value is None:
241
        return "-"
242
    if isinstance(value, float):
243
        return f"{value:.1f}{suffix}"
244
    return f"{value}{suffix}"
245
246
247
def summary_markdown(prepared: Prepared, report: List[Dict[str, Any]], meta: Dict[str, Any]) -> str:
248
    lines = [
249
        f"# Scale lane: {prepared.tier.tier_id} (DEVELOPMENT ONLY - not a claim)",
250
        "",
251
        f"- bench_id `{meta['bench_id']}`; runs {meta['runs']}; subjects {', '.join(meta['subjects'])}",
252
        f"- fixture `{prepared.tier.fixture_id}` refs_sha256 `{prepared.manifest['refs_sha256'][:16]}` "
253
        f"spec_sha256 `{prepared.manifest['spec_sha256'][:16]}`",
254
        f"- host load at start {meta.get('loadavg_start')} / end {meta.get('loadavg_end')}: timings are "
255
        "development evidence on a loaded host. MDE = smallest change detectable at 80% power here.",
256
        "",
257
        "| op | git ms | oak ms | oak/git (dev) | git A/A floor | oak A/A floor | oak-vs-git MDE | oak-vs-oak MDE | notes |",
258
        "|---|---:|---:|---:|---:|---:|---:|---:|---|",
259
    ]
260
    diagnostic_ops = {phase for phase in prepared.phases}
261
    for item in report:
262
        if item["operation"] in diagnostic_ops:
263
            continue
264
        g = item["subjects"].get("git", {})
265
        o = item["subjects"].get("oak_installed", {})
266
        ratio = None
267
        if g.get("median_ms") and o.get("median_ms"):
268
            ratio = o["median_ms"] / g["median_ms"]
269
        notes = []
270
        for name, data in item["subjects"].items():
271
            if data.get("skip_reason"):
272
                notes.append(f"{name}: skip {data['skip_reason']}")
273
            elif data.get("failures"):
274
                notes.append(f"{name}: {data['failures']} fail ({data.get('failure')})")
275
            if data.get("teardown_errors"):
276
                notes.append(f"{name}: {data['teardown_errors']} untimed-teardown defect(s)")
277
        lines.append(
278
            f"| {item['operation']} | {_fmt(g.get('median_ms'))} | {_fmt(o.get('median_ms'))} | "
279
            f"{_fmt(ratio) if ratio is None else f'{ratio:.2f}x'} | {_fmt(item['git_aa_floor_pct'], '%')} | "
280
            f"{_fmt(item['oak_aa_floor_pct'], '%')} | {_fmt(item['oak_vs_git_mde_pct_80'], '%')} | "
281
            f"{_fmt(item['oak_vs_oak_mde_pct_80'], '%')} | {'; '.join(notes)[:300]} |")
282
    lines += ["", "## Fixture preparation (untimed, seconds)", ""]
283
    for key, value in prepared.phases.items():
284
        lines.append(f"- {key}: {value}")
285
    if prepared.notes:
286
        lines += ["", "## Notes", ""] + [f"- {note}" for note in prepared.notes]
287
    return "\n".join(lines) + "\n"
288
289
290
def ingest_rows(prepared: Prepared, bench_id: str, runner_fields: Dict[str, Any]) -> List[Dict[str, Any]]:
291
    """One diagnostic row per preparation phase (setup cost is scale evidence)."""
292
    rows = []
293
    for phase_name, seconds in prepared.phases.items():
294
        if phase_name.endswith("_reused"):
295
            continue
296
        kind = "oak" if ".oak_" in phase_name else ("git" if ".git_" in phase_name else "harness")
297
        rows.append({
298
            "bench_id": bench_id, "profile": "scale", "scale_row_version": SCALE_ROW_VERSION,
299
            "scenario": prepared.tier.tier_id, "operation": phase_name, "run": 0,
300
            "subject": {"oak": "oak_installed", "git": "git"}.get(kind, "harness"), "subject_kind": kind,
301
            "command": [], "elapsed_ms": round(seconds * 1000.0, 3), "returncode": 0, "diagnostic": True,
302
            "measurement_source": "harness_phase_wall_clock", "evidence_class": EVIDENCE_CLASS,
303
            "claim_eligible": False, **prepared.fixture_fields(), **runner_fields,
304
        })
305
    return rows