|
| 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