mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
perf(ports): serve unchanged macOS listeners from remembered metadata
The workspace port scan shells out three times every 30s while the window is visible. Profiling an idle app showed the cycle costs 0.67s of CPU — about 2.2% of a core sustained, roughly 65% of Orca's total idle CPU — and two of those three commands only re-derive the command line and cwd of listeners that have not changed since the last scan. Reuse the metadata already remembered for a listener when this scan's free lsof -F c process name still matches, so a recycled pid re-probes instead of inheriting a dead process's attribution. In steady state that drops the cycle to the single listening scan.
This commit is contained in:
@@ -3,6 +3,7 @@ import { getProcessOutputFields } from '../../shared/process-output-field-scanne
|
||||
import { readWindowsProcessTable } from '../windows/windows-process-table'
|
||||
import { runPortScanCommand } from './port-scan-command-client'
|
||||
import {
|
||||
partitionListenersNeedingMetadata,
|
||||
recallListenerMetadata,
|
||||
rememberListenerMetadata,
|
||||
shouldSkipMetadataCommands,
|
||||
@@ -124,11 +125,15 @@ async function scanDarwinLsofPorts(
|
||||
if (shouldSkipMetadataCommands(spawnMs, options)) {
|
||||
return { ports, metadataAvailable: false }
|
||||
}
|
||||
const metadata = await loadDarwinProcessMetadata(
|
||||
new Set(ports.flatMap((p) => (p.pid ? [p.pid] : [])))
|
||||
)
|
||||
// Why: on a quiet machine the same servers keep listening, so the two metadata commands — the
|
||||
// expensive half of the scan — would re-derive answers the last scan already has.
|
||||
const { hydrated, pidsNeedingMetadata } = partitionListenersNeedingMetadata(ports)
|
||||
if (pidsNeedingMetadata.size === 0) {
|
||||
return { ports: hydrated, metadataAvailable: true }
|
||||
}
|
||||
const metadata = await loadDarwinProcessMetadata(pidsNeedingMetadata)
|
||||
return {
|
||||
ports: ports.map((port) => ({ ...metadata.get(port.pid ?? -1), ...port })),
|
||||
ports: hydrated.map((port) => ({ ...metadata.get(port.pid ?? -1), ...port })),
|
||||
metadataAvailable: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,43 @@ export function rememberListenerMetadata(ports: readonly RawListeningPort[]): vo
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split listeners into those a previous scan already resolved and the pids still needing a probe.
|
||||
*
|
||||
* Why: the metadata commands are the expensive half of a macOS scan, and a listener that is still
|
||||
* the same process on the same address has the same command line and cwd it had 30s ago. The
|
||||
* remembered entry is only trusted when the process name from this scan's free `lsof -F c` field
|
||||
* still matches, so a recycled pid re-probes instead of inheriting the dead process's metadata.
|
||||
*/
|
||||
export function partitionListenersNeedingMetadata(ports: readonly RawListeningPort[]): {
|
||||
hydrated: RawListeningPort[]
|
||||
pidsNeedingMetadata: Set<number>
|
||||
} {
|
||||
const hydrated: RawListeningPort[] = []
|
||||
const pidsNeedingMetadata = new Set<number>()
|
||||
for (const port of ports) {
|
||||
const remembered = lastListenerMetadata.get(listenerMetadataKey(port))
|
||||
// Why require commandLine: a probe that returned nothing must not be cached as an answer.
|
||||
if (
|
||||
remembered?.commandLine !== undefined &&
|
||||
remembered.processName === port.processName &&
|
||||
port.pid !== undefined
|
||||
) {
|
||||
hydrated.push({
|
||||
...port,
|
||||
commandLine: port.commandLine ?? remembered.commandLine,
|
||||
cwd: port.cwd ?? remembered.cwd
|
||||
})
|
||||
continue
|
||||
}
|
||||
hydrated.push(port)
|
||||
if (port.pid !== undefined) {
|
||||
pidsNeedingMetadata.add(port.pid)
|
||||
}
|
||||
}
|
||||
return { hydrated, pidsNeedingMetadata }
|
||||
}
|
||||
|
||||
export function recallListenerMetadata(port: RawListeningPort): RawListeningPort {
|
||||
const remembered = lastListenerMetadata.get(listenerMetadataKey(port))
|
||||
if (!remembered) {
|
||||
|
||||
@@ -387,7 +387,19 @@ describe('scanWorkspacePorts with delayed process creation', () => {
|
||||
|
||||
it('does not let a required-metadata scan reset the background skip parity', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
|
||||
mockStalledDarwinScan()
|
||||
// Why a fresh pid each cycle: a listener the previous scan already resolved is served from the
|
||||
// remembered metadata, so a stable pid would hide whether this scan skipped the probe or not.
|
||||
let listenerPid = 123
|
||||
runPortScanCommandMock.mockImplementation(async (command: string, args: string[]) => {
|
||||
if (command === 'lsof' && args.includes('-iTCP')) {
|
||||
listenerPid += 1
|
||||
return { stdout: `p${listenerPid}\ncnode\nn127.0.0.1:5173`, spawnMs: 4_200 }
|
||||
}
|
||||
if (command === 'lsof') {
|
||||
return { stdout: [`p${listenerPid}`, 'n/repo'].join('\n'), spawnMs: 4_200 }
|
||||
}
|
||||
return { stdout: `${listenerPid} node /repo/server.js`, spawnMs: 4_200 }
|
||||
})
|
||||
|
||||
await scanWorkspacePorts(worktrees, urlWatcherStub())
|
||||
await scanWorkspacePorts(worktrees, urlWatcherStub(), { requireMetadata: true })
|
||||
@@ -397,6 +409,51 @@ describe('scanWorkspacePorts with delayed process creation', () => {
|
||||
expect(runPortScanCommandMock).toHaveBeenCalledTimes(7)
|
||||
})
|
||||
|
||||
it('serves an unchanged listener from remembered metadata instead of re-probing', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
|
||||
runPortScanCommandMock.mockImplementation(async (command: string, args: string[]) => {
|
||||
if (command === 'lsof' && args.includes('-iTCP')) {
|
||||
return { stdout: LSOF_LISTEN_OUTPUT, spawnMs: 5 }
|
||||
}
|
||||
if (command === 'lsof') {
|
||||
return { stdout: ['p123', 'n/repo'].join('\n'), spawnMs: 5 }
|
||||
}
|
||||
return { stdout: '123 node /repo/server.js', spawnMs: 5 }
|
||||
})
|
||||
|
||||
const first = await scanWorkspacePorts(worktrees, urlWatcherStub())
|
||||
expect(runPortScanCommandMock).toHaveBeenCalledTimes(3)
|
||||
|
||||
const second = await scanWorkspacePorts(worktrees, urlWatcherStub())
|
||||
|
||||
// Only the listening scan itself runs; the two metadata commands are served from the cache.
|
||||
expect(runPortScanCommandMock).toHaveBeenCalledTimes(4)
|
||||
expect(second.ports).toEqual(first.ports)
|
||||
})
|
||||
|
||||
it('re-probes when a recycled pid is running a different process', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin')
|
||||
let processName = 'node'
|
||||
runPortScanCommandMock.mockImplementation(async (command: string, args: string[]) => {
|
||||
if (command === 'lsof' && args.includes('-iTCP')) {
|
||||
return { stdout: `p123\nc${processName}\nn127.0.0.1:5173`, spawnMs: 5 }
|
||||
}
|
||||
if (command === 'lsof') {
|
||||
return { stdout: ['p123', 'n/repo'].join('\n'), spawnMs: 5 }
|
||||
}
|
||||
return { stdout: `123 ${processName} /repo/server.js`, spawnMs: 5 }
|
||||
})
|
||||
|
||||
await scanWorkspacePorts(worktrees, urlWatcherStub())
|
||||
expect(runPortScanCommandMock).toHaveBeenCalledTimes(3)
|
||||
|
||||
// Same pid and address, different process: the remembered metadata must not be reused.
|
||||
processName = 'python3'
|
||||
await scanWorkspacePorts(worktrees, urlWatcherStub())
|
||||
|
||||
expect(runPortScanCommandMock).toHaveBeenCalledTimes(6)
|
||||
})
|
||||
|
||||
// Regression for #11161 review: without carry-forward the panel moves every
|
||||
// workspace port into External on each skipped cycle.
|
||||
it('carries the previous cycle attribution through a skipped scan', async () => {
|
||||
|
||||
Reference in New Issue
Block a user