Tests/Measure-PowerTreePerformance.ps1
|
[CmdletBinding()] param( [string]$ModulePath = (Join-Path $PSScriptRoot "../PowerTree.psd1"), [string]$JsonPath, [string]$BaselinePath, [string]$MarkdownPath, [ValidateRange(1, 20)] [int]$Iterations = 3 ) $ErrorActionPreference = "Stop" $resolvedModulePath = (Resolve-Path $ModulePath).Path $benchmarkRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("powertree-performance-" + [guid]::NewGuid().ToString("N")) $fixturePath = Join-Path $benchmarkRoot "fixture" $configPath = Join-Path $benchmarkRoot ".PowerTree/config.json" $originalHome = $env:HOME $originalUserProfile = $env:USERPROFILE $module = $null function Get-Median { param([double[]]$Values) $ordered = @($Values | Sort-Object) $middle = [math]::Floor($ordered.Count / 2) if ($ordered.Count % 2 -eq 1) { return $ordered[$middle] } return ($ordered[$middle - 1] + $ordered[$middle]) / 2 } function Format-Milliseconds { param([double]$Value) return [string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:0.0}", $Value) } function Format-Percentage { param([double]$Value) return [string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:+0.0;-0.0;0.0}%", $Value) } try { [void][System.IO.Directory]::CreateDirectory($fixturePath) $level = @($fixturePath) $extensions = @("txt", "log", "ps1") foreach ($depth in 1..5) { $nextLevel = [System.Collections.Generic.List[string]]::new() foreach ($parent in $level) { foreach ($directoryIndex in 1..4) { $directory = Join-Path $parent "d${depth}_${directoryIndex}" [void][System.IO.Directory]::CreateDirectory($directory) $nextLevel.Add($directory) foreach ($fileIndex in 1..3) { $extension = $extensions[$fileIndex - 1] $filePath = Join-Path $directory "f${fileIndex}.${extension}" $size = ($depth * 128) + ($directoryIndex * 16) + $fileIndex [System.IO.File]::WriteAllBytes($filePath, [byte[]]::new($size)) } } } $level = $nextLevel } $config = @{ Shared = @{ ShowConnectorLines = $true ShowExecutionStats = $false ShowConfigurations = $false LineStyle = "Unicode" OpenOutputFileOnFinish = $false } FileSystem = @{ MaxDepth = -1 ExcludeDirectories = @() HumanReadableSizes = $true Files = @{ ExcludeExtensions = @() IncludeExtensions = @() FileSizeMinimum = "-1kb" FileSizeMaximum = "-1kb" FileLimit = -1 } Sorting = @{ By = "Name" SortFolders = $true } } Registry = @{ MaxDepth = -1 ExcludeKeys = @() } } [void][System.IO.Directory]::CreateDirectory((Split-Path $configPath -Parent)) [System.IO.File]::WriteAllText($configPath, ($config | ConvertTo-Json -Depth 5)) $env:HOME = $benchmarkRoot $env:USERPROFILE = $benchmarkRoot $module = Import-Module $resolvedModulePath -Force -PassThru $cases = @( @{ Name = "Default" Command = "ptree fixture" Parameters = @{} } @{ Name = "Display size" Command = "ptree fixture -DisplaySize" Parameters = @{ DisplaySize = $true } } @{ Name = "Size sort" Command = "ptree fixture -DisplaySize -SortBySize -Descending" Parameters = @{ DisplaySize = $true SortBySize = $true Descending = $true } } @{ Name = "Prune and filter" Command = "ptree fixture -PruneEmptyFolders -IncludeExtensions txt" Parameters = @{ PruneEmptyFolders = $true IncludeExtensions = @("txt") } } ) $results = @( foreach ($case in $cases) { $parameters = @{ LiteralPath = $fixturePath } foreach ($entry in $case.Parameters.GetEnumerator()) { $parameters[$entry.Key] = $entry.Value } Show-PowerTree @parameters *> $null $samples = @( foreach ($iteration in 1..$Iterations) { (Measure-Command { Show-PowerTree @parameters *> $null }).TotalMilliseconds } ) [pscustomobject]@{ Name = $case.Name Command = $case.Command MedianMilliseconds = [math]::Round((Get-Median $samples), 3) MinimumMilliseconds = [math]::Round(($samples | Measure-Object -Minimum).Minimum, 3) MaximumMilliseconds = [math]::Round(($samples | Measure-Object -Maximum).Maximum, 3) Iterations = $Iterations } } ) $results | Format-Table Name, MedianMilliseconds, MinimumMilliseconds, MaximumMilliseconds -AutoSize if ($JsonPath) { $resolvedJsonPath = [System.IO.Path]::GetFullPath($JsonPath) [System.IO.File]::WriteAllText($resolvedJsonPath, ($results | ConvertTo-Json -Depth 3)) } if ($MarkdownPath) { $lines = [System.Collections.Generic.List[string]]::new() $lines.Add("## PowerTree performance") $lines.Add("") $lines.Add("Fixture: 1,364 directories and 4,092 files. Median of $Iterations measured runs after one warm-up.") $lines.Add("") $baselineResults = if ($BaselinePath -and (Test-Path $BaselinePath)) { @(Get-Content $BaselinePath -Raw | ConvertFrom-Json) } else { @() } if ($baselineResults.Count -gt 0) { $lines.Add("| Command | Base median | PR median | Change | PR range |") $lines.Add("|---|---:|---:|---:|---:|") foreach ($result in $results) { $baseline = $baselineResults | Where-Object Name -eq $result.Name | Select-Object -First 1 $change = if ($baseline.MedianMilliseconds -gt 0) { (($result.MedianMilliseconds - $baseline.MedianMilliseconds) / $baseline.MedianMilliseconds) * 100 } else { 0 } $baseMedian = Format-Milliseconds $baseline.MedianMilliseconds $prMedian = Format-Milliseconds $result.MedianMilliseconds $changeText = Format-Percentage $change $minimum = Format-Milliseconds $result.MinimumMilliseconds $maximum = Format-Milliseconds $result.MaximumMilliseconds $lines.Add("| ``$($result.Command)`` | ${baseMedian} ms | ${prMedian} ms | $changeText | ${minimum}–${maximum} ms |") } } else { $lines.Add("| Command | Median | Range |") $lines.Add("|---|---:|---:|") foreach ($result in $results) { $median = Format-Milliseconds $result.MedianMilliseconds $minimum = Format-Milliseconds $result.MinimumMilliseconds $maximum = Format-Milliseconds $result.MaximumMilliseconds $lines.Add("| ``$($result.Command)`` | ${median} ms | ${minimum}–${maximum} ms |") } } $lines.Add("") $lines.Add("Negative change is faster. Results are informational because GitHub-hosted runner performance varies.") $markdown = ($lines -join [Environment]::NewLine) + [Environment]::NewLine [System.IO.File]::AppendAllText([System.IO.Path]::GetFullPath($MarkdownPath), $markdown) } } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction Ignore } $env:HOME = $originalHome $env:USERPROFILE = $originalUserProfile if ($benchmarkRoot.StartsWith([System.IO.Path]::GetTempPath() + "powertree-performance-")) { [System.IO.Directory]::Delete($benchmarkRoot, $true) } } |