DFiP_Budget_planing/perf/perfkit/modals_inventory.py
Andrey Epifancev 627ea82321 perf: замеры открытия модалок
Перечень модалок (modals.json), measureOpen с main_thread_ms, профиль
перерисовок React (reactProfile), сценарий modals.spec.ts, подкоманда
stand.sh web-profiling (:8081), генератор modals-inventory.md.

Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:53:55 +04:00

95 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Перечень модалок: python -m perfkit.modals_inventory <run_dir> -> perf/report-data/<run_id>/modals-inventory.md.
Источник - perf/e2e/modals.json (строится по коду web/src); если в <run_dir> есть modal.jsonl, в таблицу
добавляются n и медиана click_to_visible_ms по каждому target (включая варианты `<id>@big`).
"""
from __future__ import annotations
import json
import statistics
import sys
from pathlib import Path
from . import PERF_DIR, REPORT_DATA_DIR
from .results import read_all
MODALS_JSON = PERF_DIR / "e2e" / "modals.json"
def load_modals(path: Path = MODALS_JSON) -> list[dict]:
return json.loads(Path(path).read_text(encoding="utf-8"))
def _measured(run_dir) -> dict[str, list[float]]:
out: dict[str, list[float]] = {}
if run_dir is None:
return out
for r in read_all(run_dir):
if r.get("scenario") != "modal" or r.get("incomplete"):
continue
v = r.get("metrics", {}).get("click_to_visible_ms")
if isinstance(v, (int, float)):
out.setdefault(r["target"], []).append(v)
return out
def build(modals: list[dict], run_dir=None) -> list[dict]:
meas = _measured(run_dir)
rows = []
for m in modals:
row = {k: m.get(k) for k in ("id", "title", "page", "source", "priority", "role", "skip_reason", "note")}
row["open"] = m["open"]["selector"]
row["pre"] = len(m["open"].get("pre") or [])
vals = meas.get(m["id"], [])
if m.get("skip_reason"):
row.update(status="skip", n=0, median_ms=None)
elif vals:
row.update(status="measured", n=len(vals), median_ms=statistics.median(vals))
else:
row.update(status="not_run", n=0, median_ms=None)
rows.append(row)
if m.get("big_sheet"):
bv = meas.get(f"{m['id']}@big", [])
rows.append({**row, "id": f"{m['id']}@big", "title": f"{m['title']} (самый большой лист {m['big_sheet']})",
"status": "measured" if bv else "not_run", "n": len(bv),
"median_ms": statistics.median(bv) if bv else None})
return rows
def render_md(rows: list[dict]) -> str:
lines = [
"# Перечень модалок",
"",
"Источник: `perf/e2e/modals.json` (поиск по `web/src`: `common/Modal`, emotion-модалки `ModalBackdrop`, MUI `Dialog`/`Modal`/`Popover`).",
"priority 1 - листы форм, задача, проект, проекты, задачи; priority 2 - админка и справочник ВСП.",
"",
"| id | страница | source | priority | роль | открытие | статус | n | медиана, мс | skip_reason |",
"|---|---|---|---|---|---|---|---|---|---|",
]
for r in rows:
med = "" if r["median_ms"] is None else f"{r['median_ms']:.0f}"
opn = f"`{r['open']}`" + (f" (+{r['pre']} шаг.)" if r["pre"] else "") if r["open"] else ""
lines.append(
f"| `{r['id']}` | {r['page']} | `{r['source']}` | {r['priority']} | {r['role']} | {opn} | {r['status']} | {r['n']} | {med} | {r.get('skip_reason') or ''} |"
)
skipped = sum(1 for r in rows if r["status"] == "skip")
lines += ["", f"Всего: {len(rows)}, без skip_reason: {len(rows) - skipped}, skip_reason: {skipped}."]
return "\n".join(lines) + "\n"
def main(argv=None) -> int:
argv = sys.argv[1:] if argv is None else argv
if len(argv) != 1:
print("usage: python -m perfkit.modals_inventory <run_dir>", file=sys.stderr)
return 2
run_dir = Path(argv[0])
out = REPORT_DATA_DIR / run_dir.resolve().name
out.mkdir(parents=True, exist_ok=True)
(out / "modals-inventory.md").write_text(render_md(build(load_modals(), run_dir)), encoding="utf-8")
print(out / "modals-inventory.md")
return 0
if __name__ == "__main__":
raise SystemExit(main())