Tests/GraphShell.Performance.Tests.ps1
|
#Requires -Modules Pester <# Performance/benchmark checks for the GraphShell module. These are informative Pester tests: they assert loose upper bounds and print real measurements (memory delta, first/second call latency) with Write-Host, visible with -Output Detailed. Run in a fresh PowerShell process so the "before load" measurement is meaningful: pwsh -NoProfile -Command "Invoke-Pester -Path .\powershell\GraphShell\Tests\GraphShell.Performance.Tests.ps1 -Output Detailed" #> BeforeAll { $script:ModuleRoot = Split-Path -Parent $PSScriptRoot $script:ManifestPath = Join-Path $ModuleRoot 'GraphShell.psd1' Remove-Module GraphShell -ErrorAction SilentlyContinue Import-Module $ManifestPath -Force -ErrorAction Stop } AfterAll { Remove-Module GraphShell -ErrorAction SilentlyContinue } Describe 'GraphShell catalog load performance' { It 'answers -Cmdlet lookups without loading the full command index' { [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() $before = [System.GC]::GetTotalMemory($true) $sw = [Diagnostics.Stopwatch]::StartNew() Get-GraphMapping -Cmdlet 'Get-MgUser' | Out-Null $sw.Stop() $firstCallMs = $sw.ElapsedMilliseconds $after = [System.GC]::GetTotalMemory($false) $deltaMb = [math]::Round(($after - $before) / 1MB, 1) Write-Host "Managed memory before -Cmdlet lookup: $([math]::Round($before/1MB,1)) MB" Write-Host "Managed memory after -Cmdlet lookup: $([math]::Round($after/1MB,1)) MB" Write-Host "Approximate -Cmdlet memory delta: $deltaMb MB" Write-Host "First Get-GraphMapping -Cmdlet call: $firstCallMs ms" $sw.Restart() Get-GraphMapping -Cmdlet 'Get-MgUser' | Out-Null $sw.Stop() Write-Host "Second (cached) call: $($sw.ElapsedMilliseconds) ms" # -Cmdlet only loads its one resource shard (data/graph-command-by-cmdlet/*.json), not # the full ~11 MB index, so the memory delta should be a small fraction of the ~200-250 # MB the full catalog used to cost every single lookup. $deltaMb | Should -BeLessThan 60 $firstCallMs | Should -BeLessThan 5000 } It 'reports the memory cost of loading the full command index for -Query' { [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() $before = [System.GC]::GetTotalMemory($true) $sw = [Diagnostics.Stopwatch]::StartNew() Get-GraphMapping -Query 'PIM eligible' | Out-Null $sw.Stop() $firstCallMs = $sw.ElapsedMilliseconds $after = [System.GC]::GetTotalMemory($false) $deltaMb = [math]::Round(($after - $before) / 1MB, 1) Write-Host "Managed memory before -Query catalog load: $([math]::Round($before/1MB,1)) MB" Write-Host "Managed memory after -Query catalog load: $([math]::Round($after/1MB,1)) MB" Write-Host "Approximate -Query catalog memory delta: $deltaMb MB" Write-Host "First (cold) Get-GraphMapping -Query call: $firstCallMs ms" $sw.Restart() Get-GraphMapping -Query 'PIM eligible' | Out-Null $sw.Stop() Write-Host "Second (warm, cached inverted index) call: $($sw.ElapsedMilliseconds) ms" # -Query is the one parameter set intentionally allowed to pay the full-catalog cost # because it also builds and caches per-session inverted word indexes. Deserialized # PowerShell object graphs have materially higher managed-memory overhead on the # Linux/.NET runtime used by GitHub Actions (~407 MB measured versus ~275 MB on # Windows), so retain a platform-specific regression ceiling rather than treating # that runtime difference as a functional failure. $memoryLimitMb = if ($IsLinux) { 500 } else { 350 } Write-Host "Platform-specific -Query memory ceiling: $memoryLimitMb MB" $deltaMb | Should -BeLessThan $memoryLimitMb # First call includes a one-time inverted-index build across the whole catalog. $firstCallMs | Should -BeLessThan 20000 } It 'answers subsequent cmdlet lookups fast once cached' { Get-GraphMapping -Cmdlet 'Get-MgUser' | Out-Null # warm cache $sw = [Diagnostics.Stopwatch]::StartNew() 1..20 | ForEach-Object { Get-GraphMapping -Cmdlet 'Get-MgUser' | Out-Null } $sw.Stop() $avgMs = [math]::Round($sw.ElapsedMilliseconds / 20, 2) Write-Host "Average cached cmdlet lookup: $avgMs ms" $avgMs | Should -BeLessThan 100 } It 'answers permission lookups fast using the reverse index' { $sw = [Diagnostics.Stopwatch]::StartNew() $result = Get-GraphMapping -Permission 'User.Read.All' $sw.Stop() Write-Host "Get-GraphMapping -Permission 'User.Read.All': $($sw.ElapsedMilliseconds) ms, $($result.Count) results" $sw.ElapsedMilliseconds | Should -BeLessThan 2000 } It 'answers endpoint lookups fast' { $sw = [Diagnostics.Stopwatch]::StartNew() Get-GraphMapping -Endpoint '/users' | Out-Null $sw.Stop() Write-Host "Get-GraphMapping -Endpoint '/users': $($sw.ElapsedMilliseconds) ms" $sw.ElapsedMilliseconds | Should -BeLessThan 2000 } It 'answers a warm free-text query search fast once the inverted index is cached' { Get-GraphMapping -Query 'PIM eligible' | Out-Null # warm the inverted index $sw = [Diagnostics.Stopwatch]::StartNew() Get-GraphMapping -Query 'PIM eligible' | Out-Null $sw.Stop() Write-Host "Get-GraphMapping -Query 'PIM eligible' (warm): $($sw.ElapsedMilliseconds) ms" $sw.ElapsedMilliseconds | Should -BeLessThan 1500 } It 'loads only one detail shard for Get-GraphDetail, not the whole detail set' { InModuleScope GraphShell { $script:GraphDetailShardCache = $null Get-GraphDetail -Name 'Get-MgUser' | Out-Null $script:GraphDetailShardCache.Keys.Count | Should -Be 1 $script:GraphDetailShardCache.Keys | Should -Contain 'g' } } } |