diff --git a/perf/README.md b/perf/README.md index 39e5eb5..44342db 100644 --- a/perf/README.md +++ b/perf/README.md @@ -9,7 +9,7 @@ perf/stand.sh all # up, migrate, bootstrap, web-build, api, web, smoke perf/stand.sh down # остановить бэк, фронт и Postgres ``` -Подкоманды: `up | migrate | bootstrap | api | web-build | web | smoke | reset | down | all`. +Подкоманды: `up | migrate | bootstrap | api | web-build | web | web-profiling | web-sourcemap | smoke | reset | down | all` (`web-profiling` — профилирующая сборка на :8081, `web-sourcemap` — prod-сборка с source maps на :8082 для CPU-профилей, раздел «Анализ и отчёт»). `reset` — чистая БД: останавливает бэк, сносит volume Postgres, поднимает заново (`up`, `migrate`, `bootstrap`, `api`); фронт не трогает. - Postgres 16 (`pg_stat_statements`) на `localhost:55432`, `perf/perf/perf`. @@ -198,3 +198,66 @@ PERF_SCALES=1 PERF_RUN_TAG=20261026 PERF_SKIP_RESET=1 PERF_STEPS=control4x,aggre - `PERF_CONTROL_TARGETS`: `modal:` — модалка, `coauthor:` — скролл при соавторе, остальное — glob целей скролла; `PERF_CONTROL_ROLES` (`rf,dfip`) — роли скролла. CPU 4, 1 повтор (без trace); записи идут в тот же run_id (группы с `cpu_rate=4`), SQL — в `sql/control4x_*`. - Контроль выполняется после `grow`: для FORM_2 AHR_UTILITY/AHR_SECURITY и FORM_4 CAP цифры 4x несопоставимы со скроллом 1x. + +## Анализ и отчёт: `perf/analysis/`, `perf/REPORT.md` + +Отчёт — `perf/REPORT.md` (каждая цифра ссылается на файл в `perf/report-data//`). Порядок воспроизведения: + +1. Полный прогон: `PERF_S2_REPEATS=1 perf/run-all.sh` -> `results/<дата>-s1|s2`, `report-data/<дата>-s1|s2` (summary, sql, memtrend...). +2. После прогона в БД масштаб 2. Для ручного разбора и контроля CPU 4x восстановить масштаб 1 (~20 мин): + `perf/stand.sh reset && cd perf && .venv/bin/python -m perfkit.gen.run --scale 1 && .venv/bin/python -m perfkit.gen.verify --scale 1 && .venv/bin/python -m perfkit.gen.grow --scale 1`. + Состав данных эквивалентен замеренному (тот же план), но не побитово; `results/manifest-s1.json` перезаписывается (копия прогона - в `report-data//`). +3. Разбор сырых данных прогона (без живой БД): + +``` +cd perf +.venv/bin/python analysis/timeline_breakdown.py results/20261026-s1 --out report-data/20261026-s1/analysis # trace -> scripting/rendering/gc на шаг, точки входа JS +.venv/bin/python analysis/heapdiff.py results/20261026-s1/artifacts/memory-cycle10.heapsnapshot \ + results/20261026-s1/artifacts/memory-cycle20.heapsnapshot --base results/20261026-s1/artifacts/memory-cycle00.heapsnapshot \ + --out report-data/20261026-s1/analysis # рост классов, retainers новых объектов +.venv/bin/python analysis/memory_cycles.py results/20261026-s1 --out report-data/20261026-s1/analysis # таблица циклов памяти (cycle_s) +.venv/bin/python analysis/scale_compare.py --s1 20261026-s1 --s2 20261026-s2 --out report-data/20261026-s2/analysis # масштаб 1 против 2 +``` + +4. Разбор на живой БД масштаба 1 (шаг 2): + +``` +.venv/bin/python analysis/explain_manual.py --scale 1 --out report-data/20261026-s1/explain-manual # EXPLAIN вложенных запросов, jit/work_mem, pg_stat_statements +.venv/bin/python analysis/api_split.py --scale 1 --out report-data/20261026-s1/analysis # полное время API против X-DB-Time-Ms +``` + +`explain_manual.py` берёт тексты запросов из последних версий функций в `api/alembic/versions/sql/` по диапазонам строк +с якорями (при изменении функции скрипт падает с сообщением), подставляет параметры литералами, выполняет EXPLAIN +(ANALYZE, BUFFERS) в транзакции с ROLLBACK и делает `pg_stat_statements_reset()` - не запускать параллельно с замерами. + +5. Горячие точки фронта (`perf/e2e/scenarios/hotspots.spec.ts`, в run-all не входит): CPU-профиль на prod-сборке с source maps +и счётчики рендеров React на профилирующей сборке. + +``` +perf/stand.sh web-sourcemap # prod-сборка с source maps (PERF_SOURCEMAP=1, perf/.build/prod-sourcemap) на :8082; код = :8080 с точностью до хешей чанков +perf/stand.sh web-profiling # профилирующая сборка на :8081 +cd perf/e2e +PERF_RUN_ID=20261026-hot PERF_SQLSTATS=0 PERF_HOT_MODE=cpu PERF_WEB=http://localhost:8082 \ + PERF_HOT_TARGETS='FORM_2/AHR/null:y,FORM_2/AHR/null:x,FORM_4/STRUCTURE/null:y,project/LIMIT/1/2026:y,svod/svod1_MAIN:y,svod/projects_summaries:y,svod/svod2_GO:y,svod/svod2_GO:filter,svod/svod2_RF:filter,svod/svod1_MAIN:filter' \ + npx playwright test scenarios/hotspots.spec.ts +PERF_RUN_ID=20261026-hot PERF_SQLSTATS=0 PERF_HOT_MODE=react PERF_WEB=http://localhost:8081 \ + PERF_HOT_TARGETS='FORM_2/AHR/null:y,FORM_2/AHR/null:x,FORM_4/STRUCTURE/null:y,project/LIMIT/1/2026:y,svod/svod1_MAIN:y,svod/projects_summaries:y,svod/svod2_GO:y,svod/svod2_GO:filter' \ + npx playwright test scenarios/hotspots.spec.ts +PERF_RUN_ID=20261026-hot PERF_SQLSTATS=0 PERF_HOT_MODE=cpu PERF_WEB=http://localhost:8080 PERF_HOT_CYCLES=15 \ + PERF_HOT_SHEET2=FORM_4/STRUCTURE/null PERF_HOT_TARGETS='FORM_2/AHR/null:cssgrowth' npx playwright test scenarios/hotspots.spec.ts +PERF_RUN_ID=20261026-hot PERF_SQLSTATS=0 PERF_WEB=http://localhost:8081 PERF_PROFILING=1 PERF_PRIORITY=2 PERF_REPEATS=1 \ + PERF_MODALS='dict_vsp_*' npx playwright test scenarios/modals.spec.ts # рендеры модалок справочника ВСП +cd .. +.venv/bin/python analysis/cpuprofile_top.py results/20261026-hot --maps .build/prod-sourcemap --out report-data/20261026-s1/analysis +.venv/bin/python analysis/hotspots_summary.py results/20261026-hot --out report-data/20261026-s1/analysis +``` + +- `hotspots.spec.ts`: `PERF_HOT_TARGETS` - `:` (target как в scroll.spec без оси; action `y`, `x`, `filter`, + `cssgrowth`), `PERF_HOT_MODE` (`cpu` - `artifacts/hot-*.cpuprofile`; `react` - рендеры по компонентам), `PERF_HOT_STEPS` (40 шагов + вперёд и столько же назад), `PERF_HOT_FILTER_MS` (30 с наблюдения за мутациями DOM после ввода фильтра), `PERF_HOT_CYCLES`, + `PERF_HOT_SHEET2`, `PERF_HOT_ROLE`. Записи - `hotspot_cpu.jsonl` / `hotspot_react.jsonl`; под профайлером абсолютное время выше, + сравнивать доли. +- `cpuprofile_top.py` декодирует source maps сам (VLQ, без зависимостей): self time по функциям и пакетам, «ближайший кадр + `web/src`» в стеке каждого сэмпла (кто в коде приложения вызвал работу библиотек) и inclusive time функций `web/src`. +- Имена выходных файлов без «trace» (`timeline-breakdown.*`): шаблон `.gitignore` `perf/**/*trace*.json` исключает трассы. +- py-spy (`perf/requirements.txt`) на macOS требует root - не запускается без sudo; вместо него `analysis/api_split.py`.