Skip to content

Commit 29fa7d7

Browse files
committed
add CI perf-bench job to run short filtered benchmarks on PRs
1 parent 5144a8a commit 29fa7d7

File tree

5 files changed

+107
-10
lines changed

5 files changed

+107
-10
lines changed

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,49 @@ jobs:
6161
uses: github/codeql-action/autobuild@v3
6262
- name: Analyze with CodeQL
6363
uses: github/codeql-action/analyze@v3
64+
perf-bench:
65+
if: (github.event_name == 'pull_request' || github.ref == 'refs/heads/main') && github.actor != 'dependabot[bot]'
66+
needs: build
67+
runs-on: windows-latest
68+
timeout-minutes: 9
69+
strategy:
70+
fail-fast: false
71+
matrix:
72+
shard:
73+
# original pieces, but 1 per shard
74+
- { name: jwt-core, filter: "*Jwt*ciOi*" }
75+
- { name: phone-long, filter: "*Phone*0958*" }
76+
- { name: email-simple, filter: "*Email*(Sample:*[email protected]*)" }
77+
- { name: email-long, filter: "*Email*(Sample:*@domain.co.uk*)" }
78+
79+
steps:
80+
- uses: actions/checkout@v4
81+
- uses: actions/setup-dotnet@v4
82+
with: { dotnet-version: '8.0.x' }
83+
84+
- name: Run perf benchmarks (${{ matrix.shard.name }})
85+
shell: pwsh
86+
run: >
87+
pwsh -NoProfile -File bench/Run-Benchmarks.ps1
88+
-Job Short
89+
-Filter "${{ matrix.shard.filter }}"
90+
-MaxStdevPct 12
91+
-Ci
92+
-CoolDownSec 0
93+
94+
- name: Upload benchmark artifacts (${{ matrix.shard.name }})
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: perf-benchmarks-${{ matrix.shard.name }}
98+
path: artifacts/benchmarks
99+
if-no-files-found: error
100+
101+
- name: Publish benchmark summary to PR (${{ matrix.shard.name }})
102+
shell: pwsh
103+
run: |
104+
$f = Get-ChildItem "artifacts/benchmarks/*/SUMMARY.md" -ErrorAction SilentlyContinue |
105+
Sort-Object LastWriteTime -Descending | Select-Object -First 1
106+
if ($f) { Get-Content $f.FullName | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 }
64107
65108
# --- Optional companion gates (disabled by default) ---
66109
# - name: Unrecorded HTTP Call gate

bench/BENCHMARKS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ This will:
2929
pwsh -NoProfile -File bench/Run-Benchmarks.ps1
3030
```
3131

32+
- **Run everything from the RedactionBench class:**
33+
34+
```powershell
35+
pwsh -NoProfile -File bench/Run-Benchmarks.ps1 `
36+
-Filter "*RedactionBench.*" `
37+
-Job Default `
38+
-Framework net8.0 `
39+
-CoolDownSec 0
40+
```
41+
3242
- **Run only PhoneRedactor benchmarks:**
3343

3444
```powershell
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using BenchmarkDotNet.Configs;
2+
using BenchmarkDotNet.Exporters.Json;
3+
4+
namespace KeelMatrix.QueryWatch.Redaction.Benchmarks {
5+
internal sealed class CiAwareConfig : ManualConfig {
6+
public CiAwareConfig() {
7+
AddLogger([.. DefaultConfig.Instance.GetLoggers()]);
8+
AddColumnProvider([.. DefaultConfig.Instance.GetColumnProviders()]);
9+
AddDiagnoser([.. DefaultConfig.Instance.GetDiagnosers()]);
10+
11+
bool isCi = Environment.GetEnvironmentVariable("CI") is not null;
12+
if (!isCi) {
13+
AddExporter(JsonExporter.FullCompressed);
14+
}
15+
}
16+
}
17+
}

bench/KeelMatrix.QueryWatch.Redaction.Benchmarks/Program.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace KeelMatrix.QueryWatch.Redaction.Benchmarks {
55
public static class Program {
66
public static void Main(string[] args) =>
7-
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
7+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new CiAwareConfig());
88
}
99

