name: focused performance

# not on pull requests: of 439 hosted measurements 12 % were inconclusive and 2 of the 6 regressions reported cleared on a rerun of the same commit, so a pull
# request cannot wait for its verdict. The nightly run compares with the commit of 24 hours before; a dispatch from a branch does the same.
# No scheduled run is enabled in this repository: dispatch the workflow to run it.
on:
  workflow_dispatch:
    inputs:
      verify_control:
        description: Acceptance only, unchanged SHA plus controlled SQL slowdown
        type: boolean
        default: false

permissions:
  contents: read

concurrency:
  group: performance-${{ github.event_name }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  focused:
    runs-on: ubuntu-24.04
    timeout-minutes: 60
    env:
      CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
      BASE_SHA: ${{ github.event.pull_request.base.sha }}
      TIER: ${{ (github.event_name == 'pull_request' || inputs.verify_control == true) && 'pr' || 'nightly' }}
      # a pull request or acceptance run: the whole job. The nightly tier (both revisions built at the larger fixture, every case measured, 490 s hosted) counts
      # from the end of the package install, which took from 18 s to 1172 s on hosted runners
      BUDGET_SECONDS: ${{ (github.event_name == 'pull_request' || inputs.verify_control == true) && 300 || 1500 }}
      VERIFY_CONTROL: ${{ github.event_name == 'workflow_dispatch' && inputs.verify_control == true && 'true' || 'false' }}
    steps:
      - name: Start end-to-end clock
        run: |
          ARTIFACTS="$RUNNER_TEMP/focused-perf-artifacts"
          echo "ARTIFACTS=$ARTIFACTS" >> "$GITHUB_ENV"
          mkdir -p "$ARTIFACTS"
          date +%s.%N > "$ARTIFACTS/workflow-start"
          printf 'Elapsed time is the primary metric.\n' > "$ARTIFACTS/workflow-summary.md"

      # PRs are untrusted: no credentials persisted, secrets, PR-target event or merge checkout.
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event.pull_request.head.sha || github.sha }}
          fetch-depth: 0
          persist-credentials: false

      - name: Resolve exact revisions and select cases
        id: scope
        run: |
          set -euo pipefail
          started=$(date +%s)
          finish_phase() { echo "selection $(( $(date +%s) - started ))" >> "$ARTIFACTS/workflow-phases.txt"; }
          trap finish_phase EXIT
          [[ "$CANDIDATE_SHA" =~ ^[0-9a-f]{40}$ ]]
          if [[ "$VERIFY_CONTROL" == true ]]; then
            # Explicit acceptance builds the exact same source twice. Injection
            # is only enabled by the fixed flag below, never on normal PR runs.
            baseline=$CANDIDATE_SHA
          elif [[ "$GITHUB_EVENT_NAME" == pull_request ]]; then
            [[ "$BASE_SHA" =~ ^[0-9a-f]{40}$ ]]
            git fetch --no-tags origin "$BASE_SHA" "$CANDIDATE_SHA"
            baseline=$(git merge-base "$BASE_SHA" "$CANDIDATE_SHA")
          else
            # Daily comparison: newest first-parent ancestor at least 24h before the
            # exact candidate commit. Never a PR merge ref, last job, or moving HEAD.
            cutoff=$(( $(git show -s --format=%ct "$CANDIDATE_SHA") - 86400 ))
            baseline=$(git rev-list --first-parent --max-count=1 --before="@$cutoff" "$CANDIDATE_SHA^")
            [[ "$baseline" =~ ^[0-9a-f]{40}$ ]]
            [[ "$baseline" != "$CANDIDATE_SHA" ]]
            git merge-base --is-ancestor "$baseline" "$CANDIDATE_SHA"
          fi
          test "$(git rev-parse HEAD)" = "$CANDIDATE_SHA"
          echo "baseline=$baseline" >> "$GITHUB_OUTPUT"
          printf 'Baseline: %s\nCandidate: %s\nTier: %s\n' "$baseline" "$CANDIDATE_SHA" "$TIER" >> "$ARTIFACTS/workflow-summary.md"
          python3 scripts/test-perf-select.py 2>&1 | tee "$ARTIFACTS/selection-tests.log"
          python3 -m unittest discover -s bench/perf -p 'test_*.py' -v 2>&1 | tee "$ARTIFACTS/methodology-tests.log"
          python3 scripts/perf-select.py --baseline-ref "$baseline" --candidate-ref "$CANDIDATE_SHA" \
            --tier "$TIER" --output "$ARTIFACTS/selection.json" | tee -a "$GITHUB_OUTPUT"
          if [[ "$VERIFY_CONTROL" == true ]]; then
            echo 'run=true' >> "$GITHUB_OUTPUT"
            echo 'groups=smoke' >> "$GITHUB_OUTPUT"
            echo 'Explicit acceptance: unchanged and controlled slowdown; see gate report for both verdicts.' >> "$ARTIFACTS/workflow-summary.md"
          fi

      - uses: ./.github/actions/apt-limits
        if: steps.scope.outputs.run == 'true'

      - name: Install PostgreSQL 18 and measurement dependencies
        if: steps.scope.outputs.run == 'true'
        timeout-minutes: 10
        run: |
          set -euo pipefail
          started=$(date +%s)
          finish_phase() { echo "dependencies $(( $(date +%s) - started ))" >> "$ARTIFACTS/workflow-phases.txt"; }
          trap finish_phase EXIT
          exec > >(tee "$ARTIFACTS/dependencies.log") 2>&1
          scripts/pgdg-repo.sh
          scripts/apt-install.sh postgresql-18 postgresql-server-dev-18 liblz4-dev libzstd-dev libicu-dev build-essential python3
          echo /usr/lib/postgresql/18/bin >> "$GITHUB_PATH"

      # Both exact SHA builds, isolated libraries/clusters and paired samples run here,
      # with one PG18/compiler/configuration. No matrix or source/object cache.
      - name: Paired focused gate
        if: steps.scope.outputs.run == 'true'
        env:
          BASELINE_SHA: ${{ steps.scope.outputs.baseline }}
          PERF_GROUPS: ${{ steps.scope.outputs.groups }}
        run: |
          set -euo pipefail
          started=$(date +%s)
          # shellcheck disable=SC2317,SC2329  # the EXIT trap calls it; shellcheck loses that after the final exit
          finish_phase() { echo "driver-build-setup-measure-cleanup $(( $(date +%s) - started ))" >> "$ARTIFACTS/workflow-phases.txt"; }
          trap finish_phase EXIT
          [[ $TIER == nightly ]] && date +%s.%N > "$ARTIFACTS/budget-start"
          acceptance=()
          if [[ "$VERIFY_CONTROL" == true ]]; then acceptance+=(--verify-control); fi
          rc=0
          nice -n 19 python3 bench/steps/perf_gate.py \
            --baseline-ref "$BASELINE_SHA" --candidate-ref "$CANDIDATE_SHA" \
            --groups "$PERF_GROUPS" --output "$ARTIFACTS/gate" --tier "$TIER" \
            --scratch-root "$RUNNER_TEMP/focused-perf-scratch" --port-base 56240 "${acceptance[@]}" \
            --budget-seconds "$BUDGET_SECONDS" --workflow-start-seconds "$(cat "$ARTIFACTS"/budget-start "$ARTIFACTS"/workflow-start 2>/dev/null | head -1)" \
            --cleanup-report-reserve-seconds 30 \
            2>&1 | tee "$ARTIFACTS/driver.log" || rc=${PIPESTATUS[0]}
          # exit 2 is an inconclusive measurement: a hosted runner shares its host (12 % of 439 runs), so it is a warning, not a failed run; the acceptance
          # run exists to prove the gate can tell, and a regression (1) or an error (3) fail either way
          if [[ $rc == 2 && "$VERIFY_CONTROL" != true ]]; then
            echo "::warning::inconclusive measurement: $(sed -n 's/^Error: //p' "$ARTIFACTS/gate/summary.md" | head -1) (see the gate summary)"
            rc=0
          fi
          exit "$rc"

      - name: Report total elapsed and budget
        if: always()
        env:
          SCOPE_RUN: ${{ steps.scope.outputs.run }}
          GATE_STATUS: ${{ job.status }}
        run: |
          mkdir -p "$ARTIFACTS"
          python3 - <<'PY'
          import json, os, time
          from pathlib import Path
          root = Path(os.environ['ARTIFACTS'])
          start = root / 'budget-start'
          if not start.exists():
              start = root / 'workflow-start'
          elapsed = time.time() - float(start.read_text()) if start.exists() else None
          code = os.environ['SCOPE_RUN'] == 'true'
          budget = float(os.environ['BUDGET_SECONDS'])
          over = elapsed is not None and elapsed > budget and code
          result = {'elapsed_seconds_including_checkout_dependencies_build_setup_cleanup': elapsed,
                    'budget_seconds': budget, 'budget_exceeded': over,
                    'gate_status': os.environ['GATE_STATUS'], 'code_change': code,
                    'note': 'Driver allowance deducts prior workflow elapsed time and reserves 30s for cleanup/report/upload. This is not a hard end-to-end deadline: dependency steps, in-flight SQL and cleanup can overrun; upload overhead follows.'}
          (root / 'workflow-timing.json').write_text(json.dumps(result, indent=2) + '\n')
          with (root / 'workflow-summary.md').open('a') as out:
              out.write(f'\nJob status: {result["gate_status"]}.\n')
              out.write(f'Elapsed since {start.name}: {elapsed} seconds.\n')
              if not code:
                  out.write('No measurement ran (documentation-only selection or earlier setup failure; see job status).\n')
              elif over:
                  out.write(f'BUDGET EXCEEDED: {budget:.0f} seconds for the whole job; this is an explicit measured overage.\n')
              else:
                  out.write(f'Within the budget of {budget:.0f} seconds for the whole job.\n')
          with Path(os.environ['GITHUB_STEP_SUMMARY']).open('a') as out:
              out.write((root / 'workflow-summary.md').read_text())
              summary = root / 'gate/summary.md'
              if summary.exists():
                  out.write('\n' + summary.read_text())
          PY

      - name: Upload selection, JSON, raw samples, plans, logs and summaries
        if: always()
        uses: actions/upload-artifact@v6
        with:
          name: focused-perf-${{ github.run_id }}-${{ github.run_attempt }}
          path: ${{ runner.temp }}/focused-perf-artifacts
          if-no-files-found: error
          retention-days: 14
