profile.sh raw

   1  #!/usr/bin/env bash
   2  set -euo pipefail
   3  
   4  # Runs the ORLY relay with CPU profiling enabled and opens the resulting
   5  # pprof profile in a local web UI.
   6  #
   7  # Usage:
   8  #   ./profile.sh [duration_seconds]
   9  #
  10  # - Builds the relay.
  11  # - Starts it with ORLY_PPROF=cpu and minimal logging.
  12  # - Waits for the profile path printed at startup.
  13  # - Runs for DURATION seconds (default 10), then stops the relay to flush the
  14  #   CPU profile to disk.
  15  # - Launches `go tool pprof -http=:8000` for convenient browsing.
  16  #
  17  # Notes:
  18  # - The profile file path is detected from the relay's stdout/stderr lines
  19  #   emitted by github.com/pkg/profile, typically like:
  20  #     profile: cpu profiling enabled, path: /tmp/profile123456/cpu.pprof
  21  # - You can change DURATION by passing a number of seconds as the first arg
  22  #   or by setting DURATION env var.
  23  
  24  SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
  25  REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
  26  cd "$REPO_ROOT"
  27  
  28  DURATION="${1:-${DURATION:-10}}"
  29  PPROF_HTTP_PORT="${PPROF_HTTP_PORT:-8000}"
  30  
  31  # Load generation controls
  32  LOAD_ENABLED="${LOAD_ENABLED:-1}"               # set to 0 to disable load
  33  # Use the benchmark main package in cmd/benchmark as the load generator
  34  BENCHMARK_PKG_DIR="$REPO_ROOT/cmd/benchmark"
  35  BENCHMARK_BIN="${BENCHMARK_BIN:-}"            # if empty, we will build to $RUN_DIR/benchmark
  36  BENCHMARK_EVENTS="${BENCHMARK_EVENTS:-}"      # optional override for -events
  37  BENCHMARK_DURATION="${BENCHMARK_DURATION:-}"  # optional override for -duration (e.g. 30s); defaults to DURATION seconds
  38  
  39  BIN="$REPO_ROOT/next.orly.dev"
  40  LOG_DIR="${LOG_DIR:-$REPO_ROOT/cmd/benchmark/reports}"
  41  mkdir -p "$LOG_DIR"
  42  RUN_TS="$(date +%Y%m%d_%H%M%S)"
  43  RUN_DIR="$LOG_DIR/profile_run_${RUN_TS}"
  44  mkdir -p "$RUN_DIR"
  45  LOG_FILE="$RUN_DIR/relay.log"
  46  LOAD_LOG_FILE="$RUN_DIR/load.log"
  47  
  48  echo "[profile.sh] Building relay binary (pure Go + purego)..."
  49  CGO_ENABLED=0 go build -o "$BIN" .
  50  
  51  # Copy libsecp256k1.so next to binary if available (runtime optional)
  52  if [[ -f "pkg/crypto/p8k/libsecp256k1.so" ]]; then
  53      cp pkg/crypto/p8k/libsecp256k1.so "$(dirname "$BIN")/"
  54  fi
  55  
  56  # Ensure we clean up the child process on exit
  57  RELAY_PID=""
  58  LOAD_PID=""
  59  cleanup() {
  60    if [[ -n "$LOAD_PID" ]] && kill -0 "$LOAD_PID" 2>/dev/null; then
  61      echo "[profile.sh] Stopping load generator (pid=$LOAD_PID) ..."
  62      kill -INT "$LOAD_PID" 2>/dev/null || true
  63      sleep 0.5
  64      kill -TERM "$LOAD_PID" 2>/dev/null || true
  65    fi
  66    if [[ -n "$RELAY_PID" ]] && kill -0 "$RELAY_PID" 2>/dev/null; then
  67      echo "[profile.sh] Stopping relay (pid=$RELAY_PID) ..."
  68      kill -INT "$RELAY_PID" 2>/dev/null || true
  69      # give it a moment to exit and flush profile
  70      sleep 1
  71      kill -TERM "$RELAY_PID" 2>/dev/null || true
  72    fi
  73  }
  74  trap cleanup EXIT
  75  
  76  # Start the relay with CPU profiling enabled. Capture both stdout and stderr.
  77  echo "[profile.sh] Starting relay with CPU profiling enabled ..."
  78  (
  79    ORLY_LOG_LEVEL=off \
  80    ORLY_LISTEN="${ORLY_LISTEN:-127.0.0.1}" \
  81    ORLY_PORT="${ORLY_PORT:-3334}" \
  82    ORLY_PPROF=cpu \
  83    "$BIN"
  84  ) >"$LOG_FILE" 2>&1 &
  85  RELAY_PID=$!
  86  echo "[profile.sh] Relay started with pid $RELAY_PID; logging to $LOG_FILE"
  87  
  88  # Wait until the profile path is printed. Timeout after reasonable period.
  89  PPROF_FILE=""
  90  START_TIME=$(date +%s)
  91  TIMEOUT=30
  92  
  93  echo "[profile.sh] Waiting for profile path to appear in relay output ..."
  94  while :; do
  95    if grep -Eo "/tmp/profile[^ ]+/cpu\.pprof" "$LOG_FILE" >/dev/null 2>&1; then
  96      PPROF_FILE=$(grep -Eo "/tmp/profile[^ ]+/cpu\.pprof" "$LOG_FILE" | tail -n1)
  97      break
  98    fi
  99    NOW=$(date +%s)
 100    if (( NOW - START_TIME > TIMEOUT )); then
 101      echo "[profile.sh] ERROR: Timed out waiting for profile path in $LOG_FILE" >&2
 102      echo "Last 50 log lines:" >&2
 103      tail -n 50 "$LOG_FILE" >&2
 104      exit 1
 105    fi
 106    sleep 0.3
 107  done
 108  
 109  echo "[profile.sh] Detected profile file: $PPROF_FILE"
 110  
 111  # Optionally start load generator to exercise the relay
 112  if [[ "$LOAD_ENABLED" == "1" ]]; then
 113    # Build benchmark binary if not provided
 114    if [[ -z "$BENCHMARK_BIN" ]]; then
 115      BENCHMARK_BIN="$RUN_DIR/benchmark"
 116      echo "[profile.sh] Building benchmark load generator (pure Go + purego) ($BENCHMARK_PKG_DIR) ..."
 117      cd "$BENCHMARK_PKG_DIR"
 118      CGO_ENABLED=0 go build -o "$BENCHMARK_BIN" .
 119      # Copy libsecp256k1.so if available (runtime optional)
 120      if [[ -f "$REPO_ROOT/pkg/crypto/p8k/libsecp256k1.so" ]]; then
 121          cp "$REPO_ROOT/pkg/crypto/p8k/libsecp256k1.so" "$RUN_DIR/"
 122      fi
 123      cd "$REPO_ROOT"
 124    fi
 125    BENCH_DB_DIR="$RUN_DIR/benchdb"
 126    mkdir -p "$BENCH_DB_DIR"
 127    DURATION_ARG="${BENCHMARK_DURATION:-${DURATION}s}"
 128    EXTRA_EVENTS=""
 129    if [[ -n "$BENCHMARK_EVENTS" ]]; then
 130      EXTRA_EVENTS="-events=$BENCHMARK_EVENTS"
 131    fi
 132    echo "[profile.sh] Starting benchmark load generator for duration $DURATION_ARG ..."
 133    RELAY_URL="ws://${ORLY_LISTEN:-127.0.0.1}:${ORLY_PORT:-3334}"
 134    echo "[profile.sh] Using relay URL: $RELAY_URL"
 135    (
 136      "$BENCHMARK_BIN" -relay-url="$RELAY_URL" -net-workers="${NET_WORKERS:-2}" -net-rate="${NET_RATE:-20}" -duration="$DURATION_ARG" $EXTRA_EVENTS \
 137        >"$LOAD_LOG_FILE" 2>&1 &
 138    )
 139    LOAD_PID=$!
 140    echo "[profile.sh] Load generator started (pid=$LOAD_PID); logging to $LOAD_LOG_FILE"
 141  else
 142    echo "[profile.sh] LOAD_ENABLED=0; not starting load generator."
 143  fi
 144  
 145  echo "[profile.sh] Letting the relay run for ${DURATION}s to collect CPU samples ..."
 146  sleep "$DURATION"
 147  
 148  # Stop the relay to flush the CPU profile
 149  cleanup
 150  # Disable trap so we don't double-kill
 151  trap - EXIT
 152  
 153  # Wait briefly to ensure the profile file is finalized
 154  for i in {1..20}; do
 155    if [[ -s "$PPROF_FILE" ]]; then
 156      break
 157    fi
 158    sleep 0.2
 159  done
 160  
 161  if [[ ! -s "$PPROF_FILE" ]]; then
 162    echo "[profile.sh] WARNING: Profile file exists but is empty or missing: $PPROF_FILE" >&2
 163  fi
 164  
 165  # Launch pprof HTTP UI
 166  echo "[profile.sh] Launching pprof web UI (http://localhost:${PPROF_HTTP_PORT}) ..."
 167  exec go tool pprof -http=":${PPROF_HTTP_PORT}" "$BIN" "$PPROF_FILE"
 168