1name: Bot - Benchmark PR Comment
2
3on:
4 workflow_run:
5 workflows: ["Benchmarks"]
6 types: [completed]
7
8permissions:
9 contents: read
10 pull-requests: write
11 actions: read
12
13jobs:
14 comment:
15 if: github.event.workflow_run.event == 'pull_request'
16 runs-on: ubuntu-latest
17 steps:
18 - name: Download benchmarks artifact
19 uses: actions/github-script@v9
20 with:
21 script: |
22 const run_id = context.payload.workflow_run.id;
23 const { data: list } = await github.rest.actions.listWorkflowRunArtifacts({
24 owner: context.repo.owner,
25 repo: context.repo.repo,
26 run_id,
27 });
28 const art = list.artifacts.find(a => a.name === 'benchmarks');
29 if (!art) {
30 core.setFailed('benchmarks artifact missing');
31 return;
32 }
33 const dl = await github.rest.actions.downloadArtifact({
34 owner: context.repo.owner,
35 repo: context.repo.repo,
36 artifact_id: art.id,
37 archive_format: 'zip',
38 });
39 const fs = require('fs');
40 fs.writeFileSync('benchmarks.zip', Buffer.from(dl.data));
41
42 - name: Unzip
43 run: |
44 mkdir -p bench
45 unzip -o benchmarks.zip -d bench
46 ls -la bench
47
48 - name: Post comment
49 uses: actions/github-script@v9
50 with:
51 github-token: ${{ secrets.HOMEBREW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
52 script: |
53 const fs = require('fs');
54 const read = (p, fallback = '') => {
55 try { return fs.readFileSync(p, 'utf8'); } catch (_) { return fallback; }
56 };
57
58 const prRaw = read('bench/pr-number.txt').trim();
59 const pr_number = parseInt(prRaw, 10);
60 if (!pr_number) {
61 core.info('no PR number recorded; skipping');
62 return;
63 }
64
65 const report = read('bench/benchstat.txt', '(empty)');
66 const verdict = read('bench/verdict.txt');
67 const parse = key => {
68 const m = verdict.match(new RegExp("^" + key + "=(.+)$", "m"));
69 return m ? m[1].trim() : '';
70 };
71 const status = parse('status') || 'neutral';
72 const worse = parse('worse') || '0';
73 const better = parse('better') || '0';
74
75 const headers = {
76 regression: "### Benchmark report — regression detected",
77 improvement: "### Benchmark report — improvement detected",
78 neutral: "### Benchmark report — no significant change",
79 };
80 const body = [
81 headers[status] || headers.neutral,
82 `Metrics worse: **${worse}** · better: **${better}** (threshold: ±3%).`,
83 "",
84 "<details><summary>benchstat output</summary>",
85 "",
86 "```",
87 report.trim() || "(empty)",
88 "```",
89 "",
90 "</details>",
91 "",
92 "<sub>auto-generated by benchmarks.yml</sub>",
93 ].join("\n");
94
95 const marker = "<sub>auto-generated by benchmarks.yml</sub>";
96 const { data: comments } = await github.rest.issues.listComments({
97 owner: context.repo.owner,
98 repo: context.repo.repo,
99 issue_number: pr_number,
100 });
101 const existing = comments.find(c => c.body && c.body.includes(marker));
102 if (existing) {
103 await github.rest.issues.updateComment({
104 owner: context.repo.owner,
105 repo: context.repo.repo,
106 comment_id: existing.id,
107 body,
108 });
109 } else {
110 await github.rest.issues.createComment({
111 owner: context.repo.owner,
112 repo: context.repo.repo,
113 issue_number: pr_number,
114 body,
115 });
116 }