Files
dolphin/Tools/PerformanceTrace/compare_p1.py
T
Yaya48 c230971f07 IOS LLE: checkpoint NAND conformance and diagnostic tooling
Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
2026-09-23 13:30:17 +02:00

99 lines
5.7 KiB
Python

"""Compare alternating fixed-work local runs without claiming a native gain."""
import argparse
import json
import math
from pathlib import Path
import re
import statistics
def load(directory):
manifest = json.loads((directory / "manifest.json").read_text(encoding="utf-8-sig"))
log = (directory / "probe.stdout.log").read_text(encoding="utf-8-sig")
match = re.search(r"arm-ms=([0-9.]+) instructions=(\d+) journal-pages=128 nat-arp-replies=32", log)
if not match or manifest["ProbeExitCode"] != 0 or int(match.group(2)) != 655360000:
raise ValueError(f"Probe did not finish the exact work: {directory}")
collection = manifest.get("Collection", "WPRSystemAndNative" if "Profile" in manifest else None)
requested = collection != "NoTrace"
if f"trace-requested={int(requested)} trace-enabled={int(requested)}" not in log:
raise ValueError(f"Probe trace state does not match collection: {directory}")
if collection == "WPRSystemAndNative":
decoder = json.loads((directory / "decoder-summary.json").read_text(encoding="utf-8-sig"))
if any(decoder.get(key) != 0 for key in
("status", "native_decode_errors", "events_lost", "buffers_lost")):
raise ValueError(f"WPR trace has decode errors or lost events: {directory}")
return {"directory": str(directory), "collection": collection,
"hash": manifest["ProbeSHA256"], "pdb_hash": manifest["ProbePDBSHA256"],
"affinity_mask": manifest.get("ProbeAffinityMask", 0),
"observed_affinity_mask": manifest.get("ProbeObservedAffinityMask"),
"arm_ms": float(match.group(1)), "wall_ms": 1000 * manifest["ProbeWallSeconds"],
"cpu_ms": 1000 * manifest["ProbeCPUSeconds"]}
def upper_95(values):
# Two-sided Student 95% upper bound; report but never equate with a
# guarantee for a different (native Wii) workload.
critical = {4: 3.182, 5: 2.776, 6: 2.571, 7: 2.447, 8: 2.365}
return statistics.mean(values) + critical[len(values)] * statistics.stdev(values) / math.sqrt(len(values))
def compare(directories):
runs = [load(path) for path in directories]
on_mode = runs[1]["collection"] if len(runs) >= 2 else None
if (on_mode not in ("PrivateUserMode", "WPRSystemAndNative") or
len(runs) < 4 or len(runs) > 16 or len(runs) % 2 or
any(r["collection"] != kind for r, kind in
zip(runs, ("NoTrace", on_mode) * (len(runs) // 2)))):
raise ValueError("Provide at least two OFF/ON pairs in alternating order")
if len({(r["hash"], r["pdb_hash"]) for r in runs}) != 1:
raise ValueError("The compared executables or PDBs differ")
if len({r["affinity_mask"] for r in runs}) != 1 or any(
r["affinity_mask"] and r["observed_affinity_mask"] != r["affinity_mask"]
for r in runs):
raise ValueError("Probe CPU affinity differs across compared runs")
pairs = []
for off, on in zip(runs[::2], runs[1::2]):
pairs.append({"off": off["directory"], "on": on["directory"],
"arm_phase_percent": 100 * (on["arm_ms"] / off["arm_ms"] - 1),
"whole_probe_percent": 100 * (on["wall_ms"] / off["wall_ms"] - 1),
"cpu_percent": 100 * (on["cpu_ms"] / off["cpu_ms"] - 1),
"non_arm_wall_delta_ms": (on["wall_ms"] - on["arm_ms"]) -
(off["wall_ms"] - off["arm_ms"])})
arm_differences = [pair["arm_phase_percent"] for pair in pairs]
wall_differences = [pair["whole_probe_percent"] for pair in pairs]
cpu_differences = [pair["cpu_percent"] for pair in pairs]
baseline_range = 100 * (max(r["arm_ms"] for r in runs[::2]) /
min(r["arm_ms"] for r in runs[::2]) - 1)
wall_baseline_range = 100 * (max(r["wall_ms"] for r in runs[::2]) /
min(r["wall_ms"] for r in runs[::2]) - 1)
bounds = (dict(arm_phase_percent=upper_95(arm_differences),
whole_probe_percent=upper_95(wall_differences),
cpu_percent=upper_95(cpu_differences)) if len(pairs) >= 4 else None)
qualified = (on_mode == "WPRSystemAndNative" and bounds is not None and
baseline_range <= 2 and wall_baseline_range <= 2 and
all(value <= 2 for value in bounds.values()))
return {"schema": 1, "on_collection": on_mode, "runs": runs, "pairs": pairs,
"mean_arm_phase_percent": statistics.mean(arm_differences),
"off_arm_phase_range_percent": baseline_range,
"off_wall_range_percent": wall_baseline_range,
"upper_95_percent": bounds,
"mean_non_arm_wall_delta_ms": statistics.mean(p["non_arm_wall_delta_ms"] for p in pairs),
"two_percent_budget_qualified": qualified,
"note": "Fixed-work local collector cost only, not a native Wii speed estimate. Four or more OFF/ON pairs, low baseline spread, and upper bounds below 2% are required for this conservative local gate."}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("captures", nargs="+", type=Path)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
result = compare(args.captures)
with args.output.open("x", encoding="utf-8") as target:
json.dump(result, target, indent=2)
print(json.dumps({key: result[key] for key in
("on_collection", "pairs", "mean_arm_phase_percent",
"off_arm_phase_range_percent", "off_wall_range_percent",
"upper_95_percent", "mean_non_arm_wall_delta_ms",
"two_percent_budget_qualified")}, indent=2))