50 lines
2.2 KiB
Python
50 lines
2.2 KiB
Python
"""Запись результатов прогонов.
|
|
|
|
Файл: perf/results/<run_id>/<scenario>.jsonl, одна JSON-строка на один замер (repeat).
|
|
Формат строки (Node-сторона, Task 4, пишет тот же формат):
|
|
|
|
{
|
|
"run_id": "2026-10-25T12-00-00", # str, имя каталога прогона
|
|
"scenario": "open-sheet", # str
|
|
"target": "dev" | "prod" | ..., # str, какая сборка фронта
|
|
"role": "admin", # str, ключ из manifest.users
|
|
"scale": 1, # int
|
|
"cpu_rate": 1, # int/float, CPU throttling rate
|
|
"repeat": 0, # int, номер повтора
|
|
"metrics": {"long_task_ms": 123.4}, # {str: number}; нечисловые значения агрегатор игнорирует
|
|
"artifacts": ["trace.json"], # list[str], пути относительно каталога прогона
|
|
"incomplete": false, # bool; true = замер не завершён, в статистику не входит
|
|
"ts": "2026-10-25T12:00:01Z" # str, ISO-8601 UTC
|
|
}
|
|
|
|
Кодировка UTF-8, без переносов строк внутри записи.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
REQUIRED = ("run_id", "scenario", "target", "role", "scale", "cpu_rate", "repeat",
|
|
"metrics", "artifacts", "incomplete", "ts")
|
|
|
|
|
|
def append(run_dir, record: dict) -> Path:
|
|
missing = [k for k in REQUIRED if k not in record]
|
|
if missing:
|
|
raise ValueError(f"record missing keys: {missing}")
|
|
run_dir = Path(run_dir)
|
|
run_dir.mkdir(parents=True, exist_ok=True)
|
|
f = run_dir / f"{record['scenario']}.jsonl"
|
|
with f.open("a", encoding="utf-8") as fh:
|
|
fh.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
return f
|
|
|
|
|
|
def read_all(run_dir) -> list[dict]:
|
|
out = []
|
|
for f in sorted(Path(run_dir).glob("*.jsonl")):
|
|
for line in f.read_text(encoding="utf-8").splitlines():
|
|
if line.strip():
|
|
out.append(json.loads(line))
|
|
return out
|