-
Notifications
You must be signed in to change notification settings - Fork 25
210 lines (203 loc) · 7.98 KB
/
Copy pathbenchmark.yml
File metadata and controls
210 lines (203 loc) · 7.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
name: Benchmark
# A/B benchmark of two fuzzamoto-libafl versions: run each version N times for a fixed
# duration in parallel, then aggregate and statistically compare the campaigns.
#
# Bitcoin Core (the target) is built once into a shared image and held constant; only the
# bind-mounted fuzzamoto source differs between the two arms, so the comparison isolates
# the fuzzer change. See ci/benchmark-evaluation.py for the metrics.
#
# Runner specs are inlined in each job's `runs-on:` label (runs-on.com syntax); there is
# no .github/runs-on.yml. Campaign runners are pinned to a single exact instance type so
# every campaign runs on identical hardware (see the campaign job).
#
# Triggers:
# - workflow_dispatch: compare two arbitrary refs (full control over N / duration).
# - PR label 'needs benchmark': full run, N=10, 3600s/campaign (PR base vs head).
# - PR label 'needs benchmark smoke': cheap wiring test, N=2, 300s/campaign.
on:
workflow_dispatch:
inputs:
baseline_ref:
description: "Baseline fuzzamoto git ref"
default: "master"
treatment_ref:
description: "Treatment fuzzamoto git ref"
required: true
runs:
description: "Campaigns per version (N)"
default: "10"
duration_secs:
description: "Seconds per campaign"
default: "3600"
pull_request:
# Gated on the 'needs benchmark' / 'needs benchmark smoke' labels (checked in setup's `if:`).
types: [labeled]
permissions:
contents: read
pull-requests: write # for the PR comment
jobs:
setup:
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'needs benchmark' || github.event.label.name == 'needs benchmark smoke'
runs-on:
- runs-on=${{ github.run_id }}
- cpu=2+4
- family=m7i+c7i
- image=ubuntu22-full-x64
- extras=s3-cache
outputs:
baseline: ${{ steps.refs.outputs.baseline }}
treatment: ${{ steps.refs.outputs.treatment }}
duration: ${{ steps.refs.outputs.duration }}
runs_json: ${{ steps.refs.outputs.runs_json }} # e.g. "[1,2,...,10]"
steps:
- id: refs
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "baseline=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
echo "treatment=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
if [ "${{ github.event.label.name }}" = "needs benchmark smoke" ]; then
N=2
echo "duration=300" >> "$GITHUB_OUTPUT"
else
N=10
echo "duration=3600" >> "$GITHUB_OUTPUT"
fi
else
echo "baseline=${{ inputs.baseline_ref }}" >> "$GITHUB_OUTPUT"
echo "treatment=${{ inputs.treatment_ref }}" >> "$GITHUB_OUTPUT"
N=${{ inputs.runs }}
echo "duration=${{ inputs.duration_secs }}" >> "$GITHUB_OUTPUT"
fi
echo "runs_json=$(seq -s, 1 "$N" | sed 's/^/[/;s/$/]/')" >> "$GITHUB_OUTPUT"
build:
needs: setup
# Fat instance to build the shared image (Bitcoin Core + AFL++ + depends).
runs-on:
- runs-on=${{ github.run_id }}
- cpu=16+32
- family=c7i+m7i+c7a
- image=ubuntu22-full-x64
- volume=120gb:gp3
- extras=s3-cache # S3-backed docker layer cache (no 10GB cap)
- spot=false
steps:
- uses: actions/checkout@v4 # Bitcoin Core build is version-independent
- uses: docker/setup-buildx-action@v3
- name: Build & cache the shared image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.libafl
push: false
tags: fuzzamoto-libafl:bench
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max # backed by runs-on s3-cache (no 10GB cap)
campaign:
needs: [setup, build]
# nested-virt exposes /dev/kvm (required by Nyx). IMPORTANT: pinned to a single,
# exact instance type so every campaign (both arms, all N runs) runs on identical
# hardware - otherwise a coverage/exec-rate delta could reflect instance variance
# rather than the fuzzer change. spot=false so an interruption can't void a 1h run.
runs-on:
- runs-on=${{ github.run_id }}
- family=c7i.2xlarge # exact type; adjust to one nested-virt type in your account
- image=ubuntu22-full-x64
- nested-virt
- volume=80gb:gp3
- extras=s3-cache
- spot=false
timeout-minutes: 90 # 60m campaign + build/compile headroom
strategy:
fail-fast: false # a dead campaign must not cancel the others
matrix:
version: [baseline, treatment]
run: ${{ fromJSON(needs.setup.outputs.runs_json) }}
steps:
- name: Checkout fuzzer version under test
uses: actions/checkout@v4
with:
ref: ${{ matrix.version == 'baseline' && needs.setup.outputs.baseline || needs.setup.outputs.treatment }}
- uses: docker/setup-buildx-action@v3
- name: Restore image from cache
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.libafl
load: true
tags: fuzzamoto-libafl:bench
cache-from: type=gha # near-instant; layers built by `build`
- name: Run campaign
run: |
rm -rf /tmp/out && mkdir -p /tmp/out
docker run --privileged --device=/dev/kvm \
-e BENCH_DURATION=${{ needs.setup.outputs.duration }} \
-v ./:/fuzzamoto -v /tmp/out:/tmp/out \
fuzzamoto-libafl:bench just -f /ci/libafl.justfile bench
- name: Collect results
run: |
mkdir -p artifact
cp /tmp/out/bench/bench-cpu_000.csv artifact/ \
|| echo "no bench csv (campaign may have died early)"
cp -r /tmp/out/cpu_000/crashes artifact/crashes 2>/dev/null || true
- uses: actions/upload-artifact@v4
with:
name: bench-${{ matrix.version }}-${{ matrix.run }}
path: artifact
retention-days: 14
compare:
needs: [setup, campaign]
if: always()
# Lightweight runner for report aggregation; does not affect measurements.
runs-on:
- runs-on=${{ github.run_id }}
- cpu=2+4
- family=m7i+c7i
- image=ubuntu22-full-x64
- extras=s3-cache
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: bench-*
path: dl
- name: Arrange into baseline/treatment layout
run: |
shopt -s nullglob
for d in dl/bench-baseline-*; do
n=${d##*-}; mkdir -p results/baseline/ir/$n
cp "$d/bench-cpu_000.csv" results/baseline/ir/$n/ 2>/dev/null || true
done
for d in dl/bench-treatment-*; do
n=${d##*-}; mkdir -p results/treatment/ir/$n
cp "$d/bench-cpu_000.csv" results/treatment/ir/$n/ 2>/dev/null || true
done
- name: Install evaluation dependencies
run: pip install numpy pandas matplotlib seaborn scipy statsmodels tabulate
- name: Evaluate
run: |
python3 ci/benchmark-evaluation.py results --out report --hours min
{
echo "## fuzzamoto-libafl benchmark"
echo
echo "Baseline: \`${{ needs.setup.outputs.baseline }}\` • Treatment: \`${{ needs.setup.outputs.treatment }}\`"
echo
cat report/evaluation_report.md
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
with:
name: benchmark-report
path: report
retention-days: 90
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('report/evaluation_report.md', 'utf8');
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body,
});