68 lines
2.8 KiB
Python
68 lines
2.8 KiB
Python
"""Тренд памяти по записям сценария memory (Task 8).
|
||
|
||
CLI: python -m perfkit.memtrend <run_dir> [--from-cycle N]
|
||
Читает <run_dir>/memory.jsonl (по умолчанию с цикла 1, цикл 0 - базовая точка), сортирует записи по metrics.cycle и печатает наклон МНК
|
||
(единиц метрики на цикл) для heap_used_mb, nodes, js_event_listeners.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
METRICS = ("heap_used_mb", "nodes", "js_event_listeners")
|
||
|
||
|
||
def slope_per_cycle(values: list[float]) -> float:
|
||
"""Наклон прямой МНК по точкам (0, v0), (1, v1), ... Меньше двух точек - ValueError."""
|
||
n = len(values)
|
||
if n < 2:
|
||
raise ValueError("нужно минимум две точки")
|
||
mx = (n - 1) / 2
|
||
my = sum(values) / n
|
||
sxx = sum((i - mx) ** 2 for i in range(n))
|
||
sxy = sum((i - mx) * (v - my) for i, v in enumerate(values))
|
||
return sxy / sxx
|
||
|
||
|
||
def load_memory(run_dir) -> list[dict]:
|
||
f = Path(run_dir) / "memory.jsonl"
|
||
recs = []
|
||
for line in f.read_text(encoding="utf-8").splitlines():
|
||
if not line.strip():
|
||
continue
|
||
r = json.loads(line)
|
||
if r.get("scenario") == "memory":
|
||
recs.append(r)
|
||
return sorted(recs, key=lambda r: r["metrics"]["cycle"])
|
||
|
||
|
||
def _print_slopes(title: str, recs: list[dict]) -> None:
|
||
cs = [r["metrics"]["cycle"] for r in recs]
|
||
print(f"{title}: циклы {cs[0]}-{cs[-1]}, {len(recs)} точек")
|
||
for m in METRICS:
|
||
vals = [float(r["metrics"][m]) for r in recs]
|
||
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
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|