|
3 | 3 | ) |
4 | 4 | $ErrorActionPreference='Stop' |
5 | 5 |
|
| 6 | +function Export-BenchmarkSummary { |
| 7 | + param( |
| 8 | + [string]$BenchmarkProjectPath |
| 9 | + ) |
| 10 | + try { |
| 11 | + if(-not $BenchmarkProjectPath){ return } |
| 12 | + $projectDir = Split-Path $BenchmarkProjectPath -Parent |
| 13 | + $artifactRoot = Join-Path $projectDir 'BenchmarkDotNet.Artifacts' 'results' |
| 14 | + if(-not (Test-Path $artifactRoot)) { |
| 15 | + # Fallback to solution root artifacts |
| 16 | + $solutionRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path |
| 17 | + $artifactRoot = Join-Path $solutionRoot 'BenchmarkDotNet.Artifacts' 'results' |
| 18 | + } |
| 19 | + if(-not (Test-Path $artifactRoot)) { Write-Host "No BenchmarkDotNet results directory found (checked project + solution): $artifactRoot" -ForegroundColor Yellow; return } |
| 20 | + $outDir = Join-Path $PSScriptRoot '..' '.tmp' 'benchmarks' |
| 21 | + New-Item -ItemType Directory -Force -Path $outDir | Out-Null |
| 22 | + $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' |
| 23 | + $sessionDir = Join-Path $outDir $timestamp |
| 24 | + New-Item -ItemType Directory -Force -Path $sessionDir | Out-Null |
| 25 | + $copied = @() |
| 26 | + Get-ChildItem $artifactRoot -File | ForEach-Object { |
| 27 | + Copy-Item $_.FullName -Destination (Join-Path $sessionDir $_.Name) -Force |
| 28 | + $copied += $_.Name |
| 29 | + } |
| 30 | + # Build JSON summary from github md report if present |
| 31 | + $githubMd = Get-ChildItem $sessionDir -Filter '*-report-github.md' | Select-Object -First 1 |
| 32 | + $csv = Get-ChildItem $sessionDir -Filter '*-report.csv' | Select-Object -First 1 |
| 33 | + $summary = [ordered]@{ |
| 34 | + project = $BenchmarkProjectPath |
| 35 | + timestamp = $timestamp |
| 36 | + artifacts = $copied |
| 37 | + csvPath = $csv?.FullName |
| 38 | + markdownGithubPath = $githubMd?.FullName |
| 39 | + } |
| 40 | + $summaryFile = Join-Path $sessionDir 'summary.json' |
| 41 | + $summary | ConvertTo-Json -Depth 5 | Out-File -FilePath $summaryFile -Encoding UTF8 |
| 42 | + Write-Host "Benchmark summary exported: $summaryFile" -ForegroundColor Green |
| 43 | + } catch { |
| 44 | + Write-Host "Failed exporting benchmark summary: $($_.Exception.Message)" -ForegroundColor Yellow |
| 45 | + } |
| 46 | +} |
| 47 | + |
6 | 48 | function Ensure-Tool($tool,$install){ |
7 | 49 | if(-not (Get-Command $tool -ErrorAction SilentlyContinue)){ |
8 | 50 | Write-Host "Installing missing tool: $tool" -ForegroundColor Yellow |
@@ -44,6 +86,7 @@ switch($Command.ToLowerInvariant()){ |
44 | 86 | Remove-Item Env:DOTNET_EnableDiagnostics -ErrorAction SilentlyContinue |
45 | 87 | if($LASTEXITCODE -ne 0){ throw 'BenchmarkDotNet failed' } |
46 | 88 | Write-Host 'Benchmarks completed.' -ForegroundColor Green |
| 89 | + Export-BenchmarkSummary -BenchmarkProjectPath $benchProj.FullName |
47 | 90 | } |
48 | 91 | catch { |
49 | 92 | Write-Host "Benchmark run failed: $($_.Exception.Message). Falling back to simple performance smoke (build + run)." -ForegroundColor Yellow |
@@ -102,25 +145,94 @@ switch($Command.ToLowerInvariant()){ |
102 | 145 | if($LASTEXITCODE -ne 0){ throw 'GC stats collection failed' } |
103 | 146 | Write-Host 'GC sampling complete.' -ForegroundColor Green |
104 | 147 | } |
105 | | - 'aspnet-metrics' { |
| 148 | + 'aspnet-metrics' { |
| 149 | + Ensure-Tool 'dotnet-counters' 'dotnet-counters' |
| 150 | + $script:DotNetOnly = $true |
| 151 | + $procId = Select-Pid 'Select ASP.NET Core process for metrics' |
| 152 | + if(-not $procId){ Write-Host 'No PID selected.' -ForegroundColor Yellow; break } |
| 153 | + Write-Host "Monitoring ASP.NET Core counters for PID $procId (10s) ..." -ForegroundColor Cyan |
| 154 | + $counterGroups = @('Microsoft.AspNetCore.Hosting') |
| 155 | + $countersArg = $counterGroups -join ' ' |
| 156 | + & dotnet-counters monitor --process-id $procId --counters $countersArg --refresh-interval 1 --duration 10 |
| 157 | + if($LASTEXITCODE -ne 0){ throw 'ASP.NET metrics collection failed' } |
| 158 | + Write-Host 'ASP.NET metrics sampling complete.' -ForegroundColor Green |
| 159 | + } |
| 160 | + 'quick' { |
| 161 | + # Combined diagnostics: CPU trace + GC trace + ASP.NET metrics (best effort) |
| 162 | + Ensure-Tool 'dotnet-trace' 'dotnet-trace' |
106 | 163 | Ensure-Tool 'dotnet-counters' 'dotnet-counters' |
107 | 164 | $script:DotNetOnly = $true |
108 | | - $procId = Select-Pid 'Select ASP.NET Core process for metrics' |
| 165 | + $procId = Select-Pid 'Select process for QUICK diagnostics' |
109 | 166 | if(-not $procId){ Write-Host 'No PID selected.' -ForegroundColor Yellow; break } |
110 | | - Write-Host "Monitoring ASP.NET Core + runtime counters for PID $procId (10s) ..." -ForegroundColor Cyan |
111 | | - # $counterGroups = @( |
112 | | - # 'Microsoft.AspNetCore.Hosting[requests-started;requests-completed;current-requests]', |
113 | | - # 'Microsoft.AspNetCore.Server.Kestrel[connection-queue-length;connections-active;connections-opened;connections-closed]', |
114 | | - # 'System.Net.Http[requests-started;requests-failed]', |
115 | | - # 'System.Runtime[cpu-usage;working-set;gc-heap-size;gen-0-gc-count;gen-1-gc-count;gen-2-gc-count;time-in-gc]' |
116 | | - # ) |
117 | | - $counterGroups = @( |
118 | | - 'Microsoft.AspNetCore.Hosting' |
119 | | - ) |
120 | | - $countersArg = $counterGroups -join ' ' |
121 | | - & dotnet-counters monitor --process-id $procId --counters $countersArg --refresh-interval 1 --duration 10 |
122 | | - if($LASTEXITCODE -ne 0){ throw 'ASP.NET metrics collection failed' } |
123 | | - Write-Host 'ASP.NET metrics sampling complete.' -ForegroundColor Green |
| 167 | + $outDir = Join-Path $PSScriptRoot '..' '.tmp' 'diagnostics' |
| 168 | + New-Item -ItemType Directory -Force -Path $outDir | Out-Null |
| 169 | + $errors = @() |
| 170 | + # CPU trace (5s) |
| 171 | + try { |
| 172 | + $cpuFile = Join-Path $outDir "cpuQuick_${procId}_$(Get-Date -Format 'yyyyMMdd_HHmmss').nettrace" |
| 173 | + Write-Host "[Quick] CPU trace (5s) for PID $procId" -ForegroundColor Cyan |
| 174 | + & dotnet-trace collect --process-id $procId --providers Microsoft-DotNETCore-SampleProfiler:1 --duration 00:00:05 -o $cpuFile |
| 175 | + if($LASTEXITCODE -ne 0){ throw 'CPU trace failed' } |
| 176 | + Write-Host "[Quick] CPU trace saved: $cpuFile" -ForegroundColor Green |
| 177 | + } catch { $errors += $_.Exception.Message; Write-Host "[Quick] CPU trace error: $($_.Exception.Message)" -ForegroundColor Yellow } |
| 178 | + # GC trace (5s) |
| 179 | + try { |
| 180 | + $gcFile = Join-Path $outDir "gcQuick_${procId}_$(Get-Date -Format 'yyyyMMdd_HHmmss').nettrace" |
| 181 | + Write-Host "[Quick] GC trace (5s) for PID $procId" -ForegroundColor Cyan |
| 182 | + & dotnet-trace collect --process-id $procId --providers Microsoft-DotNETCore-SampleProfiler:1,System.Runtime:4 --duration 00:00:05 -o $gcFile |
| 183 | + if($LASTEXITCODE -ne 0){ throw 'GC trace failed' } |
| 184 | + Write-Host "[Quick] GC trace saved: $gcFile" -ForegroundColor Green |
| 185 | + } catch { $errors += $_.Exception.Message; Write-Host "[Quick] GC trace error: $($_.Exception.Message)" -ForegroundColor Yellow } |
| 186 | + # ASP.NET metrics (6s) limited group |
| 187 | + try { |
| 188 | + Write-Host "[Quick] ASP.NET metrics (6s) for PID $procId" -ForegroundColor Cyan |
| 189 | + & dotnet-counters monitor --process-id $procId --counters "Microsoft.AspNetCore.Hosting" --refresh-interval 1 --duration 6 |
| 190 | + if($LASTEXITCODE -ne 0){ throw 'ASP.NET metrics failed' } |
| 191 | + Write-Host '[Quick] ASP.NET metrics sampling complete.' -ForegroundColor Green |
| 192 | + } catch { $errors += $_.Exception.Message; Write-Host "[Quick] ASP.NET metrics error: $($_.Exception.Message)" -ForegroundColor Yellow } |
| 193 | + if($errors.Count -gt 0){ |
| 194 | + Write-Host "Quick diagnostics finished with $($errors.Count) error(s)." -ForegroundColor Yellow |
| 195 | + foreach($e in $errors){ Write-Host " - $e" -ForegroundColor DarkYellow } |
| 196 | + } else { |
| 197 | + Write-Host 'Quick diagnostics completed successfully.' -ForegroundColor Green |
| 198 | + } |
124 | 199 | } |
| 200 | + 'bench-select' { |
| 201 | + # Enumerate benchmark projects and allow Spectre selection (force user selection even if single project) |
| 202 | + $benchProjects = Get-ChildItem -Recurse -Filter '*Benchmarks.csproj' -File 2>$null |
| 203 | + if(-not $benchProjects){ Write-Host 'No benchmark projects (*.Benchmarks.csproj) found.' -ForegroundColor Yellow; break } |
| 204 | + Import-Module PwshSpectreConsole -ErrorAction Stop |
| 205 | + $solutionRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path |
| 206 | + $projectMap = [ordered]@{} |
| 207 | + $labels = @() |
| 208 | + $i = 0 |
| 209 | + foreach($p in ($benchProjects | Sort-Object FullName)){ |
| 210 | + $full = (Resolve-Path $p.FullName).Path |
| 211 | + if($full.StartsWith($solutionRoot)) { $rel = $full.Substring($solutionRoot.Length).TrimStart('\\') } else { $rel = Split-Path $full -Leaf } |
| 212 | + # Avoid Spectre markup brackets by using parentheses for index |
| 213 | + $label = "$rel" |
| 214 | + $projectMap[$label] = $full |
| 215 | + $labels += $label |
| 216 | + $i++ |
| 217 | + } |
| 218 | + $selected = Read-SpectreSelection -Title 'Select Benchmark Project' -Choices ($labels + 'Cancel') -EnableSearch -PageSize 15 |
| 219 | + if(-not $selected -or $selected -eq 'Cancel'){ Write-Host 'Benchmark selection cancelled.' -ForegroundColor Yellow; $LASTEXITCODE = 0; break } |
| 220 | + if(-not $projectMap.Contains($selected)){ Write-Host 'Invalid selection mapping.' -ForegroundColor Red; $LASTEXITCODE = 1; break } |
| 221 | + $projPath = $projectMap[$selected] |
| 222 | + Write-Host "Running selected benchmarks: $projPath" -ForegroundColor Cyan |
| 223 | + try { |
| 224 | + $env:DOTNET_EnableDiagnostics=0 |
| 225 | + & dotnet run --project "$projPath" -c Release -- --filter '*' --anyCategories '*' |
| 226 | + $runExit = $LASTEXITCODE |
| 227 | + Remove-Item Env:DOTNET_EnableDiagnostics -ErrorAction SilentlyContinue |
| 228 | + if($runExit -ne 0){ throw "Benchmark run failed (exit $runExit)" } |
| 229 | + Write-Host 'Selected benchmarks completed.' -ForegroundColor Green |
| 230 | + Export-BenchmarkSummary -BenchmarkProjectPath $projPath |
| 231 | + } catch { |
| 232 | + Write-Host "Benchmark run failed: $($_.Exception.Message)" -ForegroundColor Red |
| 233 | + Remove-Item Env:DOTNET_EnableDiagnostics -ErrorAction SilentlyContinue |
| 234 | + $LASTEXITCODE = 1 |
| 235 | + } |
| 236 | + } |
125 | 237 | default { throw "Unknown diagnostics command: $Command" } |
126 | 238 | } |
0 commit comments