Skip to content

Commit 26c0292

Browse files
committed
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.
1 parent e1ff5aa commit 26c0292

2 files changed

Lines changed: 118 additions & 9 deletions

File tree

.github/workflows/ci-perf.yml

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,41 @@ on:
2121
options:
2222
- 'latest'
2323
- 'rebaseline'
24+
pull:
25+
description: 'Pull request number to benchmark (optional)'
26+
required: false
2427

2528
jobs:
26-
tests:
27-
name: Benchmarks
29+
pr-benchmarks:
30+
name: PR Benchmarks
31+
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.pull }}
2832
runs-on: ubuntu-latest
33+
env:
34+
BENCHMARK_SHA: ${{ github.sha }}
2935

3036
permissions:
31-
contents: write
37+
contents: read
3238

3339
steps:
3440
- uses: actions/checkout@v6
3541
with:
36-
persist-credentials: true
42+
persist-credentials: false
43+
44+
- name: Checkout PR head
45+
run: |
46+
set -euo pipefail
47+
PR=${{ github.event.inputs.pull }}
48+
# Validate that PR is numeric to avoid injection or unexpected ref resolution
49+
if ! printf '%s\n' "$PR" | grep -Eq '^[0-9]+$'; then
50+
echo "Invalid pull request number: '$PR'. Expected a numeric value." >&2
51+
exit 1
52+
fi
53+
# fetch the pull request head (works for forks)
54+
git fetch origin "pull/${PR}/head:pr-${PR}" || git fetch origin "+refs/pull/${PR}/head:pr-${PR}"
55+
git checkout "pr-${PR}"
56+
echo "Checked out PR #${PR}"
57+
echo "BENCHMARK_SHA=$(git rev-parse --verify HEAD)" >> "$GITHUB_ENV"
58+
3759
- uses: ./.github/actions/setup-action
3860
with:
3961
extensions: xdebug
@@ -48,13 +70,57 @@ jobs:
4870
run: |
4971
# Baseline does not exist or rebaseline requested. Generate it.
5072
if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then
51-
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA}
73+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA}
5274
5375
# Baseline exists. Compare against it.
5476
else
55-
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${GITHUB_SHA} --ref=latest --tolerate-failure
77+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} --ref=latest --tolerate-failure
5678
fi
5779
80+
# Generate report for human consumption
81+
vendor/bin/phpbench report --report=aggregate --ref=latest |
82+
tail -n+2 | head -n-2 | tr '+' '|' > report.md
83+
84+
cat report.md > "$GITHUB_STEP_SUMMARY"
85+
86+
historical-benchmarks:
87+
name: Historical Benchmarks
88+
if: >-
89+
github.event_name != 'workflow_dispatch' ||
90+
!github.event.inputs.pull
91+
runs-on: ubuntu-latest
92+
env:
93+
BENCHMARK_SHA: ${{ github.sha }}
94+
95+
permissions:
96+
contents: write
97+
98+
steps:
99+
- uses: actions/checkout@v6
100+
with:
101+
persist-credentials: true
102+
103+
- uses: ./.github/actions/setup-action
104+
with:
105+
extensions: xdebug
106+
107+
- name: Fetch Benchmarks
108+
run: |
109+
git fetch origin benchmarks
110+
mkdir -p .phpbench
111+
git checkout origin/benchmarks -- .phpbench || echo "No previous benchmarks found"
112+
113+
- name: Run Benchmarks
114+
run: |
115+
# Baseline does not exist or rebaseline requested. Generate it.
116+
if [ -z "$(ls -A .phpbench)" ] || [ "${{ github.event.inputs.baseline || 'latest' }}" = "rebaseline" ]; then
117+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA}
118+
119+
# Baseline exists. Compare against it.
120+
else
121+
vendor/bin/phpbench run --report=aggregate --progress=plain --store --tag=${BENCHMARK_SHA} --ref=latest --tolerate-failure
122+
fi
123+
58124
# Generate report for human consumption
59125
vendor/bin/phpbench report --report=aggregate --ref=latest |
60126
tail -n+2 | head -n-2 | tr '+' '|' > report.md

.github/workflows/panda.yml

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,77 @@ name: TheRespectPanda Bot
33
on:
44
issue_comment:
55
types: [created]
6-
if: startsWith(github.event.comment.body, '@TheRespectPanda')
76

87
jobs:
98
listen-comment:
9+
if: startsWith(github.event.comment.body, '@TheRespectPanda')
1010
runs-on: ubuntu-latest
1111
permissions:
1212
issues: write
13-
pull-requests: write
13+
actions: write
1414
steps:
1515
- uses: actions/github-script@v8
1616
with:
1717
github-token: ${{ secrets.PANDA_GITHUB_PAT }}
1818
script: |
1919
const body = (context.payload.comment && context.payload.comment.body).trim() || '';
20-
const usage = 'Usage: `@TheRespectPanda ping|help`';
20+
const usage = 'Usage: `@TheRespectPanda ping|help|benchmark`';
2121
2222
if (!body.startsWith('@TheRespectPanda')) {
2323
return;
2424
}
2525
26+
let answer;
27+
2628
switch (body) {
2729
case '@TheRespectPanda ping':
2830
answer = 'Pong! 🐼';
2931
break;
32+
3033
case '@TheRespectPanda':
3134
case '@TheRespectPanda help':
3235
answer = 'Hello! I am TheRespectPanda Bot. ' + usage;
3336
break;
37+
38+
case '@TheRespectPanda benchmark':
39+
// Only runnable on pull requests
40+
if (!context.payload.issue.pull_request) {
41+
answer = 'The `benchmark` command can only be used on pull requests.';
42+
break;
43+
}
44+
45+
// Only members can trigger benchmarks
46+
const association = context.payload.comment.author_association;
47+
const allowedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
48+
if (!allowedAssociations.includes(association)) {
49+
answer = 'Only repository members can trigger benchmarks.';
50+
break;
51+
}
52+
53+
try {
54+
// dispatch the perf workflow
55+
const ref = (context.payload.repository && context.payload.repository.default_branch) || 'main';
56+
57+
const workflowId = 'ci-perf.yml';
58+
await github.rest.actions.createWorkflowDispatch({
59+
owner: context.repo.owner,
60+
repo: context.repo.repo,
61+
workflow_id: workflowId,
62+
ref,
63+
inputs: {
64+
baseline: 'latest',
65+
pull: String(context.issue.number)
66+
}
67+
});
68+
69+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/workflows/${workflowId}`;
70+
answer = `Triggered phpbench benchmarks for PR #${context.issue.number} — workflow run: ${runUrl}`;
71+
} catch (err) {
72+
answer = `Failed to trigger benchmarks: ${err.message}`;
73+
}
74+
75+
break;
76+
3477
default:
3578
answer = "I'm sorry, I don't understand that command. " + usage;
3679
}

0 commit comments

Comments
 (0)