Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions .github/workflows/benchmark-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
name: Benchmark Comment

on:
workflow_run:
workflows: [Benchmark]
types: [completed]

# Artifact parsing and comment publication intentionally use separate tokens.
# Neither job checks out or executes code from the triggering pull request.
permissions: {}

concurrency:
group: benchmark-comment-${{ github.event.workflow_run.head_sha }}
cancel-in-progress: true

jobs:
prepare:
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
actions: read
outputs:
report: ${{ steps.report.outputs.report }}

steps:
- name: Download Bun benchmark artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: benchmark-results-bun-latest
path: ${{ runner.temp }}/benchmark-results

- name: Download Node.js 22 benchmark artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: benchmark-results-node-22
path: ${{ runner.temp }}/benchmark-results

- name: Download Node.js 24 benchmark artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: benchmark-results-node-24
path: ${{ runner.temp }}/benchmark-results

- name: Download Node.js 25 benchmark artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
name: benchmark-results-node-25
path: ${{ runner.temp }}/benchmark-results

- name: Sanitize benchmark report
id: report
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
BENCHMARK_RESULTS: ${{ runner.temp }}/benchmark-results
with:
script: |
const fs = require('fs')
const path = require('path')

const root = fs.realpathSync(process.env.BENCHMARK_RESULTS)
const expectedRuntimes = ['bun-latest', 'node-22', 'node-24', 'node-25']

const truncateUtf8 = (value, maxBytes) => {
const bytes = Buffer.from(value, 'utf8')
if (bytes.length <= maxBytes) return value

let end = maxBytes
while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--
return bytes.subarray(0, end).toString('utf8')
}
const sanitize = value =>
truncateUtf8(value, 13000)
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/<[^>]*>/g, '')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '')
.replace(/[\u202a-\u202e\u2066-\u2069]/g, '')
.replaceAll('@', '@\u200b')
.replaceAll('](', ']\u200b(')
.replace(/https?:\/\//gi, match => match.replace('://', ':\u200b//'))

const sections = expectedRuntimes.map(runtime => {
const name = `final-comparison-${runtime}.md`
const file = path.join(root, name)
const metadata = fs.lstatSync(file)
if (!metadata.isFile() || metadata.size > 50000) {
throw new Error(`Invalid benchmark comparison artifact: ${name}`)
}
if (path.dirname(fs.realpathSync(file)) !== root) {
throw new Error(`Benchmark artifact escapes its directory: ${name}`)
}
return sanitize(fs.readFileSync(file, 'utf8'))
})

const report = [
'> The tables below were generated by pull-request-controlled benchmark code.',
'> Links, HTML, mentions, control characters, and bidirectional controls were sanitized.',
'',
...sections
].join('\n')
if (Buffer.byteLength(report, 'utf8') > 56000) {
throw new Error('Sanitized benchmark report exceeds the size limit')
}

core.setOutput('report', Buffer.from(report, 'utf8').toString('base64'))

publish:
needs: prepare
if: needs.prepare.result == 'success' && needs.prepare.outputs.report != ''
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
issues: write
pull-requests: read

steps:
- name: Create or update benchmark comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
BENCHMARK_REPORT: ${{ needs.prepare.outputs.report }}
with:
script: |
const marker = '<!-- benchmark-results -->'
const runIdMarker = '<!-- benchmark-run-id:'
const run = context.payload.workflow_run
const expectedRepository = `${context.repo.owner}/${context.repo.repo}`

if (
run.name !== 'Benchmark' ||
run.path !== '.github/workflows/benchmark.yml' ||
run.event !== 'pull_request' ||
run.conclusion !== 'success' ||
run.repository.full_name !== expectedRepository
) {
throw new Error('Unexpected benchmark workflow identity')
}

const associatedPullRequests = await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit,
{
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: run.head_sha,
per_page: 100
}
)
const candidates = associatedPullRequests.filter(pullRequest =>
pullRequest.state === 'open' &&
pullRequest.base.repo.full_name === expectedRepository &&
['main', 'master'].includes(pullRequest.base.ref) &&
pullRequest.head.sha === run.head_sha &&
pullRequest.head.repo?.full_name === run.head_repository?.full_name
)
if (candidates.length === 0) {
core.warning('No current pull request matches this benchmark run')
return
}
if (candidates.length !== 1) {
throw new Error('Benchmark run is associated with multiple matching pull requests')
}

const { data: pullRequest } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: candidates[0].number
})
if (
pullRequest.state !== 'open' ||
pullRequest.base.repo.full_name !== expectedRepository ||
!['main', 'master'].includes(pullRequest.base.ref) ||
pullRequest.head.sha !== run.head_sha ||
pullRequest.head.repo?.full_name !== run.head_repository?.full_name
) {
core.info('Skipping results from a stale or retargeted pull request run')
return
}

const report = Buffer.from(process.env.BENCHMARK_REPORT, 'base64').toString('utf8')
if (Buffer.byteLength(report, 'utf8') > 56000) {
throw new Error('Prepared benchmark report exceeds the size limit')
}

const issueNumber = pullRequest.number
const shortHead = run.head_sha.slice(0, 7)
const commitUrl = `${context.serverUrl}/${expectedRepository}/commit/${run.head_sha}`
const body = [
marker,
`${runIdMarker}${run.id} -->`,
'# 🚀 Benchmark Results',
'',
`Benchmark report for PR head [\`${shortHead}\`](${commitUrl}) from [workflow run ${run.id}](${run.html_url}).`,
'',
report,
'',
'<details>',
'<summary>ℹ️ Benchmark Details</summary>',
'',
`- **Base branch**: \`${pullRequest.base.ref}\``,
`- **PR head**: [\`${shortHead}\`](${commitUrl})`,
`- **Workflow run**: [${run.id}](${run.html_url})`,
`- **Completed**: ${run.updated_at}`,
'</details>'
].join('\n').slice(0, 60000)

const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
})
const previous = comments.find(comment =>
comment.user?.login === 'github-actions[bot]' && comment.body?.startsWith(marker)
)

if (previous) {
const previousRunId = Number(
previous.body?.match(/<!-- benchmark-run-id:(\d+) -->/)?.[1] ?? 0
)
if (previousRunId > run.id) {
core.info(`Skipping older workflow run ${run.id}`)
return
}
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previous.id,
body
})
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body
})
}
Loading
Loading