Skip to content

Commit 801abed

Browse files
Add repeatable Profiler perf review script
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent ff59f0d commit 801abed

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
[CmdletBinding()]
2+
param (
3+
[Parameter()]
4+
[ValidateSet('Baseline', 'Regression')]
5+
[string] $Mode = 'Baseline',
6+
7+
[Parameter()]
8+
[ValidateRange(1, 200)]
9+
[int] $Runs = 12,
10+
11+
[Parameter()]
12+
[ValidateRange(0, 50)]
13+
[int] $Preheat = 2,
14+
15+
[Parameter()]
16+
[string] $OutputPath = (Join-Path $PSScriptRoot "..\fixtures\perf\$Mode.json")
17+
)
18+
19+
Set-StrictMode -Version Latest
20+
$ErrorActionPreference = 'Stop'
21+
22+
if ($PSVersionTable.PSVersion -lt [version] '7.6' -or $PSVersionTable.PSEdition -cne 'Core') {
23+
throw 'Invoke-YamlPerfReview.ps1 requires PowerShell 7.6+ (Core).'
24+
}
25+
26+
Import-Module -Name Profiler -ErrorAction Stop
27+
. (Join-Path $PSScriptRoot '..\TestBootstrap.ps1')
28+
29+
function Get-Percentile {
30+
[CmdletBinding()]
31+
param (
32+
[Parameter(Mandatory)]
33+
[double[]] $Values,
34+
35+
[Parameter(Mandatory)]
36+
[ValidateRange(0.0, 1.0)]
37+
[double] $Percentile
38+
)
39+
40+
if ($Values.Count -eq 0) {
41+
return 0.0
42+
}
43+
44+
$sorted = @($Values | Sort-Object)
45+
$index = [Math]::Ceiling($Percentile * $sorted.Count) - 1
46+
$index = [Math]::Max(0, [Math]::Min($index, $sorted.Count - 1))
47+
return [double] $sorted[$index]
48+
}
49+
50+
function Measure-Scenario {
51+
[CmdletBinding()]
52+
param (
53+
[Parameter(Mandatory)]
54+
[string] $Name,
55+
56+
[Parameter(Mandatory)]
57+
[scriptblock] $Script,
58+
59+
[Parameter(Mandatory)]
60+
[int] $RunCount,
61+
62+
[Parameter(Mandatory)]
63+
[int] $WarmupCount
64+
)
65+
66+
for ($i = 0; $i -lt $WarmupCount; $i++) {
67+
& $Script > $null
68+
}
69+
70+
$elapsedMs = [System.Collections.Generic.List[double]]::new()
71+
for ($i = 0; $i -lt $RunCount; $i++) {
72+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
73+
& $Script > $null
74+
$stopwatch.Stop()
75+
$elapsedMs.Add($stopwatch.Elapsed.TotalMilliseconds)
76+
}
77+
78+
$trace = Trace-Script -ScriptBlock $Script -Preheat 1 -DisableWarning
79+
$topFunctions = @(
80+
$trace.Top50FunctionSelfDuration |
81+
Select-Object -First 5 Function, Module, Line, HitCount, @{
82+
Name = 'SelfDurationMs'
83+
Expression = { [Math]::Round($_.SelfDuration.TotalMilliseconds, 3) }
84+
}, @{
85+
Name = 'DurationMs'
86+
Expression = { [Math]::Round($_.Duration.TotalMilliseconds, 3) }
87+
}
88+
)
89+
90+
$values = [double[]] $elapsedMs.ToArray()
91+
[pscustomobject]@{
92+
Name = $Name
93+
Runs = $RunCount
94+
Preheat = $WarmupCount
95+
AverageMs = [Math]::Round(($values | Measure-Object -Average).Average, 3)
96+
MedianMs = [Math]::Round((Get-Percentile -Values $values -Percentile 0.50), 3)
97+
P95Ms = [Math]::Round((Get-Percentile -Values $values -Percentile 0.95), 3)
98+
MinMs = [Math]::Round(($values | Measure-Object -Minimum).Minimum, 3)
99+
MaxMs = [Math]::Round(($values | Measure-Object -Maximum).Maximum, 3)
100+
SamplesMs = $values
101+
TraceTopSelf = $topFunctions
102+
}
103+
}
104+
105+
Write-Host "Preparing performance fixtures for mode: $Mode"
106+
107+
$smallYaml = @'
108+
name: small
109+
enabled: true
110+
ports: [80, 443]
111+
'@
112+
113+
$mediumYamlBuilder = [System.Text.StringBuilder]::new()
114+
[void] $mediumYamlBuilder.AppendLine('items:')
115+
for ($index = 0; $index -lt 1200; $index++) {
116+
[void] $mediumYamlBuilder.AppendLine(" - id: $index")
117+
[void] $mediumYamlBuilder.AppendLine(" name: item-$index")
118+
[void] $mediumYamlBuilder.AppendLine(" enabled: true")
119+
[void] $mediumYamlBuilder.AppendLine(" value: $($index * 3)")
120+
}
121+
$mediumYaml = $mediumYamlBuilder.ToString().TrimEnd("`r", "`n")
122+
123+
$overlayYamlBuilder = [System.Text.StringBuilder]::new()
124+
[void] $overlayYamlBuilder.AppendLine('items:')
125+
for ($index = 0; $index -lt 1200; $index++) {
126+
[void] $overlayYamlBuilder.AppendLine(" - id: $index")
127+
[void] $overlayYamlBuilder.AppendLine(" name: item-$index-override")
128+
[void] $overlayYamlBuilder.AppendLine(" enabled: false")
129+
}
130+
$overlayYaml = $overlayYamlBuilder.ToString().TrimEnd("`r", "`n")
131+
132+
$mediumObject = ConvertFrom-Yaml -Yaml $mediumYaml -NoEnumerate
133+
$tempPath = Join-Path ([System.IO.Path]::GetTempPath()) 'yaml-perf-review.yaml'
134+
135+
$scenarios = @(
136+
@{
137+
Name = 'ConvertFrom-Yaml/small'
138+
Script = { ConvertFrom-Yaml -Yaml $smallYaml | Out-Null }
139+
},
140+
@{
141+
Name = 'ConvertFrom-Yaml/medium'
142+
Script = { ConvertFrom-Yaml -Yaml $mediumYaml -NoEnumerate | Out-Null }
143+
},
144+
@{
145+
Name = 'ConvertTo-Yaml/medium'
146+
Script = { ConvertTo-Yaml -InputObject $mediumObject | Out-Null }
147+
},
148+
@{
149+
Name = 'Format-Yaml/medium'
150+
Script = { Format-Yaml -InputObject $mediumYaml -Indent 2 | Out-Null }
151+
},
152+
@{
153+
Name = 'Merge-Yaml/medium'
154+
Script = { Merge-Yaml -InputObject @($mediumYaml, $overlayYaml) -SequenceAction Replace | Out-Null }
155+
},
156+
@{
157+
Name = 'Remove-YamlEntry/medium'
158+
Script = { Remove-YamlEntry -InputObject $mediumYaml -Path '/items/12/name' -IgnoreMissing | Out-Null }
159+
},
160+
@{
161+
Name = 'Test-Yaml/medium'
162+
Script = { Test-Yaml -Yaml $mediumYaml | Out-Null }
163+
},
164+
@{
165+
Name = 'Export-Import-Yaml/medium'
166+
Script = {
167+
Export-Yaml -InputObject $mediumObject -Path $tempPath -Force | Out-Null
168+
Import-Yaml -LiteralPath $tempPath -NoEnumerate | Out-Null
169+
}
170+
}
171+
)
172+
173+
$results = [System.Collections.Generic.List[object]]::new()
174+
foreach ($scenario in $scenarios) {
175+
Write-Host "Measuring $($scenario.Name)"
176+
$results.Add((Measure-Scenario -Name $scenario.Name -Script $scenario.Script -RunCount $Runs -WarmupCount $Preheat))
177+
}
178+
179+
if (Test-Path -LiteralPath $tempPath) {
180+
Remove-Item -LiteralPath $tempPath -Force
181+
}
182+
183+
$outputDirectory = Split-Path -Parent $OutputPath
184+
if (-not [string]::IsNullOrWhiteSpace($outputDirectory) -and -not (Test-Path -LiteralPath $outputDirectory)) {
185+
$null = New-Item -Path $outputDirectory -ItemType Directory -Force
186+
}
187+
188+
$report = [pscustomobject]@{
189+
Mode = $Mode
190+
TimestampUtc = [DateTime]::UtcNow.ToString('o')
191+
PowerShell = $PSVersionTable.PSVersion.ToString()
192+
Edition = $PSVersionTable.PSEdition
193+
Runs = $Runs
194+
Preheat = $Preheat
195+
RegressionFail = 'Greater than 5 percent slowdown on critical-path scenarios.'
196+
Scenarios = $results
197+
}
198+
199+
$report | ConvertTo-Json -Depth 8 | Set-Content -Path $OutputPath -Encoding utf8NoBOM
200+
Write-Host "Performance report written to: $OutputPath"

0 commit comments

Comments
 (0)