ARGOS-MUKHTAR-Lab / benchmark_data.py
AvaSiG's picture
Display verified schema 2 locomotion results and provenance
800f9af verified
Raw History Blame Contribute Delete
4 kB
"""Read a complete schema 2 benchmark snapshot and verify its recorded hashes."""
import hashlib
import json
from pathlib import Path
class BenchmarkData:
def __init__(self, root):
self.root = Path(root).resolve()
manifest_bytes = self._path("manifest.json").read_bytes()
self.manifest = json.loads(manifest_bytes)
if (self.manifest.get("schema_version") != 2 or
self.manifest.get("status") != "complete"):
raise ValueError("Нужен complete manifest schema 2")
source_files = self.manifest.get("source_files")
if not isinstance(source_files, dict) or not source_files:
raise ValueError("Manifest не содержит source_files")
source_digest = hashlib.sha256(json.dumps(
source_files, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
if source_digest != self.manifest.get("source_sha256"):
raise ValueError("Не совпадает source_sha256 в manifest")
artifacts = self.manifest.get("artifacts")
if not isinstance(artifacts, dict) or "summary.json" not in artifacts:
raise ValueError("Manifest не содержит summary.json")
self._raw = {"manifest.json": manifest_bytes.decode("utf-8")}
for name, expected in artifacts.items():
if name == "manifest.json":
raise ValueError("Manifest не может включать собственный хэш")
contents = self._path(name).read_bytes()
if hashlib.sha256(contents).hexdigest() != expected:
raise ValueError(f"Не совпадает SHA256: {name}")
self._raw[name] = contents.decode("utf-8")
self.summary = json.loads(self._raw["summary.json"])
if self.summary.get("schema_version") != 2:
raise ValueError("Нужен summary schema 2")
results = self.summary.get("results")
if not isinstance(results, list) or not results:
raise ValueError("Summary не содержит результатов")
self._results = {}
for row in results:
if row.get("metrics_schema_version") != 2:
raise ValueError("Нужны метрики schema 2")
key = (row["bench_id"], row["controller"])
if key in self._results:
raise ValueError("Повтор сценария/контроллера в summary")
if (row["events_file"] not in artifacts or
row["model_file"] not in artifacts or
row["model_sha256"] != artifacts[row["model_file"]]):
raise ValueError("Файлы прогона не совпадают с manifest")
self._results[key] = row
def _path(self, name):
if (not isinstance(name, str) or not name or
Path(name).name != name or "\\" in name or
Path(name).suffix not in (".json", ".jsonl", ".xml")):
raise ValueError("Недопустимое имя файла")
path = (self.root / name).resolve()
if path.parent != self.root or not path.is_file():
raise ValueError(f"Файл отсутствует или находится вне schema2: {name}")
return path
@property
def raw_files(self):
return sorted(self._raw)
def read_raw(self, name):
if not isinstance(name, str) or name not in self._raw:
raise ValueError("Недоступный файл: выберите файл из списка schema 2")
return self._raw[name]
def result(self, scenario, controller):
return self._results.get((scenario, controller))
def events(self, scenario, controller):
row = self.result(scenario, controller)
if row is None:
return []
return [json.loads(line) for line in self.read_raw(row["events_file"]).splitlines()
if line.strip()]