From d63b08c4924bd29ff9a22f9b0218cb09fca41fad Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 20:56:39 +0800 Subject: [PATCH] fix(workbench): flush sse and report open latency (#1986) Co-authored-by: root --- internal/cloud/server-workbench-http.ts | 3 ++- web/hwlab-cloud-web/src/api/workbench-events.ts | 4 +++- web/hwlab-cloud-web/src/utils/workbench-performance.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/cloud/server-workbench-http.ts b/internal/cloud/server-workbench-http.ts index e51cfaa3..680a38e7 100644 --- a/internal/cloud/server-workbench-http.ts +++ b/internal/cloud/server-workbench-http.ts @@ -106,6 +106,7 @@ export async function handleWorkbenchRealtimeHttp(request, response, url, option "x-accel-buffering": "no", "x-content-type-options": "nosniff" }); + if (typeof response.flushHeaders === "function") response.flushHeaders(); const writeEvent = (name, payload = {}) => { 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" })); } 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; const outboxTraceSnapshots = new Set(activeTraceId ? [activeTraceId] : []); if (typeof runtimeStore?.readWorkbenchProjectionOutbox === "function" && (streamSessionId || activeTraceId)) { diff --git a/web/hwlab-cloud-web/src/api/workbench-events.ts b/web/hwlab-cloud-web/src/api/workbench-events.ts index feeadc62..712aee32 100644 --- a/web/hwlab-cloud-web/src/api/workbench-events.ts +++ b/web/hwlab-cloud-web/src/api/workbench-events.ts @@ -65,10 +65,12 @@ export function connectWorkbenchEvents(options: WorkbenchEventStreamOptions): Wo appendParam(params, "sessionId", options.sessionId); appendParam(params, "traceId", options.traceId); 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 }); const source = new EventSource(eventRoute, { withCredentials: true }); 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?.(); }; source.onerror = (event) => { diff --git a/web/hwlab-cloud-web/src/utils/workbench-performance.ts b/web/hwlab-cloud-web/src/utils/workbench-performance.ts index 03b93d8b..179273de 100644 --- a/web/hwlab-cloud-web/src/utils/workbench-performance.ts +++ b/web/hwlab-cloud-web/src/utils/workbench-performance.ts @@ -299,18 +299,20 @@ function serverTimingSummary(entries: readonly PerformanceServerTiming[] | undef 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(); const state = input.state; + const endedAt = wallNow(); + const valueMs = roundedNonNegative(Number(input.valueMs ?? 0)) ?? 0; enqueueWorkbenchUiEvent({ eventType: "sse_lifecycle", loadingScope: "sse", state, reason: state === "connect" ? "sse_connect" : state === "open" ? "sse_open" : state === "close" ? "sse_close" : "sse_error", route: input.route, - valueMs: 0, - startedAtEpochMs: wallNow(), - endedAtEpochMs: wallNow(), + valueMs, + startedAtEpochMs: Math.max(0, endedAt - valueMs), + endedAtEpochMs: endedAt, sessionHash: hashIdentifier(input.sessionId, "ses"), traceHash: hashIdentifier(input.traceId, "trc"), outcome: state === "error" ? "network" : "ok",