Skip to content

Commit 731be6d

Browse files
committed
ci: add dns benchmark comparison workflow
1 parent 9a855c9 commit 731be6d

File tree

2 files changed

+359
-0
lines changed

2 files changed

+359
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
name: DNS Benchmark Compare
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
paths:
7+
- "control/**"
8+
- "component/dns/**"
9+
- "go.mod"
10+
- "go.sum"
11+
- "scripts/ci/dns-benchmark-compare.sh"
12+
- ".github/workflows/dns-benchmark-compare.yml"
13+
workflow_dispatch:
14+
inputs:
15+
base_ref:
16+
description: "Base ref for merge-base (for example: origin/main)"
17+
required: false
18+
default: "origin/main"
19+
head_ref:
20+
description: "Head ref to benchmark"
21+
required: false
22+
default: "HEAD"
23+
bench_filter:
24+
description: "Regex to select benchmark names"
25+
required: false
26+
default: "^Benchmark(AsyncCache|SingleflightOverhead|HighQpsScenario|RealisticDnsQuery|DnsController_Singleflight|PipelinedConn_Concurrent|PipelinedConn_Sequential)$"
27+
28+
permissions:
29+
contents: read
30+
pull-requests: write
31+
32+
jobs:
33+
bench-compare:
34+
name: DNS Bench Compare
35+
runs-on: ubuntu-22.04
36+
continue-on-error: true
37+
timeout-minutes: 30
38+
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@v4
42+
with:
43+
fetch-depth: 0
44+
submodules: recursive
45+
persist-credentials: false
46+
47+
- name: Setup Go
48+
uses: actions/setup-go@v5
49+
with:
50+
go-version-file: go.mod
51+
cache-dependency-path: |
52+
go.mod
53+
go.sum
54+
55+
- name: Install Dependencies
56+
run: |
57+
sudo apt-get update -y
58+
sudo apt-get install -y clang llvm make
59+
60+
- name: Install benchstat
61+
run: |
62+
GOTOOLCHAIN=auto go install golang.org/x/perf/cmd/benchstat@latest
63+
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
64+
65+
- name: Run Benchmark Compare
66+
env:
67+
BENCH_FILTER: ${{ github.event.inputs.bench_filter }}
68+
run: |
69+
set -euo pipefail
70+
chmod +x scripts/ci/dns-benchmark-compare.sh
71+
mkdir -p bench-artifacts
72+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
73+
BASE_REF="origin/${{ github.base_ref }}"
74+
HEAD_REF="${{ github.sha }}"
75+
else
76+
BASE_REF="${{ github.event.inputs.base_ref }}"
77+
HEAD_REF="${{ github.event.inputs.head_ref }}"
78+
fi
79+
set +e
80+
./scripts/ci/dns-benchmark-compare.sh "$BASE_REF" "$HEAD_REF" 2>&1 | tee bench-artifacts/run.log
81+
status=${PIPESTATUS[0]}
82+
set -e
83+
if [[ $status -ne 0 && ! -f bench-artifacts/report.md ]]; then
84+
{
85+
echo "## DNS Benchmark Compare"
86+
echo
87+
echo "- Status: failed"
88+
echo "- Exit code: \`$status\`"
89+
echo "- Base ref: \`$BASE_REF\`"
90+
echo "- Head ref: \`$HEAD_REF\`"
91+
echo
92+
echo "### Failure Log (tail)"
93+
echo
94+
echo '```text'
95+
tail -n 200 bench-artifacts/run.log || true
96+
echo '```'
97+
} > bench-artifacts/report.md
98+
fi
99+
exit $status
100+
101+
- name: Upload Benchmark Artifacts
102+
if: always()
103+
uses: actions/upload-artifact@v4
104+
with:
105+
name: dns-benchmark-compare-${{ github.run_id }}
106+
path: bench-artifacts
107+
if-no-files-found: warn
108+
109+
- name: Update PR Comment
110+
if: always() && github.event_name == 'pull_request' && hashFiles('bench-artifacts/report.md') != ''
111+
uses: actions/github-script@v7
112+
with:
113+
script: |
114+
const fs = require("fs");
115+
const marker = "<!-- dae-dns-benchmark-compare -->";
116+
const report = fs.readFileSync("bench-artifacts/report.md", "utf8");
117+
const body = `${marker}\n${report}`;
118+
const { owner, repo } = context.repo;
119+
const issue_number = context.issue.number;
120+
121+
const comments = await github.paginate(github.rest.issues.listComments, {
122+
owner,
123+
repo,
124+
issue_number,
125+
per_page: 100
126+
});
127+
128+
const existing = comments.find((c) => c.body && c.body.includes(marker));
129+
if (existing) {
130+
await github.rest.issues.updateComment({
131+
owner,
132+
repo,
133+
comment_id: existing.id,
134+
body
135+
});
136+
} else {
137+
await github.rest.issues.createComment({
138+
owner,
139+
repo,
140+
issue_number,
141+
body
142+
});
143+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
log() {
5+
printf '[dns-bench] %s\n' "$*"
6+
}
7+
8+
die() {
9+
printf '[dns-bench] ERROR: %s\n' "$*" >&2
10+
exit 1
11+
}
12+
13+
if ! command -v git >/dev/null 2>&1; then
14+
die "git is required"
15+
fi
16+
if ! command -v go >/dev/null 2>&1; then
17+
die "go is required"
18+
fi
19+
if ! command -v benchstat >/dev/null 2>&1; then
20+
die "benchstat is required (go install golang.org/x/perf/cmd/benchstat@latest)"
21+
fi
22+
23+
REPO_ROOT="$(git rev-parse --show-toplevel)"
24+
cd "$REPO_ROOT"
25+
26+
BASE_REF="${1:-${BASE_REF:-origin/main}}"
27+
HEAD_REF="${2:-${HEAD_REF:-HEAD}}"
28+
BENCH_PACKAGE="${BENCH_PACKAGE:-./control}"
29+
BENCH_FILTER="${BENCH_FILTER:-^Benchmark(AsyncCache|SingleflightOverhead|HighQpsScenario|RealisticDnsQuery|DnsController_Singleflight|PipelinedConn_Concurrent|PipelinedConn_Sequential)$}"
30+
BENCH_COUNT="${BENCH_COUNT:-3}"
31+
BENCH_TIME="${BENCH_TIME:-200ms}"
32+
ARTIFACT_DIR="${ARTIFACT_DIR:-bench-artifacts}"
33+
KEEP_WORKTREES="${KEEP_WORKTREES:-0}"
34+
WORKTREE_ROOT="${WORKTREE_ROOT:-$(mktemp -d -t dae-dns-bench-XXXXXX)}"
35+
36+
BASE_WT="$WORKTREE_ROOT/base"
37+
HEAD_WT="$WORKTREE_ROOT/head"
38+
39+
mkdir -p "$ARTIFACT_DIR"
40+
ARTIFACT_DIR="$(cd "$ARTIFACT_DIR" && pwd)"
41+
42+
cleanup() {
43+
if [[ "$KEEP_WORKTREES" == "1" ]]; then
44+
log "keeping worktrees at $WORKTREE_ROOT"
45+
return
46+
fi
47+
git worktree remove "$BASE_WT" --force >/dev/null 2>&1 || true
48+
git worktree remove "$HEAD_WT" --force >/dev/null 2>&1 || true
49+
rm -rf "$WORKTREE_ROOT"
50+
}
51+
trap cleanup EXIT
52+
53+
resolve_ref() {
54+
local ref="$1"
55+
if git rev-parse --verify "${ref}^{commit}" >/dev/null 2>&1; then
56+
return 0
57+
fi
58+
if [[ "$ref" == origin/* ]]; then
59+
local branch="${ref#origin/}"
60+
log "fetching missing ref $ref from origin/$branch"
61+
git fetch --no-tags origin "$branch" >/dev/null 2>&1 || true
62+
else
63+
log "fetching missing ref $ref from origin"
64+
git fetch --no-tags origin "$ref" >/dev/null 2>&1 || true
65+
fi
66+
git rev-parse --verify "${ref}^{commit}" >/dev/null 2>&1
67+
}
68+
69+
resolve_ref "$BASE_REF" || die "cannot resolve base ref: $BASE_REF"
70+
resolve_ref "$HEAD_REF" || die "cannot resolve head ref: $HEAD_REF"
71+
72+
BASE_COMMIT="$(git merge-base "$BASE_REF" "$HEAD_REF")"
73+
HEAD_COMMIT="$(git rev-parse "$HEAD_REF")"
74+
75+
log "base ref: $BASE_REF ($BASE_COMMIT)"
76+
log "head ref: $HEAD_REF ($HEAD_COMMIT)"
77+
78+
git worktree add --detach "$BASE_WT" "$BASE_COMMIT" >/dev/null
79+
git worktree add --detach "$HEAD_WT" "$HEAD_COMMIT" >/dev/null
80+
81+
prepare_tree() {
82+
local wt="$1"
83+
(
84+
cd "$wt"
85+
git submodule update --init --recursive >/dev/null 2>&1 || true
86+
export GOWORK=off
87+
export GOFLAGS="${GOFLAGS:-} -buildvcs=false"
88+
export BPF_CLANG="${BPF_CLANG:-clang}"
89+
export BPF_STRIP_FLAG="${BPF_STRIP_FLAG:--no-strip}"
90+
export BPF_CFLAGS="${BPF_CFLAGS:--O2 -Wall -Werror -DMAX_MATCH_SET_LEN=1024}"
91+
export BPF_TARGET="${BPF_TARGET:-bpfel}"
92+
go generate ./control/control.go >/dev/null
93+
)
94+
}
95+
96+
list_benchmarks() {
97+
local wt="$1"
98+
local out="$2"
99+
local all_tmp="$out.all"
100+
(
101+
cd "$wt"
102+
export GOWORK=off
103+
go test "$BENCH_PACKAGE" -run '^$' -list '^Benchmark' \
104+
| awk '/^Benchmark/ {print $1}' \
105+
| sort -u >"$all_tmp"
106+
)
107+
if [[ -n "$BENCH_FILTER" ]]; then
108+
grep -E "$BENCH_FILTER" "$all_tmp" >"$out" || true
109+
else
110+
cp "$all_tmp" "$out"
111+
fi
112+
rm -f "$all_tmp"
113+
}
114+
115+
run_benchmarks() {
116+
local wt="$1"
117+
local names_file="$2"
118+
local output_file="$3"
119+
if [[ ! -s "$names_file" ]]; then
120+
: >"$output_file"
121+
return 0
122+
fi
123+
local regex
124+
regex="$(paste -sd'|' "$names_file")"
125+
(
126+
cd "$wt"
127+
export GOWORK=off
128+
export GOFLAGS="${GOFLAGS:-} -buildvcs=false"
129+
go test "$BENCH_PACKAGE" \
130+
-run '^$' \
131+
-bench "^(${regex})$" \
132+
-benchmem \
133+
-count "$BENCH_COUNT" \
134+
-benchtime "$BENCH_TIME" \
135+
| tee "$REPO_ROOT/$output_file"
136+
)
137+
}
138+
139+
prepare_tree "$BASE_WT"
140+
prepare_tree "$HEAD_WT"
141+
142+
BASE_LIST="$ARTIFACT_DIR/base_benchmarks.txt"
143+
HEAD_LIST="$ARTIFACT_DIR/head_benchmarks.txt"
144+
COMMON_LIST="$ARTIFACT_DIR/common_benchmarks.txt"
145+
HEAD_ONLY_LIST="$ARTIFACT_DIR/head_only_benchmarks.txt"
146+
147+
list_benchmarks "$BASE_WT" "$BASE_LIST"
148+
list_benchmarks "$HEAD_WT" "$HEAD_LIST"
149+
150+
comm -12 "$BASE_LIST" "$HEAD_LIST" >"$COMMON_LIST" || true
151+
comm -13 "$BASE_LIST" "$HEAD_LIST" >"$HEAD_ONLY_LIST" || true
152+
153+
BASE_COMMON_OUT="$ARTIFACT_DIR/base_common.txt"
154+
HEAD_COMMON_OUT="$ARTIFACT_DIR/head_common.txt"
155+
HEAD_ONLY_OUT="$ARTIFACT_DIR/head_only.txt"
156+
BENCHSTAT_OUT="$ARTIFACT_DIR/benchstat_common.txt"
157+
158+
run_benchmarks "$BASE_WT" "$COMMON_LIST" "$BASE_COMMON_OUT"
159+
run_benchmarks "$HEAD_WT" "$COMMON_LIST" "$HEAD_COMMON_OUT"
160+
run_benchmarks "$HEAD_WT" "$HEAD_ONLY_LIST" "$HEAD_ONLY_OUT"
161+
162+
if [[ -s "$COMMON_LIST" ]]; then
163+
benchstat "$BASE_COMMON_OUT" "$HEAD_COMMON_OUT" | tee "$BENCHSTAT_OUT"
164+
else
165+
printf 'No common benchmarks matched filter: %s\n' "$BENCH_FILTER" >"$BENCHSTAT_OUT"
166+
fi
167+
168+
REPORT_MD="$ARTIFACT_DIR/report.md"
169+
{
170+
echo "## DNS Benchmark Compare"
171+
echo
172+
echo "- Base ref: \`$BASE_REF\`"
173+
echo "- Base commit (merge-base): \`$BASE_COMMIT\`"
174+
echo "- Head ref: \`$HEAD_REF\`"
175+
echo "- Head commit: \`$HEAD_COMMIT\`"
176+
echo "- Package: \`$BENCH_PACKAGE\`"
177+
echo "- Benchmark filter: \`$BENCH_FILTER\`"
178+
echo "- Benchmark count: \`$BENCH_COUNT\`"
179+
echo "- Benchmark time: \`$BENCH_TIME\`"
180+
echo
181+
echo "### Common Benchmarks"
182+
echo
183+
echo "- Count: $(wc -l <"$COMMON_LIST" | tr -d '[:space:]')"
184+
if [[ -s "$COMMON_LIST" ]]; then
185+
echo
186+
echo '```text'
187+
cat "$BENCHSTAT_OUT"
188+
echo '```'
189+
else
190+
echo "- None"
191+
fi
192+
echo
193+
echo "### Head-Only Benchmarks"
194+
echo
195+
echo "- Count: $(wc -l <"$HEAD_ONLY_LIST" | tr -d '[:space:]')"
196+
if [[ -s "$HEAD_ONLY_LIST" ]]; then
197+
while IFS= read -r b; do
198+
echo "- \`$b\`"
199+
done <"$HEAD_ONLY_LIST"
200+
else
201+
echo "- None"
202+
fi
203+
echo
204+
echo "### Output Files"
205+
echo
206+
echo "- \`$BENCHSTAT_OUT\`"
207+
echo "- \`$BASE_COMMON_OUT\`"
208+
echo "- \`$HEAD_COMMON_OUT\`"
209+
echo "- \`$HEAD_ONLY_OUT\`"
210+
} >"$REPORT_MD"
211+
212+
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
213+
cat "$REPORT_MD" >>"$GITHUB_STEP_SUMMARY"
214+
fi
215+
216+
log "report generated at $REPORT_MD"

0 commit comments

Comments
 (0)