218 lines
8.5 KiB
Bash
Executable File
218 lines
8.5 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
: "${PRE15_DUT:?PRE15_DUT is required}"
|
|
: "${PRE15_ROOT:?PRE15_ROOT is required}"
|
|
: "${PRE15_CASE_TMP:?PRE15_CASE_TMP is required}"
|
|
: "${PRE15_RUN_DIR:?PRE15_RUN_DIR is required}"
|
|
: "${PRE15_LIB_DIR:?PRE15_LIB_DIR is required}"
|
|
. "$PRE15_LIB_DIR/runner.sh"
|
|
|
|
fixture_dir=$PRE15_DUT/tests/pre15/fixtures
|
|
policy=$fixture_dir/B12-readahead-policy.json
|
|
kld_builder=$fixture_dir/B12-build-kld.sh
|
|
artifacts=$PRE15_RUN_DIR/artifacts
|
|
|
|
for tool in awk clang file git nm python3 sha256sum timeout; do
|
|
command -v "$tool" >/dev/null 2>&1 || \
|
|
pre15_infra_blocked "missing B12 host tool: $tool"
|
|
done
|
|
|
|
pre15_record_fixture b12-policy "$policy"
|
|
pre15_record_fixture b12-kld-builder "$kld_builder"
|
|
pre15_record_fixture b12-case "$PRE15_DUT/tests/pre15/cases/B12-readahead.sh"
|
|
for source in data.c dir.c internal.h; do
|
|
pre15_record_fixture "b12-$source" "$PRE15_DUT/src/$source"
|
|
done
|
|
mkdir -p "$artifacts"
|
|
pre15_target_reached
|
|
|
|
if python3 -B - "$PRE15_ROOT" "$PRE15_DUT" "$policy" \
|
|
"$artifacts/B12-policy-report.json" <<'PY'
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import statistics
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
root = Path(sys.argv[1])
|
|
dut = Path(sys.argv[2])
|
|
policy_path = Path(sys.argv[3])
|
|
report_path = Path(sys.argv[4])
|
|
policy = json.loads(policy_path.read_text(encoding="ascii"))
|
|
if policy.get("schema") != 1 or policy.get("candidate") != "P15-087":
|
|
raise SystemExit("invalid B12 policy identity")
|
|
|
|
|
|
def digest(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
source_hashes = {
|
|
relative: digest(dut / relative)
|
|
for relative in policy["source_sha256"]
|
|
}
|
|
if source_hashes != policy["source_sha256"]:
|
|
raise SystemExit("B12 production source differs from the accepted source set")
|
|
|
|
changed = subprocess.run(
|
|
[
|
|
"git", "-C", str(root), "diff", "--name-only",
|
|
policy["baseline"], "--", "repo-pre-15/src",
|
|
],
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
).stdout.splitlines()
|
|
expected_changed = [
|
|
"repo-pre-15/src/data.c",
|
|
"repo-pre-15/src/dir.c",
|
|
"repo-pre-15/src/internal.h",
|
|
]
|
|
if sorted(changed) != expected_changed:
|
|
raise SystemExit(f"B12 production write set mismatch: {changed}")
|
|
|
|
data = (dut / "src/data.c").read_text(encoding="utf-8")
|
|
directory = (dut / "src/dir.c").read_text(encoding="utf-8")
|
|
internal = (dut / "src/internal.h").read_text(encoding="utf-8")
|
|
if "cluster_read(" in data + directory + internal:
|
|
raise SystemExit("B12 must not introduce cluster_read")
|
|
if data.count("breadn(") != 1:
|
|
raise SystemExit("B12 must contain one backing-vnode breadn call")
|
|
if not re.search(r"breadn\(dif->devvp,", data):
|
|
raise SystemExit("B12 breadn is not issued on erofs_device_info.devvp")
|
|
required_data = (
|
|
"#define EROFS_DIR_READAHEAD_BYTES\t(1024 * 1024)",
|
|
"#define EROFS_DIR_READAHEAD_SLOTS\t(EROFS_DIR_READAHEAD_BYTES / PAGE_SIZE)",
|
|
"future.m_dif != current.m_dif",
|
|
"future.m_pa != current.m_pa + step",
|
|
"vi->datalayout == EROFS_INODE_FLAT_PLAIN",
|
|
"return (erofs_read_data_impl(sbi, vi, loff, len, 0, bufp));",
|
|
)
|
|
for marker in required_data:
|
|
if marker not in data:
|
|
raise SystemExit(f"missing B12 data policy marker: {marker}")
|
|
if directory.count("erofs_read_data_readahead(") != 1:
|
|
raise SystemExit("B12 directory hint is not limited to readdir")
|
|
if "sequential = logical_off == 0;" not in directory:
|
|
raise SystemExit("B12 random seek no-op predicate is absent")
|
|
if internal.count("erofs_read_data_readahead(") != 1:
|
|
raise SystemExit("B12 internal prototype is absent or duplicated")
|
|
|
|
evidence = root / policy["gate_evidence"]
|
|
result_path = evidence / "result.json"
|
|
rows_path = evidence / "runs.tsv"
|
|
if digest(result_path) != policy["gate_result_sha256"]:
|
|
raise SystemExit("B12 gate result hash mismatch")
|
|
if digest(rows_path) != policy["gate_rows_sha256"]:
|
|
raise SystemExit("B12 gate rows hash mismatch")
|
|
result = json.loads(result_path.read_text(encoding="ascii"))
|
|
if result.get("status") != "GO" or result.get("b12") != "AUTHORIZED":
|
|
raise SystemExit("P15-087 did not authorize B12")
|
|
if result.get("qemu") != "PASS" or not all(result.get("checks", {}).values()):
|
|
raise SystemExit("P15-087 runtime or semantic checks are incomplete")
|
|
thresholds = result.get("thresholds", {})
|
|
if thresholds != {
|
|
"maximum_extra_provider_reads_percent": policy["maximum_extra_provider_reads_percent"],
|
|
"minimum_cold_median_improvement_percent": policy["minimum_cold_median_improvement_percent"],
|
|
}:
|
|
raise SystemExit("P15-087 thresholds differ from the frozen B12 policy")
|
|
|
|
rows = []
|
|
with rows_path.open(encoding="ascii", newline="") as source:
|
|
for raw in csv.reader(source, delimiter="\t"):
|
|
if len(raw) != 10:
|
|
raise SystemExit(f"invalid B12 gate row: {raw}")
|
|
rows.append({
|
|
"variant": raw[0],
|
|
"run": raw[1],
|
|
"mode": raw[3],
|
|
"elapsed_ns": int(raw[4]),
|
|
"provider_reads": int(raw[5]),
|
|
"entry_count": int(raw[7]),
|
|
"hash": raw[8],
|
|
"final_cookie": int(raw[9]),
|
|
})
|
|
sequential = [row for row in rows if row["mode"] == "sequential"]
|
|
random_rows = [row for row in rows if row["mode"] == "random"]
|
|
baseline = [row for row in sequential if row["variant"] == "baseline"]
|
|
candidate = [row for row in sequential if row["variant"] == "candidate"]
|
|
if len(baseline) != 5 or len(candidate) != 5 or len(random_rows) != 2:
|
|
raise SystemExit("B12 gate does not contain the complete 5+5 and 1+1 sample set")
|
|
baseline_ns = statistics.median(row["elapsed_ns"] for row in baseline)
|
|
candidate_ns = statistics.median(row["elapsed_ns"] for row in candidate)
|
|
improvement = (baseline_ns - candidate_ns) * 100.0 / baseline_ns
|
|
baseline_reads = statistics.median(row["provider_reads"] for row in baseline)
|
|
candidate_reads = statistics.median(row["provider_reads"] for row in candidate)
|
|
extra_reads = (candidate_reads - baseline_reads) * 100.0 / baseline_reads
|
|
if improvement < policy["minimum_cold_median_improvement_percent"]:
|
|
raise SystemExit("B12 recomputed cold median improvement misses the gate")
|
|
if extra_reads > policy["maximum_extra_provider_reads_percent"]:
|
|
raise SystemExit("B12 recomputed provider-read delta misses the gate")
|
|
if any(
|
|
(row["entry_count"], row["hash"], row["final_cookie"])
|
|
!= (18002, "427bb414efa99dc0", 3880368)
|
|
for row in sequential
|
|
):
|
|
raise SystemExit("B12 sequential correctness rows differ from the oracle")
|
|
if {
|
|
(row["provider_reads"], row["entry_count"], row["hash"], row["final_cookie"])
|
|
for row in random_rows
|
|
} != {(19, 292, "9805eadece9f500e", 1998932)}:
|
|
raise SystemExit("B12 random path is not an exact no-op pair")
|
|
|
|
report = {
|
|
"baseline_samples": len(baseline),
|
|
"candidate_samples": len(candidate),
|
|
"baseline_median_elapsed_ns": baseline_ns,
|
|
"candidate_median_elapsed_ns": candidate_ns,
|
|
"cold_median_improvement_percent": improvement,
|
|
"extra_provider_reads_percent": extra_reads,
|
|
"gate": "GO",
|
|
"qemu_runtime": "PASS",
|
|
"random_samples": len(random_rows),
|
|
"source_sha256": source_hashes,
|
|
"status": "PASS",
|
|
"window_bytes": policy["maximum_readahead_bytes"],
|
|
}
|
|
report_path.write_text(
|
|
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="ascii"
|
|
)
|
|
PY
|
|
then
|
|
:
|
|
else
|
|
pre15_dut_fail 'B12 source, write-set, or frozen G11 policy replay failed'
|
|
fi
|
|
pre15_record_fixture b12-policy-report "$artifacts/B12-policy-report.json"
|
|
|
|
module=$PRE15_CASE_TMP/B12-erofs-zstdio0.ko
|
|
if ! timeout -k 10 600 /bin/sh "$kld_builder" "$PRE15_DUT" \
|
|
"$PRE15_FREEBSD_SRC" "$module" "$PRE15_CASE_TMP/kld-work" \
|
|
>"$artifacts/B12-kld-build.stdout" 2>"$artifacts/B12-kld-build.stderr"; then
|
|
pre15_dut_fail 'B12 exact-ABI zstdio0 KLD build failed'
|
|
fi
|
|
pre15_record_module "$module"
|
|
file "$module" >"$artifacts/B12-kld-file.txt"
|
|
sha256sum "$module" >"$artifacts/B12-kld-sha256.txt"
|
|
nm -g "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-global.txt"
|
|
nm -u "$module" | LC_ALL=C sort >"$artifacts/B12-kld-nm-u.txt"
|
|
if ! awk '$NF == "erofs_read_data_readahead" { found = 1 } END { exit !found }' \
|
|
"$artifacts/B12-kld-nm-global.txt"; then
|
|
pre15_dut_fail 'B12 exact-ABI KLD lacks the directory readahead symbol'
|
|
fi
|
|
if grep -q ' ZSTD_' "$artifacts/B12-kld-nm-u.txt"; then
|
|
pre15_dut_fail 'B12 zstdio0 KLD contains an unexpected Zstd symbol'
|
|
fi
|
|
|
|
printf '%s\n' \
|
|
'B12-policy PASS: exact source/write set and frozen 5+5 G11 statistics' \
|
|
'K zstdio0 PASS: FreeBSD 15 exact-ABI cross-KLD and symbol contract' \
|
|
'TC183 correctness/performance PASS from immutable P15-087 QEMU evidence'
|