Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions .github/workflows/dns-benchmark-compare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: DNS Benchmark Compare

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "control/**"
- "component/dns/**"
- "go.mod"
- "go.sum"
- "scripts/ci/dns-benchmark-compare.sh"
- "scripts/ci/dns-benchmark-suite-runner.sh"
- "scripts/ci/dns-benchmark-suites.sh"
- "scripts/ci/benchmarks/**"
- ".github/workflows/dns-benchmark-compare.yml"
workflow_dispatch:
inputs:
base_ref:
description: "Base ref for merge-base (for example: origin/main)"
required: false
default: "origin/main"
head_ref:
description: "Head ref to benchmark"
required: false
default: "HEAD"
base_strategy:
description: "Base commit strategy: merge-base or exact"
required: false
default: "merge-base"
suite_profile:
description: "Suite profile: quick or dns-module"
required: false
default: "dns-module"
suite_list:
description: "Optional comma-separated suite list. Overrides profile."
required: false
default: ""
bench_count:
description: "go test -count"
required: false
default: "3"
bench_time:
description: "go test -benchtime"
required: false
default: "200ms"

permissions:
contents: read
pull-requests: write

jobs:
bench-compare:
name: DNS Bench Compare
runs-on: ubuntu-22.04
continue-on-error: true
timeout-minutes: 45

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache-dependency-path: |
go.mod
go.sum

- name: Install Dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y clang llvm make

- name: Install benchstat
run: |
GOTOOLCHAIN=auto go install golang.org/x/perf/cmd/benchstat@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Run Benchmark Compare
run: |
set -euo pipefail
chmod +x scripts/ci/dns-benchmark-compare.sh
chmod +x scripts/ci/dns-benchmark-suite-runner.sh
mkdir -p bench-artifacts
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
BASE_REF="origin/${{ github.base_ref }}"
HEAD_REF="${{ github.sha }}"
BASE_STRATEGY="merge-base"
SUITE_PROFILE="dns-module"
SUITE_LIST=""
BENCH_COUNT="3"
BENCH_TIME="200ms"
else
BASE_REF="${{ github.event.inputs.base_ref }}"
HEAD_REF="${{ github.event.inputs.head_ref }}"
BASE_STRATEGY="${{ github.event.inputs.base_strategy }}"
SUITE_PROFILE="${{ github.event.inputs.suite_profile }}"
SUITE_LIST="${{ github.event.inputs.suite_list }}"
BENCH_COUNT="${{ github.event.inputs.bench_count }}"
BENCH_TIME="${{ github.event.inputs.bench_time }}"
fi

set +e
DNS_BENCH_PROFILE="$SUITE_PROFILE" \
DNS_BENCH_SUITES="$SUITE_LIST" \
BENCH_COUNT="$BENCH_COUNT" \
BENCH_TIME="$BENCH_TIME" \
BASE_COMMIT_STRATEGY="$BASE_STRATEGY" \
ARTIFACT_DIR="$PWD/bench-artifacts" \
./scripts/ci/dns-benchmark-suite-runner.sh "$BASE_REF" "$HEAD_REF" \
2>&1 | tee bench-artifacts/run.log
status=${PIPESTATUS[0]}
set -e

if [[ $status -ne 0 && ! -f bench-artifacts/report.md ]]; then
{
echo "## DNS Benchmark Compare"
echo
echo "- Status: failed"
echo "- Exit code: \`$status\`"
echo "- Base ref: \`$BASE_REF\`"
echo "- Head ref: \`$HEAD_REF\`"
echo "- Base strategy: \`$BASE_STRATEGY\`"
echo "- Suite profile: \`$SUITE_PROFILE\`"
if [[ -n "$SUITE_LIST" ]]; then
echo "- Suite selection: \`$SUITE_LIST\`"
fi
echo
echo "### Failure Log (tail)"
echo
echo '```text'
tail -n 200 bench-artifacts/run.log || true
echo '```'
} > bench-artifacts/report.md
fi
exit $status

- name: Upload Benchmark Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: dns-benchmark-compare-${{ github.run_id }}
path: bench-artifacts
if-no-files-found: warn

- name: Update PR Comment
if: always() && github.event_name == 'pull_request' && hashFiles('bench-artifacts/report.md') != ''
uses: actions/github-script@v7
with:
script: |
const fs = require("fs");
const marker = "<!-- dae-dns-benchmark-compare -->";
const report = fs.readFileSync("bench-artifacts/report.md", "utf8");
const body = `${marker}\n${report}`;
const { owner, repo } = context.repo;
const issue_number = context.issue.number;

const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100
});

const existing = comments.find((c) => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body
});
}
91 changes: 91 additions & 0 deletions scripts/ci/benchmarks/component/dns/upstream_hotpath_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package dns

import (
"reflect"
"testing"
"unsafe"
)

var benchUpstreamSink *Upstream

func setField(field reflect.Value, value reflect.Value) {
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(value)
}

func setBoolField(field reflect.Value, value bool) {
reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().SetBool(value)
}

func benchmarkInitializedResolver(b *testing.B) *UpstreamResolver {
r := &UpstreamResolver{}
up := &Upstream{}
rv := reflect.ValueOf(r).Elem()

// Legacy layout (mutex + init flag).
if initField := rv.FieldByName("init"); initField.IsValid() {
upField := rv.FieldByName("upstream")
if !upField.IsValid() {
b.Fatalf("legacy upstream field missing")
}
setField(upField, reflect.ValueOf(up))
setBoolField(initField, true)
return r
}

// New layout (atomic state pointer).
stateField := rv.FieldByName("state")
if !stateField.IsValid() {
b.Fatalf("unsupported UpstreamResolver layout")
}
// atomic.Pointer[T] internals: write pointer directly into field "v".
vField := stateField.FieldByName("v")
if !vField.IsValid() {
b.Fatalf("atomic pointer backing field not found")
}
if vField.Type().Kind() != reflect.UnsafePointer {
b.Fatalf("unexpected atomic backing field type: %v", vField.Type())
}
// Derive *upstreamState type from stateField methods.
load := stateField.Addr().MethodByName("Load")
if !load.IsValid() || load.Type().NumOut() != 1 {
b.Fatalf("state.Load method unavailable")
}
upstreamStatePtrType := load.Type().Out(0)
if upstreamStatePtrType.Kind() != reflect.Pointer {
b.Fatalf("unexpected Load output type: %v", upstreamStatePtrType)
}
state := reflect.New(upstreamStatePtrType.Elem())
stateUpstreamField := state.Elem().FieldByName("upstream")
if !stateUpstreamField.IsValid() {
b.Fatalf("upstreamState.upstream field missing")
}
setField(stateUpstreamField, reflect.ValueOf(up))
setField(vField, reflect.ValueOf(unsafe.Pointer(state.Pointer())))
return r
}

func BenchmarkUpstreamResolver_GetUpstream_Serial(b *testing.B) {
r := benchmarkInitializedResolver(b)
b.ResetTimer()
for i := 0; i < b.N; i++ {
u, err := r.GetUpstream()
if err != nil {
b.Fatalf("GetUpstream failed: %v", err)
}
benchUpstreamSink = u
}
}

func BenchmarkUpstreamResolver_GetUpstream_Parallel(b *testing.B) {
r := benchmarkInitializedResolver(b)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
u, err := r.GetUpstream()
if err != nil {
b.Fatalf("GetUpstream failed: %v", err)
}
benchUpstreamSink = u
}
})
}
Loading
Loading