fix(workbench): flush sse and report open latency (#1986)

Co-authored-by: root <root@lyon.remote>
This commit is contained in:
Lyon
2026-06-23 20:56:39 +08:00
committed by GitHub
parent 839b85d17c
commit d63b08c492
3 changed files with 11 additions and 6 deletions
+2 -1
View File
@@ -106,6 +106,7 @@ export async function handleWorkbenchRealtimeHttp(request, response, url, option
"x-accel-buffering": "no", "x-accel-buffering": "no",
"x-content-type-options": "nosniff" "x-content-type-options": "nosniff"
}); });
if (typeof response.flushHeaders === "function") response.flushHeaders();
const writeEvent = (name, payload = {}) => { const writeEvent = (name, payload = {}) => {
if (closed || response.destroyed) return; if (closed || response.destroyed) return;
@@ -146,7 +147,7 @@ export async function handleWorkbenchRealtimeHttp(request, response, url, option
await (perf ? perf.measure("workbench_initial_trace", () => writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" })) : writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" })); await (perf ? perf.measure("workbench_initial_trace", () => writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" })) : writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" }));
} }
const runtimeStore = options.runtimeStore ?? null; const runtimeStore = options.runtimeStore ?? null;
const outboxPollMs = parsePositiveInteger(options.env?.HWLAB_WORKBENCH_SSE_OUTBOX_POLL_MS, 2000); const outboxPollMs = parsePositiveInteger(options.env?.HWLAB_WORKBENCH_SSE_OUTBOX_POLL_MS, 500);
let outboxCursor = 0; let outboxCursor = 0;
const outboxTraceSnapshots = new Set(activeTraceId ? [activeTraceId] : []); const outboxTraceSnapshots = new Set(activeTraceId ? [activeTraceId] : []);
if (typeof runtimeStore?.readWorkbenchProjectionOutbox === "function" && (streamSessionId || activeTraceId)) { if (typeof runtimeStore?.readWorkbenchProjectionOutbox === "function" && (streamSessionId || activeTraceId)) {
@@ -65,10 +65,12 @@ export function connectWorkbenchEvents(options: WorkbenchEventStreamOptions): Wo
appendParam(params, "sessionId", options.sessionId); appendParam(params, "sessionId", options.sessionId);
appendParam(params, "traceId", options.traceId); appendParam(params, "traceId", options.traceId);
const eventRoute = `/v1/workbench/events?${params.toString()}`; const eventRoute = `/v1/workbench/events?${params.toString()}`;
const connectStartedAt = typeof performance === "undefined" ? Date.now() : performance.now();
recordWorkbenchSseLifecycle({ state: "connect", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId }); recordWorkbenchSseLifecycle({ state: "connect", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId });
const source = new EventSource(eventRoute, { withCredentials: true }); const source = new EventSource(eventRoute, { withCredentials: true });
source.onopen = () => { source.onopen = () => {
recordWorkbenchSseLifecycle({ state: "open", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId }); const openedAt = typeof performance === "undefined" ? Date.now() : performance.now();
recordWorkbenchSseLifecycle({ state: "open", route: eventRoute, sessionId: options.sessionId, traceId: options.traceId, valueMs: Math.max(0, Math.round(openedAt - connectStartedAt)) });
options.onOpen?.(); options.onOpen?.();
}; };
source.onerror = (event) => { source.onerror = (event) => {
@@ -299,18 +299,20 @@ function serverTimingSummary(entries: readonly PerformanceServerTiming[] | undef
return summary.slice(0, 240) || undefined; return summary.slice(0, 240) || undefined;
} }
export function recordWorkbenchSseLifecycle(input: { state: "connect" | "open" | "error" | "close"; route: string; sessionId?: string | null; traceId?: string | null; errorName?: string | null }): void { export function recordWorkbenchSseLifecycle(input: { state: "connect" | "open" | "error" | "close"; route: string; sessionId?: string | null; traceId?: string | null; errorName?: string | null; valueMs?: number | null }): void {
ensureInstalled(); ensureInstalled();
const state = input.state; const state = input.state;
const endedAt = wallNow();
const valueMs = roundedNonNegative(Number(input.valueMs ?? 0)) ?? 0;
enqueueWorkbenchUiEvent({ enqueueWorkbenchUiEvent({
eventType: "sse_lifecycle", eventType: "sse_lifecycle",
loadingScope: "sse", loadingScope: "sse",
state, state,
reason: state === "connect" ? "sse_connect" : state === "open" ? "sse_open" : state === "close" ? "sse_close" : "sse_error", reason: state === "connect" ? "sse_connect" : state === "open" ? "sse_open" : state === "close" ? "sse_close" : "sse_error",
route: input.route, route: input.route,
valueMs: 0, valueMs,
startedAtEpochMs: wallNow(), startedAtEpochMs: Math.max(0, endedAt - valueMs),
endedAtEpochMs: wallNow(), endedAtEpochMs: endedAt,
sessionHash: hashIdentifier(input.sessionId, "ses"), sessionHash: hashIdentifier(input.sessionId, "ses"),
traceHash: hashIdentifier(input.traceId, "trc"), traceHash: hashIdentifier(input.traceId, "trc"),
outcome: state === "error" ? "network" : "ok", outcome: state === "error" ? "network" : "ok",