mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
perf(ui): record pending send paint timing (#88960)
This commit is contained in:
@@ -1057,6 +1057,7 @@ describe("handleSendChat", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
@@ -1223,6 +1224,42 @@ describe("handleSendChat", () => {
|
||||
expect(ack?.requestDurationMs).toEqual(expect.any(Number));
|
||||
});
|
||||
|
||||
it("records pending send paint timing before a delayed chat.send ACK", async () => {
|
||||
vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
|
||||
queueMicrotask(() => callback(0));
|
||||
return 1;
|
||||
});
|
||||
const chatSend = createDeferred<{ status: "started" }>();
|
||||
const request = vi.fn((method: string) => {
|
||||
if (method === "chat.send") {
|
||||
return chatSend.promise;
|
||||
}
|
||||
throw new Error(`Unexpected request: ${method}`);
|
||||
});
|
||||
const host = makeHost({
|
||||
client: { request } as unknown as ChatHost["client"],
|
||||
chatMessage: "measure painted pending send",
|
||||
eventLogBuffer: [],
|
||||
tab: "debug",
|
||||
});
|
||||
|
||||
const send = handleSendChat(host);
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(eventPayloads(host, "control-ui.chat.send").map((payload) => payload.phase)).toEqual(
|
||||
expect.arrayContaining(["pending-visible", "request-start", "pending-painted"]),
|
||||
),
|
||||
);
|
||||
|
||||
chatSend.resolve({ status: "started" });
|
||||
await send;
|
||||
|
||||
const phasesAfterAck = eventPayloads(host, "control-ui.chat.send").map(
|
||||
(payload) => payload.phase,
|
||||
);
|
||||
expect(phasesAfterAck).toEqual(expect.arrayContaining(["ack"]));
|
||||
});
|
||||
|
||||
it("waits for an in-flight model picker update before sending chat", async () => {
|
||||
const switchUpdate = createDeferred<boolean>();
|
||||
const request = vi.fn(async (method: string) => {
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
controlUiNowMs,
|
||||
recordControlUiPerformanceEvent,
|
||||
roundedControlUiDurationMs,
|
||||
scheduleControlUiAfterPaint,
|
||||
} from "./control-ui-performance.ts";
|
||||
import { resolveControlUiAuthHeader } from "./control-ui-auth.ts";
|
||||
import {
|
||||
@@ -417,6 +418,7 @@ function enqueuePendingSendMessage(
|
||||
};
|
||||
host.chatQueue = [...host.chatQueue, pending];
|
||||
recordChatSendTiming(host, pending, "pending-visible", submittedAtMs);
|
||||
schedulePendingSendPaintTiming(host, pending, submittedAtMs);
|
||||
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0], true);
|
||||
return pending;
|
||||
}
|
||||
@@ -563,6 +565,7 @@ type QueuedChatSendResult = "sent" | "pending" | "failed";
|
||||
|
||||
type ChatSendTimingPhase =
|
||||
| "pending-visible"
|
||||
| "pending-painted"
|
||||
| "request-start"
|
||||
| "ack"
|
||||
| "queued-busy"
|
||||
@@ -600,6 +603,42 @@ function recordChatSendTiming(
|
||||
);
|
||||
}
|
||||
|
||||
function shouldRecordPendingSendPaint(item: ChatQueueItem): boolean {
|
||||
return (
|
||||
typeof item.sendSubmittedAtMs === "number" &&
|
||||
(item.sendState === "waiting-model" ||
|
||||
item.sendState === "sending" ||
|
||||
item.sendState === "waiting-reconnect")
|
||||
);
|
||||
}
|
||||
|
||||
function schedulePendingSendPaintTiming(
|
||||
host: ChatHost,
|
||||
item: ChatQueueItem,
|
||||
startedAtMs = item.sendSubmittedAtMs,
|
||||
) {
|
||||
const sessionKey = item.sessionKey ?? host.sessionKey;
|
||||
const sendRunId = item.sendRunId;
|
||||
if (!sendRunId || startedAtMs == null) {
|
||||
return;
|
||||
}
|
||||
scheduleControlUiAfterPaint(
|
||||
host as Parameters<typeof scheduleControlUiAfterPaint>[0],
|
||||
() => {
|
||||
if (!visibleSessionMatches(host, sessionKey, item.agentId)) {
|
||||
return;
|
||||
}
|
||||
const queued = readChatQueueForSession(host, sessionKey).find(
|
||||
(entry) => entry.id === item.id && entry.sendRunId === sendRunId,
|
||||
);
|
||||
if (!queued || !shouldRecordPendingSendPaint(queued)) {
|
||||
return;
|
||||
}
|
||||
recordChatSendTiming(host, queued, "pending-painted", startedAtMs);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function ensureQueuedSendState(
|
||||
host: ChatHost,
|
||||
item: ChatQueueItem,
|
||||
|
||||
@@ -159,6 +159,15 @@ export function scheduleControlUiTabVisibleTiming(
|
||||
.then(() => runAfterPaint(record));
|
||||
}
|
||||
|
||||
export function scheduleControlUiAfterPaint(
|
||||
host: Pick<ControlUiPerformanceHost, "updateComplete">,
|
||||
callback: () => void,
|
||||
) {
|
||||
void Promise.resolve(host.updateComplete)
|
||||
.catch(() => undefined)
|
||||
.then(() => runAfterPaint(callback));
|
||||
}
|
||||
|
||||
export function beginControlUiRefresh(
|
||||
host: ControlUiPerformanceHost,
|
||||
tab: Tab,
|
||||
|
||||
Reference in New Issue
Block a user