Andrey Epifancev 8e9f53a478 perf: скрипты разбора и сводные данные прогонов 20261026
- 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>
2026-10-26 12:43:46 +04:00

118 lines
4.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Стенд профилирования: up | migrate | bootstrap | api | web-build | web | web-profiling | web-sourcemap | smoke | reset | down | all
set -euo pipefail
SELF="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
cd "$(dirname "$0")/.."
ROOT="$(pwd)"
PERF="$ROOT/perf"
RUN="$PERF/.run"
VENV="$PERF/.venv"
mkdir -p "$RUN"
export PERF_API="${PERF_API:-http://localhost:8000}"
export PERF_WEB="${PERF_WEB:-http://localhost:8080}"
export PERF_DB_DSN="${PERF_DB_DSN:-postgresql://perf:perf@localhost:55432/perf}"
export DATABASE_URL="postgresql+asyncpg://perf:perf@localhost:55432/perf"
export DEBUG=true
export npm_config_registry="${npm_config_registry:-https://registry.npmjs.org}"
venv() {
if [ ! -x "$VENV/bin/python" ]; then
python3.12 -m venv "$VENV"
"$VENV/bin/pip" install -q -r "$ROOT/api/requirements-dev.txt" -r "$PERF/requirements.txt"
fi
}
start_bg() { # name, workdir, cmd...
local name="$1" dir="$2"; shift 2
if [ -f "$RUN/$name.pid" ] && kill -0 "$(cat "$RUN/$name.pid")" 2>/dev/null; then
echo "$name уже запущен (pid $(cat "$RUN/$name.pid"))"; return
fi
# внешний >/dev/null: фоновая подоболочка не должна держать stdout вызывающего (иначе `| tail` виснет)
(cd "$dir" && nohup "$@" >"$RUN/$name.log" 2>&1 </dev/null & echo $! >"$RUN/$name.pid") >/dev/null 2>&1
}
wait_http() { # url, tries
for _ in $(seq 1 "${2:-60}"); do
curl -fsS -o /dev/null "$1" 2>/dev/null && return 0
sleep 1
done
echo "не дождались $1" >&2; return 1
}
cmd="${1:-}"
case "$cmd" in
up)
docker compose -f "$PERF/docker-compose.yml" up -d pg
for _ in $(seq 1 60); do
docker exec perf-pg pg_isready -U perf -d perf >/dev/null 2>&1 && break
sleep 1
done
docker exec perf-pg pg_isready -U perf -d perf
;;
migrate)
venv
(cd "$ROOT/api" && "$VENV/bin/alembic" upgrade head)
;;
bootstrap)
venv
"$VENV/bin/python" "$PERF/stand/bootstrap.py"
;;
api)
venv
start_bg api "$ROOT/api" "$VENV/bin/python" run_server.py
wait_http "$PERF_API/healthz" 60
;;
web-build)
(cd "$ROOT/web" && npm ci && npx rsbuild build -c ../perf/web/rsbuild.perf.config.ts \
&& PERF_PROFILING=1 npx rsbuild build -c ../perf/web/rsbuild.perf.config.ts)
;;
web)
start_bg web "$ROOT/web" npx rsbuild preview -c ../perf/web/rsbuild.perf.config.ts
wait_http "$PERF_WEB/" 60
;;
web-profiling)
# профилирующая сборка (react-dom/profiling, без минификации) на :8081, prod на :8080 не трогаем
[ -f "$PERF/.build/profiling/index.html" ] || (cd "$ROOT/web" && PERF_PROFILING=1 npx rsbuild build -c ../perf/web/rsbuild.perf.config.ts)
PERF_PROFILING=1 PERF_WEB_PORT=8081 start_bg web-profiling "$ROOT/web" npx rsbuild preview -c ../perf/web/rsbuild.perf.config.ts
wait_http "http://localhost:8081/" 60
;;
web-sourcemap)
# prod-сборка с source maps на :8082 (CPU-профиль -> file:line, analysis/cpuprofile_top.py); :8080 и :8081 не трогаем
[ -f "$PERF/.build/prod-sourcemap/index.html" ] || (cd "$ROOT/web" && PERF_SOURCEMAP=1 npx rsbuild build -c ../perf/web/rsbuild.perf.config.ts)
PERF_SOURCEMAP=1 PERF_WEB_PORT=8082 start_bg web-sourcemap "$ROOT/web" npx rsbuild preview -c ../perf/web/rsbuild.perf.config.ts
wait_http "http://localhost:8082/" 60
;;
smoke)
venv
"$VENV/bin/python" "$PERF/stand/smoke.py"
;;
reset)
# Чистая БД: останавливаем api (держит соединения), сносим volume Postgres, поднимаем заново
# и снова запускаем api. Фронт (web) не трогаем: БД он не использует.
if [ -f "$RUN/api.pid" ]; then
pkill -P "$(cat "$RUN/api.pid")" 2>/dev/null || true
kill "$(cat "$RUN/api.pid")" 2>/dev/null || true
rm -f "$RUN/api.pid"
fi
docker compose -f "$PERF/docker-compose.yml" down -v
"$SELF" up; "$SELF" migrate; "$SELF" bootstrap; "$SELF" api
;;
down)
for n in web web-profiling web-sourcemap api; do
if [ -f "$RUN/$n.pid" ]; then
pkill -P "$(cat "$RUN/$n.pid")" 2>/dev/null || true
kill "$(cat "$RUN/$n.pid")" 2>/dev/null || true
rm -f "$RUN/$n.pid"
fi
done
docker compose -f "$PERF/docker-compose.yml" down
;;
all)
"$SELF" up; "$SELF" migrate; "$SELF" bootstrap; "$SELF" web-build; "$SELF" api; "$SELF" web; "$SELF" smoke
;;
*)
echo "usage: $0 up|migrate|bootstrap|api|web-build|web|web-profiling|web-sourcemap|smoke|reset|down|all" >&2; exit 2
;;
esac