@@ -30,28 +30,32 @@ jobs:
3030 \`\`\`
3131
3232 **Examples:**
33- - \`/benchmark\` - Run with default solver , all scenarios
33+ - \`/benchmark\` - Run all solvers , all scenarios
3434 - \`/benchmark AutoroutingPipelineSolver3_HgPortPointPathing\` - Run with specific solver
3535 - \`/benchmark AutoroutingPipelineSolver3_HgPortPointPathing 10\` - Run with specific solver, 10 scenarios
36- - \`/benchmark _ 20\` - Run with default solver , 20 scenarios`
36+ - \`/benchmark _ 20\` - Run all solvers , 20 scenarios`
3737 });
3838
39- benchmark :
40- name : Run autorouting benchmark
41- # Only run on PR comments with /benchmark command from maintainers (not bots)
39+ setup :
40+ name : Setup benchmark
4241 if : |
4342 github.event_name == 'issue_comment' &&
4443 github.event.issue.pull_request &&
4544 github.event.comment.user.type != 'Bot' &&
46- contains (github.event.comment.body, '/benchmark') &&
45+ startsWith (github.event.comment.body, '/benchmark') &&
4746 (
4847 github.event.comment.author_association == 'OWNER' ||
4948 github.event.comment.author_association == 'MEMBER' ||
5049 github.event.comment.author_association == 'COLLABORATOR'
5150 )
5251 runs-on : ubuntu-latest
53- timeout-minutes : 30
52+ timeout-minutes : 360
5453
54+ outputs :
55+ solvers : ${{ steps.discover.outputs.solvers }}
56+ scenario_limit : ${{ steps.parse.outputs.scenario_limit }}
57+ comment_id : ${{ steps.running-comment.outputs.comment_id }}
58+ sha : ${{ steps.parse.outputs.sha }}
5559 steps :
5660 - name : React to comment and post running status
5761 id : running-comment
7478 });
7579 core.setOutput('comment_id', comment.data.id);
7680
77- - name : Get PR branch and parse solver name
78- id : pr-info
81+ - name : Parse command and get PR info
82+ id : parse
7983 uses : actions/github-script@v7
8084 with :
8185 script : |
8488 repo: context.repo.repo,
8589 pull_number: context.issue.number
8690 });
87- core.setOutput('ref', pr.data.head.ref);
8891 core.setOutput('sha', pr.data.head.sha);
8992
90- // Parse solver name and scenario limit from comment: /benchmark [SolverName] [limit]
91- // Examples: /benchmark, /benchmark SolverName, /benchmark SolverName 50, /benchmark _ 50
9293 const comment = context.payload.comment.body;
9394 const match = comment.match(/\/benchmark(?:\s+(\S+))?(?:\s+(\d+))?/);
9495 const solverName = match && match[1] && match[1] !== '_' ? match[1] : '';
@@ -101,6 +102,48 @@ jobs:
101102 with :
102103 ref : refs/pull/${{ github.event.issue.number }}/head
103104
105+ - name : Discover solvers from exports
106+ id : discover
107+ uses : actions/github-script@v7
108+ with :
109+ script : |
110+ const fs = require('fs');
111+
112+ // Read the pipeline solvers index - this is the source of truth for benchmarkable solvers
113+ const pipelineIndex = fs.readFileSync('lib/autorouter-pipelines/index.ts', 'utf8');
114+ const pipelineNames = [...pipelineIndex.matchAll(/export\s*\{\s*(\w+)\s*\}/g)].map(m => m[1]);
115+
116+ // Read lib/index.ts to find aliases (e.g. "X as Y")
117+ const libIndex = fs.readFileSync('lib/index.ts', 'utf8');
118+
119+ // Build a map of original name -> exported name (alias or original)
120+ const solvers = [];
121+ for (const name of pipelineNames) {
122+ const aliasMatch = libIndex.match(new RegExp(`${name}\\s+as\\s+(\\w+)`));
123+ solvers.push(aliasMatch ? aliasMatch[1] : name);
124+ }
125+
126+ const solverName = '${{ steps.parse.outputs.solver_name }}';
127+ const finalSolvers = solverName ? [solverName] : solvers;
128+
129+ console.log('Discovered solvers:', finalSolvers);
130+ core.setOutput('solvers', JSON.stringify(finalSolvers));
131+
132+ benchmark :
133+ name : Benchmark ${{ matrix.solver }}
134+ needs : setup
135+ runs-on : ubuntu-latest
136+ timeout-minutes : 360
137+ strategy :
138+ fail-fast : false
139+ matrix :
140+ solver : ${{ fromJson(needs.setup.outputs.solvers) }}
141+ steps :
142+ - name : Checkout PR branch
143+ uses : actions/checkout@v4
144+ with :
145+ ref : refs/pull/${{ github.event.issue.number }}/head
146+
104147 - name : Setup bun
105148 uses : oven-sh/setup-bun@v2
106149 with :
@@ -121,75 +164,138 @@ jobs:
121164 export * from "./lib"
122165 EOF
123166
124- - name : Run benchmark
167+ - name : Run benchmark for ${{ matrix.solver }}
125168 id : benchmark
126169 run : |
127- SOLVER_NAME="${{ steps.pr-info.outputs.solver_name }}"
128- SCENARIO_LIMIT="${{ steps.pr-info.outputs.scenario_limit }}"
129-
130- # Build command with optional solver name and scenario limit
131- CMD="autorouting-dataset-runner benchmark-solver.ts"
132-
133- if [ -n "$SOLVER_NAME" ]; then
134- CMD="$CMD $SOLVER_NAME"
135- echo "Running benchmark with solver: $SOLVER_NAME"
136- else
137- echo "Running benchmark with default solver"
138- fi
170+ SCENARIO_LIMIT="${{ needs.setup.outputs.scenario_limit }}"
171+ CMD="autorouting-dataset-runner benchmark-solver.ts ${{ matrix.solver }}"
139172
140173 if [ -n "$SCENARIO_LIMIT" ]; then
141174 CMD="$CMD --scenario-limit $SCENARIO_LIMIT"
142- echo "Scenario limit: $SCENARIO_LIMIT"
143- else
144- echo "Running all scenarios"
145175 fi
146176
147- # Run and capture output while showing live progress
148177 $CMD 2>&1 | tee /tmp/benchmark_output.txt || true
149178
150- # Extract only key info for the comment (to avoid "Argument list too long" error)
151- {
152- echo "BENCHMARK_OUTPUT<<EOFMARKER"
153- # Get loading/using autorouter lines
154- grep -E "^(Loading|✓ Using|✓ From)" /tmp/benchmark_output.txt || true
155- echo ""
156- # Get the results table (lines with + or |)
157- grep -E "^[+|]" /tmp/benchmark_output.txt || true
158- echo ""
159- # Get summary lines
160- grep -E "^(Scenarios:|✓)" /tmp/benchmark_output.txt | tail -3 || true
161- echo "EOFMARKER"
162- } >> $GITHUB_OUTPUT
179+ # Extract the data row from the results table and save to file
180+ mkdir -p /tmp/benchmark-row
181+ grep -E "^\|" /tmp/benchmark_output.txt | grep -v "Completed %" > /tmp/benchmark-row/row.txt || true
163182
164183 - name : Upload benchmark results
165184 uses : actions/upload-artifact@v4
166185 with :
167- name : benchmark- results
186+ name : results-${{ matrix.solver }}
168187 path : results/
169188 retention-days : 30
170189
171- - name : Update comment with results
190+ - name : Upload benchmark row
191+ uses : actions/upload-artifact@v4
192+ with :
193+ name : benchmark-row-${{ matrix.solver }}
194+ path : /tmp/benchmark-row/row.txt
195+ retention-days : 1
196+
197+ report :
198+ name : Post benchmark results
199+ needs : [setup, benchmark]
200+ if : always() && needs.setup.result == 'success'
201+ runs-on : ubuntu-latest
202+ steps :
203+ - name : Download benchmark row artifacts
204+ uses : actions/download-artifact@v4
205+ with :
206+ path : benchmark-rows/
207+ pattern : benchmark-row-*
208+
209+ - name : Download all benchmark result artifacts
210+ uses : actions/download-artifact@v4
211+ with :
212+ path : all-results/
213+ pattern : results-*
214+ merge-multiple : true
215+
216+ - name : Upload combined benchmark results
217+ uses : actions/upload-artifact@v4
218+ with :
219+ name : benchmark-results
220+ path : all-results/
221+ retention-days : 30
222+
223+ - name : Delete per-solver artifacts
224+ uses : actions/github-script@v7
225+ with :
226+ script : |
227+ const artifacts = await github.rest.actions.listArtifactsForRepo({
228+ owner: context.repo.owner,
229+ repo: context.repo.repo,
230+ per_page: 100
231+ });
232+ const runArtifacts = artifacts.data.artifacts.filter(a =>
233+ a.workflow_run?.id === ${{ github.run_id }} &&
234+ (a.name.startsWith('results-') || a.name.startsWith('benchmark-row-'))
235+ );
236+ for (const artifact of runArtifacts) {
237+ await github.rest.actions.deleteArtifact({
238+ owner: context.repo.owner,
239+ repo: context.repo.repo,
240+ artifact_id: artifact.id
241+ });
242+ }
243+
244+ - name : Post combined results
172245 uses : actions/github-script@v7
173246 with :
174247 github-token : ${{ secrets.TSCIRCUIT_BOT_GITHUB_TOKEN }}
175248 script : |
176- const output = `${{ steps.benchmark.outputs.BENCHMARK_OUTPUT }}`;
177- const solverName = '${{ steps.pr-info.outputs.solver_name }}' || 'default';
178- const scenarioLimit = '${{ steps.pr-info.outputs.scenario_limit }}' || 'all';
249+ const fs = require('fs');
250+ const solvers = ${{ needs.setup.outputs.solvers }};
251+
252+ // Parse each solver's result row into columns
253+ const results = [];
254+ for (const solver of solvers) {
255+ const rowFile = `benchmark-rows/benchmark-row-${solver}/row.txt`;
256+ let cols = null;
257+ if (fs.existsSync(rowFile)) {
258+ const row = fs.readFileSync(rowFile, 'utf8').trim();
259+ if (row) {
260+ cols = row.split('|').filter(c => c.trim()).map(c => c.trim());
261+ }
262+ }
263+ if (cols && cols.length >= 5) {
264+ results.push(cols);
265+ } else {
266+ results.push([solver, 'failed', 'n/a', 'n/a', 'n/a']);
267+ }
268+ }
269+
270+ // Calculate max width for each column
271+ const headers = ['Solver', 'Completed %', 'Relaxed DRC Pass %', 'P50 Time', 'P95 Time'];
272+ const widths = headers.map((h, i) =>
273+ Math.max(h.length, ...results.map(r => (r[i] || '').length))
274+ );
275+
276+ const sep = '+' + widths.map(w => '-'.repeat(w + 2)).join('+') + '+';
277+ const headerRow = '|' + headers.map((h, i) => ` ${h.padEnd(widths[i])} `).join('|') + '|';
278+ const dataRows = results.map(r =>
279+ '|' + r.map((c, i) => ` ${c.padEnd(widths[i])} `).join('|') + '|'
280+ ).join('\n');
281+
282+ const table = [sep, headerRow, sep, dataRows, sep].join('\n');
283+
284+ const scenarioLimit = '${{ needs.setup.outputs.scenario_limit }}' || 'all';
179285
180286 await github.rest.issues.updateComment({
181287 owner: context.repo.owner,
182288 repo: context.repo.repo,
183- comment_id: ${{ steps.running-comment .outputs.comment_id }},
289+ comment_id: ${{ needs.setup .outputs.comment_id }},
184290 body: `## 🏃 Autorouting Benchmark Results
185291
186- **Solver :** ${solverName } | **Scenarios:** ${scenarioLimit}
292+ **Solvers :** ${solvers.length } | **Scenarios:** ${scenarioLimit}
187293
188294 \`\`\`
189- ${output }
295+ ${table }
190296 \`\`\`
191297
192298 📊 [Download HTML visualization and bundle](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts)
193299
194- <sub>Triggered by @${{ github.event.comment.user.login }} • Commit: ${{ steps.pr-info .outputs.sha }}</sub>`
300+ <sub>Triggered by @${{ github.event.comment.user.login }} • Commit: ${{ needs.setup .outputs.sha }}</sub>`
195301 });
0 commit comments