From 26c0292bf9bf541f8877546303c28bb7f0109168 Mon Sep 17 00:00:00 2001 From: Alexandre Gomes Gaigalas Date: Fri, 20 Feb 2026 01:31:30 -0300 Subject: [PATCH] Allow panda.yml to request benchmark runs for PRs This change introduces a new command to `@TheRespectPanda` bot, allowing him to dispatch the ci-perf.yml workflow benchmarks for a pull request. Initially, the bot will just trigger it and return the workflow run URL for manual inspection. Future iterations on this feature could then grab the benchmark results and update the comment. --- .github/workflows/ci-perf.yml | 78 ++++++++++++++++++++++++++++++++--- .github/workflows/panda.yml | 49 ++++++++++++++++++++-- 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-perf.yml b/.github/workflows/ci-perf.yml index b8d2ef358..2c6f7edf9 100644 --- a/.github/workflows/ci-perf.yml +++ b/.github/workflows/ci-perf.yml @@ -21,19 +21,41 @@ on: options: - 'latest' - 'rebaseline' + pull: + description: 'Pull request number to benchmark (optional)' + required: false jobs: - tests: - name: Benchmarks + pr-benchmarks: + name: PR Benchmarks + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.pull }} runs-on: ubuntu-latest + env: + BENCHMARK_SHA: ${{ github.sha }} permissions: - contents: write + contents: read steps: - uses: actions/checkout@v6 with: - persist-credentials: true + persist-credentials: false + + - name: Checkout PR head + run: | + set -euo pipefail + PR=${{ github.event.inputs.pull }} + # Validate that PR is numeric to avoid injection or unexpected ref resolution + if ! printf '%s\n' "$PR" | grep -Eq '^[0-9]+$'; then + echo "Invalid pull request number: '$PR'. Expected a numeric value." >&2 + exit 1 + fi + # fetch the pull request head (works for forks) + git fetch origin "pull/${PR}/head:pr-${PR}" || git fetch origin "+refs/pull/${PR}/head:pr-${PR}" + git checkout "pr-${PR}" + echo "Checked out PR #${PR}" + echo "BENCHMARK_SHA=$(git rev-parse --verify HEAD)" >> "$GITHUB_ENV" + - uses: ./.github/actions/setup-action with: extensions: xdebug @@ -48,13 +70,57 @@ jobs: run: | # Baseline does not exist or rebaseline requested. Generate it. if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then - vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} # Baseline exists. Compare against it. else - vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest --tolerate-failure + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} --ref=latest --tolerate-failure fi + # Generate report for human consumption + vendor/bin/phpbench report --report=aggregate --ref=latest | + tail -n+2 | head -n-2 | tr '+' '|' > report.md + + cat report.md > "$GITHUB_STEP_SUMMARY" + + historical-benchmarks: + name: Historical Benchmarks + if: >- + github.event_name != 'workflow_dispatch' || + !github.event.inputs.pull + runs-on: ubuntu-latest + env: + BENCHMARK_SHA: ${{ github.sha }} + + permissions: + contents: write + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: true + + - uses: ./.github/actions/setup-action + with: + extensions: xdebug + + - name: Fetch Benchmarks + run: | + git fetch origin benchmarks + mkdir -p .phpbench + git checkout origin/benchmarks -- .phpbench || echo "No previous benchmarks found" + + - name: Run Benchmarks + run: | + # Baseline does not exist or rebaseline requested. Generate it. + if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} + + # Baseline exists. Compare against it. + else + vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} --ref=latest --tolerate-failure + fi + # Generate report for human consumption vendor/bin/phpbench report --report=aggregate --ref=latest | tail -n+2 | head -n-2 | tr '+' '|' > report.md diff --git a/.github/workflows/panda.yml b/.github/workflows/panda.yml index 83359bb29..bab98f1a5 100644 --- a/.github/workflows/panda.yml +++ b/.github/workflows/panda.yml @@ -3,34 +3,77 @@ name: TheRespectPanda Bot on: issue_comment: types: [created] - if: startsWith(github.event.comment.body, '@TheRespectPanda') jobs: listen-comment: + if: startsWith(github.event.comment.body, '@TheRespectPanda') runs-on: ubuntu-latest permissions: issues: write - pull-requests: write + actions: write steps: - uses: actions/github-script@v8 with: github-token: ${{ secrets.PANDA_GITHUB_PAT }} script: | const body = (context.payload.comment && context.payload.comment.body).trim() || ''; - const usage = 'Usage: `@TheRespectPanda ping|help`'; + const usage = 'Usage: `@TheRespectPanda ping|help|benchmark`'; if (!body.startsWith('@TheRespectPanda')) { return; } + let answer; + switch (body) { case '@TheRespectPanda ping': answer = 'Pong! 🐼'; break; + case '@TheRespectPanda': case '@TheRespectPanda help': answer = 'Hello! I am TheRespectPanda Bot. ' + usage; break; + + case '@TheRespectPanda benchmark': + // Only runnable on pull requests + if (!context.payload.issue.pull_request) { + answer = 'The `benchmark` command can only be used on pull requests.'; + break; + } + + // Only members can trigger benchmarks + const association = context.payload.comment.author_association; + const allowedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR']; + if (!allowedAssociations.includes(association)) { + answer = 'Only repository members can trigger benchmarks.'; + break; + } + + try { + // dispatch the perf workflow + const ref = (context.payload.repository && context.payload.repository.default_branch) || 'main'; + + const workflowId = 'ci-perf.yml'; + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: workflowId, + ref, + inputs: { + baseline: 'latest', + pull: String(context.issue.number) + } + }); + + const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/workflows/${workflowId}`; + answer = `Triggered phpbench benchmarks for PR #${context.issue.number} — workflow run: ${runUrl}`; + } catch (err) { + answer = `Failed to trigger benchmarks: ${err.message}`; + } + + break; + default: answer = "I'm sorry, I don't understand that command. " + usage; }