fix: report tran ssh runtime timing
This commit is contained in:
+79
-9
@@ -41,8 +41,25 @@ export interface SshFailureHint {
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface SshRuntimeTimingHint {
|
||||
code: "ssh-runtime-timing";
|
||||
level: "info" | "warning";
|
||||
providerId: string;
|
||||
route: string;
|
||||
transport: "backend-core-broker" | "frontend-websocket";
|
||||
invocationKind: SshInvocationKind;
|
||||
exitCode: number;
|
||||
elapsedMs: number;
|
||||
elapsedSeconds: number;
|
||||
thresholdMs: number;
|
||||
slow: boolean;
|
||||
message: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
const argvQuotedSshSubcommands = new Set(["git", "rg", "grep", "sed", "nl", "stat", "du", "ls", "cat", "head", "tail", "wc", "pwd"]);
|
||||
const nativeK3sKubeconfig = "/etc/rancher/k3s/k3s.yaml";
|
||||
const defaultSshSlowWarningMs = 10_000;
|
||||
const k3sResourceKindAliases = new Set(["pod", "po", "pods", "deployment", "deploy", "deployments", "statefulset", "sts", "daemonset", "ds", "job", "jobs"]);
|
||||
const legacyK3sOperationRouteSegments = new Set([
|
||||
"guard",
|
||||
@@ -1556,6 +1573,50 @@ export function formatSshFailureHint(hint: SshFailureHint): string {
|
||||
return `UNIDESK_SSH_HINT ${JSON.stringify(hint)}\n`;
|
||||
}
|
||||
|
||||
export function sshSlowWarningThresholdMs(env: NodeJS.ProcessEnv = process.env): number {
|
||||
const raw = env.UNIDESK_SSH_SLOW_WARNING_MS;
|
||||
const parsed = raw === undefined ? NaN : Number(raw);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return defaultSshSlowWarningMs;
|
||||
return Math.max(1000, Math.trunc(parsed));
|
||||
}
|
||||
|
||||
export function sshRuntimeTimingHint(options: {
|
||||
invocation: ParsedSshInvocation;
|
||||
transport: SshRuntimeTimingHint["transport"];
|
||||
exitCode: number;
|
||||
startedAtMs: number;
|
||||
finishedAtMs?: number;
|
||||
thresholdMs?: number;
|
||||
}): SshRuntimeTimingHint {
|
||||
const finishedAtMs = options.finishedAtMs ?? Date.now();
|
||||
const thresholdMs = options.thresholdMs ?? sshSlowWarningThresholdMs();
|
||||
const elapsedMs = Math.max(0, Math.round(finishedAtMs - options.startedAtMs));
|
||||
const elapsedSeconds = Number((elapsedMs / 1000).toFixed(3));
|
||||
const slow = elapsedMs > thresholdMs;
|
||||
const thresholdSeconds = Number((thresholdMs / 1000).toFixed(3));
|
||||
return {
|
||||
code: "ssh-runtime-timing",
|
||||
level: slow ? "warning" : "info",
|
||||
providerId: safeProviderId(options.invocation.providerId),
|
||||
route: options.invocation.route.raw,
|
||||
transport: options.transport,
|
||||
invocationKind: options.invocation.parsed.invocationKind,
|
||||
exitCode: options.exitCode,
|
||||
elapsedMs,
|
||||
elapsedSeconds,
|
||||
thresholdMs,
|
||||
slow,
|
||||
message: slow
|
||||
? `ssh operation took ${elapsedSeconds}s, above the ${thresholdSeconds}s warning threshold; consider checking provider/session latency, remote command cost, helper bootstrap, or tran/apply-patch optimization before repeating high-frequency operations.`
|
||||
: `ssh operation completed in ${elapsedSeconds}s.`,
|
||||
note: "Timing hint is written to stderr and intentionally does not echo the original remote command.",
|
||||
};
|
||||
}
|
||||
|
||||
export function formatSshRuntimeTimingHint(hint: SshRuntimeTimingHint): string {
|
||||
return `UNIDESK_SSH_TIMING ${JSON.stringify(hint)}\n`;
|
||||
}
|
||||
|
||||
function brokerSource(): string {
|
||||
return String.raw`
|
||||
const open = JSON.parse(process.argv[2] || process.argv[1] || "{}");
|
||||
@@ -1690,6 +1751,7 @@ function terminalSize(): { cols: number; rows: number } {
|
||||
export async function runSsh(config: UniDeskConfig, providerId: string, args: string[]): Promise<number> {
|
||||
const invocation = parseSshInvocation(providerId, args);
|
||||
const parsed = invocation.parsed;
|
||||
const startedAtMs = Date.now();
|
||||
const size = terminalSize();
|
||||
const openTimeoutMs = Math.max(15000, Number(process.env.UNIDESK_SSH_OPEN_TIMEOUT_MS || 60000));
|
||||
const payload = {
|
||||
@@ -1751,25 +1813,33 @@ export async function runSsh(config: UniDeskConfig, providerId: string, args: st
|
||||
});
|
||||
|
||||
return await new Promise<number>((resolve) => {
|
||||
let settled = false;
|
||||
const restore = (): void => {
|
||||
process.stdin.unpipe(child.stdin);
|
||||
if (rawMode) process.stdin.setRawMode(false);
|
||||
};
|
||||
child.on("error", (error) => {
|
||||
const finish = (exitCode: number): void => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
restore();
|
||||
const hint = sshFailureHint(invocation.providerId, parsed, exitCode, stderrTail);
|
||||
if (hint !== null) process.stderr.write(formatSshFailureHint(hint));
|
||||
process.stderr.write(formatSshRuntimeTimingHint(sshRuntimeTimingHint({
|
||||
invocation,
|
||||
transport: "backend-core-broker",
|
||||
exitCode,
|
||||
startedAtMs,
|
||||
})));
|
||||
resolve(exitCode);
|
||||
};
|
||||
child.on("error", (error) => {
|
||||
const message = `unidesk ssh failed to start broker: ${error.message}\n`;
|
||||
appendStderrTail(message);
|
||||
process.stderr.write(message);
|
||||
const hint = sshFailureHint(invocation.providerId, parsed, 255, stderrTail);
|
||||
if (hint !== null) process.stderr.write(formatSshFailureHint(hint));
|
||||
resolve(255);
|
||||
finish(255);
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
restore();
|
||||
const exitCode = code ?? 255;
|
||||
const hint = sshFailureHint(invocation.providerId, parsed, exitCode, stderrTail);
|
||||
if (hint !== null) process.stderr.write(formatSshFailureHint(hint));
|
||||
resolve(exitCode);
|
||||
finish(code ?? 255);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user