nsip 0.4.0

NSIP Search API client for nsipsearch.nsip.org/api
Documentation
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  # v6.0.2

      - 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 # v5.0.3
        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  # v7.0.0
        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 # v8.0.0
        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 # v5.0.3
        with:
          path: target/criterion
          key: benchmark-baseline-${{ github.ref_name }}-${{ github.sha }}