Skip to content
Merged
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
213 changes: 180 additions & 33 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ permissions:
contents: write
pull-requests: write

concurrency:
group: bench-${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
codspeed:
if: github.event_name == 'push'
runs-on: depot-ubuntu-latest
concurrency:
group: bench-codspeed-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
strategy:
matrix:
partition: [1, 2]
Expand Down Expand Up @@ -90,16 +89,22 @@ jobs:
mode: instrumentation
token: ${{ secrets.CODSPEED_TOKEN }}

reth-bench:
reth-bench-ack:
if: |
(github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, 'derek bench')) ||
github.event_name == 'workflow_dispatch'
name: reth-bench
runs-on: [self-hosted, Linux, X64]
timeout-minutes: 120
env:
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
SCHELK_MOUNT: /reth-bench
name: reth-bench-ack
runs-on: ubuntu-latest
outputs:
pr: ${{ steps.args.outputs.pr }}
actor: ${{ steps.args.outputs.actor }}
blocks: ${{ steps.args.outputs.blocks }}
warmup: ${{ steps.args.outputs.warmup }}
baseline: ${{ steps.args.outputs.baseline }}
feature: ${{ steps.args.outputs.feature }}
baseline-name: ${{ steps.args.outputs.baseline-name }}
feature-name: ${{ steps.args.outputs.feature-name }}
comment-id: ${{ steps.ack.outputs.comment-id }}
steps:
- name: Check org membership
if: github.event_name == 'issue_comment'
Expand Down Expand Up @@ -202,18 +207,30 @@ jobs:
feature = defaults.feature;
}

core.exportVariable('BENCH_PR', pr);
core.exportVariable('BENCH_ACTOR', actor);
core.exportVariable('BENCH_BLOCKS', blocks);
core.exportVariable('BENCH_WARMUP_BLOCKS', warmup);
// Resolve display names for baseline/feature
let baselineName = baseline || 'main';
let featureName = feature;
if (!featureName) {
if (pr) {
const { data: prData } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: parseInt(pr),
});
featureName = prData.head.ref;
} else {
featureName = '${{ github.ref_name }}';
}
}

core.setOutput('pr', pr || '');
core.setOutput('actor', actor);
core.setOutput('blocks', blocks);
core.setOutput('warmup', warmup);
core.setOutput('baseline', baseline);
core.setOutput('feature', feature);

- uses: actions/checkout@v6
with:
submodules: true
fetch-depth: 0
ref: ${{ env.BENCH_PR && format('refs/pull/{0}/merge', env.BENCH_PR) || github.ref }}
core.setOutput('baseline-name', baselineName);
core.setOutput('feature-name', featureName);

- name: Acknowledge request
id: ack
Expand All @@ -229,36 +246,166 @@ jobs:
});
}

const pr = process.env.BENCH_PR;
const pr = '${{ steps.args.outputs.pr }}';
if (!pr) return;

// Resolve job URL for direct linking to logs
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

// Count queued/waiting bench runs ahead of this one
let queueMsg = '';
let ahead = 0;
try {
const statuses = ['queued', 'in_progress', 'waiting', 'requested', 'pending'];
const allRuns = [];
for (const status of statuses) {
const { data: { workflow_runs: r } } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'bench.yml',
status,
per_page: 100,
});
allRuns.push(...r);
}
// Only count runs that trigger reth-bench (not push-triggered codspeed runs)
const benchRuns = allRuns.filter(r => r.event === 'issue_comment' || r.event === 'workflow_dispatch');
const thisRun = benchRuns.find(r => r.id === context.runId);
const thisCreatedAt = thisRun ? new Date(thisRun.created_at) : new Date();
ahead = benchRuns.filter(r => r.id !== context.runId && new Date(r.created_at) <= thisCreatedAt).length;
if (ahead > 0) {
queueMsg = `\n🔢 **Queue position:** \`#${ahead + 1}\` (${ahead} job(s) ahead)`;
}
} catch (e) {
// Non-fatal — queue info is best-effort
core.info(`Could not fetch queue info: ${e.message}`);
}

const actor = '${{ steps.args.outputs.actor }}';
const blocks = '${{ steps.args.outputs.blocks }}';
const warmup = '${{ steps.args.outputs.warmup }}';
const baseline = '${{ steps.args.outputs.baseline-name }}';
const feature = '${{ steps.args.outputs.feature-name }}';
const config = `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``;

const { data: comment } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(pr),
body: `cc @${actor}\n\n🚀 Benchmark queued! [View run](${runUrl})\n\n⏳ **Status:** Waiting for runner...${queueMsg}\n\n${config}`,
});
core.setOutput('comment-id', String(comment.id));
core.setOutput('queue-position', String(ahead || 0));

- name: Poll queue position
if: steps.ack.outputs.comment-id && steps.ack.outputs.queue-position != '0'
uses: actions/github-script@v7
with:
script: |
const pr = '${{ steps.args.outputs.pr }}';
const commentId = parseInt('${{ steps.ack.outputs.comment-id }}');
const actor = '${{ steps.args.outputs.actor }}';
const blocks = '${{ steps.args.outputs.blocks }}';
const warmup = '${{ steps.args.outputs.warmup }}';
const baseline = '${{ steps.args.outputs.baseline-name }}';
const feature = '${{ steps.args.outputs.feature-name }}';
const config = `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``;
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;

