Files
hrynco-kswitcher/tools/measure-memory-components.ps1
T

62 lines
3.5 KiB
PowerShell

param([ValidateRange(1,20)][int]$Runs=3,[ValidateRange(100,10000)][int]$SettleMilliseconds=1500)
# Private resident pages are counted externally, so the measurement buffer is
# allocated in PowerShell, not in the measured process. Never trim working sets.
$ErrorActionPreference='Stop'
$root=Split-Path $PSScriptRoot -Parent
$output=Join-Path $root 'build/memory-components'
New-Item -ItemType Directory -Force $output | Out-Null
$exe=Join-Path $output 'probe.exe'
& "$root/.tools/w64devkit/bin/g++.exe" -std=c++20 -Os -static -s -DUNICODE -D_UNICODE -D_WIN32_WINNT=0x0A00 "$PSScriptRoot/memory-components.cpp" -o $exe -lole32 -loleaut32 -lshell32 -lpropsys -lshlwapi -luuid -luiautomationcore
if($LASTEXITCODE) { throw 'Probe compilation failed' }
Add-Type @'
using System;
using System.Runtime.InteropServices;
public static class ComponentWorkingSet {
[DllImport("psapi.dll", SetLastError=true)] static extern bool QueryWorkingSet(IntPtr process,IntPtr buffer,int size);
public static long PrivateBytes(IntPtr process) {
int size=4*1024*1024;
IntPtr buffer=Marshal.AllocHGlobal(size);
try {
if(!QueryWorkingSet(process,buffer,size)) throw new System.ComponentModel.Win32Exception();
long count=Marshal.ReadIntPtr(buffer).ToInt64(), pages=0;
if(count>(size/IntPtr.Size)-1) throw new InvalidOperationException("Working set buffer is too small");
for(int i=0;i<count;i++) if((Marshal.ReadIntPtr(buffer,(i+1)*IntPtr.Size).ToInt64() & 256)==0) pages++;
return pages*Environment.SystemPageSize;
} finally { Marshal.FreeHGlobal(buffer); }
}
}
'@
$rows=[Collections.Generic.List[object]]::new()
foreach($run in 1..$Runs) {
foreach($mode in @('baseline','sta','mta','shell','uia','focused','combined')) {
$start=[Diagnostics.ProcessStartInfo]::new($exe,$mode)
$start.UseShellExecute=$false; $start.CreateNoWindow=$true
$start.RedirectStandardOutput=$true; $start.RedirectStandardInput=$true; $start.RedirectStandardError=$true
$process=[Diagnostics.Process]::Start($start)
$errors=$process.StandardError.ReadToEndAsync()
try {
foreach($expected in @('baseline','com','active','released','uninitialized')) {
$pending=$process.StandardOutput.ReadLineAsync()
if(!$pending.Wait(15000)) { throw "Timeout: $mode/$expected" }
$phase=$pending.Result
if($phase -ne $expected) { throw "Unexpected phase '$phase', expected $expected" }
Start-Sleep -Milliseconds $SettleMilliseconds
$process.Refresh()
$rows.Add([pscustomobject]@{Run=$run;Mode=$mode;Phase=$phase;PrivateBytes=$process.PrivateMemorySize64;PrivateWorkingSet=[ComponentWorkingSet]::PrivateBytes($process.Handle);WorkingSet=$process.WorkingSet64;Threads=$process.Threads.Count;Modules=@($process.Modules | ForEach-Object ModuleName)})
$process.StandardInput.Write("`n"); $process.StandardInput.Flush()
}
if(!$process.WaitForExit(15000)) { throw 'Exit timeout' }
if($process.ExitCode) { throw "Probe failed: $($errors.Result)" }
$errors.Result | Set-Content (Join-Path $output "$run-$mode.txt")
} finally {
if(!$process.HasExited) { $process.Kill(); $process.WaitForExit() }
$process.Dispose()
$rows | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $output 'results.json')
}
Write-Host "Completed $run/$Runs $mode"
}
}
$rows | Group-Object Mode,Phase | ForEach-Object {
[pscustomobject]@{Case=$_.Name;PrivateMiB=[math]::Round(($_.Group.PrivateBytes | Measure-Object -Average).Average/1MB,3);PrivateWorkingSetMiB=[math]::Round(($_.Group.PrivateWorkingSet | Measure-Object -Average).Average/1MB,3)}
} | Format-Table