MaxKK

PSBenchFrame - Power Shell Benchmark Framework - PS script performance test

May 30th, 2026 (edited)
45
0
Never
4
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PowerShell 10.47 KB | Software | 0 0
  1. # PSBenchFrame 1.1
  2. # by MaxK 2026.05.30
  3. param(
  4.     # Execution mode: "Sequential" or "Interlaced"
  5.     [string]$ExecutionMode = "Interlaced",
  6.     [int]$TotalIterations = 10000
  7. )
  8.  
  9. # ==============================================================================
  10. # Configuration
  11. # ==============================================================================
  12. $Global:PauseProbabilityPercent = 0.25 # Pause probability(eg. 0.25%)
  13.  
  14. # Pause type: "Random" or "KeyPress"
  15. $Global:PauseType               = "Random"
  16.  
  17. # Pause length - random between min and max in ms
  18. $Global:PauseMinMilliseconds   = 10
  19. $Global:PauseMaxMilliseconds   = 500
  20.  
  21. # Probability of switching to the next function to be tested in %
  22. $Global:SwitchProbabilityPercent = 6.0
  23.  
  24. $Global:LogFile = "PSBenchFrame.log"
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31.  
  32.  
  33.  
  34. # ==============================================================================
  35. # Functions to be tested (and their init-helpers)
  36. # ==============================================================================
  37.  
  38. # A helper init function for Get-XYZ invoked before benchmark
  39. function Init-XYZ {
  40.     $Global:XYZ_Salt = [Guid]::NewGuid().ToString()
  41.     $Global:XYZ_Base = "SystemSession_SecureKey_2026_"
  42.     $Global:XYZ_Counter = 0
  43. }
  44.  
  45. function Init-XYZ-Fat {
  46.     $Global:XYZ_Fat_Counter = 0
  47. }
  48.  
  49. function Init-XYZ-Turbo {
  50.     $Global:XYZ_Turbo_Counter = 0
  51.     $Global:sha = [System.Security.Cryptography.SHA256]::Create()
  52. }
  53.  
  54. function Dispose-XYZ-Turbo {
  55.     $Global:XYZ_Turbo_Counter = 0
  56.     $Global:sha = [System.Security.Cryptography.SHA256]::Create()
  57. }
  58.  
  59. # The function to be tested itself
  60. function Get-XYZ {
  61.     $Global:XYZ_Counter++
  62.     $rawString = "$Global:XYZ_Base$Global:XYZ_Salt$Global:XYZ_Counter"
  63.     $bytes = [System.Text.Encoding]::UTF8.GetBytes($rawString)
  64.     $sha = [System.Security.Cryptography.SHA256]::Create()
  65.     $hash = $sha.ComputeHash($bytes)
  66.     $sha.Dispose()
  67.     $token = [System.Convert]::ToBase64String($hash)
  68.     return $token
  69. }
  70.  
  71. function Get-XYZ-Turbo {
  72.     $Global:XYZ_Turbo_Counter++
  73.     $rawString = "$Global:XYZ_Base$Global:XYZ_Salt$Global:XYZ_Turbo_Counter"
  74.     $bytes = [System.Text.Encoding]::UTF8.GetBytes($rawString)
  75.     $hash = $Global:sha.ComputeHash($bytes)
  76.     $token = [System.Convert]::ToBase64String($hash)
  77.     return $token
  78. }
  79.  
  80.  
  81. function Get-XYZ-Fat {
  82.     $Global:XYZ_Fat_Counter++
  83.     $rawString = "$Global:XYZ_Base$Global:XYZ_Salt$Global:XYZ_Fat_Counter"
  84.     $bytes = [System.Text.Encoding]::UTF8.GetBytes($rawString)
  85.     $sha = [System.Security.Cryptography.SHA256]::Create()
  86.     for ([int]$i = 0; $i -lt 50; $i++) {
  87.         $hash = $Global:sha.ComputeHash($bytes)
  88.     }
  89.     $sha.Dispose()
  90.     $token = [System.Convert]::ToBase64String($hash)
  91.     return $token
  92. }
  93.  
  94. function Try-To-Melt-Single-Core {
  95.     [double]$x = 1.0000001
  96.     for ([int]$i = 0; $i -lt 100000; $i++) {
  97.         $x = $x * $x
  98.         $x = $x / 0.98999999
  99.         if ($x -gt 1e100) { $x = 1.0000001 }
  100.     }
  101. }
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109. # ==============================================================================
  110. # Test suite
  111. # ==============================================================================
  112. $Global:TestEntries = @(
  113.     #1st function to be tested
  114.     [PSCustomObject]@{
  115.         Name = "Get-XYZ"
  116.         Code = { $null = Get-XYZ } # the function above
  117.     },
  118.     #2nd function to be tested
  119.     [PSCustomObject]@{
  120.         Name = "Get-XYZ-Fat"
  121.         Code = { $null = Get-XYZ-Fat } # some alternative to the function above
  122.     },
  123.     #3rd function to be tested
  124.     [PSCustomObject]@{
  125.         Name = "Try-To-Melt-Single-Core"
  126.         Code = { $null = Try-To-Melt-Single-Core }
  127.     },
  128.     #4th function to be tested
  129.     [PSCustomObject]@{
  130.         Name = "Get-XYZ-Turbo"
  131.         Code = { $null = Get-XYZ-Turbo } # some alternative to the function above
  132.     }
  133.     #5th function to be tested
  134.     #[PSCustomObject]@{
  135.     #    Name = "Get-XYZ-Turbo-Ultra"
  136.     #    Code = { $null = Get-XYZ-Turbo-Ultra } # some other alternative to the function above
  137.     #}
  138.     # and so on - you can add here further functions to be tested...
  139. )
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153. # ==============================================================================
  154. # Benchmark engine helper functions
  155. # ==============================================================================
  156.  
  157. # Progress bar
  158. function Show-DismProgressBar {
  159.     param (
  160.         [int]$Current,
  161.         [int]$Total,
  162.         [string]$Status = ""
  163.     )
  164.     $percent = [math]::Round(($Current / $Total) * 100)
  165.     $barLength = 20
  166.     $filledLength = [math]::Round(($percent / 100) * $barLength)
  167.     $filled = "+" * $filledLength
  168.     $empty = "-" * ($barLength - $filledLength)
  169.     $clearBuffer = " " * 20
  170.     $output = "`rProgress: [$filled$empty] $percent% | Status: $Status$clearBuffer"
  171.    
  172.     [System.Console]::Write($output)
  173. }
  174.  
  175. # Random pause during test - not added to the execution time of course
  176. function Invoke-StochasticPause {
  177.     param ([int]$PauseIndex)    
  178.     $roll = Get-Random -Minimum 1 -Maximum 10001
  179.     if ($roll -le ($Global:PauseProbabilityPercent * 100)) {
  180.         if ($Global:PauseType -eq "KeyPress") {
  181.             Show-DismProgressBar -Current $PauseIndex -Total $TotalIterations -Status "[PAUSE] Press any key..."
  182.             $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
  183.         }
  184.         elseif ($Global:PauseType -eq "Random") {
  185.             $totalSleepMs = Get-Random -Minimum $Global:PauseMinMilliseconds -Maximum ($Global:PauseMaxMilliseconds + 1)
  186.             for ($remaining = $totalSleepMs; $remaining -gt 0; $remaining -= 100) {
  187.                 Show-DismProgressBar -Current $PauseIndex -Total $TotalIterations -Status "[PAUSE] Resuming in ${remaining}ms..."
  188.                 Start-Sleep -Milliseconds 100
  189.             }
  190.         }
  191.         return $true
  192.     }
  193.     Show-DismProgressBar -Current $i -Total $TotalIterations -Status "Active: [$($Global:activeTest.Name)]"
  194.     return $false
  195. }
  196.  
  197. # ==============================================================================
  198. # Main benchmark engine
  199. # ==============================================================================
  200. function Start-BenchmarkSuite {
  201.     Write-Host "=== ENGINE START: Mode = $ExecutionMode ===" -ForegroundColor Cyan
  202.    
  203.     [System.GC]::Collect()
  204.     [System.GC]::WaitForPendingFinalizers()
  205.     $results = @()
  206.     # Sequential mode
  207.     if ($ExecutionMode -eq "Sequential") {
  208.         foreach ($test in $Global:TestEntries) {
  209.             Write-Host "Running: $($test.Name)... " -NoNewline -ForegroundColor Gray
  210.             $Global:activeTest = $test
  211.             # One StopWatch instance for the whole loop = no-GC, zero-overhead
  212.            
  213.             $sw = [System.Diagnostics.Stopwatch]::StartNew()
  214.             $pauseTicks = 0
  215.            
  216.             for ($i = 1; $i -le $TotalIterations; $i++) {
  217.                
  218.                 $pauseSw = [System.Diagnostics.Stopwatch]::StartNew()
  219.                 $hadPause = Invoke-StochasticPause -PauseIndex $i
  220.                 $pauseSw.Stop()
  221.                 if ($hadPause) { $pauseTicks += $pauseSw.ElapsedTicks }
  222.  
  223.                 # direct call = no thousands of Measure-Command in the loop
  224.                 & $test.Code
  225.             }
  226.             $sw.Stop()
  227.            
  228.        
  229.             $netTicks = $sw.ElapsedTicks - $pauseTicks
  230.             $netTimeSpan = [TimeSpan]::FromTicks($netTicks)
  231.  
  232.             $results+=([PSCustomObject]@{
  233.                 TestName         = $test.Name
  234.                 TotalIterations  = $TotalIterations
  235.                 TotalTimeSeconds = [math]::Round($netTimeSpan.TotalSeconds, 4)
  236.                 AvgTimeMsPerIter = [math]::Round(($netTimeSpan.TotalMilliseconds / $TotalIterations), 4)
  237.             })
  238.             Show-DismProgressBar -Current 100 -Total 100 -Status "Finished $TotalIterations iterations of [$($Global:activeTest.Name)]"
  239.             Write-Host ""
  240.         }
  241.     }
  242.     # Interlaced mode
  243.     elseif ($ExecutionMode -eq "Interlaced") {
  244.         $testCount = $Global:TestEntries.Count
  245.         $metrics = [System.Collections.Generic.Dictionary[int, PSCustomObject]]::new()
  246.        
  247.         for ($t = 0; $t -lt $testCount; $t++) {
  248.             $metrics[$t] = [PSCustomObject]@{
  249.                 Stopwatch  = [System.Diagnostics.Stopwatch]::new()
  250.                 Executions = 0
  251.             }
  252.         }
  253.  
  254.         $currentTestIndex = 0
  255.         Show-DismProgressBar -Current 0 -Total $TotalIterations -Status "Starting Interlaced..."
  256.  
  257.         for ($i = 1; $i -le $TotalIterations; $i++) {
  258.            
  259.             # Stochastic pause
  260.             if (Invoke-StochasticPause -PauseIndex $i) { }
  261.  
  262.             $Global:activeTest = $Global:TestEntries[$currentTestIndex]
  263.             $activeMetric = $metrics[$currentTestIndex]
  264.  
  265.             # Precise measurement of time
  266.             # Start
  267.             $activeMetric.Stopwatch.Start()
  268.            
  269.             & $Global:activeTest.Code # Run currently selected function
  270.            
  271.             # Stop
  272.             $activeMetric.Stopwatch.Stop()
  273.             # ---------------------------------
  274.  
  275.             $activeMetric.Executions++
  276.            
  277.             $switchRoll = Get-Random -Minimum 1 -Maximum 10001
  278.             if ($switchRoll -le ($Global:SwitchProbabilityPercent * 100)) {
  279.                 $currentTestIndex = ($currentTestIndex + 1) % $testCount
  280.             }
  281.  
  282.             # Progress bar
  283.             if ($i % 10 -eq 0 -or $i -eq $TotalIterations) {
  284.                 Show-DismProgressBar -Current $i -Total $TotalIterations -Status "Active: [$($Global:activeTest.Name)]"
  285.             }
  286.         }
  287.         Show-DismProgressBar -Current 100 -Total 100 -Status "Finished $TotalIterations iterations"
  288.         Write-Host ""
  289.  
  290.         # Results
  291.         for ($t = 0; $t -lt $testCount; $t++) {
  292.             $execs = $metrics[$t].Executions
  293.             $sw = $metrics[$t].Stopwatch
  294.    
  295.             $avg = 0
  296.             if ($execs -gt 0) {
  297.                 $avg = [math]::Round(($sw.Elapsed.TotalMilliseconds / $execs), 4)
  298.             }
  299.            
  300.             $results += [PSCustomObject]@{
  301.                 TestName         = $Global:TestEntries[$t].Name
  302.                 TotalIterations  = $execs
  303.                 TotalTimeSeconds = [math]::Round($sw.Elapsed.TotalSeconds, 4)
  304.                 AvgTimeMsPerIter = $avg
  305.             }
  306.         }
  307.     }
  308.  
  309.     # ==============================================================================
  310.     # Output
  311.     # ==============================================================================
  312.     Write-Host "`n================ FINAL TEST RESULTS ================" -ForegroundColor Green
  313.  
  314.     $timestamp = [DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss")
  315.     $logHeader = "`n[BENCHMARK RUN: $timestamp]`n"
  316.  
  317.     $(
  318.         $logHeader
  319.         $results | Format-Table -AutoSize | Out-String
  320.     ) | Tee-Object -FilePath $Global:LogFile -Append   
  321. }
  322.  
  323. function Init-Functions {
  324.     Init-XYZ
  325.     Init-XYZ-Turbo
  326.     Init-XYZ-Fat
  327.     # Place here all other init functions required by test functions
  328. }
  329.  
  330. function Dispose-Functions {
  331.     Dispose-XYZ-Turbo
  332. }
  333.  
  334. Init-Functions
  335.  
  336. Start-BenchmarkSuite
  337.  
  338. Dispose-Functions
Comments
  • MaxKK
    149 days
    Comment was deleted
  • MaxKK
    149 days (edited)
    # text 0.06 KB | 0 0
    1. Update 1.1 - less waste for GC, much more precise time measurement
  • MaxKK
    148 days (edited)
    Comment was deleted
  • MaxKK
    148 days
    # text 1.38 KB | 0 0
    1. How to Use
    2. 1. Copy/Import functions you want to test. Add Init-functions if required.
    3. 2. Populate the Suite: Go to the $Global:TestEntries array and add your blocks. Define a user-friendly Name and wrap your target commands inside the Code = { ... } script block.
    4. 3. Configure the Chaos Engine:
    5. - Adjust $Global:SwitchProbabilityPercent to set how often the engine switches tasks in Interlaced mode. Higher values mean shorter, frantic bursts of alternating tasks.
    6. - Adjust $Global:PauseProbabilityPercent to simulate system hiccups or user idling. You can use values like 0.15 for fine granularity. Or 0 if you don't want any pauses.
    7. 4. Run the script in a standard PowerShell console window (powershell.exe or pwsh). Avoid VS Code's integrated terminal for best UI rendering accuracy.
    8. Parameters:
    9. - ExecutionMode: either "Sequential" if you want to baseline individual commands back-to-back or "Interlaced" if you want to simulate real-world environment context-switching stress.
    10. - TotalIterations: in "Sequential" mode it defines how mant times each function will be executed and measured. In "Interlaced" mode it's a total number of function calls. Each function will be called and measured more or less as frequent as others. So for 5 functions and 1000 iterations each should be called around 200 times.
    11.  
    12. Hope this helps anyone looking to pull highly accurate metrics out of their automated scripts.
Add Comment
Please, Sign In to add comment