perf: memtrend считает с цикла 1, плато по второй половине
Refs #202 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fa5dff8c6a
commit
4a539d803f
@ -95,10 +95,16 @@ cd .. && .venv/bin/python -m perfkit.modals_inventory results/m
|
|||||||
cd perf/e2e
|
cd perf/e2e
|
||||||
PERF_RUN_ID=mem npx playwright test scenarios/memory.spec.ts # полный прогон, около часа
|
PERF_RUN_ID=mem npx playwright test scenarios/memory.spec.ts # полный прогон, около часа
|
||||||
PERF_RUN_ID=memsmall PERF_MEM_CYCLES=2 PERF_MEM_SNAPSHOTS=0,2 npx playwright test scenarios/memory.spec.ts # малый
|
PERF_RUN_ID=memsmall PERF_MEM_CYCLES=2 PERF_MEM_SNAPSHOTS=0,2 npx playwright test scenarios/memory.spec.ts # малый
|
||||||
cd .. && .venv/bin/python -m perfkit.memtrend results/mem # наклоны МНК на цикл
|
cd .. && .venv/bin/python -m perfkit.memtrend results/mem [--from-cycle 1] # наклоны МНК на цикл
|
||||||
```
|
```
|
||||||
|
|
||||||
- Метрики записи: `cycle`, `heap_used_mb` (после gc), `documents`, `nodes`, `js_event_listeners` (CDP `Memory.getDOMCounters`), `ws_open` (обёртка над `window.WebSocket`), `ws_created`, `edits_ok`, `cycle_s`, `relogin`.
|
- Метрики записи: `cycle`, `heap_used_mb` (после gc), `documents`, `nodes`, `js_event_listeners` (CDP `Memory.getDOMCounters`), `ws_open` (обёртка над `window.WebSocket`), `ws_created`, `edits_ok`, `cycle_s`, `relogin`.
|
||||||
- Heap snapshots на циклах 0, 10, 20 (`PERF_MEM_SNAPSHOTS`) - `perf/results/<run_id>/artifacts/memory-cycleNN.heapsnapshot`, в git не попадают; открываются в Chrome DevTools (Memory). Разбор retainers - Task 10.
|
- Heap snapshots на циклах 0, 10, 20 (`PERF_MEM_SNAPSHOTS`) - `perf/results/<run_id>/artifacts/memory-cycleNN.heapsnapshot`, в git не попадают; открываются в Chrome DevTools (Memory). Разбор retainers - Task 10.
|
||||||
- Переменные: `PERF_MEM_CYCLES` (20), `PERF_MEM_SHEET1/2` (`FORM_x/SHEET/direction|null`), `PERF_TEST_TIMEOUT_MS` (3 ч). Токен (60 мин) перевыпускается по API без перезагрузки, если до конца < 10 мин.
|
- Переменные: `PERF_MEM_CYCLES` (20), `PERF_MEM_SHEET1/2` (`FORM_x/SHEET/direction|null`), `PERF_TEST_TIMEOUT_MS` (3 ч). Токен (60 мин) перевыпускается по API без перезагрузки, если до конца < 10 мин.
|
||||||
- Любая неудачная правка (нет эха, отказ сервера, нет редактируемых ячеек) - ошибка сценария.
|
- Любая неудачная правка (нет эха, отказ сервера, нет редактируемых ячеек) - ошибка сценария.
|
||||||
|
|
||||||
|
Как читать `perfkit.memtrend`:
|
||||||
|
- Цикл 0 - базовая точка: страница `/tasks` до первого открытия листа (heap 7 МБ, nodes 289). Скачок цикл 0 -> 1 - загрузка кода/данных первого листа, а не рост. Поэтому МНК по умолчанию считается с цикла 1 (`--from-cycle N`); вывод печатает использованные циклы и число точек.
|
||||||
|
- Вторым блоком печатается наклон по второй половине (циклы 11-20) - индикатор плато: если он заметно меньше общего, рост затухает.
|
||||||
|
- Snapshots на циклах 10 и 20 слегка влияют на heap следующего замера (после снимка остаются служебные структуры/GC), поэтому единичные скачки в точках 11 и 21 не интерпретировать.
|
||||||
|
- Результат прогона t8-mem (с цикла 1): heap 0.046 МБ/цикл (вторая половина 0.027), nodes 0, listeners 0, ws_open = 1 всегда. DOM, listeners и WS стабильны; остаточный рост JS-heap ~27-46 КБ/цикл неубедителен на 20 циклах - кандидат на diff retainers снимков 10 и 20 (Task 10).
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
"""Тренд памяти по записям сценария memory (Task 8).
|
"""Тренд памяти по записям сценария memory (Task 8).
|
||||||
|
|
||||||
CLI: python -m perfkit.memtrend <run_dir>
|
CLI: python -m perfkit.memtrend <run_dir> [--from-cycle N]
|
||||||
Читает <run_dir>/memory.jsonl, сортирует записи по metrics.cycle и печатает наклон МНК
|
Читает <run_dir>/memory.jsonl (по умолчанию с цикла 1, цикл 0 - базовая точка), сортирует записи по metrics.cycle и печатает наклон МНК
|
||||||
(единиц метрики на цикл) для heap_used_mb, nodes, js_event_listeners.
|
(единиц метрики на цикл) для heap_used_mb, nodes, js_event_listeners.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -37,16 +38,28 @@ def load_memory(run_dir) -> list[dict]:
|
|||||||
return sorted(recs, key=lambda r: r["metrics"]["cycle"])
|
return sorted(recs, key=lambda r: r["metrics"]["cycle"])
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def _print_slopes(title: str, recs: list[dict]) -> None:
|
||||||
argv = sys.argv[1:] if argv is None else argv
|
cs = [r["metrics"]["cycle"] for r in recs]
|
||||||
if len(argv) != 1:
|
print(f"{title}: циклы {cs[0]}-{cs[-1]}, {len(recs)} точек")
|
||||||
print("usage: python -m perfkit.memtrend <run_dir>", file=sys.stderr)
|
|
||||||
return 2
|
|
||||||
recs = load_memory(argv[0])
|
|
||||||
print(f"циклов (записей): {len(recs)}")
|
|
||||||
for m in METRICS:
|
for m in METRICS:
|
||||||
vals = [float(r["metrics"][m]) for r in recs]
|
vals = [float(r["metrics"][m]) for r in recs]
|
||||||
print(f"{m}: {slope_per_cycle(vals):.3f} на цикл (первый {vals[0]:.1f}, последний {vals[-1]:.1f})")
|
print(f"{m}: {slope_per_cycle(vals):.3f} на цикл (первый {vals[0]:.1f}, последний {vals[-1]:.1f})")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
ap = argparse.ArgumentParser(prog="python -m perfkit.memtrend")
|
||||||
|
ap.add_argument("run_dir")
|
||||||
|
ap.add_argument("--from-cycle", type=int, default=1,
|
||||||
|
help="первый цикл в МНК (по умолчанию 1: цикл 0 - базовая точка, страница /tasks до открытия листа)")
|
||||||
|
args = ap.parse_args(sys.argv[1:] if argv is None else argv)
|
||||||
|
recs = load_memory(args.run_dir)
|
||||||
|
used = [r for r in recs if r["metrics"]["cycle"] >= args.from_cycle]
|
||||||
|
print(f"записей memory: {len(recs)}")
|
||||||
|
_print_slopes(f"наклон с цикла {args.from_cycle}", used)
|
||||||
|
last = used[-1]["metrics"]["cycle"]
|
||||||
|
half = [r for r in used if r["metrics"]["cycle"] > last // 2]
|
||||||
|
if len(half) >= 2 and len(half) < len(used):
|
||||||
|
_print_slopes(f"вторая половина (циклы {half[0]['metrics']['cycle']}-{last}), индикатор плато", half)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -36,3 +36,35 @@ def test_cli_prints_three_slopes(tmp_path, capsys):
|
|||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "heap_used_mb" in out and "nodes" in out and "js_event_listeners" in out
|
assert "heap_used_mb" in out and "nodes" in out and "js_event_listeners" in out
|
||||||
assert "2.000" in out and "5.000" in out and "0.000" in out
|
assert "2.000" in out and "5.000" in out and "0.000" in out
|
||||||
|
|
||||||
|
|
||||||
|
def _write(tmp_path, heap):
|
||||||
|
rows = [{"scenario": "memory", "metrics": {"cycle": c, "heap_used_mb": h, "nodes": 350 if c else 289, "js_event_listeners": 336 if c else 322}} for c, h in enumerate(heap)]
|
||||||
|
(tmp_path / "memory.jsonl").write_text("\n".join(json.dumps(r) for r in rows) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_skips_baseline_step_by_default(tmp_path, capsys):
|
||||||
|
# ступенька на цикле 0 (7 -> 11), дальше +1 на цикл, 0 для nodes/listeners
|
||||||
|
_write(tmp_path, [7, 11, 12, 13, 14, 15, 16])
|
||||||
|
assert main([str(tmp_path)]) == 0
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "циклы 1-6" in out and "6 точек" in out
|
||||||
|
assert "heap_used_mb: 1.000" in out
|
||||||
|
assert "nodes: 0.000" in out and "js_event_listeners: 0.000" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_from_cycle_zero_includes_step(tmp_path, capsys):
|
||||||
|
_write(tmp_path, [7, 11, 12, 13, 14, 15, 16])
|
||||||
|
assert main([str(tmp_path), "--from-cycle", "0"]) == 0
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "циклы 0-6" in out and "7 точек" in out
|
||||||
|
assert "heap_used_mb: 1.000" not in out.split("вторая половина")[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_second_half_slope(tmp_path, capsys):
|
||||||
|
# рост до цикла 10, потом плато: вторая половина (циклы 11-20) = 0
|
||||||
|
_write(tmp_path, [7] + [11 + min(c, 10) for c in range(0, 20)])
|
||||||
|
assert main([str(tmp_path)]) == 0
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "вторая половина (циклы 11-20)" in out
|
||||||
|
assert "heap_used_mb: 0.000" in out.split("вторая половина")[1]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user