From 30a612161561aef1829295b662f2529bce25bbbf Mon Sep 17 00:00:00 2001 From: UtkarshUsername Date: Wed, 29 Jul 2026 15:16:25 +0530 Subject: [PATCH 1/2] fix(server): eliminate N+1 PowerShell CIM query on Windows process diagnostics The readWindowsProcessRows function spawned powershell.exe with an N+1 WMI query pattern: for each process (via Get-CimInstance Win32_Process), it queried Win32_PerfFormattedData_PerfProc_Process individually using a -IDProcess filter. On machines with 200+ processes, this created 1+N CIM queries per sampling cycle. The old command timed out after 60s on a machine with ~205 processes. Fix: query all performance counter data once into a hashtable (IDProcess -> perf object), then join it with the process list in PowerShell memory. This reduces 1+N queries to exactly 2 queries regardless of process count. Measured improvement (~205 processes): - Old: >60s (timed out) - New: ~2.7s average per query - Speedup: ~22x+ per sampling cycle --- apps/server/src/diagnostics/ProcessDiagnostics.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/server/src/diagnostics/ProcessDiagnostics.ts b/apps/server/src/diagnostics/ProcessDiagnostics.ts index b39d560a228..8127f9e8138 100644 --- a/apps/server/src/diagnostics/ProcessDiagnostics.ts +++ b/apps/server/src/diagnostics/ProcessDiagnostics.ts @@ -443,11 +443,11 @@ function readWindowsProcessRows(): Effect.Effect< ChildProcessSpawner.ChildProcessSpawner > { const command = [ - "$processes = Get-CimInstance Win32_Process | ForEach-Object {", - '$perf = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess = $($_.ProcessId)" -ErrorAction SilentlyContinue;', - "[pscustomobject]@{ ProcessId = $_.ProcessId; ParentProcessId = $_.ParentProcessId; Name = $_.Name; CommandLine = $_.CommandLine; Status = $_.Status; WorkingSetSize = $_.WorkingSetSize; PercentProcessorTime = if ($perf) { $perf.PercentProcessorTime } else { 0 } }", - "};", - "$processes | ConvertTo-Json -Compress -Depth 3", + "$perfData=@{};Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -ErrorAction SilentlyContinue|ForEach-Object{$perfData[$_.IDProcess]=$_};", + "Get-CimInstance Win32_Process|ForEach-Object{", + "$perf=$perfData[$_.ProcessId];", + "[pscustomobject]@{ProcessId=$_.ProcessId;ParentProcessId=$_.ParentProcessId;Name=$_.Name;CommandLine=$_.CommandLine;Status=$_.Status;WorkingSetSize=$_.WorkingSetSize;PercentProcessorTime=if($perf){$perf.PercentProcessorTime}else{0}}", + "}|ConvertTo-Json -Compress -Depth 3", ].join(" "); return runProcess({ From 2db12504c96da3b61ac64968db31f9ad892fbe00 Mon Sep 17 00:00:00 2001 From: UtkarshUsername Date: Wed, 29 Jul 2026 15:16:54 +0530 Subject: [PATCH 2/2] perf(server): reduce process resource sampling interval from 5s to 15s The ProcessResourceMonitor was sampling process resources every 5 seconds. Combined with the PowerShell CIM query overhead, this caused excessive CPU usage on Windows. Increasing to 15s reduces sampling frequency by 3x while still providing adequate granularity for the resource history visualization (which aggregates into multi-second buckets). Test: update cpuSecondsApprox expectation from 2 to 6 to match the new interval (the calculation is SAMPLE_INTERVAL_MS / 1000 * cpuPercent/100, so tripling the interval triples the per-sample approximation). --- apps/server/src/diagnostics/ProcessResourceMonitor.test.ts | 4 ++-- apps/server/src/diagnostics/ProcessResourceMonitor.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/diagnostics/ProcessResourceMonitor.test.ts b/apps/server/src/diagnostics/ProcessResourceMonitor.test.ts index d9c4eb06ef1..b718c15e6cd 100644 --- a/apps/server/src/diagnostics/ProcessResourceMonitor.test.ts +++ b/apps/server/src/diagnostics/ProcessResourceMonitor.test.ts @@ -118,8 +118,8 @@ describe("ProcessResourceMonitor", () => { expect(result.topProcesses).toHaveLength(1); expect(result.topProcesses[0]?.avgCpuPercent).toBe(20); expect(result.topProcesses[0]?.maxCpuPercent).toBe(30); - expect(result.topProcesses[0]?.cpuSecondsApprox).toBe(2); - expect(result.totalCpuSecondsApprox).toBe(2); + expect(result.topProcesses[0]?.cpuSecondsApprox).toBe(6); + expect(result.totalCpuSecondsApprox).toBe(6); expect(result.buckets.some((bucket) => bucket.maxCpuPercent === 30)).toBe(true); }), ); diff --git a/apps/server/src/diagnostics/ProcessResourceMonitor.ts b/apps/server/src/diagnostics/ProcessResourceMonitor.ts index 6030e4172e1..65d442ab431 100644 --- a/apps/server/src/diagnostics/ProcessResourceMonitor.ts +++ b/apps/server/src/diagnostics/ProcessResourceMonitor.ts @@ -17,7 +17,7 @@ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawne import * as ProcessDiagnostics from "./ProcessDiagnostics.ts"; -const SAMPLE_INTERVAL_MS = 5_000; +const SAMPLE_INTERVAL_MS = 15_000; const RETENTION_MS = 60 * 60_000; const MAX_RETAINED_SAMPLES = 20_000;