Skip to content

Commit 003cd01

Browse files
Junyi-99claude
andcommitted
feat: LLM TTFT + heap/event telemetry
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECe2qZWextwVCycC9EveFe
1 parent fef462c commit 003cd01

3 files changed

Lines changed: 34 additions & 3 deletions

File tree

webapp/_webapp/src/libs/telemetry.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,21 @@ onLCP((m) => vitalsMs.record(m.value, { vital: "LCP" }));
5252
onINP((m) => vitalsMs.record(m.value, { vital: "INP" }));
5353
onCLS((m) => vitalsCls.record(m.value));
5454

55-
// Error capture — also exported for manual use
55+
// Memory — JS heap size sampled every export interval
56+
const heapGauge = meter.createObservableGauge("js_heap_bytes", { unit: "By" });
57+
heapGauge.addCallback((result) => {
58+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
59+
const mem = (performance as any).memory;
60+
if (mem) result.observe(mem.usedJSHeapSize);
61+
});
62+
63+
// LLM metrics
64+
const llmTTFT = meter.createHistogram("llm_ttft_ms", { unit: "ms", description: "Time to first token" });
65+
export function recordLLMTTFT(ms: number, model: string) {
66+
llmTTFT.record(ms, { model });
67+
}
68+
69+
// Error capture
5670
export function captureError(error: unknown, attrs?: Record<string, string>) {
5771
const err = error instanceof Error ? error : new Error(String(error));
5872
logger.emit({
@@ -67,6 +81,15 @@ export function captureError(error: unknown, attrs?: Record<string, string>) {
6781
});
6882
}
6983

84+
// Event capture
85+
export function captureEvent(name: string, attrs?: Record<string, string>) {
86+
logger.emit({
87+
severityText: "INFO",
88+
body: name,
89+
attributes: attrs,
90+
});
91+
}
92+
7093
window.addEventListener("error", (e) => captureError(e.error ?? e.message));
7194
window.addEventListener("unhandledrejection", (e) => captureError(e.reason));
7295

webapp/_webapp/src/query/api.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import apiclient, { apiclientV2 } from "../libs/apiclient";
2+
import { recordLLMTTFT } from "../libs/telemetry";
23
import {
34
LoginByGoogleRequest,
45
LoginByGoogleResponseSchema,
@@ -126,8 +127,16 @@ export const createConversationMessageStream = async (
126127
data: PlainMessage<CreateConversationMessageStreamRequest>,
127128
onMessage: (chunk: CreateConversationMessageStreamResponse) => void,
128129
) => {
130+
const start = performance.now();
131+
let ttftRecorded = false;
129132
const stream = await apiclientV2.postStream(`/chats/conversations/messages/stream`, data);
130-
await processStream(stream, CreateConversationMessageStreamResponseSchema, onMessage);
133+
await processStream(stream, CreateConversationMessageStreamResponseSchema, (chunk: CreateConversationMessageStreamResponse) => {
134+
if (!ttftRecorded) {
135+
recordLLMTTFT(performance.now() - start, data.modelSlug);
136+
ttftRecorded = true;
137+
}
138+
onMessage(chunk);
139+
});
131140
};
132141

133142
export const deleteConversation = async (data: PlainMessage<DeleteConversationRequest>) => {

webapp/_webapp/vite.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ function generateConfig(
2727
VERSION: process.env.VERSION,
2828
MONOREPO_REVISION: process.env.MONOREPO_REVISION,
2929
SAFARI_BUILD: process.env.SAFARI_BUILD || "false",
30-
OTEL_AUTH_TOKEN: process.env.OTEL_AUTH_TOKEN || "ff4b3c7c2a66c5c9252696cf6cb111f79ff983d68700ff1051455cfe4ecf55ef",
3130
},
3231
},
3332
build: {

0 commit comments

Comments
 (0)