From b1ce1927022d374198441afafeb27199fc8a052a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 24 Sep 2026 17:14:54 +0100 Subject: Establish native micro runtime checkpoint --- research/experiments/002-buffer-retention/run.py | 300 +++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 research/experiments/002-buffer-retention/run.py (limited to 'research/experiments/002-buffer-retention/run.py') diff --git a/research/experiments/002-buffer-retention/run.py b/research/experiments/002-buffer-retention/run.py new file mode 100644 index 0000000..49a693c --- /dev/null +++ b/research/experiments/002-buffer-retention/run.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +"""Build, independently verify, and measure the retention experiment.""" +import argparse +import csv +import hashlib +import json +import os +from pathlib import Path +import platform +import random +import shlex +import signal +import statistics +import struct +import subprocess +import tempfile +import time + +ROOT = Path(__file__).resolve().parent +FLAGS = ["-std=c17", "-Wall", "-Wextra", "-Wpedantic", "-Werror", "-O2", "-g"] + + +def digest(path): + with open(path, "rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +def dump(path, value): + path.write_text(json.dumps(value, indent=2, ensure_ascii=False) + "\n") + + +def build(cc, output, sanitizer=False): + command = [*cc, *FLAGS] + if sanitizer: + command += ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"] + command += [str(ROOT / "benchmark.c"), "-o", str(output)] + result = subprocess.run(command, capture_output=True, text=True, check=True) + return {"command": command, "stderr": result.stderr, "sha256": digest(output), + "size_bytes": output.stat().st_size} + + +def shape_for(shape, rng, i): + if shape == "sparse": + return 65536, 64 + if shape == "dense": + return 65536, 49152 + if shape == "mixed": + source = rng.choice([4096, 16384, 65536, 262144]) + keep = max(16, source // rng.choice([1, 2, 4, 8, 64, 1024])) + return source, keep + if shape == "test": + return [(1, 1), (63, 17), (4097, 12), (65536, 49152), (127, 127)][i % 5] + raise ValueError(shape) + + +def fixture(path, shape, count, seed): + rng = random.Random(seed) + expected = hashlib.sha256() + total = 0 + with path.open("wb") as stream: + for i in range(count): + size, keep = shape_for(shape, rng, i) + offset = rng.randrange(size - keep + 1) + # All input bytes exist on disk; no sparse files or seeking past data. + tile = rng.randbytes(256) + payload = (tile * ((size + 255) // 256))[:size] + stream.write(struct.pack(" size - keep: + raise ValueError("invalid frame") + if len(data) - at < size: + raise ValueError("short payload") + result.extend(struct.pack(" 0 + values[policy["policy"]] = m["rss_peak_bytes"] + # Only a sanity check for this deliberately wide memory gap, not exact RSS arithmetic. + assert values["borrow"] > values["project"], values + checks.append("current-image VmHWM distinguishes large retained sources from small projections") + return checks + + +def sample(binary, source, output, case, policy): + with source.open("rb") as inp, output.open("wb") as out: + result = subprocess.run(command(binary, case, policy), stdin=inp, stdout=out, + stderr=subprocess.PIPE, timeout=30) + m = metrics(result.stderr) + assert result.returncode == 0 and m["status"] == "ok", result.stderr + return m + + +def summarize(rows): + result = [] + names = ["elapsed_ns", "cpu_ns", "peak_reserved_bytes", "peak_active_bytes", "rss_peak_bytes", + "copy_bytes", "active_byte_seconds", "latency_mean_ns", "latency_p99_upper_ns", + "budget_waits", "allocations", "reuses"] + groups = sorted({(r["case"], r["variant"]) for r in rows}) + for case, variant in groups: + group = [r for r in rows if (r["case"], r["variant"]) == (case, variant)] + value = {"case": case, "variant": variant, "samples": len(group)} + for name in names: + values = [r[name] for r in group] + value[name] = {"median": statistics.median(values), "min": min(values), "max": max(values)} + result.append(value) + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check-only", action="store_true") + parser.add_argument("--output", type=Path) + args = parser.parse_args() + if not args.check_only and args.output is None: + parser.error("provide --check-only or --output (new directory)") + if args.output: + args.output.mkdir(parents=True, exist_ok=False) + cc = shlex.split(os.environ.get("CC", "cc")) + plan = json.loads((ROOT / "plan.json").read_text()) + affinity = None + if hasattr(os, "sched_getaffinity"): + affinity = min(os.sched_getaffinity(0)) + os.sched_setaffinity(0, {affinity}) + with tempfile.TemporaryDirectory(prefix="pure-retention-") as folder: + tmp = Path(folder) + native, sanitized = tmp / "bench", tmp / "bench-sanitized" + builds = [build(cc, native), build(cc, sanitized, True)] + verification = {"native": check(native, tmp, plan["policies"]), + "asan_ubsan": check(sanitized, tmp, plan["policies"])} + print(f'Verified {sum(map(len, verification.values()))} scenario groups (native + ASan/UBSan).', flush=True) + if args.check_only: + return + sources = {name: digest(ROOT / name) for name in ["benchmark.c", "run.py", "plan.json"]} + environment = {"platform": platform.platform(), "machine": platform.machine(), + "python": platform.python_version(), "cpu_affinity": affinity, + "compiler": subprocess.check_output([*cc, "--version"], text=True), + "builds": builds, "source_sha256": sources, + "clock": "CLOCK_MONOTONIC; CPU = CLOCK_PROCESS_CPUTIME_ID", + "timing": "includes file IO, scheduling, deliberate sleep, metrics, buffer cleanup; excludes startup/build/fixture/verification", + "limitations": "shared host; unregulated thermals; cached files; output page cache, no fsync; synthetic closed-loop arrivals", + "started_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())} + if Path("/proc/cpuinfo").exists(): + environment["cpuinfo"] = Path("/proc/cpuinfo").read_text() + dump(args.output / "environment.json", environment) + dump(args.output / "verification.json", verification) + dump(args.output / "plan.json", plan) + (args.output / "sources").mkdir() + for name in sources: + (args.output / "sources" / name).write_bytes((ROOT / name).read_bytes()) + fixtures, rows = [], [] + rng = random.Random(plan["seed"]) + for case in plan["cases"]: + source, output = tmp / "input.frames", tmp / "output.frames" + info = fixture(source, case["shape"], case["records"], plan["seed"]) + info["case"] = case["name"] + fixtures.append(info) + # Warm each strategy once. The following measurements use fresh processes. + for policy in plan["policies"]: + sample(native, source, output, case, policy) + assert digest(output) == info["expected_output_sha256"] + for repeat in range(plan["repeats"]): + order = list(plan["policies"]) + rng.shuffle(order) + for policy in order: + m = sample(native, source, output, case, policy) + assert m["accepted"] == m["emitted"] == case["records"] + assert m["input_bytes"] == info["input_bytes"] + assert m["output_bytes"] == info["expected_output_bytes"] + assert digest(output) == info["expected_output_sha256"] + rows.append({"case": case["name"], "variant": policy["name"], "repeat": repeat, **m}) + dump(args.output / "raw.json", rows) + dump(args.output / "fixtures.json", fixtures) + print(f'Completed {case["name"]}: {len(plan["policies"])} variants x {plan["repeats"]} trials.', flush=True) + dump(args.output / "summary.json", summarize(rows)) + with (args.output / "raw.csv").open("w") as out: + writer = csv.DictWriter(out, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + # Detect accidental source edits during a measurement run. + assert sources == {name: digest(ROOT / name) for name in sources} + dump(args.output / "complete.json", {"samples": len(rows), "all_output_hashes_match": True, + "finished_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}) + print(args.output.resolve()) + + +if __name__ == "__main__": + main() -- cgit v1.2.3