- perf/analysis: разбор trace (timeline_breakdown), CPU-профилей с source maps (cpuprofile_top), heap snapshots (heapdiff), циклов памяти, сравнения масштабов, ручной EXPLAIN вложенных запросов (explain_manual), API против X-DB-Time-Ms (api_split), сводка горячих точек; тесты ядра в perf/tests/test_analysis.py - perf/e2e/scenarios/hotspots.spec.ts: CPU-профиль, рендеры React, мутации DOM после фильтра, рост CSS-правил по циклам (вне run-all) - prod-сборка с source maps: PERF_SOURCEMAP=1, perf/stand.sh web-sourcemap (:8082) - perf/report-data/20261026-s1, 20261026-s2 (summary, sql, analysis, explain-manual), t5-types (иллюстрация CPU 6x) - design.md: решения T10b без обсуждения Refs #202 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
2.2 KiB
Python
48 lines
2.2 KiB
Python
"""Таблица циклов сценария памяти (memory.jsonl) с длительностью цикла - для report-data (сырой jsonl в git не попадает).
|
||
|
||
cd perf && .venv/bin/python analysis/memory_cycles.py results/20261026-s1 --out report-data/20261026-s1/analysis
|
||
|
||
Вывод: <out>/memory-cycles.md - по циклу heap_used_mb, nodes, js_event_listeners, ws_open, documents, cycle_s, relogin;
|
||
наклон МНК cycle_s по циклам 1..N (секунд на цикл) и рост последнего цикла к первому.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
from pathlib import Path
|
||
|
||
|
||
def slope(xs, ys):
|
||
n = len(xs)
|
||
mx, my = sum(xs) / n, sum(ys) / n
|
||
den = sum((x - mx) ** 2 for x in xs)
|
||
return sum((x - mx) * (y - my) for x, y in zip(xs, ys)) / den if den else 0.0
|
||
|
||
|
||
def main(argv=None):
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("run_dir")
|
||
ap.add_argument("--out", required=True)
|
||
a = ap.parse_args(argv)
|
||
rows = [json.loads(x) for x in (Path(a.run_dir) / "memory.jsonl").read_text().splitlines()]
|
||
rows.sort(key=lambda r: r["metrics"]["cycle"])
|
||
md = [f"# Циклы сценария памяти ({Path(a.run_dir).name}, memory_cycles.py)", "",
|
||
"| цикл | heap_used_mb | nodes | js_event_listeners | ws_open | documents | cycle_s | relogin |", "|---|---|---|---|---|---|---|---|"]
|
||
for r in rows:
|
||
m = r["metrics"]
|
||
md.append(f"| {m['cycle']} | {m['heap_used_mb']:.2f} | {m['nodes']} | {m['js_event_listeners']} | {m['ws_open']} | {m.get('documents')} | "
|
||
f"{m.get('cycle_s', 0):.0f} | {m.get('relogin', '')} |")
|
||
c = [r for r in rows if r["metrics"]["cycle"] >= 1]
|
||
xs = [r["metrics"]["cycle"] for r in c]
|
||
ys = [r["metrics"].get("cycle_s", 0) for r in c]
|
||
md += ["", f"cycle_s: наклон МНК по циклам {xs[0]}-{xs[-1]} - {slope(xs, ys):.2f} с на цикл; первый {ys[0]:.0f} с, последний {ys[-1]:.0f} с "
|
||
f"(+{100 * (ys[-1] / ys[0] - 1):.0f} %)."]
|
||
out = Path(a.out)
|
||
out.mkdir(parents=True, exist_ok=True)
|
||
(out / "memory-cycles.md").write_text("\n".join(md) + "\n")
|
||
print("\n".join(md[-3:]))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|