Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
126 lines
3.7 KiB
TypeScript
126 lines
3.7 KiB
TypeScript
import { execFile, execFileSync } from 'node:child_process'
|
|
import { promisify } from 'node:util'
|
|
import { isStartupDiagnosticsEnabled, logStartupDiagnostic } from '../startup/startup-diagnostics'
|
|
|
|
const PS_IDENTITY_TIMEOUT_MS = 2_000
|
|
|
|
const execFileAsync = promisify(execFile)
|
|
|
|
export type WindowsProcessIdentity = {
|
|
commandLine: string
|
|
startedAtMs: number | null
|
|
}
|
|
|
|
export type PsProcessIdentity = {
|
|
commandLine: string
|
|
startedAtMs: number | null
|
|
}
|
|
|
|
function parsePsProcessIdentity(output: string): PsProcessIdentity {
|
|
// BSD ps formats lstart as a fixed-width 24-character timestamp.
|
|
const startedAtMs = Date.parse(output.slice(0, 24))
|
|
return {
|
|
commandLine: output.slice(24).trim(),
|
|
startedAtMs: Number.isFinite(startedAtMs) ? startedAtMs : null
|
|
}
|
|
}
|
|
|
|
export function getPsProcessIdentity(pid: number): PsProcessIdentity | null {
|
|
try {
|
|
const output = execFileSync('ps', ['-p', String(pid), '-o', 'lstart=', '-o', 'command='], {
|
|
encoding: 'utf8',
|
|
timeout: 2_000
|
|
})
|
|
return parsePsProcessIdentity(output)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export async function getPsProcessIdentityAsync(pid: number): Promise<PsProcessIdentity | null> {
|
|
try {
|
|
const stdout = await new Promise<string>((resolve, reject) => {
|
|
execFile(
|
|
'ps',
|
|
['-p', String(pid), '-o', 'lstart=', '-o', 'command='],
|
|
{
|
|
encoding: 'utf8',
|
|
timeout: PS_IDENTITY_TIMEOUT_MS
|
|
},
|
|
(error, output) => {
|
|
if (error) {
|
|
reject(error)
|
|
return
|
|
}
|
|
resolve(output)
|
|
}
|
|
)
|
|
})
|
|
return parsePsProcessIdentity(stdout)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function parseWindowsProcessIdentityJson(stdout: string): WindowsProcessIdentity | null {
|
|
const trimmed = stdout.trim()
|
|
if (!trimmed) {
|
|
return null
|
|
}
|
|
try {
|
|
const parsed = JSON.parse(trimmed) as { cmd?: unknown; start?: unknown }
|
|
if (typeof parsed.cmd !== 'string' || !parsed.cmd) {
|
|
return null
|
|
}
|
|
return {
|
|
commandLine: parsed.cmd,
|
|
startedAtMs:
|
|
typeof parsed.start === 'number' && Number.isFinite(parsed.start) ? parsed.start : null
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
// Why: the only reliable command-line source on Windows is a CIM query, which
|
|
// costs a full powershell.exe spawn (300-800ms cold, worse under Defender).
|
|
// Async because the sync version measurably froze the Electron main thread at
|
|
// startup for the whole spawn (benchmark: ~0.5s warm, 3s timeout cap cold).
|
|
// CreationDate rides along in the same spawn so start-time verification adds
|
|
// zero extra process launches. Timed under ORCA_STARTUP_DIAGNOSTICS so the
|
|
// cold-start benchmark can attribute startup cost to these checks.
|
|
export async function queryWindowsProcessIdentity(
|
|
pid: number
|
|
): Promise<WindowsProcessIdentity | null> {
|
|
const startedAt = performance.now()
|
|
try {
|
|
const { stdout } = await execFileAsync(
|
|
'powershell.exe',
|
|
[
|
|
'-NoProfile',
|
|
'-NonInteractive',
|
|
'-Command',
|
|
`$p = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}"; ` +
|
|
`if ($p) { $start = $null; ` +
|
|
`if ($p.CreationDate) { $start = [long]([DateTimeOffset]$p.CreationDate).ToUnixTimeMilliseconds() }; ` +
|
|
`@{ cmd = $p.CommandLine; start = $start } | ConvertTo-Json -Compress }`
|
|
],
|
|
{
|
|
encoding: 'utf8',
|
|
timeout: 3_000
|
|
}
|
|
)
|
|
return parseWindowsProcessIdentityJson(stdout)
|
|
} catch {
|
|
return null
|
|
} finally {
|
|
if (isStartupDiagnosticsEnabled()) {
|
|
logStartupDiagnostic('daemon-pid-check', {
|
|
t: Math.round(performance.now()),
|
|
pid,
|
|
ms: Math.round(performance.now() - startedAt)
|
|
})
|
|
}
|
|
}
|
|
}
|