|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -euo pipefail |
| 4 | + |
| 5 | +# This script can run directly from a checkout or from GitHub Actions. |
| 6 | +# |
| 7 | +# Run the current checkout: |
| 8 | +# scripts/benchmark.sh |
| 9 | +# |
| 10 | +# Compare the current checkout with another checkout: |
| 11 | +# BENCHMARK_MODE=compare BASELINE_ROOT=/path/to/baseline scripts/benchmark.sh |
| 12 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 13 | +REPOSITORY_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 14 | + |
| 15 | +BENCHMARK_MODE="${BENCHMARK_MODE:-run}" |
| 16 | +BENCHMARK_FILTER="${BENCHMARK_FILTER:-}" |
| 17 | +BENCHMARK_PAIRS="${BENCHMARK_PAIRS:-1}" |
| 18 | +CANDIDATE_ROOT="${CANDIDATE_ROOT:-$REPOSITORY_ROOT}" |
| 19 | +BASELINE_ROOT="${BASELINE_ROOT:-}" |
| 20 | +BENCHMARK_RESULTS_DIR="${BENCHMARK_RESULTS_DIR:-/tmp/machokit-benchmark-results}" |
| 21 | +MACHOKIT_BENCH_MACHO="${MACHOKIT_BENCH_MACHO:-$BENCHMARK_RESULTS_DIR/MachOKitBenchmarks-fixture}" |
| 22 | +BENCHMARK_DISABLE_JEMALLOC="${BENCHMARK_DISABLE_JEMALLOC:-1}" |
| 23 | + |
| 24 | +export BENCHMARK_DISABLE_JEMALLOC |
| 25 | +export MACHOKIT_BENCH_MACHO |
| 26 | + |
| 27 | +fail() { |
| 28 | + echo "benchmark: $*" >&2 |
| 29 | + exit 1 |
| 30 | +} |
| 31 | + |
| 32 | +canonical_directory() { |
| 33 | + local path="$1" |
| 34 | + [[ -d "$path" ]] || fail "directory does not exist: $path" |
| 35 | + (cd "$path" && pwd) |
| 36 | +} |
| 37 | + |
| 38 | +case "$BENCHMARK_MODE" in |
| 39 | +run | compare) |
| 40 | + ;; |
| 41 | +*) |
| 42 | + fail "BENCHMARK_MODE must be 'run' or 'compare'" |
| 43 | + ;; |
| 44 | +esac |
| 45 | + |
| 46 | +case "$BENCHMARK_PAIRS" in |
| 47 | +1 | 3) |
| 48 | + ;; |
| 49 | +*) |
| 50 | + fail "BENCHMARK_PAIRS must be '1' or '3'" |
| 51 | + ;; |
| 52 | +esac |
| 53 | + |
| 54 | +CANDIDATE_ROOT="$(canonical_directory "$CANDIDATE_ROOT")" |
| 55 | +[[ -d "$CANDIDATE_ROOT/Benchmarks" ]] || fail "candidate has no Benchmarks directory" |
| 56 | + |
| 57 | +if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 58 | + [[ -n "$BASELINE_ROOT" ]] || fail "BASELINE_ROOT is required in compare mode" |
| 59 | + BASELINE_ROOT="$(canonical_directory "$BASELINE_ROOT")" |
| 60 | + [[ -e "$BASELINE_ROOT/.git" ]] || fail "baseline must be a Git checkout" |
| 61 | + [[ -d "$BASELINE_ROOT/Benchmarks" ]] || fail "baseline has no Benchmarks directory" |
| 62 | + [[ "$BASELINE_ROOT" != "$CANDIDATE_ROOT" ]] || fail "baseline and candidate must be different directories" |
| 63 | +fi |
| 64 | + |
| 65 | +mkdir -p "$BENCHMARK_RESULTS_DIR" |
| 66 | +SUMMARY_FILE="$BENCHMARK_RESULTS_DIR/summary.md" |
| 67 | +: > "$SUMMARY_FILE" |
| 68 | + |
| 69 | +append_summary() { |
| 70 | + local section_file="$1" |
| 71 | + cat "$section_file" >> "$SUMMARY_FILE" |
| 72 | + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then |
| 73 | + cat "$section_file" >> "$GITHUB_STEP_SUMMARY" |
| 74 | + fi |
| 75 | +} |
| 76 | + |
| 77 | +prepare_benchmark_sources() { |
| 78 | + # The baseline checkout is disposable. Mirror the candidate's benchmark |
| 79 | + # definitions and dependency lock so only the MachOKit source ref differs. |
| 80 | + rsync -a --delete \ |
| 81 | + --exclude .benchmarkBaselines/ \ |
| 82 | + --exclude .build/ \ |
| 83 | + --exclude .swiftpm/ \ |
| 84 | + --exclude result.txt \ |
| 85 | + "$CANDIDATE_ROOT/Benchmarks/" \ |
| 86 | + "$BASELINE_ROOT/Benchmarks/" |
| 87 | +} |
| 88 | + |
| 89 | +build_benchmarks() { |
| 90 | + local checkout="$1" |
| 91 | + local label="$2" |
| 92 | + ( |
| 93 | + cd "$checkout/Benchmarks" |
| 94 | + swift build -c release --product MachOKitBenchmarks |
| 95 | + ) 2>&1 | tee "$BENCHMARK_RESULTS_DIR/build-$label.log" |
| 96 | +} |
| 97 | + |
| 98 | +create_fixed_fixture() { |
| 99 | + local candidate_bin_path |
| 100 | + candidate_bin_path="$( |
| 101 | + cd "$CANDIDATE_ROOT/Benchmarks" |
| 102 | + swift build -c release --show-bin-path |
| 103 | + )" |
| 104 | + cp "$candidate_bin_path/MachOKitBenchmarks" "$MACHOKIT_BENCH_MACHO" |
| 105 | +} |
| 106 | + |
| 107 | +record_environment() { |
| 108 | + local section_file="$BENCHMARK_RESULTS_DIR/environment.md" |
| 109 | + local cpu |
| 110 | + local candidate_sha |
| 111 | + local baseline_sha="not applicable" |
| 112 | + local filter_display |
| 113 | + |
| 114 | + cpu="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || uname -m)" |
| 115 | + candidate_sha="$(git -C "$CANDIDATE_ROOT" rev-parse HEAD)" |
| 116 | + if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 117 | + baseline_sha="$(git -C "$BASELINE_ROOT" rev-parse HEAD)" |
| 118 | + fi |
| 119 | + |
| 120 | + filter_display="${BENCHMARK_FILTER//$'\n'/ }" |
| 121 | + filter_display="${filter_display//$'\r'/ }" |
| 122 | + filter_display="${filter_display//\`/\\\`}" |
| 123 | + filter_display="${filter_display//|/\\|}" |
| 124 | + |
| 125 | + { |
| 126 | + echo "## Benchmark environment" |
| 127 | + echo |
| 128 | + echo "| Item | Value |" |
| 129 | + echo "| --- | --- |" |
| 130 | + echo "| Mode | \`$BENCHMARK_MODE\` |" |
| 131 | + echo "| Candidate | \`$candidate_sha\` |" |
| 132 | + echo "| Baseline | \`$baseline_sha\` |" |
| 133 | + echo "| Runner | \`${RUNNER_NAME:-local} (${RUNNER_OS:-$(uname -s)})\` |" |
| 134 | + echo "| Runner image | \`${ImageOS:-unknown} ${ImageVersion:-unknown}\` |" |
| 135 | + echo "| CPU | \`$cpu\` |" |
| 136 | + echo "| Memory | \`$(sysctl -n hw.memsize) bytes\` |" |
| 137 | + echo "| Xcode | \`$(xcodebuild -version | tr '\n' ' ')\` |" |
| 138 | + echo "| Fixture SHA-256 | \`$(shasum -a 256 "$MACHOKIT_BENCH_MACHO" | awk '{print $1}')\` |" |
| 139 | + echo "| Filter | \`${filter_display:-all benchmarks}\` |" |
| 140 | + if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 141 | + echo "| Pairs | \`$BENCHMARK_PAIRS\` |" |
| 142 | + else |
| 143 | + echo "| Pairs | \`not applicable\` |" |
| 144 | + fi |
| 145 | + echo |
| 146 | + } > "$section_file" |
| 147 | + |
| 148 | + append_summary "$section_file" |
| 149 | +} |
| 150 | + |
| 151 | +benchmark_args=(--no-progress --quiet) |
| 152 | +if [[ -n "$BENCHMARK_FILTER" ]]; then |
| 153 | + benchmark_args+=(--filter "$BENCHMARK_FILTER") |
| 154 | +fi |
| 155 | + |
| 156 | +run_baseline() { |
| 157 | + local checkout="$1" |
| 158 | + local baseline_name="$2" |
| 159 | + local log_file="$3" |
| 160 | + ( |
| 161 | + cd "$checkout/Benchmarks" |
| 162 | + swift package --allow-writing-to-package-directory benchmark baseline update \ |
| 163 | + "$baseline_name" \ |
| 164 | + "${benchmark_args[@]}" |
| 165 | + ) 2>&1 | tee "$log_file" |
| 166 | +} |
| 167 | + |
| 168 | +copy_baseline_to_candidate() { |
| 169 | + local baseline_name="$1" |
| 170 | + local source_dir="$BASELINE_ROOT/Benchmarks/.benchmarkBaselines/MachOKitBenchmarks/$baseline_name" |
| 171 | + local destination_dir="$CANDIDATE_ROOT/Benchmarks/.benchmarkBaselines/MachOKitBenchmarks/$baseline_name" |
| 172 | + mkdir -p "$destination_dir" |
| 173 | + cp "$source_dir/results.json" "$destination_dir/results.json" |
| 174 | +} |
| 175 | + |
| 176 | +export_baseline() { |
| 177 | + local baseline_name="$1" |
| 178 | + local output_dir="$2" |
| 179 | + mkdir -p "$output_dir/samples" |
| 180 | + ( |
| 181 | + cd "$CANDIDATE_ROOT/Benchmarks" |
| 182 | + swift package --allow-writing-to-directory "$output_dir/samples" \ |
| 183 | + benchmark baseline read "$baseline_name" \ |
| 184 | + --format histogramSamples \ |
| 185 | + --path "$output_dir/samples" \ |
| 186 | + --no-progress \ |
| 187 | + --quiet |
| 188 | + ) |
| 189 | + cp "$CANDIDATE_ROOT/Benchmarks/.benchmarkBaselines/MachOKitBenchmarks/$baseline_name/results.json" \ |
| 190 | + "$output_dir/$baseline_name.results.json" |
| 191 | +} |
| 192 | + |
| 193 | +run_single() { |
| 194 | + local section_file="$BENCHMARK_RESULTS_DIR/run.md" |
| 195 | + |
| 196 | + run_baseline "$CANDIDATE_ROOT" candidate "$BENCHMARK_RESULTS_DIR/run.log" |
| 197 | + |
| 198 | + { |
| 199 | + echo "## Benchmark results" |
| 200 | + echo |
| 201 | + ( |
| 202 | + cd "$CANDIDATE_ROOT/Benchmarks" |
| 203 | + swift package benchmark baseline read candidate \ |
| 204 | + --format markdown \ |
| 205 | + --no-progress |
| 206 | + ) |
| 207 | + } > "$section_file" |
| 208 | + append_summary "$section_file" |
| 209 | + |
| 210 | + export_baseline candidate "$BENCHMARK_RESULTS_DIR" |
| 211 | +} |
| 212 | + |
| 213 | +run_comparison() { |
| 214 | + local pair |
| 215 | + local pair_dir |
| 216 | + local baseline_name |
| 217 | + local candidate_name |
| 218 | + local measurement_order |
| 219 | + |
| 220 | + for ((pair = 1; pair <= BENCHMARK_PAIRS; pair++)); do |
| 221 | + pair_dir="$BENCHMARK_RESULTS_DIR/pair-$pair" |
| 222 | + baseline_name="baseline-pair-$pair" |
| 223 | + candidate_name="candidate-pair-$pair" |
| 224 | + mkdir -p "$pair_dir" |
| 225 | + |
| 226 | + if ((pair % 2 == 1)); then |
| 227 | + measurement_order="baseline → candidate" |
| 228 | + run_baseline "$BASELINE_ROOT" "$baseline_name" "$pair_dir/baseline.log" |
| 229 | + run_baseline "$CANDIDATE_ROOT" "$candidate_name" "$pair_dir/candidate.log" |
| 230 | + else |
| 231 | + measurement_order="candidate → baseline" |
| 232 | + run_baseline "$CANDIDATE_ROOT" "$candidate_name" "$pair_dir/candidate.log" |
| 233 | + run_baseline "$BASELINE_ROOT" "$baseline_name" "$pair_dir/baseline.log" |
| 234 | + fi |
| 235 | + |
| 236 | + copy_baseline_to_candidate "$baseline_name" |
| 237 | + |
| 238 | + { |
| 239 | + echo "## Comparison pair $pair" |
| 240 | + echo |
| 241 | + echo "Measurement order: $measurement_order" |
| 242 | + echo |
| 243 | + ( |
| 244 | + cd "$CANDIDATE_ROOT/Benchmarks" |
| 245 | + swift package benchmark baseline compare \ |
| 246 | + "$baseline_name" \ |
| 247 | + "$candidate_name" \ |
| 248 | + --format markdown \ |
| 249 | + --no-progress |
| 250 | + ) |
| 251 | + } > "$pair_dir/comparison.md" |
| 252 | + append_summary "$pair_dir/comparison.md" |
| 253 | + |
| 254 | + export_baseline "$baseline_name" "$pair_dir" |
| 255 | + export_baseline "$candidate_name" "$pair_dir" |
| 256 | + done |
| 257 | +} |
| 258 | + |
| 259 | +if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 260 | + prepare_benchmark_sources |
| 261 | +fi |
| 262 | + |
| 263 | +build_benchmarks "$CANDIDATE_ROOT" candidate |
| 264 | +if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 265 | + build_benchmarks "$BASELINE_ROOT" baseline |
| 266 | +fi |
| 267 | + |
| 268 | +create_fixed_fixture |
| 269 | +record_environment |
| 270 | + |
| 271 | +if [[ "$BENCHMARK_MODE" == "compare" ]]; then |
| 272 | + run_comparison |
| 273 | +else |
| 274 | + run_single |
| 275 | +fi |
| 276 | + |
| 277 | +echo "Benchmark results: $BENCHMARK_RESULTS_DIR" |
0 commit comments