Repository navigation
153 lines (141 loc) · 5.85 KB
/
Copy pathbenchmarks.yml
File metadata and controls
153 lines (141 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
name: Benchmarks
# Benchmarks are not a gate. GitHub-hosted runners are shared and their timings vary far more
# than most of the changes worth catching, so a threshold here would either never fire or fire
# constantly. This exists so that anyone can get a full run without a local .NET setup, and so
# that the results are archived against the commit that produced them.
on:
workflow_dispatch:
inputs:
filter:
description: "BenchmarkDotNet filter, e.g. *ArithmeticBenchmarks* or *.Multiply"
required: false
default: "*"
type: string
job:
description: "Measurement length"
required: false
default: "short"
type: choice
options:
- short
- default
- long
baseline:
description: "Git ref to measure alongside this one, e.g. v1.9.0. Empty measures only this checkout."
required: false
default: ""
type: string
permissions:
contents: read
env:
DOTNET_VERSION: "10.0"
# Each ref measured gets its own subdirectory, the same layout benchmark-history.yml uses.
RUNS: BenchmarkDotNet.Artifacts
jobs:
benchmark:
name: Run Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
# A baseline ref is measured through a worktree, so its history has to be present.
# Quoted so the falsy-zero rule in GitHub expressions does not turn 0 into 1.
fetch-depth: ${{ inputs.baseline != '' && '0' || '1' }}
- name: Setup .NET SDK ${{ env.DOTNET_VERSION }}
uses: actions/setup-dotnet@v6
with:
dotnet-version: ${{ env.DOTNET_VERSION }}.x
cache: true
cache-dependency-path: |
**/*.csproj
**/Directory.Packages.props
**/global.json
# Measured before this checkout rather than after, so the summary reads in the order a
# comparison is written: where it was, then where it is. The point of measuring it here
# instead of in a second dispatch is that both readings come off one runner -- separate
# runs can land on different CPU generations, which is what made earlier numbers
# incomparable (see #76).
#
# The inputs go through the environment rather than being interpolated into the script.
# `filter` and `baseline` are free-form text supplied by whoever dispatches the workflow,
# and expanding either into the script body would let it run as shell.
- name: Run Benchmarks (baseline ref)
if: inputs.baseline != ''
shell: bash
env:
BENCHMARK_FILTER: ${{ inputs.filter }}
BENCHMARK_JOB: ${{ inputs.job }}
BENCHMARK_BASELINE: ${{ inputs.baseline }}
run: |
set -euo pipefail
if ! git rev-parse --verify --quiet "${BENCHMARK_BASELINE}^{commit}" > /dev/null; then
echo "::error::Baseline ref '${BENCHMARK_BASELINE}' does not resolve to a commit."
exit 1
fi
work="${RUNNER_TEMP}/bench-baseline"
git worktree add --detach "$work" "$BENCHMARK_BASELINE"
args=(--filter "$BENCHMARK_FILTER")
if [ "$BENCHMARK_JOB" != "default" ]; then
args+=(--job "$BENCHMARK_JOB")
fi
args+=(--artifacts "$GITHUB_WORKSPACE/$RUNS/baseline")
(cd "$work" && dotnet run -c Release --project PreciseNumber.Benchmarks -- "${args[@]}")
git worktree remove --force "$work"
- name: Run Benchmarks
shell: bash
env:
BENCHMARK_FILTER: ${{ inputs.filter }}
BENCHMARK_JOB: ${{ inputs.job }}
run: |
set -euo pipefail
args=(--filter "$BENCHMARK_FILTER")
if [ "$BENCHMARK_JOB" != "default" ]; then
args+=(--job "$BENCHMARK_JOB")
fi
# --artifacts is what decides where the reports land. Without it BenchmarkDotNet
# writes them below the working directory, which `dotnet run --project` leaves at the
# repository root rather than moving to the project -- so the later steps looked under
# PreciseNumber.Benchmarks/ and found nothing, and every run uploaded no artifact.
args+=(--artifacts "$GITHUB_WORKSPACE/$RUNS/head")
dotnet run -c Release --project PreciseNumber.Benchmarks -- "${args[@]}"
# The Markdown reports are the readable artifact; the JSON is what a later comparison
# against another run would be built from. Each ref is headed by the ref it came from,
# because two tables of the same benchmark names are otherwise impossible to tell apart.
- name: Summarize
if: always()
shell: bash
env:
BENCHMARK_BASELINE: ${{ inputs.baseline }}
run: |
shopt -s nullglob
found=0
for ref in baseline head; do
reports=("$RUNS/$ref"/results/*-report-github.md)
if [ ${#reports[@]} -eq 0 ]; then
continue
fi
found=1
if [ "$ref" = "baseline" ]; then
echo "## Baseline: $BENCHMARK_BASELINE" >> "$GITHUB_STEP_SUMMARY"
else
echo "## This checkout: $GITHUB_SHA" >> "$GITHUB_STEP_SUMMARY"
fi
echo >> "$GITHUB_STEP_SUMMARY"
for report in "${reports[@]}"; do
cat "$report" >> "$GITHUB_STEP_SUMMARY"
echo >> "$GITHUB_STEP_SUMMARY"
done
done
if [ "$found" -eq 0 ]; then
echo "No benchmark reports were produced." >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload Results
if: always()
uses: actions/upload-artifact@v7
with:
name: benchmark-results-${{ github.sha }}
path: BenchmarkDotNet.Artifacts/*/results/*
retention-days: 30
if-no-files-found: error