async function getQueuePosition() {
const statuses = ['queued', 'in_progress', 'waiting', 'requested', 'pending'];
const allRuns = [];
for (const status of statuses) {
const { data: { workflow_runs: r } } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'bench.yml',
status,
per_page: 100,
});
allRuns.push(...r);
}
const benchRuns = allRuns.filter(r => r.event === 'issue_comment' || r.event === 'workflow_dispatch');
const thisRun = benchRuns.find(r => r.id === context.runId);
const thisCreatedAt = thisRun ? new Date(thisRun.created_at) : new Date();
return benchRuns.filter(r => r.id !== context.runId && new Date(r.created_at) <= thisCreatedAt).length;
}

let lastPosition = parseInt('${{ steps.ack.outputs.queue-position }}');
const sleep = ms => new Promise(r => setTimeout(r, ms));

while (true) {
await sleep(10_000);
try {
const ahead = await getQueuePosition();
if (ahead !== lastPosition) {
lastPosition = ahead;
const queueMsg = ahead > 0
? `\n🔢 **Queue position:** \`#${ahead + 1}\` (${ahead} job(s) ahead)`
: '';
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: commentId,
body: `cc @${actor}\n\n🚀 Benchmark queued! [View run](${runUrl})\n\n⏳ **Status:** Waiting for runner...${queueMsg}\n\n${config}`,
});
}
if (ahead === 0) break;
} catch (e) {
core.info(`Queue poll error: ${e.message}`);
}
}

reth-bench:
needs: reth-bench-ack
name: reth-bench
runs-on: [self-hosted, Linux, X64]
timeout-minutes: 120
concurrency:
group: reth-bench-queue
cancel-in-progress: false
env:
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
SCHELK_MOUNT: /reth-bench
BENCH_PR: ${{ needs.reth-bench-ack.outputs.pr }}
BENCH_ACTOR: ${{ needs.reth-bench-ack.outputs.actor }}
BENCH_BLOCKS: ${{ needs.reth-bench-ack.outputs.blocks }}
BENCH_WARMUP_BLOCKS: ${{ needs.reth-bench-ack.outputs.warmup }}
BENCH_COMMENT_ID: ${{ needs.reth-bench-ack.outputs.comment-id }}
steps:
- uses: actions/checkout@v6
with:
submodules: true
fetch-depth: 0
ref: ${{ env.BENCH_PR && format('refs/pull/{0}/merge', env.BENCH_PR) || github.ref }}

- name: Resolve job URL and update status
if: env.BENCH_COMMENT_ID
uses: actions/github-script@v7
with:
script: |
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
const job = jobs.jobs.find(j => j.name === 'reth-bench');
const jobUrl = job ? job.html_url : `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
core.exportVariable('BENCH_JOB_URL', jobUrl);

const blocks = process.env.BENCH_BLOCKS;
const warmup = process.env.BENCH_WARMUP_BLOCKS;
const baseline = '${{ steps.args.outputs.baseline }}' || 'merge-base';
const feature = '${{ steps.args.outputs.feature }}' || 'branch head';

core.exportVariable('BENCH_JOB_URL', jobUrl);
const baseline = '${{ needs.reth-bench-ack.outputs.baseline-name }}';
const feature = '${{ needs.reth-bench-ack.outputs.feature-name }}';
core.exportVariable('BENCH_CONFIG', `**Config:** ${blocks} blocks, ${warmup} warmup blocks, baseline: \`${baseline}\`, feature: \`${feature}\``);

const { buildBody } = require('./.github/scripts/bench-update-status.js');
const { data: comment } = await github.rest.issues.createComment({
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: parseInt(pr),
comment_id: parseInt(process.env.BENCH_COMMENT_ID),
body: buildBody('Building baseline binary...'),
});

core.exportVariable('BENCH_COMMENT_ID', comment.id);

- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
continue-on-error: true
Expand Down Expand Up @@ -301,8 +448,8 @@ jobs:
- name: Resolve refs
id: refs
run: |
BASELINE_ARG="${{ steps.args.outputs.baseline }}"
FEATURE_ARG="${{ steps.args.outputs.feature }}"
BASELINE_ARG="${{ needs.reth-bench-ack.outputs.baseline }}"
FEATURE_ARG="${{ needs.reth-bench-ack.outputs.feature }}"

if [ -n "$BASELINE_ARG" ]; then
git fetch origin "$BASELINE_ARG" --quiet 2>/dev/null || true
Expand Down
Loading