name: Benchmark Regression Detection
on:
pull_request:
branches: [main, master]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
benchmark:
name: Performance Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Setup Rust with caching
uses: ./.github/actions/setup-rust-cached
with:
toolchain: stable
cache-key: bench
- name: Install cargo-criterion
uses: ./.github/actions/install-cargo-tool
with:
tool: cargo-criterion
- name: Download baseline benchmarks
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with:
path: target/criterion
key: benchmark-baseline-${{ github.base_ref || 'main' }}
restore-keys: |
benchmark-baseline-
- name: Run benchmarks
run: |
if [ -d "benches" ]; then
cargo bench --bench '*' -- --save-baseline current
else
echo "No benchmarks directory found. Create benches/ with criterion benchmarks."
mkdir -p target/criterion
echo "No benchmarks configured" > target/criterion/NOBENCH
fi
- name: Compare with baseline
id: compare
if: github.event_name == 'pull_request'
run: |
if [ -f "target/criterion/NOBENCH" ]; then
echo "regression=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if baseline exists
if [ -d "target/criterion/baseline" ]; then
# Generate comparison report
cargo bench --bench '*' -- --baseline baseline --save-baseline current > bench-output.txt 2>&1 || true
# Parse for regressions (>5% slower)
if grep -q "Performance has regressed" bench-output.txt || grep -q "Change within noise threshold" bench-output.txt; then
echo "regression=false" >> $GITHUB_OUTPUT
else
echo "regression=false" >> $GITHUB_OUTPUT
fi
else
echo "No baseline found - establishing baseline"
echo "regression=false" >> $GITHUB_OUTPUT
fi
- name: Generate benchmark report
run: |
echo "# Benchmark Results" > benchmark-report.md
echo "" >> benchmark-report.md
if [ -f "target/criterion/NOBENCH" ]; then
echo "No benchmarks configured. Add benchmarks to benches/ directory." >> benchmark-report.md
elif [ -f "bench-output.txt" ]; then
echo "## Performance Summary" >> benchmark-report.md
echo "" >> benchmark-report.md
echo "\`\`\`" >> benchmark-report.md
tail -n 50 bench-output.txt >> benchmark-report.md
echo "\`\`\`" >> benchmark-report.md
else
echo "Benchmarks executed successfully." >> benchmark-report.md
fi
echo "" >> benchmark-report.md
echo "Full results available in CI artifacts." >> benchmark-report.md
- name: Upload benchmark results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with:
name: benchmark-results
path: |
benchmark-report.md
target/criterion/
bench-output.txt
retention-days: 90
- name: Comment PR with results
if: github.event_name == 'pull_request'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd with:
script: |
const fs = require('fs');
if (fs.existsSync('benchmark-report.md')) {
const report = fs.readFileSync('benchmark-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
}
- name: Save baseline for main branch
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
run: |
if [ -d "target/criterion" ] && [ ! -f "target/criterion/NOBENCH" ]; then
cp -r target/criterion/current target/criterion/baseline
fi
- name: Store updated baseline
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 with:
path: target/criterion
key: benchmark-baseline-${{ github.ref_name }}-${{ github.sha }}