Files
retroSoC/scripts/parse_performance_log.py
T
maksyuki cf6bcf7a45 feat: refactor GA2D tests and update to Phase 4 specifications
- Updated resource_controller_tb.sv to verify resource STATUS bit layout after HP handoff.
- Modified test_ga2d.py to reflect changes for GA2D Phase 4, including memory map generation and source file handling.
- Introduced new test_ga2d_dma.py for independent FILL/COPY reference-model coverage.
- Updated test_ga2d_platform.py to check for the activation of the private AXI64 master and IRQ routing.
- Adjusted test_ga2d_register_parity.py to validate new scalar encodings for Phase 4.
- Enhanced test_hp_boot_bundle.py to ensure HP boot uses the private GA2D mailbox and resource lifecycle.
- Updated test_hp_platform.py to verify GA2D lifecycle markers and cache handshake budget.
- Modified test_performance_log.py to include GA2D workload and configuration facts in performance logs.
- Adjusted test_publication_retrieval.py and test_publication_software.py to reflect changes in GA2D stages.
- Updated test_script_tools.py to ensure GA2D formal filelists are correctly scoped.
- Refactored test_soc_topology.py to validate the private master policy and active shell in GA2D Phase 4.
2026-09-14 21:46:26 +08:00

136 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Parse reproducible SoC performance benchmark output into structured JSON."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
PERF_PREFIX = "PERF "
PASS_MARKER = "PERF_BENCHMARK_PASS"
FAIL_PREFIX = "PERF_FAIL"
REQUIRED_FIELDS = {
"region",
"op",
"words",
"checksum",
"cycles",
"mgmt_wait",
"apb4_periph_wait",
"sdram_wait",
"psram_wait",
"flash_wait",
"dma_wait",
"workload_bytes",
"pixels",
"jobs",
"cpu_cycles",
"cpu_hz",
"ga2d_features",
"ga2d_limits",
"ga2d_formats",
"ga2d_cycles",
"ga2d_read_bytes",
"ga2d_write_bytes",
}
TOKEN = re.compile(r"([a-z0-9_]+)=([^\s]+)")
def parse_number(value: str) -> int:
return int(value, 0)
def parse_log(content: str) -> dict[str, object]:
samples: list[dict[str, object]] = []
for line in content.splitlines():
if not line.startswith(PERF_PREFIX):
continue
fields = {name: value for name, value in TOKEN.findall(line)}
missing = REQUIRED_FIELDS.difference(fields)
if missing:
raise ValueError(f"performance sample is missing fields: {sorted(missing)}")
sample: dict[str, object] = {
"region": fields["region"],
"operation": fields["op"],
"words": parse_number(fields["words"]),
"checksum": int(fields["checksum"], 16),
"cycles": parse_number(fields["cycles"]),
"workload_bytes": parse_number(fields["workload_bytes"]),
"pixels": parse_number(fields["pixels"]),
"jobs": parse_number(fields["jobs"]),
"cpu_cycles": parse_number(fields["cpu_cycles"]),
"ga2d_cycles": parse_number(fields["ga2d_cycles"]),
"ga2d_read_bytes": parse_number(fields["ga2d_read_bytes"]),
"ga2d_write_bytes": parse_number(fields["ga2d_write_bytes"]),
"configuration": {
"cpu_hz": parse_number(fields["cpu_hz"]),
"ga2d_features": parse_number(fields["ga2d_features"]),
"ga2d_limits": parse_number(fields["ga2d_limits"]),
"ga2d_formats": parse_number(fields["ga2d_formats"]),
},
}
for counter in sorted(
REQUIRED_FIELDS
- {
"region",
"op",
"words",
"checksum",
"cycles",
"workload_bytes",
"pixels",
"jobs",
"cpu_cycles",
"cpu_hz",
"ga2d_features",
"ga2d_limits",
"ga2d_formats",
"ga2d_cycles",
"ga2d_read_bytes",
"ga2d_write_bytes",
}
):
sample[counter] = parse_number(fields[counter])
samples.append(sample)
passed = PASS_MARKER in content
failure_marker = any(line.startswith(FAIL_PREFIX) for line in content.splitlines())
return {
"schema_version": 2,
"status": "passed" if passed and samples and not failure_marker else "failed",
"failure_marker": failure_marker,
"pass_marker": passed,
"samples": samples,
}
def atomic_write(path: Path, content: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(content, encoding="utf-8")
temporary.replace(path)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--log", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
return parser.parse_args()
def main() -> int:
args = parse_args()
report = parse_log(args.log.read_text(encoding="utf-8", errors="replace"))
atomic_write(args.output, json.dumps(report, indent=2, sort_keys=True) + "\n")
if report["status"] != "passed":
print("performance benchmark did not emit a complete passing report", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())