#!/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 "$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