# phase1_idle_baseline.ps1 # Runs unattended for ~4 hours. Each cycle: # - samples acclient memory metrics → CSV # - every Nth cycle, takes a procdump (full memory) for later analysis # - exits if acclient dies (so we know exactly when) # # Output: # artifacts/phase1/memtrace.csv (one row per sample) # artifacts/phase1/dump_NNN.dmp (every 30 min) # artifacts/phase1/phase1.log (operator log) # # Run with: # powershell -ExecutionPolicy Bypass -File bin\phase1_idle_baseline.ps1 param( [string]$PhaseDir = "C:\Users\acbot\leakhunt\artifacts\soak", [int]$SampleEvery = 300, # 5 min [int]$DumpEvery = 6, # every 6th sample = every 30 min [int]$DurationSec = 28800, # 8 h (extend if no crash) [string]$ProcDumpExe = "C:\Tools\Sysinternals\procdump.exe" ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" Add-Type -Namespace LeakHunt -Name User32 -MemberDefinition @' [System.Runtime.InteropServices.DllImport("User32.dll")] public static extern int GetGuiResources(System.IntPtr hProcess, int uiFlags); '@ -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $PhaseDir -Force | Out-Null $csv = Join-Path $PhaseDir "memtrace.csv" $log = Join-Path $PhaseDir "phase1.log" function W([string]$msg) { $t = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") "[$t] $msg" | Out-File -Append -Encoding utf8 -FilePath $log Write-Host "[$t] $msg" } if (-not (Test-Path $csv)) { "iso,elapsed_s,pid,ws_mb,pm_mb,virtual_mb,handles,threads,gdi_handles,user_handles,cpu_s,dump_path" | Out-File -Encoding utf8 -FilePath $csv } $ac = Get-Process -Name acclient -ErrorAction SilentlyContinue if (-not $ac) { W "FATAL: acclient.exe not running"; exit 1 } $acPid = $ac.Id $start = Get-Date W "Phase 1 idle baseline started. acclient PID=$acPid, $($DurationSec)s budget, sample every $($SampleEvery)s, dump every $($DumpEvery * $SampleEvery)s ($($DumpEvery) samples)." $sampleIdx = 0 while ($true) { $now = Get-Date $elapsed = [int]($now - $start).TotalSeconds if ($elapsed -ge $DurationSec) { W "Duration budget reached. Stopping."; break } $ac = Get-Process -Id $acPid -ErrorAction SilentlyContinue if (-not $ac) { W "acclient.exe EXITED unexpectedly at elapsed=$elapsed s"; break } $sampleIdx++ $ws = [math]::Round($ac.WorkingSet64 / 1MB, 2) $pm = [math]::Round($ac.PrivateMemorySize64 / 1MB, 2) $vm = [math]::Round($ac.VirtualMemorySize64 / 1MB, 2) $cpu = [math]::Round($ac.CPU, 2) # GDI/USER handle counts via Win32 API $gdi = [LeakHunt.User32]::GetGuiResources($ac.Handle, 0) $usr = [LeakHunt.User32]::GetGuiResources($ac.Handle, 1) # Optional dump $dumpPath = "" if ($DumpEvery -gt 0 -and (($sampleIdx - 1) % $DumpEvery) -eq 0) { $dumpNum = (($sampleIdx - 1) / $DumpEvery) + 1 $dumpPath = Join-Path $PhaseDir ("dump_{0:D3}.dmp" -f $dumpNum) W "Taking dump_$('{0:D3}' -f $dumpNum) ..." $pdOut = & $ProcDumpExe -accepteula -ma $acPid $dumpPath 2>&1 | Out-String if (-not (Test-Path $dumpPath)) { W "procdump FAILED for sample $sampleIdx -- output: $pdOut" $dumpPath = "FAILED" } else { $mb = [math]::Round((Get-Item $dumpPath).Length / 1MB, 1) W "dump_$('{0:D3}' -f $dumpNum).dmp written ($mb MB)" } } $row = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11}" -f ` $now.ToString("o"), $elapsed, $acPid, $ws, $pm, $vm, $ac.HandleCount, $ac.Threads.Count, $gdi, $usr, $cpu, $dumpPath $row | Out-File -Append -Encoding utf8 -FilePath $csv W ("sample {0,3}: WS={1,8} MB PM={2,8} MB VM={3,8} MB H={4,5} T={5,3} G={6,5} U={7,5} CPU={8,7}s" -f ` $sampleIdx, $ws, $pm, $vm, $ac.HandleCount, $ac.Threads.Count, $gdi, $usr, $cpu) Start-Sleep -Seconds $SampleEvery } W "Phase 1 baseline complete." # Print growth rate summary $rows = Import-Csv $csv if ($rows.Count -ge 2) { $first = $rows[0] $last = $rows[-1] $dWs = [double]$last.ws_mb - [double]$first.ws_mb $dPm = [double]$last.pm_mb - [double]$first.pm_mb $hrs = ([double]$last.elapsed_s - [double]$first.elapsed_s) / 3600.0 $rateWs = if ($hrs -gt 0) { $dWs / $hrs } else { 0 } $ratePm = if ($hrs -gt 0) { $dPm / $hrs } else { 0 } W ("Growth over {0:N2} h: WS +{1:N2} MB ({2:N2} MB/h), PM +{3:N2} MB ({4:N2} MB/h)" -f $hrs, $dWs, $rateWs, $dPm, $ratePm) }