Skip to content

Commit 60e2dc9

Browse files
authored
Merge pull request #23 from MaurUppi/ci/dns-benchmark-compare
ci: add dns benchmark comparison workflow
2 parents 5268be5 + 4789eab commit 60e2dc9

File tree

6 files changed

+852
-0
lines changed

6 files changed

+852
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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+
- "scripts/ci/dns-benchmark-suite-runner.sh"
13+
- "scripts/ci/dns-benchmark-suites.sh"
14+
- "scripts/ci/benchmarks/**"
15+
- ".github/workflows/dns-benchmark-compare.yml"
16+
workflow_dispatch:
17+
inputs:
18+
base_ref:
19+
description: "Base ref for merge-base (for example: origin/main)"
20+
required: false
21+
default: "origin/main"
22+
head_ref:
23+
description: "Head ref to benchmark"
24+
required: false
25+
default: "HEAD"
26+
base_strategy:
27+
description: "Base commit strategy: merge-base or exact"
28+
required: false
29+
default: "merge-base"
30+
suite_profile:
31+
description: "Suite profile: quick or dns-module"
32+
required: false
33+
default: "dns-module"
34+
suite_list:
35+
description: "Optional comma-separated suite list. Overrides profile."
36+
required: false
37+
default: ""
38+
bench_count:
39+
description: "go test -count"
40+
required: false
41+
default: "3"
42+
bench_time:
43+
description: "go test -benchtime"
44+
required: false
45+
default: "200ms"
46+
47+
permissions:
48+
contents: read
49+
pull-requests: write
50+
51+
jobs:
52+
bench-compare:
53+
name: DNS Bench Compare
54+
runs-on: ubuntu-22.04
55+
continue-on-error: true
56+
timeout-minutes: 45
57+
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
61+
with:
62+
fetch-depth: 0
63+
submodules: recursive
64+
persist-credentials: false
65+
66+
- name: Setup Go
67+
uses: actions/setup-go@v5
68+
with:
69+
go-version-file: go.mod
70+
cache-dependency-path: |
71+
go.mod
72+
go.sum
73+
74+
- name: Install Dependencies
75+
run: |
76+
sudo apt-get update -y
77+
sudo apt-get install -y clang llvm make
78+
79+
- name: Install benchstat
80+
run: |
81+
GOTOOLCHAIN=auto go install golang.org/x/perf/cmd/benchstat@latest
82+
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
83+
84+
- name: Run Benchmark Compare
85+
run: |
86+
set -euo pipefail
87+
chmod +x scripts/ci/dns-benchmark-compare.sh
88+
chmod +x scripts/ci/dns-benchmark-suite-runner.sh
89+
mkdir -p bench-artifacts
90+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
91+
BASE_REF="origin/${{ github.base_ref }}"
92+
HEAD_REF="${{ github.sha }}"
93+
BASE_STRATEGY="merge-base"
94+
SUITE_PROFILE="dns-module"
95+
SUITE_LIST=""
96+
BENCH_COUNT="3"
97+
BENCH_TIME="200ms"
98+
else
99+
BASE_REF="${{ github.event.inputs.base_ref }}"
100+
HEAD_REF="${{ github.event.inputs.head_ref }}"
101+
BASE_STRATEGY="${{ github.event.inputs.base_strategy }}"
102+
SUITE_PROFILE="${{ github.event.inputs.suite_profile }}"
103+
SUITE_LIST="${{ github.event.inputs.suite_list }}"
104+
BENCH_COUNT="${{ github.event.inputs.bench_count }}"
105+
BENCH_TIME="${{ github.event.inputs.bench_time }}"
106+
fi
107+
108+
set +e
109+
DNS_BENCH_PROFILE="$SUITE_PROFILE" \
110+
DNS_BENCH_SUITES="$SUITE_LIST" \
111+
BENCH_COUNT="$BENCH_COUNT" \
112+
BENCH_TIME="$BENCH_TIME" \
113+
BASE_COMMIT_STRATEGY="$BASE_STRATEGY" \
114+
ARTIFACT_DIR="$PWD/bench-artifacts" \
115+
./scripts/ci/dns-benchmark-suite-runner.sh "$BASE_REF" "$HEAD_REF" \
116+
2>&1 | tee bench-artifacts/run.log
117+
status=${PIPESTATUS[0]}
118+
set -e
119+
120+
if [[ $status -ne 0 && ! -f bench-artifacts/report.md ]]; then
121+
{
122+
echo "## DNS Benchmark Compare"
123+
echo
124+
echo "- Status: failed"
125+
echo "- Exit code: \`$status\`"
126+
echo "- Base ref: \`$BASE_REF\`"
127+
echo "- Head ref: \`$HEAD_REF\`"
128+
echo "- Base strategy: \`$BASE_STRATEGY\`"
129+
echo "- Suite profile: \`$SUITE_PROFILE\`"
130+
if [[ -n "$SUITE_LIST" ]]; then
131+
echo "- Suite selection: \`$SUITE_LIST\`"
132+
fi
133+
echo
134+
echo "### Failure Log (tail)"
135+
echo
136+
echo '```text'
137+
tail -n 200 bench-artifacts/run.log || true
138+
echo '```'
139+
} > bench-artifacts/report.md
140+
fi
141+
exit $status
142+
143+
- name: Upload Benchmark Artifacts
144+
if: always()
145+
uses: actions/upload-artifact@v4
146+
with:
147+
name: dns-benchmark-compare-${{ github.run_id }}
148+
path: bench-artifacts
149+
if-no-files-found: warn
150+
151+
- name: Update PR Comment
152+
if: always() && github.event_name == 'pull_request' && hashFiles('bench-artifacts/report.md') != ''
153+
uses: actions/github-script@v7
154+
with:
155+
script: |
156+
const fs = require("fs");
157+
const marker = "<!-- dae-dns-benchmark-compare -->";
158+
const report = fs.readFileSync("bench-artifacts/report.md", "utf8");
159+
const body = `${marker}\n${report}`;
160+
const { owner, repo } = context.repo;
161+
const issue_number = context.issue.number;
162+
163+
const comments = await github.paginate(github.rest.issues.listComments, {
164+
owner,
165+
repo,
166+
issue_number,
167+
per_page: 100
168+
});
169+
170+
const existing = comments.find((c) => c.body && c.body.includes(marker));
171+
if (existing) {
172+
await github.rest.issues.updateComment({
173+
owner,
174+
repo,
175+
comment_id: existing.id,
176+
body
177+
});
178+
} else {
179+
await github.rest.issues.createComment({
180+
owner,
181+
repo,
182+
issue_number,
183+
body
184+
});
185+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dns
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
"unsafe"
7+
)
8+
9+
var benchUpstreamSink *Upstream
10+
11+
func setField(field reflect.Value, value reflect.Value) {
12+
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(value)
13+
}
14+
15+
func setBoolField(field reflect.Value, value bool) {
16+
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().SetBool(value)
17+
}
18+
19+
func benchmarkInitializedResolver(b *testing.B) *UpstreamResolver {
20+
r := &UpstreamResolver{}
21+
up := &Upstream{}
22+
rv := reflect.ValueOf(r).Elem()
23+
24+
// Legacy layout (mutex + init flag).
25+
if initField := rv.FieldByName("init"); initField.IsValid() {
26+
upField := rv.FieldByName("upstream")
27+
if !upField.IsValid() {
28+
b.Fatalf("legacy upstream field missing")
29+
}
30+
setField(upField, reflect.ValueOf(up))
31+
setBoolField(initField, true)
32+
return r
33+
}
34+
35+
// New layout (atomic state pointer).
36+
stateField := rv.FieldByName("state")
37+
if !stateField.IsValid() {
38+
b.Fatalf("unsupported UpstreamResolver layout")
39+
}
40+
// atomic.Pointer[T] internals: write pointer directly into field "v".
41+
vField := stateField.FieldByName("v")
42+
if !vField.IsValid() {
43+
b.Fatalf("atomic pointer backing field not found")
44+
}
45+
if vField.Type().Kind() != reflect.UnsafePointer {
46+
b.Fatalf("unexpected atomic backing field type: %v", vField.Type())
47+
}
48+
// Derive *upstreamState type from stateField methods.
49+
load := stateField.Addr().MethodByName("Load")
50+
if !load.IsValid() || load.Type().NumOut() != 1 {
51+
b.Fatalf("state.Load method unavailable")
52+
}
53+
upstreamStatePtrType := load.Type().Out(0)
54+
if upstreamStatePtrType.Kind() != reflect.Pointer {
55+
b.Fatalf("unexpected Load output type: %v", upstreamStatePtrType)
56+
}
57+
state := reflect.New(upstreamStatePtrType.Elem())
58+
stateUpstreamField := state.Elem().FieldByName("upstream")
59+
if !stateUpstreamField.IsValid() {
60+
b.Fatalf("upstreamState.upstream field missing")
61+
}
62+
setField(stateUpstreamField, reflect.ValueOf(up))
63+
setField(vField, reflect.ValueOf(unsafe.Pointer(state.Pointer())))
64+
return r
65+
}
66+
67+
func BenchmarkUpstreamResolver_GetUpstream_Serial(b *testing.B) {
68+
r := benchmarkInitializedResolver(b)
69+
b.ResetTimer()
70+
for i := 0; i < b.N; i++ {
71+
u, err := r.GetUpstream()
72+
if err != nil {
73+
b.Fatalf("GetUpstream failed: %v", err)
74+
}
75+
benchUpstreamSink = u
76+
}
77+
}
78+
79+
func BenchmarkUpstreamResolver_GetUpstream_Parallel(b *testing.B) {
80+
r := benchmarkInitializedResolver(b)
81+
b.ResetTimer()
82+
b.RunParallel(func(pb *testing.PB) {
83+
for pb.Next() {
84+
u, err := r.GetUpstream()
85+
if err != nil {
86+
b.Fatalf("GetUpstream failed: %v", err)
87+
}
88+
benchUpstreamSink = u
89+
}
90+
})
91+
}

0 commit comments

Comments
 (0)