1010
[MemoryDiagnoser]
@@ -31,10 +31,20 @@ public class RedactionBench {
3131
public string Jwt() => _jwt.Redact(Sample);
3232

3333
public static IEnumerable<string> SampleCases() {
34-
yield return "Contact me at [email protected] or +1-415-555-2671. Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.";
35-
yield return "noise-no-pii-" + new string('x', 32); // ~32B filler
36-
yield return "email test [email protected] " + new string('y', 1024) + " +44 20 7946 0958"; // larger line with phone
37-
yield return "jwt " + new string('z', 4096) + " eyJhbGciOi"; // big line w/ jwt-like token
34+
// Email-only samples
35+
yield return "simple [email protected]";
36+
yield return "very.long.email.address_" + new string('x', 128) + "@domain.co.uk";
37+
38+
// Phone-only
39+
yield return "+1-415-555-2671 is my number";
40+
yield return "call me at +44 20 7946 0958";
41+
42+
// JWT-only
43+
yield return "jwt eyJhbGciOi" + new string('z', 2048);
44+
45+
// Pure noise
46+
yield return "noise-no-pii-" + new string('x', 1024);
3847
}
48+
3949
}
4050
}

bench/Run-Benchmarks.ps1

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ param(
5454
[string]$ArtifactsRoot = 'artifacts/benchmarks',
5555
[double]$MaxStdevPct = 0,
5656
[switch]$Ci,
57+
[int]$CoolDownSec = 480,
5758
[switch]$DryRun
5859
)
5960

@@ -114,13 +115,19 @@ foreach($proj in $benchProjects){
114115

115116
# BenchmarkDotNet common CLI args (supported by BenchmarkSwitcher):
116117
# --list, --filter, --runtimes, --artifacts, --exporters, --join, --job
117-
# Multiple exporters: pass them as separate values to a **single** --exporters option.
118+
# Exporters: on CI we need JSON for machines + GitHub for PRs + CSV for gates.
119+
# Locally, CiAwareConfig already adds JsonExporter.FullCompressed, so we skip 'json'
120+
# to avoid the "already present" warning and ask for the human-friendly extras.
121+
$exporters = $Ci ? @('json','github','csv') : @('github','markdown','html','csv')
118122
$bdnArgs = @(
119123
'--filter', "$Filter",
120-
'--exporters', 'json', 'markdown',
121124
'--artifacts', "$projOut",
122125
'--join'
123126
)
127+
128+
# append exporters as separate tokens to avoid nested arrays or a single invalid value
129+
$bdnArgs = $bdnArgs + @('--exporters') + $exporters
130+
124131
if($Job -ne 'Default'){ $bdnArgs += @('--job', $Job) }
125132

126133
# dotnet run to bench project
@@ -132,9 +139,10 @@ foreach($proj in $benchProjects){
132139
) + $bdnArgs
133140

134141
Write-Host ("dotnet " + ($cmd -join ' '))
135-
$proc = Start-Process -FilePath 'dotnet' -ArgumentList $cmd -PassThru -Wait -NoNewWindow
136-
if($proc.ExitCode -ne 0){
137-
Write-Error "Benchmark run failed for $projName with exit code $($proc.ExitCode)."
142+
& dotnet @cmd
143+
$exitCode = $LASTEXITCODE
144+
if ($exitCode -ne 0) {
145+
Write-Error "dotnet run failed ($exitCode) for $projName"
138146
$failed++
139147
Pop-Location
140148
continue
@@ -230,6 +238,15 @@ if($Ci){
230238
Write-Host "CI Summary written to $summaryPath"
231239
}
232240

241+
# ---- Cooldown: CI-aware (skip in CI or when set to 0) ----
242+
# Note: "Cooling down" is just an intentional pause after a benchmark run on our local machine to let the CPU/fans/OS settle so the next local run isn’t biased by turbo/thermal effects and hot caches. It helps reduce noise between back-to-back local runs and gives us a few minutes to glance at artifacts. In CI we skip it (-Ci -CoolDownSec 0) because there’s only one short run and wall-clock is precious.
243+
if ($CoolDownSec -gt 0 -and -not $Ci) {
244+
Write-Host "Cooling down for $CoolDownSec seconds (local run). The job is complete. You can glance at artifacts while you wait..."
245+
Start-Sleep -Seconds $CoolDownSec
246+
} else {
247+
Write-Host "Skipping cooldown (CI or CoolDownSec=0)."
248+
}
249+
233250
# Exit code logic
234251
if($failed -gt 0){
235252
Write-Error "$failed project(s) failed to run."

0 commit comments

Comments
 (0)