diff --git a/vscode/src/services/open-telemetry/trace-sender.ts b/vscode/src/services/open-telemetry/trace-sender.ts index 94d1310bfd89..d7e7bb410d82 100644 --- a/vscode/src/services/open-telemetry/trace-sender.ts +++ b/vscode/src/services/open-telemetry/trace-sender.ts @@ -18,7 +18,7 @@ export const TraceSender = { * Sends trace data to the server using the provided span data as a json string * that comes from the webview. It retrieves the current resolved configuration to obtain * authentication details and constructs the trace URL. It sends a POST - * request with the span data as the body. + * request with the span data as the body. */ async function doSendTraceData(spanData: any): Promise { const { auth } = await currentResolvedConfig() diff --git a/vscode/webviews/chat/Transcript.tsx b/vscode/webviews/chat/Transcript.tsx index d54df3484612..36ca202bc262 100644 --- a/vscode/webviews/chat/Transcript.tsx +++ b/vscode/webviews/chat/Transcript.tsx @@ -276,6 +276,7 @@ const TranscriptInteraction: FC = memo(props => { editorValue: SerializedPromptEditorValue, intentFromSubmit?: ChatMessage['intent'] ) => { + performance.mark('startSubmit') // Start the span as soon as the user clicks the submit button const spanManager = new SpanManager('cody-webview') const span = spanManager.startSpan('chat-interaction', { @@ -306,7 +307,6 @@ const TranscriptInteraction: FC = memo(props => { } else { submitHumanMessage({ ...commonProps, - setActiveChatContext, }) } @@ -317,14 +317,14 @@ const TranscriptInteraction: FC = memo(props => { (editorValue: SerializedPromptEditorValue, intentFromSubmit?: ChatMessage['intent']): void => { startSpanAndSubmit('edit', editorValue, intentFromSubmit) }, - [humanMessage.index, intentResults] + [startSpanAndSubmit] ) const onFollowupSubmit = useCallback( (editorValue: SerializedPromptEditorValue, intentFromSubmit?: ChatMessage['intent']): void => { startSpanAndSubmit('submit', editorValue, intentFromSubmit) }, - [intentResults, setActiveChatContext] + [startSpanAndSubmit] ) const extensionAPI = useExtensionAPI() @@ -391,13 +391,13 @@ const TranscriptInteraction: FC = memo(props => { if (assistantMessage.isLoading && !renderSpan.current && activeChatContext) { // Create render span as child of active chat context context.with(activeChatContext, () => { - renderStartTime.current = Date.now() + performance.mark('startRender') renderSpan.current = spanManager.startSpan('assistant-message-render', { attributes: { sampled: true, 'render.state': 'started', 'message.index': assistantMessage.index, - 'render.start_time': renderStartTime.current, + 'render.start_time': performance.now(), 'parent.span.id': trace.getSpan(activeChatContext)?.spanContext().spanId, }, context: activeChatContext, @@ -413,26 +413,33 @@ const TranscriptInteraction: FC = memo(props => { }) } else if (!isLoading && renderSpan.current) { // Complete the render span - renderSpan.current.setAttributes({ - 'render.state': 'completed', - 'render.success': !assistantMessage?.error, - 'message.length': assistantMessage?.text?.length ?? 0, - 'render.total_time': Date.now() - (renderStartTime.current ?? Date.now()), - }) - renderSpan.current.end() + performance.mark('endRender') + performance.measure('renderDuration', 'startRender', 'endRender') + const measure = performance.getEntriesByName('renderDuration')[0] + if (measure.duration > 0) { + // Ensure non-zero duration + renderSpan.current.setAttributes({ + 'render.state': 'completed', + 'render.success': !assistantMessage?.error, + 'message.length': assistantMessage?.text?.length ?? 0, + 'render.total_time': measure.duration, + }) + renderSpan.current.end() + } renderSpan.current = undefined timeToFirstTokenSpan.current?.end() timeToFirstTokenSpan.current = undefined - renderStartTime.current = undefined hasRecordedFirstToken.current = false // Only end the chat context if this is truly the last message if (activeChatContext) { const rootSpan = trace.getSpan(activeChatContext) if (rootSpan) { + const chatTotalTime = + performance.now() - performance.getEntriesByName('startSubmit')[0].startTime rootSpan.setAttributes({ 'chat.completed': true, - 'chat.total_time': Date.now() - (renderStartTime.current ?? Date.now()), + 'chat.total_time': chatTotalTime, }) rootSpan.end() } @@ -441,20 +448,24 @@ const TranscriptInteraction: FC = memo(props => { } else if ( assistantMessage.text && !hasRecordedFirstToken.current && - timeToFirstTokenSpan.current && - renderStartTime.current + timeToFirstTokenSpan.current ) { // End the time-to-first-token span when first token appears - const timeToFirstToken = Date.now() - renderStartTime.current - timeToFirstTokenSpan.current.setAttributes({ - 'time.to.first.token': timeToFirstToken, - }) - timeToFirstTokenSpan.current.end() + performance.mark('firstToken') + performance.measure('timeToFirstToken', 'startRender', 'firstToken') + const firstTokenMeasure = performance.getEntriesByName('timeToFirstToken')[0] + + if (firstTokenMeasure.duration > 0) { + // Ensure non-zero duration + timeToFirstTokenSpan.current.setAttributes({ + 'time.to.first.token': firstTokenMeasure.duration, + }) + timeToFirstTokenSpan.current.end() + } timeToFirstTokenSpan.current = undefined hasRecordedFirstToken.current = true - // Also set on parent span for backwards compatibility - renderSpan.current?.setAttribute('time.to.first.token', timeToFirstToken) + renderSpan.current?.setAttribute('time.to.first.token', firstTokenMeasure.duration) } } }, [assistantMessage, activeChatContext, setActiveChatContext, spanManager, isLoading]) @@ -704,13 +715,11 @@ function submitHumanMessage({ intent, intentScores, manuallySelectedIntent, - setActiveChatContext, }: { editorValue: SerializedPromptEditorValue intent?: ChatMessage['intent'] intentScores?: { intent: string; score: number }[] manuallySelectedIntent?: boolean - setActiveChatContext: (context: Context | undefined) => void }): void { getVSCodeAPI().postMessage({ command: 'submit', diff --git a/vscode/webviews/utils/webviewOpenTelemetryService.ts b/vscode/webviews/utils/webviewOpenTelemetryService.ts index af15208e72bd..d55b57e2b67a 100644 --- a/vscode/webviews/utils/webviewOpenTelemetryService.ts +++ b/vscode/webviews/utils/webviewOpenTelemetryService.ts @@ -84,6 +84,9 @@ export class WebviewOpenTelemetryService { } public dispose(): void { + if (WebviewOpenTelemetryService.instance !== this) { + return + } this.reset() WebviewOpenTelemetryService.instance = null }