fix: report tran ssh runtime timing

This commit is contained in:
Codex
2026-05-25 15:04:12 +00:00
parent c3c50c3b36
commit 073213c302
5 changed files with 116 additions and 12 deletions
+1
View File
@@ -177,6 +177,7 @@ export function sshHelp(): unknown {
"Do not put operation names in any colon route segment, including nested k3s namespace/workload/container segments.",
"Do not use post-provider shorthand such as `ssh G14 k3s ...`; write `ssh G14:k3s ...` so location and operation stay separated.",
"If an ssh-like remote command fails with timeout/kex/exit-255 friction, stderr includes one low-noise UNIDESK_SSH_HINT JSON line with the argv retry command.",
"Every ssh/tran runtime writes one UNIDESK_SSH_TIMING JSON line to stderr with elapsedMs/elapsedSeconds; operations over 10s are marked level=warning and should be checked for provider latency, remote command cost, helper bootstrap, or tran/apply-patch optimization before repeating high-frequency work.",
"Use -- before a remote command that intentionally starts with a dash.",
],
};
+8 -1
View File
@@ -6,7 +6,7 @@ import { type UniDeskConfig } from "./config";
import { type DebugDispatchCommand, isDebugDispatchCommand } from "./debug";
import { summarizeMicroserviceHealthResponse, summarizeMicroserviceObservation, summarizeMicroserviceProxyResponse } from "./microservices";
import { parseNetworkPerfOptions, runNetworkPerf } from "./network-perf";
import { formatSshFailureHint, parseSshInvocation, sshFailureHint, wrapSshRemoteCommand } from "./ssh";
import { formatSshFailureHint, formatSshRuntimeTimingHint, parseSshInvocation, sshFailureHint, sshRuntimeTimingHint, wrapSshRemoteCommand } from "./ssh";
import { codexJudgeQueryAsync, codexOutputQueryAsync, codexPrPreflightQueryAsync, codexQueuesQueryAsync, codexTaskQueryAsync, codexTasksQueryAsync, codexUnreadTriageAsync } from "./code-queue";
import { runDecisionCenterCommandAsync } from "./decision-center";
import {
@@ -888,6 +888,7 @@ async function runRemoteSshWebSocket(
invocation: ReturnType<typeof parseSshInvocation>,
): Promise<number> {
const parsed = invocation.parsed;
const startedAtMs = Date.now();
const size = {
cols: Number(process.stdout.columns) > 0 ? Number(process.stdout.columns) : 100,
rows: Number(process.stdout.rows) > 0 ? Number(process.stdout.rows) : 30,
@@ -963,6 +964,12 @@ async function runRemoteSshWebSocket(
restore();
const hint = sshFailureHint(invocation.providerId, parsed, code, "");
if (hint !== null) process.stderr.write(formatSshFailureHint(hint));
process.stderr.write(formatSshRuntimeTimingHint(sshRuntimeTimingHint({
invocation,
transport: "frontend-websocket",
exitCode: code,
startedAtMs,
})));
resolve(code);
};
const onStdinData = (chunk: Buffer): void => {
+79 -9
View File
@@ -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);
});
});
}