perf: include process table usage metrics
This commit is contained in:
parent
3b0c2ed24b
commit
169ac8bb68
4 changed files with 62 additions and 9 deletions
|
|
@ -28,11 +28,36 @@ export interface RuntimeProcessTableRow {
|
|||
pid: number;
|
||||
ppid: number;
|
||||
command: string;
|
||||
cpuPercent?: number;
|
||||
rssBytes?: number;
|
||||
}
|
||||
|
||||
export function parseRuntimeProcessTable(output: string): RuntimeProcessTableRow[] {
|
||||
const rows: RuntimeProcessTableRow[] = [];
|
||||
for (const line of output.split('\n')) {
|
||||
const enrichedMatch = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/.exec(line);
|
||||
if (enrichedMatch) {
|
||||
const pid = Number.parseInt(enrichedMatch[1], 10);
|
||||
const ppid = Number.parseInt(enrichedMatch[2], 10);
|
||||
const cpuPercent = Number(enrichedMatch[3]);
|
||||
const rssKb = Number(enrichedMatch[4]);
|
||||
const command = enrichedMatch[5]?.trim() ?? '';
|
||||
if (
|
||||
Number.isFinite(pid) &&
|
||||
pid > 0 &&
|
||||
Number.isFinite(ppid) &&
|
||||
ppid >= 0 &&
|
||||
Number.isFinite(cpuPercent) &&
|
||||
cpuPercent >= 0 &&
|
||||
Number.isFinite(rssKb) &&
|
||||
rssKb >= 0 &&
|
||||
command.length > 0
|
||||
) {
|
||||
rows.push({ pid, ppid, command, cpuPercent, rssBytes: Math.round(rssKb * 1024) });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const match = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
||||
if (!match) continue;
|
||||
|
||||
|
|
@ -169,7 +194,12 @@ export class TmuxPlatformCommandExecutor {
|
|||
async listRuntimeProcesses(): Promise<RuntimeProcessTableRow[]> {
|
||||
const result =
|
||||
process.platform === 'win32'
|
||||
? await this.#wslService.execInPreferredDistro(['ps', '-ax', '-o', 'pid=,ppid=,command='])
|
||||
? await this.#wslService.execInPreferredDistro([
|
||||
'ps',
|
||||
'-ax',
|
||||
'-o',
|
||||
'pid=,ppid=,pcpu=,rss=,command=',
|
||||
])
|
||||
: await this.#execNativePs();
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(result.stderr || 'Failed to list runtime processes');
|
||||
|
|
@ -251,7 +281,7 @@ export class TmuxPlatformCommandExecutor {
|
|||
return new Promise((resolve) => {
|
||||
execFile(
|
||||
'ps',
|
||||
['-ax', '-o', 'pid=,ppid=,command='],
|
||||
['-ax', '-o', 'pid=,ppid=,pcpu=,rss=,command='],
|
||||
{ env: process.env, timeout: 3_000, maxBuffer: 2 * 1024 * 1024 },
|
||||
(error, stdout, stderr) => {
|
||||
const errorCode =
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ describe('TmuxPlatformCommandExecutor', () => {
|
|||
setPlatform('win32');
|
||||
const execInPreferredDistro = vi.fn(async () => ({
|
||||
exitCode: 0,
|
||||
stdout: ' 42 1 opencode runtime --team-name demo\n',
|
||||
stdout: ' 42 1 7.5 128 opencode runtime --team-name demo\n',
|
||||
stderr: '',
|
||||
}));
|
||||
const executor = new TmuxPlatformCommandExecutor(
|
||||
|
|
@ -116,9 +116,20 @@ describe('TmuxPlatformCommandExecutor', () => {
|
|||
);
|
||||
|
||||
await expect(executor.listRuntimeProcesses()).resolves.toEqual([
|
||||
{ pid: 42, ppid: 1, command: 'opencode runtime --team-name demo' },
|
||||
{
|
||||
pid: 42,
|
||||
ppid: 1,
|
||||
command: 'opencode runtime --team-name demo',
|
||||
cpuPercent: 7.5,
|
||||
rssBytes: 131_072,
|
||||
},
|
||||
]);
|
||||
expect(execInPreferredDistro).toHaveBeenCalledWith([
|
||||
'ps',
|
||||
'-ax',
|
||||
'-o',
|
||||
'pid=,ppid=,pcpu=,rss=,command=',
|
||||
]);
|
||||
expect(execInPreferredDistro).toHaveBeenCalledWith(['ps', '-ax', '-o', 'pid=,ppid=,command=']);
|
||||
expect(childProcess.execFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { parseRuntimeProcessTable } from '@features/tmux-installer/main';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('parseRuntimeProcessTable', () => {
|
||||
it('parses pid, ppid and command rows', () => {
|
||||
|
|
@ -12,6 +11,15 @@ describe('parseRuntimeProcessTable', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('parses optional cpu and rss columns', () => {
|
||||
expect(
|
||||
parseRuntimeProcessTable(' 10 1 3.5 120000 /bin/zsh\n 11 10 0.1 42 node demo')
|
||||
).toEqual([
|
||||
{ pid: 10, ppid: 1, command: '/bin/zsh', cpuPercent: 3.5, rssBytes: 122_880_000 },
|
||||
{ pid: 11, ppid: 10, command: 'node demo', cpuPercent: 0.1, rssBytes: 43_008 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('skips malformed rows', () => {
|
||||
expect(parseRuntimeProcessTable('bad\n 0 1 nope\n 12 0 /bin/node')).toEqual([
|
||||
{ pid: 12, ppid: 0, command: '/bin/node' },
|
||||
|
|
|
|||
|
|
@ -620,6 +620,9 @@ type TeamProvisioningServicePrivateHarness = {
|
|||
applyProcessBootstrapTransportOverlay: (
|
||||
input: Record<string, unknown>
|
||||
) => Record<string, unknown>;
|
||||
readProcessUsageStatsByPid: (
|
||||
pids: readonly number[]
|
||||
) => Promise<Map<number, { rssBytes?: number; cpuPercent?: number }>>;
|
||||
};
|
||||
|
||||
function privateHarness(svc: TeamProvisioningService): TeamProvisioningServicePrivateHarness {
|
||||
|
|
@ -4747,8 +4750,9 @@ describe('TeamProvisioningService', () => {
|
|||
};
|
||||
vi.mocked(pidusage).mockResolvedValueOnce(usageByPid);
|
||||
|
||||
const first = await (svc as any).readProcessUsageStatsByPid([111]);
|
||||
const second = await (svc as any).readProcessUsageStatsByPid([111]);
|
||||
const harness = privateHarness(svc);
|
||||
const first = await harness.readProcessUsageStatsByPid([111]);
|
||||
const second = await harness.readProcessUsageStatsByPid([111]);
|
||||
|
||||
expect(pidusage).toHaveBeenCalledTimes(1);
|
||||
expect(pidusage).toHaveBeenCalledWith([111], EXPECTED_RUNTIME_PIDUSAGE_OPTIONS);
|
||||
|
|
|
|||
Loading…
Reference in a new issue