Andrey Epifancev 5f4afc0926 perf: правки по финальному ревью — скрипты, данные, фильтр long tasks
- run-all.sh: копия run-all.status.json в report-data с относительными путями;
  пример control4x с отдельным тегом и предупреждением (I-3, M-8); очищены
  три закоммиченные копии статуса
- lib/modal.ts: long tasks по пересечению окна (tasksInWindow), тест (M-7)
- scale_compare.py: строки листов на момент скролла (до grow), пояснения (M-6)
- hotspots_summary.py: оговорка о разбавлении «на шаг» (M-5)
- netlog_sizes.py + report-data/20261026-s1/analysis/netlog-sizes.md (M-3)

Refs #202

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

56 lines
2.9 KiB
Python

"""Размер ответов на проводе (сжатый, CDP) из сетевого лога сценариев - для report-data (network.jsonl в git не попадает).
cd perf && .venv/bin/python analysis/netlog_sizes.py results/20261026-s1 --out report-data/20261026-s1/analysis
По каждому шаблону пути данных (лист, отчёт проекта, своды) и шагу сценария: число ответов, медиана `body_size`
(сжатый размер по CDP `request.sizes()`, gzip уровня 1 бэка) и медиана `duration_ms` в браузере.
Распакованные размеры - в summary.md (bench, `bytes`) и `json_bytes_sheet` скролла. Вывод: <out>/netlog-sizes.md.
"""
from __future__ import annotations
import argparse
import json
import re
import statistics
from collections import defaultdict
from pathlib import Path
PATTERNS = [
(re.compile(r"/api/v1/form/(\d+)/sheet/(\w+)(\?.*)?$"), lambda m: f"GET /form/{m.group(1)}/sheet/{m.group(2)}{m.group(3) or ''}"),
(re.compile(r"/api/v1/projects/(\d+)/report/(\d+)/(\w+)$"), lambda m: f"GET /projects/{{id}}/report/{{year}}/{m.group(3)}"),
(re.compile(r"/api/v1/svod/(\d)/(\w+)/(\d+)"), lambda m: f"GET /svod/{m.group(1)}/{m.group(2)}/{{year}}"),
(re.compile(r"/api/v1/projects/plan-summary"), lambda m: "GET /projects/plan-summary"),
]
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("run_dir")
ap.add_argument("--out", required=True)
a = ap.parse_args(argv)
groups = defaultdict(lambda: {"size": [], "dur": []})
for line in (Path(a.run_dir) / "network.jsonl").read_text().splitlines():
r = json.loads(line)
if r.get("method") != "GET" or r.get("status") != 200 or not r.get("body_size"):
continue
path = re.sub(r"^https?://[^/]+", "", r["url"])
for rx, name in PATTERNS:
m = rx.search(path)
if m:
g = groups[(name(m), r.get("scenario"), r.get("role"))]
g["size"].append(r["body_size"])
g["dur"].append(r.get("duration_ms") or 0)
break
md = [f"# Размер ответов на проводе (сжатый) по сетевому логу {Path(a.run_dir).name} (netlog_sizes.py)", "",
"| запрос | сценарий | роль | ответов | медиана body_size, КБ | медиана duration_ms в браузере |", "|---|---|---|---|---|---|"]
for (name, sc, role), g in sorted(groups.items(), key=lambda kv: -statistics.median(kv[1]["size"])):
md.append(f"| {name} | {sc} | {role} | {len(g['size'])} | {statistics.median(g['size']) / 1024:.0f} | {statistics.median(g['dur']):.0f} |")
out = Path(a.out)
out.mkdir(parents=True, exist_ok=True)
(out / "netlog-sizes.md").write_text("\n".join(md) + "\n")
print("\n".join(md[:14]))
if __name__ == "__main__":
main()