Files
acp/dev/ltfs_compare.py
Samuel CUI 159d8ea4ea Decouple bounded copy pipeline and preserve ordered completion
Overlap source reads, hashing, ordered linear writes, and asynchronous cleanup with bounded buffers. Preserve conflicting output order, API behavior, and no-space diagnostics. Add an LTFS drive-buffer model and reproducible performance checks.
2026-10-09 00:33:14 +08:00

130 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Compare committed ACP pipelines with one LTFS model and explicit historical I/O adapters."""
import argparse
import difflib
import json
import os
from pathlib import Path
import platform
import re
import tempfile
from release import (commit, digest, go_environment, harness_files, private_output,
require, run, save, snapshot)
BASELINES = {
"4d5dbd0b8c06c5d53f369820ac9cff20e779821f", # Published v0.2.3.
}
MODEL_FILES = ("ltfs_mock_test.go", "tape_drive_test.go", "tape_speed_test.go", "ltfs_benchmark_test.go")
SELECTOR = "^BenchmarkLTFSPipeline$/^(native47|stress100)$/^(small|large|mixed|small-delayed-source)$/^pipeline$"
def replace_once(text, old, new):
require(text.count(old) == 1, "Historical I/O adapter anchor changed.")
return text.replace(old, new)
def adapt(source, adapter):
"""Change only native I/O dispatch; retain historical scheduling and completion."""
changes = {
"acp.go": [("type StreamCopyer struct {", "type StreamCopyer struct {\n\tfilesystem transferFilesystem")],
"prepare.go": [("openSourceContent(job.path, mode, job.stat.info)", "c.fs().Open(job.path, mode, job.stat.info)")],
"copy.go": [("c.prepareTarget(job, target)", "c.fs().Create(job, target)")],
"target.go": [
("target targetSpec, out *fileio.Output, chunks", "target targetSpec, out transferOutput, chunks"),
("writeChunk(out.File, chunk)", "writeChunk(out, chunk)"),
("c.refreshCacheEntry(out.File, target.name, job.baseJob, true)", "out.Cache(job.baseJob, c.toDevice.linear)"),
("restoreTarget(out.Temporary, job.stat)", "out.Restore(job.stat)"),
("out.File.Sync()", "out.Sync()"),
("commitTarget(out, c.createFlag&os.O_TRUNC != 0)", "out.Commit(c.createFlag&os.O_TRUNC != 0)"),
],
}
patch = []
for name, replacements in changes.items():
path = source / name
before = path.read_text()
after = before
for old, new in replacements:
after = replace_once(after, old, new)
path.write_text(after)
patch.extend(difflib.unified_diff(before.splitlines(True), after.splitlines(True), "a/" + name, "b/" + name))
# Historical targets still own an open descriptor at Cache, including linear outputs.
adapter = re.sub(r"(?ms)^func \(o \*nativeTransferOutput\) Cache\(.*?^}\n", """func (o *nativeTransferOutput) Cache(job *baseJob, _ bool) {
\to.copyer.refreshCacheEntry(o.output.File, o.target.name, job, true)
}
""", adapter)
require("refreshCachePath" not in adapter, "Adapter must preserve historical descriptor cache writes.")
(source / "filesystem.go").write_text(adapter)
patch.extend(difflib.unified_diff([], adapter.splitlines(True), "/dev/null", "b/filesystem.go"))
return "".join(patch)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--baseline", action="append", required=True)
parser.add_argument("--candidate", required=True)
parser.add_argument("--harness", required=True)
parser.add_argument("--out", required=True)
parser.add_argument("--idle", action="store_true")
parser.add_argument("--scenario", choices=("ordinary", "read-delay"), default="ordinary")
args = parser.parse_args()
selector, expected = SELECTOR, 7
if args.scenario == "read-delay":
selector, expected = "^BenchmarkLTFSPipeline$/^stress100$/^medium-delayed-read$/^pipeline$", 1
require(args.idle, "Stop other builds and benchmarks before measuring.")
require(set(args.baseline) <= BASELINES, "Historical adapter is reviewed only for the listed exact revisions.")
repo = Path.cwd()
for revision in [*args.baseline, args.candidate, args.harness]:
commit(repo, revision)
output = private_output(repo, args.out)
files = {name: run(["git", "show", args.candidate + ":" + name], cwd=repo) for name in MODEL_FILES}
adapter = run(["git", "show", args.candidate + ":filesystem.go"], cwd=repo).decode()
native = harness_files(repo, args.harness)
record = {"complete": False, "candidate": args.candidate, "baselines": args.baseline,
"harness": args.harness, "harness_files": {name: digest(data) for name, data in native.items()},
"scenario": args.scenario, "model_files": {name: digest(data) for name, data in files.items()},
"collector_sha256": digest(Path(__file__).read_bytes()), "runs": [],
"note": "Historical snapshots use recorded I/O-only adapters. Scheduling and completion remain historical. Model gains are not hardware gains."}
save(output / "comparison.json", record)
with tempfile.TemporaryDirectory(prefix=".ltfs-compare-", dir=output.parent) as directory:
work = Path(directory)
temporary = work / "tmp"
temporary.mkdir()
for revision in [*args.baseline, args.candidate]:
label = revision[:12]
source = work / label
tree = snapshot(repo, revision, source)
for path in source.glob("*_test.go"):
path.unlink()
patch = ""
if revision in BASELINES:
patch = adapt(source, adapter)
(output / (label + ".adapter.patch")).write_text(patch)
for name, data in {**native, **files}.items():
(source / name).write_bytes(data)
go, env, info = go_environment(source, temporary, cpu=4)
env["ACP_MOCK_REPORT_DIR"] = str(output / label)
# Native fallback exercises the mechanical adapter before timed model comparisons.
smoke = [go, "test", "-run=^$", "-bench=^BenchmarkCopyWorkload$/^SmallOneTarget$", "-benchtime=1x", "-count=1", "-cpu=4", "."]
run(smoke, cwd=source, env=env, log=output / (label + ".native.log"))
command = [go, "test", "-run=^$", "-bench=" + selector, "-benchtime=1x", "-count=1", "-benchmem", "-cpu=4", "-p=1", "-timeout=15m", "."]
entry = {"revision": revision, "tree": tree, "adapter_sha256": digest(patch.encode()),
"command": list(map(str, command)), "go": info, "host": platform.platform(),
"filesystem_device": temporary.stat().st_dev, "load": os.getloadavg(), "complete": False}
record["runs"].append(entry)
save(output / "comparison.json", record)
data = run(command, cwd=source, env=env, log=output / (label + ".bench"))
names = re.findall(r"^BenchmarkLTFSPipeline/([^\s]+)\s+1\s+", data.decode(), re.M)
require(len(names) == expected and len(set(names)) == expected and "\nPASS\n" in data.decode(), "Incomplete model benchmark coverage.")
entry.update(complete=True, output_sha256=digest(data))
save(output / "comparison.json", record)
record["complete"] = True
save(output / "comparison.json", record)
print(json.dumps({"complete": True, "out": str(output)}))
if __name__ == "__main__":
main()