From 7d21f9bd76d4a5d79138dfec6a202ca8ae744466 Mon Sep 17 00:00:00 2001 From: 777genius Date: Fri, 29 May 2026 11:10:23 +0300 Subject: [PATCH] perf: avoid stale runtime pid sampling --- .../services/team/TeamProvisioningService.ts | 19 ++- .../team/TeamProvisioningService.test.ts | 136 ++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index 045d3392..a0c14bbb 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -25620,9 +25620,18 @@ export class TeamProvisioningService { if (!stat || typeof stat !== 'object') { return undefined; } - const candidate = stat as { memory?: unknown; cpu?: unknown }; - const rssBytes = normalizeRuntimeTelemetryNumber(candidate.memory); - const cpuPercent = normalizeRuntimeTelemetryNumber(candidate.cpu); + const candidate = stat as { + memory?: unknown; + cpu?: unknown; + rssBytes?: unknown; + cpuPercent?: unknown; + }; + const rssBytes = + normalizeRuntimeTelemetryNumber(candidate.memory) ?? + normalizeRuntimeTelemetryNumber(candidate.rssBytes); + const cpuPercent = + normalizeRuntimeTelemetryNumber(candidate.cpu) ?? + normalizeRuntimeTelemetryNumber(candidate.cpuPercent); const normalized: RuntimeProcessUsageStats = { ...(rssBytes != null && rssBytes >= 0 ? { rssBytes } : {}), ...(cpuPercent != null && cpuPercent >= 0 ? { cpuPercent } : {}), @@ -25840,6 +25849,10 @@ export class TeamProvisioningService { const pids: number[] = []; let truncated = false; const rootProcessRow = rowByPid.get(rootPid); + if (!rootProcessRow) { + usageTreesByRootPid.set(rootPid, { pids: [], truncated: false }); + continue; + } if (process.platform === 'win32' && rootProcessRow?.runtimeTelemetrySource === 'wsl') { usageTreesByRootPid.set(rootPid, { pids: [], truncated: false }); continue; diff --git a/test/main/services/team/TeamProvisioningService.test.ts b/test/main/services/team/TeamProvisioningService.test.ts index 0494ac95..dbe00b15 100644 --- a/test/main/services/team/TeamProvisioningService.test.ts +++ b/test/main/services/team/TeamProvisioningService.test.ts @@ -3494,6 +3494,142 @@ describe('TeamProvisioningService', () => { }); }); + it('uses process table CPU and RSS values before falling back to pidusage', async () => { + const svc = new TeamProvisioningService(); + (svc as any).configReader = { + getConfig: vi.fn(async () => ({ + members: [ + { name: 'team-lead', agentType: 'team-lead' }, + { name: 'alice', model: 'gpt-5.4-mini' }, + ], + })), + }; + (svc as any).readPersistedRuntimeMembers = vi.fn(() => [ + { + name: 'alice', + agentId: 'alice@runtime-team', + tmuxPaneId: '%1', + backendType: 'tmux', + }, + ]); + (svc as any).aliveRunByTeam.set('runtime-team', 'run-1'); + (svc as any).runs.set('run-1', { + runId: 'run-1', + child: { pid: 111 }, + request: { model: 'gpt-5.4' }, + processKilled: false, + cancelRequested: false, + spawnContext: null, + }); + vi.mocked(listTmuxPaneRuntimeInfoForCurrentPlatform).mockResolvedValueOnce( + new Map([ + [ + '%1', + { + paneId: '%1', + panePid: 222, + currentCommand: 'codex', + }, + ], + ]) + ); + vi.mocked(listRuntimeProcessTableForCurrentPlatform).mockResolvedValue([ + { + pid: 111, + ppid: 1, + command: '/usr/bin/node lead.js', + cpuPercent: 3.5, + rssBytes: 123_000_000, + }, + { + pid: 222, + ppid: 1, + command: + '/Users/belief/.bun/bin/bun cli.js --agent-id alice@runtime-team --agent-name alice --team-name runtime-team --model gpt-5.4-mini', + cpuPercent: 7, + rssBytes: 456_000_000, + }, + ]); + + const snapshot = await svc.getTeamAgentRuntimeSnapshot('runtime-team'); + + expect(pidusage).not.toHaveBeenCalled(); + expect(snapshot.members['team-lead']).toMatchObject({ + pid: 111, + cpuPercent: 3.5, + rssBytes: 123_000_000, + }); + expect(snapshot.members.alice).toMatchObject({ + pid: 222, + cpuPercent: 7, + rssBytes: 456_000_000, + }); + }); + + it('does not fall back to pidusage for root pids missing from an available process table', async () => { + const svc = new TeamProvisioningService(); + (svc as any).configReader = { + getConfig: vi.fn(async () => ({ + members: [ + { name: 'team-lead', agentType: 'team-lead' }, + { name: 'alice', model: 'gpt-5.4-mini' }, + ], + })), + }; + (svc as any).readPersistedRuntimeMembers = vi.fn(() => [ + { + name: 'alice', + agentId: 'alice@runtime-team', + tmuxPaneId: '%1', + backendType: 'tmux', + }, + ]); + (svc as any).aliveRunByTeam.set('runtime-team', 'run-1'); + (svc as any).runs.set('run-1', { + runId: 'run-1', + child: { pid: 111 }, + request: { model: 'gpt-5.4' }, + processKilled: false, + cancelRequested: false, + spawnContext: null, + }); + vi.mocked(listTmuxPaneRuntimeInfoForCurrentPlatform).mockResolvedValueOnce( + new Map([ + [ + '%1', + { + paneId: '%1', + panePid: 222, + currentCommand: 'codex', + }, + ], + ]) + ); + vi.mocked(listRuntimeProcessTableForCurrentPlatform).mockResolvedValue([ + { + pid: 999, + ppid: 1, + command: '/usr/bin/node unrelated.js', + cpuPercent: 1.5, + rssBytes: 12_000_000, + }, + ]); + + const snapshot = await svc.getTeamAgentRuntimeSnapshot('runtime-team'); + + expect(pidusage).not.toHaveBeenCalled(); + expect(snapshot.members['team-lead']).toMatchObject({ + pid: 111, + }); + expect(snapshot.members['team-lead'].cpuPercent).toBeUndefined(); + expect(snapshot.members['team-lead'].rssBytes).toBeUndefined(); + expect(snapshot.members.alice).toMatchObject({ + pid: 222, + }); + expect(snapshot.members.alice.cpuPercent).toBeUndefined(); + expect(snapshot.members.alice.rssBytes).toBeUndefined(); + }); + it('captures CPU and memory history on runtime snapshots', async () => { const svc = new TeamProvisioningService(); (svc as any).configReader = {