|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
| 5 | +ARTIFACT_DIR="${DW_PERF_ARTIFACT_DIR:-$ROOT_DIR/build/perf}" |
| 6 | +RUN_ID="${GITHUB_RUN_ID:-local}-$(date +%s)" |
| 7 | +PROJECT="${DW_PERF_COMPOSE_PROJECT:-dw-server-perf-$RUN_ID}" |
| 8 | +SERVER_PORT="${DW_PERF_SERVER_PORT:-18080}" |
| 9 | +MYSQL_PORT="${DW_PERF_MYSQL_PORT:-13306}" |
| 10 | +REDIS_PORT="${DW_PERF_REDIS_PORT:-16379}" |
| 11 | +METRICS_PORT="${DW_PERF_METRICS_PORT:-19090}" |
| 12 | +AUTH_TOKEN="${DW_PERF_AUTH_TOKEN:-perf-token}" |
| 13 | +POLL_TIMEOUT="${DW_PERF_POLL_TIMEOUT:-1}" |
| 14 | +PROMETHEUS_CONTAINER="${PROJECT}-prometheus" |
| 15 | +PROMETHEUS_CONFIG_DIR="" |
| 16 | + |
| 17 | +mkdir -p "$ARTIFACT_DIR" |
| 18 | + |
| 19 | +if [ -z "${APP_KEY:-}" ]; then |
| 20 | + APP_KEY="base64:$(openssl rand -base64 32)" |
| 21 | + export APP_KEY |
| 22 | +fi |
| 23 | + |
| 24 | +export APP_VERSION="${APP_VERSION:-2.0.0-perf}" |
| 25 | +export DW_AUTH_DRIVER="${DW_AUTH_DRIVER:-token}" |
| 26 | +export DW_AUTH_TOKEN="${DW_AUTH_TOKEN:-$AUTH_TOKEN}" |
| 27 | +export DW_WORKER_TOKEN="${DW_WORKER_TOKEN:-}" |
| 28 | +export DW_OPERATOR_TOKEN="${DW_OPERATOR_TOKEN:-}" |
| 29 | +export DW_ADMIN_TOKEN="${DW_ADMIN_TOKEN:-}" |
| 30 | +export DW_AUTH_BACKWARD_COMPATIBLE="${DW_AUTH_BACKWARD_COMPATIBLE:-true}" |
| 31 | + |
| 32 | +OVERRIDE_FILE="$ARTIFACT_DIR/docker-compose.perf.yml" |
| 33 | +cat > "$OVERRIDE_FILE" <<YAML |
| 34 | +services: |
| 35 | + bootstrap: |
| 36 | + environment: |
| 37 | + LOG_LEVEL: warning |
| 38 | + DW_WORKER_POLL_TIMEOUT: "$POLL_TIMEOUT" |
| 39 | + DW_WORKER_POLL_INTERVAL_MS: "50" |
| 40 | + DW_WORKER_POLL_SIGNAL_CHECK_INTERVAL_MS: "25" |
| 41 | + server: |
| 42 | + ports: !override |
| 43 | + - "${SERVER_PORT}:8080" |
| 44 | + environment: |
| 45 | + LOG_LEVEL: warning |
| 46 | + DW_WORKER_POLL_TIMEOUT: "$POLL_TIMEOUT" |
| 47 | + DW_WORKER_POLL_INTERVAL_MS: "50" |
| 48 | + DW_WORKER_POLL_SIGNAL_CHECK_INTERVAL_MS: "25" |
| 49 | + worker: |
| 50 | + environment: |
| 51 | + LOG_LEVEL: warning |
| 52 | + DW_WORKER_POLL_TIMEOUT: "$POLL_TIMEOUT" |
| 53 | + DW_WORKER_POLL_INTERVAL_MS: "50" |
| 54 | + DW_WORKER_POLL_SIGNAL_CHECK_INTERVAL_MS: "25" |
| 55 | + scheduler: |
| 56 | + environment: |
| 57 | + LOG_LEVEL: warning |
| 58 | + mysql: |
| 59 | + ports: !override [] |
| 60 | + healthcheck: |
| 61 | + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] |
| 62 | + interval: 5s |
| 63 | + timeout: 3s |
| 64 | + retries: 24 |
| 65 | + start_period: 30s |
| 66 | + redis: |
| 67 | + ports: !override [] |
| 68 | +YAML |
| 69 | + |
| 70 | +cleanup() { |
| 71 | + local status=$? |
| 72 | + |
| 73 | + docker logs "${PROJECT}-server-1" > "$ARTIFACT_DIR/server.log" 2>&1 || true |
| 74 | + docker logs "${PROJECT}-worker-1" > "$ARTIFACT_DIR/worker.log" 2>&1 || true |
| 75 | + docker logs "${PROJECT}-scheduler-1" > "$ARTIFACT_DIR/scheduler.log" 2>&1 || true |
| 76 | + docker logs "${PROJECT}-mysql-1" > "$ARTIFACT_DIR/mysql.log" 2>&1 || true |
| 77 | + docker logs "${PROJECT}-redis-1" > "$ARTIFACT_DIR/redis.log" 2>&1 || true |
| 78 | + |
| 79 | + docker rm -f "$PROMETHEUS_CONTAINER" >/dev/null 2>&1 || true |
| 80 | + if [ -n "$PROMETHEUS_CONFIG_DIR" ]; then |
| 81 | + rm -rf "$PROMETHEUS_CONFIG_DIR" |
| 82 | + fi |
| 83 | + |
| 84 | + docker compose -p "$PROJECT" -f "$ROOT_DIR/docker-compose.yml" -f "$OVERRIDE_FILE" down -v --remove-orphans || true |
| 85 | + exit "$status" |
| 86 | +} |
| 87 | +trap cleanup EXIT |
| 88 | + |
| 89 | +maybe_start_prometheus() { |
| 90 | + if [ "${DW_PERF_GRAFANA_REMOTE_WRITE_ENABLED:-true}" != "true" ]; then |
| 91 | + echo "Grafana Cloud remote_write disabled for this run; writing local perf artifacts only." |
| 92 | + return |
| 93 | + fi |
| 94 | + |
| 95 | + if [ -z "${DW_PERF_GRAFANA_REMOTE_WRITE_URL:-}" ] \ |
| 96 | + || [ -z "${DW_PERF_GRAFANA_USERNAME:-}" ] \ |
| 97 | + || [ -z "${DW_PERF_GRAFANA_API_TOKEN:-}" ]; then |
| 98 | + echo "Grafana Cloud remote_write is not configured; writing local perf artifacts only." |
| 99 | + return |
| 100 | + fi |
| 101 | + |
| 102 | + PROMETHEUS_CONFIG_DIR="$(mktemp -d)" |
| 103 | + cat > "$PROMETHEUS_CONFIG_DIR/prometheus.yml" <<YAML |
| 104 | +global: |
| 105 | + scrape_interval: 15s |
| 106 | +scrape_configs: |
| 107 | + - job_name: durable_workflow_server_perf |
| 108 | + static_configs: |
| 109 | + - targets: |
| 110 | + - host.docker.internal:${METRICS_PORT} |
| 111 | + labels: |
| 112 | + repository: "${GITHUB_REPOSITORY:-local}" |
| 113 | + workflow: "${GITHUB_WORKFLOW:-local}" |
| 114 | + run_id: "${GITHUB_RUN_ID:-local}" |
| 115 | + runner: "${RUNNER_NAME:-local}" |
| 116 | +remote_write: |
| 117 | + - url: "${DW_PERF_GRAFANA_REMOTE_WRITE_URL}" |
| 118 | + basic_auth: |
| 119 | + username: "${DW_PERF_GRAFANA_USERNAME}" |
| 120 | + password: "${DW_PERF_GRAFANA_API_TOKEN}" |
| 121 | +YAML |
| 122 | + |
| 123 | + docker run -d --rm \ |
| 124 | + --name "$PROMETHEUS_CONTAINER" \ |
| 125 | + --add-host=host.docker.internal:host-gateway \ |
| 126 | + -v "$PROMETHEUS_CONFIG_DIR/prometheus.yml:/etc/prometheus/prometheus.yml:ro" \ |
| 127 | + "${DW_PERF_PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}" \ |
| 128 | + --config.file=/etc/prometheus/prometheus.yml \ |
| 129 | + --storage.tsdb.retention.time=2h \ |
| 130 | + --web.enable-lifecycle >/dev/null |
| 131 | +} |
| 132 | + |
| 133 | +server_base_url() { |
| 134 | + local base_url="http://127.0.0.1:${SERVER_PORT}" |
| 135 | + local docker_host_url |
| 136 | + local docker_host_ip |
| 137 | + local server_id |
| 138 | + local server_ip |
| 139 | + |
| 140 | + if curl -fsS --max-time 2 "$base_url/api/health" >/dev/null 2>&1; then |
| 141 | + echo "$base_url" |
| 142 | + return |
| 143 | + fi |
| 144 | + |
| 145 | + docker_host_ip="$(ip route 2>/dev/null | awk '/default/ {print $3; exit}')" |
| 146 | + if [ -n "$docker_host_ip" ]; then |
| 147 | + docker_host_url="http://${docker_host_ip}:${SERVER_PORT}" |
| 148 | + if curl -fsS --max-time 2 "$docker_host_url/api/health" >/dev/null 2>&1; then |
| 149 | + echo "$docker_host_url" |
| 150 | + return |
| 151 | + fi |
| 152 | + fi |
| 153 | + |
| 154 | + server_id="$(docker compose -p "$PROJECT" -f "$ROOT_DIR/docker-compose.yml" -f "$OVERRIDE_FILE" ps -q server)" |
| 155 | + server_ip="$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$server_id" 2>/dev/null || true)" |
| 156 | + |
| 157 | + if [ -n "$server_ip" ]; then |
| 158 | + echo "http://${server_ip}:8080" |
| 159 | + return |
| 160 | + fi |
| 161 | + |
| 162 | + echo "$base_url" |
| 163 | +} |
| 164 | + |
| 165 | +cd "$ROOT_DIR" |
| 166 | + |
| 167 | +echo "Starting perf stack with project ${PROJECT} on http://127.0.0.1:${SERVER_PORT}" |
| 168 | +docker compose -p "$PROJECT" -f "$ROOT_DIR/docker-compose.yml" -f "$OVERRIDE_FILE" up -d --build --wait |
| 169 | + |
| 170 | +maybe_start_prometheus |
| 171 | +BASE_URL="$(server_base_url)" |
| 172 | +echo "Running perf load against ${BASE_URL}" |
| 173 | + |
| 174 | +DW_PERF_BASE_URL="$BASE_URL" \ |
| 175 | +DW_PERF_AUTH_TOKEN="$AUTH_TOKEN" \ |
| 176 | +DW_PERF_ARTIFACT_DIR="$ARTIFACT_DIR" \ |
| 177 | +DW_PERF_COMPOSE_PROJECT="$PROJECT" \ |
| 178 | +DW_PERF_METRICS_PORT="$METRICS_PORT" \ |
| 179 | +DW_PERF_POLL_TIMEOUT="$POLL_TIMEOUT" \ |
| 180 | + "$ROOT_DIR/scripts/perf/server_soak.py" |
0 commit comments