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 {
type ListRuntimeProcessesOptions,
type RuntimeProcessTableRow,
type TmuxPaneRuntimeInfo,
TmuxPlatformCommandExecutor,
@ -34,10 +35,10 @@ export async function listTmuxPaneRuntimeInfoForCurrentPlatform(
return runtimeCommandExecutor.listPaneRuntimeInfo(paneIds);
}
export async function listRuntimeProcessTableForCurrentPlatform(): Promise<
RuntimeProcessTableRow[]
> {
return runtimeCommandExecutor.listRuntimeProcesses();
export async function listRuntimeProcessTableForCurrentPlatform(
options: ListRuntimeProcessesOptions = {}
): Promise<RuntimeProcessTableRow[]> {
return runtimeCommandExecutor.listRuntimeProcesses(options);
}
export async function sendKeysToTmuxPaneForCurrentPlatform(

View file

@ -32,6 +32,10 @@ export interface RuntimeProcessTableRow {
rssBytes?: number;
}
export interface ListRuntimeProcessesOptions {
bypassCache?: boolean;
}
/**
* 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]));
}
async listRuntimeProcesses(): Promise<RuntimeProcessTableRow[]> {
async listRuntimeProcesses(
options: ListRuntimeProcessesOptions = {}
): Promise<RuntimeProcessTableRow[]> {
const cached = this.#runtimeProcessTableCache;
if (cached && cached.expiresAtMs > Date.now()) {
if (options.bypassCache !== true && cached && cached.expiresAtMs > Date.now()) {
return cached.rows;
}
if (this.#runtimeProcessTableInFlight) {

View file

@ -152,4 +152,39 @@ describe('TmuxPlatformCommandExecutor', () => {
]);
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);
this.clearOpenCodeRuntimeToolApprovals(teamName, { emitDismiss: true });
this.invalidateRuntimeSnapshotCaches(teamName);
this.runtimeProcessRowsForUsageSnapshotByTeam.delete(teamName);
this.retainedClaudeLogsByTeam.delete(teamName);
this.persistedTranscriptClaudeLogsCache.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();
this.runtimeProcessRowsForUsageSnapshotByTeam.set(teamName, {
expiresAtMs:

View file

@ -60,10 +60,12 @@ export async function cleanupManagedOpenCodeServeProcesses(
diagnostics: [],
};
const rows = await (
const 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 requiredDetailsMarkers = options.requiredDetailsMarkers ?? [];
const readDetails =