|
1 | 1 | name: Performance regression |
2 | 2 |
|
3 | | -# Nightly (and release-candidate) T5 perf gate: runtime slowdown <= 20% and |
4 | | -# memory overhead <= 15% versus the cached v1.0-equivalent baseline. |
| 3 | +# Same-run A/B perf gate on the CleanBench T5 workload: HEAD is measured against |
| 4 | +# a base commit on the same runner, interleaved, and fails only when it is >20% |
| 5 | +# slower or uses >15% more peak memory than base AND a confirmation re-run |
| 6 | +# reproduces the breach. No absolute numbers from other machines are compared. |
| 7 | +# |
| 8 | +# pull_request PR head vs the PR base commit (blocking) |
| 9 | +# daily main vs main as of ~26 hours earlier (blocking, alert issue) |
| 10 | +# weekly main vs the latest release tag (report only) |
| 11 | +# manual any base_ref; accept_regression=true reports without failing |
5 | 12 | on: |
6 | 13 | schedule: |
7 | 14 | - cron: "0 5 * * *" |
| 15 | + - cron: "30 5 * * 1" |
| 16 | + pull_request: |
| 17 | + paths: |
| 18 | + - "src/freshdata/**" |
| 19 | + - "benchmarks/cleanbench/**" |
| 20 | + - ".github/workflows/perf-regression.yml" |
8 | 21 | workflow_dispatch: |
9 | 22 | inputs: |
10 | | - update_baseline: |
11 | | - description: "Re-pin the perf baseline to this run" |
| 23 | + base_ref: |
| 24 | + description: "Base ref to compare against (default: main as of 26h ago)" |
| 25 | + type: string |
| 26 | + default: "" |
| 27 | + accept_regression: |
| 28 | + description: "Report only; do not fail on a regression" |
12 | 29 | type: boolean |
13 | 30 | default: false |
14 | 31 |
|
| 32 | +concurrency: |
| 33 | + group: perf-regression-${{ github.ref }} |
| 34 | + cancel-in-progress: true |
| 35 | + |
| 36 | +permissions: |
| 37 | + contents: read |
| 38 | + |
15 | 39 | jobs: |
16 | 40 | perf: |
17 | 41 | runs-on: ubuntu-latest |
18 | | - timeout-minutes: 20 |
| 42 | + timeout-minutes: 45 |
19 | 43 | permissions: |
20 | | - issues: write |
21 | 44 | contents: read |
| 45 | + issues: write |
22 | 46 | steps: |
23 | 47 | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 48 | + with: |
| 49 | + fetch-depth: 0 # the base commit and release tags must be resolvable |
24 | 50 | - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 |
25 | 51 | with: |
26 | 52 | python-version: "3.12" |
| 53 | + cache: pip |
27 | 54 | - name: Install |
28 | 55 | run: | |
29 | 56 | python -m pip install -U pip |
30 | 57 | pip install -e ".[bench]" |
31 | | - - name: Restore perf baseline |
32 | | - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 |
33 | | - with: |
34 | | - path: benchmarks/cleanbench/results/baseline_v1.json |
35 | | - key: cleanbench-baseline-${{ runner.os }}-v1 |
36 | | - - name: T5 perf gate |
| 58 | + - name: Resolve base commit |
| 59 | + id: base |
| 60 | + env: |
| 61 | + EVENT: ${{ github.event_name }} |
| 62 | + SCHEDULE: ${{ github.event.schedule }} |
| 63 | + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} |
| 64 | + INPUT_BASE_REF: ${{ inputs.base_ref }} |
| 65 | + INPUT_ACCEPT: ${{ inputs.accept_regression }} |
| 66 | + run: | |
| 67 | + set -euo pipefail |
| 68 | + mode=gate |
| 69 | + if [ "$EVENT" = "pull_request" ]; then |
| 70 | + ref="$PR_BASE_SHA" |
| 71 | + elif [ "$EVENT" = "workflow_dispatch" ] && [ -n "$INPUT_BASE_REF" ]; then |
| 72 | + ref="$INPUT_BASE_REF" |
| 73 | + elif [ "$SCHEDULE" = "30 5 * * 1" ]; then |
| 74 | + ref="$(git describe --tags --abbrev=0 --match 'v*' origin/main)" |
| 75 | + mode=report |
| 76 | + else |
| 77 | + ref="$(git rev-list -1 --before='26 hours ago' origin/main)" |
| 78 | + fi |
| 79 | + if [ "$INPUT_ACCEPT" = "true" ]; then mode=report; fi |
| 80 | + sha="$(git rev-parse --verify "${ref}^{commit}")" |
| 81 | + { |
| 82 | + echo "ref=$ref" |
| 83 | + echo "sha=$sha" |
| 84 | + echo "mode=$mode" |
| 85 | + } >> "$GITHUB_OUTPUT" |
| 86 | + if [ "$sha" = "$(git rev-parse HEAD)" ]; then |
| 87 | + echo "skip=true" >> "$GITHUB_OUTPUT" |
| 88 | + echo "Base \`$ref\` is HEAD; nothing to compare." >> "$GITHUB_STEP_SUMMARY" |
| 89 | + else |
| 90 | + echo "skip=false" >> "$GITHUB_OUTPUT" |
| 91 | + git worktree add --detach "$RUNNER_TEMP/perf-base" "$sha" |
| 92 | + fi |
| 93 | + - name: Same-run A/B perf gate |
| 94 | + if: steps.base.outputs.skip != 'true' |
| 95 | + env: |
| 96 | + BASE_REF: ${{ steps.base.outputs.ref }} |
| 97 | + BASE_SHA: ${{ steps.base.outputs.sha }} |
| 98 | + MODE: ${{ steps.base.outputs.mode }} |
37 | 99 | run: | |
38 | | - EXTRA="" |
39 | | - if [ "${{ inputs.update_baseline }}" = "true" ]; then EXTRA="--update-baseline"; fi |
40 | | - python -m benchmarks.cleanbench --tracks T5 --check-gates $EXTRA |
| 100 | + GATE="" |
| 101 | + if [ "$MODE" = "gate" ]; then GATE="--check-gates"; fi |
| 102 | + python -m benchmarks.cleanbench.ab \ |
| 103 | + --base-src "$RUNNER_TEMP/perf-base/src" \ |
| 104 | + --head-src src \ |
| 105 | + --base-label "$BASE_REF (${BASE_SHA:0:7})" \ |
| 106 | + --head-label "HEAD (${GITHUB_SHA:0:7})" \ |
| 107 | + --output benchmarks/cleanbench/results/latest.ab.json \ |
| 108 | + --summary "$GITHUB_STEP_SUMMARY" \ |
| 109 | + $GATE |
41 | 110 | - name: Upload results |
42 | | - if: always() |
| 111 | + if: always() && steps.base.outputs.skip == 'false' |
43 | 112 | uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
44 | 113 | with: |
45 | | - name: perf-results |
46 | | - path: benchmarks/cleanbench/results/latest.* |
| 114 | + name: perf-ab-results |
| 115 | + path: benchmarks/cleanbench/results/latest.ab.json |
47 | 116 | retention-days: 30 |
48 | 117 | - name: Open/refresh alert issue on failure |
49 | | - if: failure() |
| 118 | + if: failure() && github.event_name == 'schedule' |
50 | 119 | uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
51 | 120 | with: |
52 | 121 | script: | |
53 | 122 | const title = "nightly perf-regression gate failing"; |
54 | | - const body = `The scheduled T5 performance-regression gate failed ` + |
55 | | - `(runtime > 120% or memory > 115% of baseline).\n\n` + |
| 123 | + const body = `The scheduled same-run A/B performance gate failed: main ran ` + |
| 124 | + `>20% slower or used >15% more peak memory than main from ~26 hours ` + |
| 125 | + `earlier, reproduced on a confirmation run (or the job errored).\n\n` + |
56 | 126 | `Run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}` + |
57 | 127 | `/actions/runs/${context.runId}`; |
58 | 128 | const open = await github.rest.issues.listForRepo({ |
|
0 commit comments