perf(main): reuse cached runtime process table

This commit is contained in:
777genius 2026-05-31 12:24:29 +03:00
parent 53a4c0e9e6
commit 6bb8d87bc7
5 changed files with 58 additions and 10 deletions

View file

@ -1,5 +1,6 @@
import { TmuxStatusSourceAdapter } from '../adapters/output/sources/TmuxStatusSourceAdapter'; import { TmuxStatusSourceAdapter } from '../adapters/output/sources/TmuxStatusSourceAdapter';
import { import {
type ListRuntimeProcessesOptions,
type RuntimeProcessTableRow, type RuntimeProcessTableRow,
type TmuxPaneRuntimeInfo, type TmuxPaneRuntimeInfo,
TmuxPlatformCommandExecutor, TmuxPlatformCommandExecutor,
@ -34,10 +35,10 @@ export async function listTmuxPaneRuntimeInfoForCurrentPlatform(
return runtimeCommandExecutor.listPaneRuntimeInfo(paneIds); return runtimeCommandExecutor.listPaneRuntimeInfo(paneIds);
} }
export async function listRuntimeProcessTableForCurrentPlatform(): Promise< export async function listRuntimeProcessTableForCurrentPlatform(
RuntimeProcessTableRow[] options: ListRuntimeProcessesOptions = {}
> { ): Promise<RuntimeProcessTableRow[]> {
return runtimeCommandExecutor.listRuntimeProcesses(); return runtimeCommandExecutor.listRuntimeProcesses(options);
} }
export async function sendKeysToTmuxPaneForCurrentPlatform( export async function sendKeysToTmuxPaneForCurrentPlatform(

View file

@ -32,6 +32,10 @@ export interface RuntimeProcessTableRow {
rssBytes?: number; rssBytes?: number;
} }
export interface ListRuntimeProcessesOptions {
bypassCache?: boolean;
}
/** /**
* Short-lived cache window for the global process table. * Short-lived cache window for the global process table.
* *
@ -218,9 +222,11 @@ export class TmuxPlatformCommandExecutor {
return new Map([...info.entries()].map(([paneId, pane]) => [paneId, pane.panePid])); return new Map([...info.entries()].map(([paneId, pane]) => [paneId, pane.panePid]));
} }
async listRuntimeProcesses(): Promise<RuntimeProcessTableRow[]> { async listRuntimeProcesses(
options: ListRuntimeProcessesOptions = {}
): Promise<RuntimeProcessTableRow[]> {
const cached = this.#runtimeProcessTableCache; const cached = this.#runtimeProcessTableCache;
if (cached && cached.expiresAtMs > Date.now()) { if (options.bypassCache !== true && cached && cached.expiresAtMs > Date.now()) {
return cached.rows; return cached.rows;
} }
if (this.#runtimeProcessTableInFlight) { if (this.#runtimeProcessTableInFlight) {

View file

@ -152,4 +152,39 @@ describe('TmuxPlatformCommandExecutor', () => {
]); ]);
expect(childProcess.execFile).not.toHaveBeenCalled(); expect(childProcess.execFile).not.toHaveBeenCalled();
}); });
it('can bypass the runtime process table cache for fresh process reads', async () => {
setPlatform('win32');
const execInPreferredDistro = vi
.fn()
.mockResolvedValueOnce({
exitCode: 0,
stdout: ' 42 1 1.0 128 opencode runtime --team-name demo --agent-id alice@demo\n',
stderr: '',
})
.mockResolvedValueOnce({
exitCode: 0,
stdout: ' 43 1 1.0 128 opencode runtime --team-name demo --agent-id alice@demo\n',
stderr: '',
});
const executor = new TmuxPlatformCommandExecutor(
{
execInPreferredDistro,
getPersistedPreferredDistroSync: () => 'Ubuntu',
} as never,
{} as never
);
await expect(executor.listRuntimeProcesses()).resolves.toEqual([
expect.objectContaining({ pid: 42 }),
]);
await expect(executor.listRuntimeProcesses()).resolves.toEqual([
expect.objectContaining({ pid: 42 }),
]);
await expect(executor.listRuntimeProcesses({ bypassCache: true })).resolves.toEqual([
expect.objectContaining({ pid: 43 }),
]);
expect(execInPreferredDistro).toHaveBeenCalledTimes(2);
});
}); });

View file

@ -10522,6 +10522,7 @@ export class TeamProvisioningService {
peekAutoResumeService()?.cancelPendingAutoResume(teamName); peekAutoResumeService()?.cancelPendingAutoResume(teamName);
this.clearOpenCodeRuntimeToolApprovals(teamName, { emitDismiss: true }); this.clearOpenCodeRuntimeToolApprovals(teamName, { emitDismiss: true });
this.invalidateRuntimeSnapshotCaches(teamName); this.invalidateRuntimeSnapshotCaches(teamName);
this.runtimeProcessRowsForUsageSnapshotByTeam.delete(teamName);
this.retainedClaudeLogsByTeam.delete(teamName); this.retainedClaudeLogsByTeam.delete(teamName);
this.persistedTranscriptClaudeLogsCache.delete(teamName); this.persistedTranscriptClaudeLogsCache.delete(teamName);
this.leadInboxRelayInFlight.delete(teamName); this.leadInboxRelayInFlight.delete(teamName);
@ -25554,7 +25555,10 @@ export class TeamProvisioningService {
} }
} }
} }
if (processRowsReadForMetadata) { if (
processRowsReadForMetadata &&
this.getRuntimeSnapshotCacheGeneration(teamName) === generationAtStart
) {
const sampledAtMs = Date.now(); const sampledAtMs = Date.now();
this.runtimeProcessRowsForUsageSnapshotByTeam.set(teamName, { this.runtimeProcessRowsForUsageSnapshotByTeam.set(teamName, {
expiresAtMs: expiresAtMs:

View file

@ -60,10 +60,12 @@ export async function cleanupManagedOpenCodeServeProcesses(
diagnostics: [], diagnostics: [],
}; };
const rows = await ( const listProcessRows =
options.listProcessRows ?? options.listProcessRows ??
(platform === 'win32' ? listWindowsProcessTable : listRuntimeProcessTableForCurrentPlatform) (platform === 'win32'
)(); ? listWindowsProcessTable
: () => listRuntimeProcessTableForCurrentPlatform({ bypassCache: true }));
const rows = await listProcessRows();
const excludePids = options.excludePids ?? new Set<number>(); const excludePids = options.excludePids ?? new Set<number>();
const requiredDetailsMarkers = options.requiredDetailsMarkers ?? []; const requiredDetailsMarkers = options.requiredDetailsMarkers ?? [];
const readDetails = const readDetails =