- 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>
118 lines
4.6 KiB
Bash
Executable File
118 lines
4.6 KiB
Bash
Executable File
#!/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
|