From 5b3c62fd4b3309653a316ff3a431d4e4e7355020 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Fri, 22 Nov 2024 13:29:13 +0400 Subject: [PATCH 1/5] Adding dist tracing and the ability to trace for the smart apply operation in chat --- vscode/src/chat/chat-view/ChatController.ts | 83 ++-- vscode/src/chat/protocol.ts | 2 + vscode/src/edit/manager.ts | 391 ++++++++++-------- vscode/src/edit/smart-apply.ts | 1 + .../open-telemetry/CodyTraceExport.ts | 28 ++ .../utils/codeblock-action-tracker.ts | 4 +- vscode/webviews/Chat.tsx | 20 + vscode/webviews/chat/Transcript.tsx | 10 + .../utils/webviewOpenTelemetryService.ts | 2 +- 9 files changed, 326 insertions(+), 215 deletions(-) diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index b4eaca15639d..20a037f59103 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -139,6 +139,7 @@ import { getChatPanelTitle } from './chat-helpers' import { type HumanInput, getPriorityContext } from './context' import { DefaultPrompter, type PromptInfo } from './prompt' import { getPromptsMigrationInfo, startPromptsMigration } from './prompts-migration' +const { trace, context, propagation, ROOT_CONTEXT } = require('@opentelemetry/api') export interface ChatControllerOptions { extensionUri: vscode.Uri @@ -281,6 +282,8 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv this.setWebviewToChat() break case 'submit': { + const traceparent = message.traceparent + await this.handleUserMessageSubmission({ requestID: uuid.v4(), inputText: PromptString.unsafe_fromUserQuery(message.text), @@ -291,6 +294,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv intent: message.intent, intentScores: message.intentScores, manuallySelectedIntent: message.manuallySelectedIntent, + traceparent, }) break } @@ -322,7 +326,8 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv message.code, currentAuthStatus(), message.instruction, - message.fileName + message.fileName, + message.traceparent ) break case 'trace-export': @@ -620,6 +625,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv intent: detectedIntent, intentScores: detectedIntentScores, manuallySelectedIntent, + traceparent, }: { requestID: string inputText: PromptString @@ -631,41 +637,58 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv intent?: ChatMessage['intent'] | undefined | null intentScores?: { intent: string; score: number }[] | undefined | null manuallySelectedIntent?: boolean | undefined | null + traceparent?: string | undefined | null }): Promise { - return tracer.startActiveSpan('chat.submit', async (span): Promise => { - span.setAttribute('sampled', true) + const carrier = { + traceparent: traceparent, + } as Record + const getter = { + get(carrier: Record, key: string) { + return carrier[key] + }, + keys(carrier: Record) { + return Object.keys(carrier) + }, + } - if (inputText.toString().match(/^\/reset$/)) { - span.addEvent('clearAndRestartSession') - span.end() - return this.clearAndRestartSession() - } + const extractedContext = propagation.extract(ROOT_CONTEXT, carrier, getter) - this.chatBuilder.addHumanMessage({ - text: inputText, - editorState, - intent: detectedIntent, - }) - this.postViewTranscript({ speaker: 'assistant' }) - - await this.saveSession() - signal.throwIfAborted() + return context.with(extractedContext, () => { + tracer.startActiveSpan('chat.submit', async (span): Promise => { + span.setAttribute('sampled', true) + span.setAttribute('continued', true) + if (inputText.toString().match(/^\/reset$/)) { + span.addEvent('clearAndRestartSession') + span.end() + return this.clearAndRestartSession() + } - return this.sendChat( - { - requestID, - inputText, - mentions, + this.chatBuilder.addHumanMessage({ + text: inputText, editorState, - signal, - source, - command, intent: detectedIntent, - intentScores: detectedIntentScores, - manuallySelectedIntent, - }, - span - ) + }) + this.postViewTranscript({ speaker: 'assistant' }) + + await this.saveSession() + signal.throwIfAborted() + + return this.sendChat( + { + requestID, + inputText, + mentions, + editorState, + signal, + source, + command, + intent: detectedIntent, + intentScores: detectedIntentScores, + manuallySelectedIntent, + }, + span + ) + }) }) } diff --git a/vscode/src/chat/protocol.ts b/vscode/src/chat/protocol.ts index 6b47e370fd48..df7646bb4b47 100644 --- a/vscode/src/chat/protocol.ts +++ b/vscode/src/chat/protocol.ts @@ -96,6 +96,7 @@ export type WebviewMessage = code: string instruction?: string | undefined | null fileName?: string | undefined | null + traceparent?: string | undefined | null } | { command: 'trace-export' @@ -206,6 +207,7 @@ export interface WebviewSubmitMessage extends WebviewContextMessage { intent?: ChatMessage['intent'] | undefined | null intentScores?: { intent: string; score: number }[] | undefined | null manuallySelectedIntent?: boolean | undefined | null + traceparent?: string | undefined | null } interface WebviewEditMessage extends WebviewContextMessage { diff --git a/vscode/src/edit/manager.ts b/vscode/src/edit/manager.ts index 9c760a3b269a..9ad5d87fe77f 100644 --- a/vscode/src/edit/manager.ts +++ b/vscode/src/edit/manager.ts @@ -9,6 +9,7 @@ import { modelsService, ps, telemetryRecorder, + wrapInActiveSpan, } from '@sourcegraph/cody-shared' import type { GhostHintDecorator } from '../commands/GhostHintDecorator' @@ -17,6 +18,8 @@ import type { VSCodeEditor } from '../editor/vscode-editor' import { FixupController } from '../non-stop/FixupController' import type { FixupTask } from '../non-stop/FixupTask' +import { ROOT_CONTEXT, context } from '@opentelemetry/api' +import { propagation } from '@opentelemetry/api' import { DEFAULT_EVENT_SOURCE } from '@sourcegraph/cody-shared' import { isUriIgnoredByContextFilterWithNotification } from '../cody-ignore/context-filter' import type { ExtensionClient } from '../extension-client' @@ -236,196 +239,218 @@ export class EditManager implements vscode.Disposable { return task } - public async smartApplyEdit(args: SmartApplyArguments = {}): Promise { - const { configuration, source = 'chat' } = args - if (!configuration) { - return - } - - const document = configuration.document - if (await isUriIgnoredByContextFilterWithNotification(document.uri, 'edit')) { - return - } - - const model = - configuration.model || (await firstResultFromOperation(modelsService.getDefaultEditModel())) - if (!model) { - throw new Error('No default edit model found. Please set one.') - } - - telemetryRecorder.recordEvent('cody.command.smart-apply', 'executed', { - billingMetadata: { - product: 'cody', - category: 'core', + public async smartApplyEdit(args: SmartApplyArguments = {}): Promise { + const carrier = { + traceparent: args.configuration?.traceparent, + } as Record + const getter = { + get(carrier: Record, key: string) { + return carrier[key] }, - }) - - const editor = await vscode.window.showTextDocument(document.uri) - - if (args.configuration?.isNewFile) { - // We are creating a new file, this means we are only _adding_ new code and _inserting_ it into the document. - // We do not need to re-prompt the LLM for this, let's just add the code directly. - const task = await this.controller.createTask( - document, - configuration.instruction, - [], - new vscode.Range(0, 0, 0, 0), - 'add', - 'insert', - model, - source, - configuration.document.uri, - undefined, - {}, - configuration.id - ) - - const legacyMetadata = { - intent: task.intent, - mode: task.mode, - source: task.source, - } - const { metadata, privateMetadata } = splitSafeMetadata(legacyMetadata) - telemetryRecorder.recordEvent('cody.command.edit', 'executed', { - metadata, - privateMetadata: { - ...privateMetadata, - model: task.model, - }, - billingMetadata: { - product: 'cody', - category: 'core', - }, - }) - - const provider = this.getProviderForTask(task) - await provider.applyEdit(configuration.replacement) - return task - } - - // Apply some decorations to the editor, this showcases that Cody is working on the full file range - // of the document. We will narrow it down to a selection soon. - const documentRange = new vscode.Range(0, 0, document.lineCount, 0) - editor.setDecorations(SMART_APPLY_FILE_DECORATION, [documentRange]) - - // We need to extract the proposed code, provided by the LLM, so we can use it in future - // queries to ask the LLM to generate a selection, and then ultimately apply the edit. - const replacementCode = PromptString.unsafe_fromLLMResponse(configuration.replacement) - - const versions = await currentSiteVersion() - if (!versions) { - throw new Error('unable to determine site version') - } - - const selection = await getSmartApplySelection( - configuration.id, - configuration.instruction, - replacementCode, - configuration.document, - model, - this.options.chat, - versions.codyAPIVersion - ) - - // We finished prompting the LLM for the selection, we can now remove the "progress" decoration - // that indicated we where working on the full file. - editor.setDecorations(SMART_APPLY_FILE_DECORATION, []) - - if (!selection) { - // We couldn't figure out the selection, let's inform the user and return early. - // TODO: Should we add a "Copy" button to this error? Then the user can copy the code directly. - void vscode.window.showErrorMessage( - 'Unable to apply this change to the file. Please try applying this code manually' - ) - telemetryRecorder.recordEvent('cody.smart-apply.selection', 'not-found') - return - } - - telemetryRecorder.recordEvent('cody.smart-apply', 'selected', { - metadata: { - [selection.type]: 1, + keys(carrier: Record) { + return Object.keys(carrier) }, - }) + } - // Move focus to the determined selection - editor.revealRange(selection.range, vscode.TextEditorRevealType.InCenter) - - if (selection.range.isEmpty) { - let insertionRange = selection.range - - if ( - selection.type === 'insert' && - document.lineAt(document.lineCount - 1).text.trim().length !== 0 - ) { - // Inserting to the bottom of the file, but the last line is not empty - // Inject an additional new line for us to use as the insertion range. - await editor.edit( - editBuilder => { - editBuilder.insert(selection.range.start, '\n') + const extractedContext = propagation.extract(ROOT_CONTEXT, carrier, getter) + console.log('extractedContext', extractedContext) + + return context.with(extractedContext, async () => { + await wrapInActiveSpan('edit.smart-apply', async span => { + span.setAttribute('sampled', true) + span.setAttribute('continued', true) + const { configuration, source = 'chat' } = args + if (!configuration) { + return + } + + const document = configuration.document + if (await isUriIgnoredByContextFilterWithNotification(document.uri, 'edit')) { + return + } + + const model = + configuration.model || + (await firstResultFromOperation(modelsService.getDefaultEditModel())) + if (!model) { + throw new Error('No default edit model found. Please set one.') + } + + telemetryRecorder.recordEvent('cody.command.smart-apply', 'executed', { + billingMetadata: { + product: 'cody', + category: 'core', }, - { undoStopAfter: false, undoStopBefore: false } + }) + + const editor = await vscode.window.showTextDocument(document.uri) + + if (args.configuration?.isNewFile) { + // We are creating a new file, this means we are only _adding_ new code and _inserting_ it into the document. + // We do not need to re-prompt the LLM for this, let's just add the code directly. + const task = await this.controller.createTask( + document, + configuration.instruction, + [], + new vscode.Range(0, 0, 0, 0), + 'add', + 'insert', + model, + source, + configuration.document.uri, + undefined, + {}, + configuration.id + ) + + const legacyMetadata = { + intent: task.intent, + mode: task.mode, + source: task.source, + } + const { metadata, privateMetadata } = splitSafeMetadata(legacyMetadata) + telemetryRecorder.recordEvent('cody.command.edit', 'executed', { + metadata, + privateMetadata: { + ...privateMetadata, + model: task.model, + }, + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) + + const provider = this.getProviderForTask(task) + await provider.applyEdit(configuration.replacement) + return task + } + + // Apply some decorations to the editor, this showcases that Cody is working on the full file range + // of the document. We will narrow it down to a selection soon. + const documentRange = new vscode.Range(0, 0, document.lineCount, 0) + editor.setDecorations(SMART_APPLY_FILE_DECORATION, [documentRange]) + + // We need to extract the proposed code, provided by the LLM, so we can use it in future + // queries to ask the LLM to generate a selection, and then ultimately apply the edit. + const replacementCode = PromptString.unsafe_fromLLMResponse(configuration.replacement) + + const versions = await currentSiteVersion() + if (!versions) { + throw new Error('unable to determine site version') + } + + const selection = await getSmartApplySelection( + configuration.id, + configuration.instruction, + replacementCode, + configuration.document, + model, + this.options.chat, + versions.codyAPIVersion ) - // Update the range to reflect the new end of document - insertionRange = document.lineAt(document.lineCount - 1).range - } - - // We determined a selection, but it was empty. This means that we will be _adding_ new code - // and _inserting_ it into the document. We do not need to re-prompt the LLM for this, let's just - // add the code directly. - const task = await this.controller.createTask( - document, - configuration.instruction, - [], - insertionRange, - 'add', - 'insert', - model, - source, - configuration.document.uri, - undefined, - {}, - configuration.id - ) - - const legacyMetadata = { - intent: task.intent, - mode: task.mode, - source: task.source, - } - const { metadata, privateMetadata } = splitSafeMetadata(legacyMetadata) - telemetryRecorder.recordEvent('cody.command.edit', 'executed', { - metadata, - privateMetadata: { - ...privateMetadata, - model: task.model, - }, - billingMetadata: { - product: 'cody', - category: 'core', - }, + // We finished prompting the LLM for the selection, we can now remove the "progress" decoration + // that indicated we where working on the full file. + editor.setDecorations(SMART_APPLY_FILE_DECORATION, []) + + if (!selection) { + // We couldn't figure out the selection, let's inform the user and return early. + // TODO: Should we add a "Copy" button to this error? Then the user can copy the code directly. + void vscode.window.showErrorMessage( + 'Unable to apply this change to the file. Please try applying this code manually' + ) + telemetryRecorder.recordEvent('cody.smart-apply.selection', 'not-found') + return + } + + telemetryRecorder.recordEvent('cody.smart-apply', 'selected', { + metadata: { + [selection.type]: 1, + }, + }) + + // Move focus to the determined selection + editor.revealRange(selection.range, vscode.TextEditorRevealType.InCenter) + + if (selection.range.isEmpty) { + let insertionRange = selection.range + + if ( + selection.type === 'insert' && + document.lineAt(document.lineCount - 1).text.trim().length !== 0 + ) { + // Inserting to the bottom of the file, but the last line is not empty + // Inject an additional new line for us to use as the insertion range. + await editor.edit( + editBuilder => { + editBuilder.insert(selection.range.start, '\n') + }, + { undoStopAfter: false, undoStopBefore: false } + ) + + // Update the range to reflect the new end of document + insertionRange = document.lineAt(document.lineCount - 1).range + } + + // We determined a selection, but it was empty. This means that we will be _adding_ new code + // and _inserting_ it into the document. We do not need to re-prompt the LLM for this, let's just + // add the code directly. + const task = await this.controller.createTask( + document, + configuration.instruction, + [], + insertionRange, + 'add', + 'insert', + model, + source, + configuration.document.uri, + undefined, + {}, + configuration.id + ) + + const legacyMetadata = { + intent: task.intent, + mode: task.mode, + source: task.source, + } + const { metadata, privateMetadata } = splitSafeMetadata(legacyMetadata) + telemetryRecorder.recordEvent('cody.command.edit', 'executed', { + metadata, + privateMetadata: { + ...privateMetadata, + model: task.model, + }, + billingMetadata: { + product: 'cody', + category: 'core', + }, + }) + + const provider = this.getProviderForTask(task) + await provider.applyEdit('\n' + configuration.replacement) + return task + } + + // We have a selection to replace, we re-prompt the LLM to generate the changes to ensure that + // we can reliably apply this edit. + // Just using the replacement code from the response is not enough, as it may contain parts that are not suitable to apply, + // e.g. // ... + return this.executeEdit({ + configuration: { + id: configuration.id, + document: configuration.document, + range: selection.range, + mode: 'edit', + instruction: ps`Ensuring that you do not duplicate code that it outside of the selection, apply the following change:\n${replacementCode}`, + model, + intent: 'edit', + }, + source, + }) }) - - const provider = this.getProviderForTask(task) - await provider.applyEdit('\n' + configuration.replacement) - return task - } - - // We have a selection to replace, we re-prompt the LLM to generate the changes to ensure that - // we can reliably apply this edit. - // Just using the replacement code from the response is not enough, as it may contain parts that are not suitable to apply, - // e.g. // ... - return this.executeEdit({ - configuration: { - id: configuration.id, - document: configuration.document, - range: selection.range, - mode: 'edit', - instruction: ps`Ensuring that you do not duplicate code that it outside of the selection, apply the following change:\n${replacementCode}`, - model, - intent: 'edit', - }, - source, }) } diff --git a/vscode/src/edit/smart-apply.ts b/vscode/src/edit/smart-apply.ts index 2534f49e7085..722b0d707120 100644 --- a/vscode/src/edit/smart-apply.ts +++ b/vscode/src/edit/smart-apply.ts @@ -11,6 +11,7 @@ export interface SmartApplyArguments { document: vscode.TextDocument model?: EditModel isNewFile?: boolean + traceparent: string | undefined | null } source?: EventSource } diff --git a/vscode/src/services/open-telemetry/CodyTraceExport.ts b/vscode/src/services/open-telemetry/CodyTraceExport.ts index 015d743a41d0..e703a54a259c 100644 --- a/vscode/src/services/open-telemetry/CodyTraceExport.ts +++ b/vscode/src/services/open-telemetry/CodyTraceExport.ts @@ -49,7 +49,17 @@ export class CodyTraceExporter extends OTLPTraceExporter { const spansToExport: ReadableSpan[] = [] for (const span of spans) { const rootSpan = getRootSpan(spanMap, span) + if (span.name === 'edit.smart-apply' || span.name === 'command.edit.start') { + console.log('span', span) + } if (rootSpan === null) { + // the child of the root is sampled but root is not and the span is continued + const rootChildSpan = getRootChildSpan(spanMap, span) + if (rootChildSpan && isSampled(rootChildSpan) && isContinued(rootChildSpan)) { + spansToExport.push(span) + continue + } + const spanId = span.spanContext().spanId if (!this.queuedSpans.has(spanId)) { // No root span was found yet, so let's queue this span for a @@ -68,6 +78,24 @@ export class CodyTraceExporter extends OTLPTraceExporter { super.export(spansToExport, resultCallback) } } +function isContinued(span: ReadableSpan): boolean { + return span.attributes.continued === true +} +// keeps jumping up the chain to return the child of root span for every span and null if its root +function getRootChildSpan(spanMap: Map, span: ReadableSpan): ReadableSpan | null { + if (span.parentSpanId) { + const parentSpan = spanMap.get(span.parentSpanId) + if (!parentSpan) { + return span + } + return getRootChildSpan(spanMap, parentSpan) + } + return null +} + +function isSampled(span: ReadableSpan): boolean { + return span.attributes.sampled === true +} function getRootSpan(spanMap: Map, span: ReadableSpan): ReadableSpan | null { if (span.parentSpanId) { diff --git a/vscode/src/services/utils/codeblock-action-tracker.ts b/vscode/src/services/utils/codeblock-action-tracker.ts index e97a2c0ad7b8..ef913457c787 100644 --- a/vscode/src/services/utils/codeblock-action-tracker.ts +++ b/vscode/src/services/utils/codeblock-action-tracker.ts @@ -158,7 +158,8 @@ export async function handleSmartApply( code: string, authStatus: AuthStatus, instruction?: string | null, - fileUri?: string | null + fileUri?: string | null, + traceparent?: string | undefined | null ): Promise { const activeEditor = getEditor()?.active const workspaceUri = vscode.workspace.workspaceFolders?.[0].uri @@ -196,6 +197,7 @@ export async function handleSmartApply( model: getSmartApplyModel(authStatus), replacement: code, isNewFile, + traceparent, }, source: 'chat', }) diff --git a/vscode/webviews/Chat.tsx b/vscode/webviews/Chat.tsx index 78ae80c9d7b5..5a2fcb3bfe8d 100644 --- a/vscode/webviews/Chat.tsx +++ b/vscode/webviews/Chat.tsx @@ -20,6 +20,7 @@ import WelcomeFooter from './chat/components/WelcomeFooter' import { WelcomeMessage } from './chat/components/WelcomeMessage' import { ScrollDown } from './components/ScrollDown' import type { View } from './tabs' +import { SpanManager } from './utils/spanManager' import { useTelemetryRecorder } from './utils/telemetry' import { useUserAccountInfo } from './utils/useConfig' interface ChatboxProps { @@ -135,6 +136,23 @@ export const Chat: React.FunctionComponent instruction?: PromptString, fileName?: string ): void => { + const spanManager = new SpanManager('cody-webview') + const span = spanManager.startSpan('smartApplySubmit', { + attributes: { + sampled: true, + }, + }) + if (!span) { + throw new Error('Failed to start span for smartApplySubmit') + } + span.setAttributes({ + 'smartApply.id': id, + }) + const currentSpanContext = span.spanContext() + const traceparent = `00-${currentSpanContext.traceId}-${ + currentSpanContext.spanId + }-${currentSpanContext.traceFlags.toString(16).padStart(2, '0')}` + vscodeAPI.postMessage({ command: 'smartApplySubmit', id, @@ -142,7 +160,9 @@ export const Chat: React.FunctionComponent // remove the additional /n added by the text area at the end of the text code: text.replace(/\n$/, ''), fileName, + traceparent, }) + span.end() }, onAccept: (id: string) => { vscodeAPI.postMessage({ diff --git a/vscode/webviews/chat/Transcript.tsx b/vscode/webviews/chat/Transcript.tsx index 84549055f82e..03057acf0eb8 100644 --- a/vscode/webviews/chat/Transcript.tsx +++ b/vscode/webviews/chat/Transcript.tsx @@ -293,6 +293,11 @@ const TranscriptInteraction: FC = memo(props => { const spanContext = trace.setSpan(context.active(), span) setActiveChatContext(spanContext) + const currentSpanContext = span.spanContext() + + const traceparent = `00-${currentSpanContext.traceId}-${ + currentSpanContext.spanId + }-${currentSpanContext.traceFlags.toString(16).padStart(2, '0')}` // Serialize the editor value after starting the span const editorValue = humanEditorRef.current?.getSerializedValue() @@ -306,6 +311,7 @@ const TranscriptInteraction: FC = memo(props => { intent: intentFromSubmit || intentResults.current?.intent, intentScores: intentFromSubmit ? undefined : intentResults.current?.allScores, manuallySelectedIntent: !!intentFromSubmit, + traceparent, } if (action === 'edit') { @@ -433,6 +439,7 @@ const TranscriptInteraction: FC = memo(props => { }) renderSpan.current.end() } + renderSpan.current = undefined hasRecordedFirstToken.current = false @@ -714,11 +721,13 @@ function submitHumanMessage({ intent, intentScores, manuallySelectedIntent, + traceparent, }: { editorValue: SerializedPromptEditorValue intent?: ChatMessage['intent'] intentScores?: { intent: string; score: number }[] manuallySelectedIntent?: boolean + traceparent: string }): void { getVSCodeAPI().postMessage({ command: 'submit', @@ -728,6 +737,7 @@ function submitHumanMessage({ intent, intentScores, manuallySelectedIntent, + traceparent, }) focusLastHumanMessageEditor() } diff --git a/vscode/webviews/utils/webviewOpenTelemetryService.ts b/vscode/webviews/utils/webviewOpenTelemetryService.ts index ca73234b90c6..2ddde3a12cc3 100644 --- a/vscode/webviews/utils/webviewOpenTelemetryService.ts +++ b/vscode/webviews/utils/webviewOpenTelemetryService.ts @@ -49,7 +49,7 @@ export class WebviewOpenTelemetryService { try { this.tracerProvider = new WebTracerProvider({ resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'cody-webview', + [SemanticResourceAttributes.SERVICE_NAME]: 'cody-client', [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0', }), }) From 5fba6d5c5620e7f7e23ed42e0caf8b3f16ba1766 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Mon, 25 Nov 2024 13:57:58 +0400 Subject: [PATCH 2/5] separating the traceparent function --- vscode/webviews/Chat.tsx | 7 ++----- vscode/webviews/chat/Transcript.tsx | 6 ++---- vscode/webviews/utils/telemetry.ts | 7 +++++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/vscode/webviews/Chat.tsx b/vscode/webviews/Chat.tsx index 5a2fcb3bfe8d..6c8432ac04db 100644 --- a/vscode/webviews/Chat.tsx +++ b/vscode/webviews/Chat.tsx @@ -21,7 +21,7 @@ import { WelcomeMessage } from './chat/components/WelcomeMessage' import { ScrollDown } from './components/ScrollDown' import type { View } from './tabs' import { SpanManager } from './utils/spanManager' -import { useTelemetryRecorder } from './utils/telemetry' +import { getTraceparentFromSpanContext, useTelemetryRecorder } from './utils/telemetry' import { useUserAccountInfo } from './utils/useConfig' interface ChatboxProps { chatEnabled: boolean @@ -148,10 +148,7 @@ export const Chat: React.FunctionComponent span.setAttributes({ 'smartApply.id': id, }) - const currentSpanContext = span.spanContext() - const traceparent = `00-${currentSpanContext.traceId}-${ - currentSpanContext.spanId - }-${currentSpanContext.traceFlags.toString(16).padStart(2, '0')}` + const traceparent = getTraceparentFromSpanContext(span.spanContext()) vscodeAPI.postMessage({ command: 'smartApplySubmit', diff --git a/vscode/webviews/chat/Transcript.tsx b/vscode/webviews/chat/Transcript.tsx index 03057acf0eb8..21af9f8d5eaa 100644 --- a/vscode/webviews/chat/Transcript.tsx +++ b/vscode/webviews/chat/Transcript.tsx @@ -35,7 +35,7 @@ import type { ApiPostMessage } from '../Chat' import { Button } from '../components/shadcn/ui/button' import { getVSCodeAPI } from '../utils/VSCodeApi' import { SpanManager } from '../utils/spanManager' -import { useTelemetryRecorder } from '../utils/telemetry' +import { getTraceparentFromSpanContext, useTelemetryRecorder } from '../utils/telemetry' import { useExperimentalOneBox } from '../utils/useExperimentalOneBox' import type { CodeBlockActionsProps } from './ChatMessageContent/ChatMessageContent' import { @@ -295,9 +295,7 @@ const TranscriptInteraction: FC = memo(props => { setActiveChatContext(spanContext) const currentSpanContext = span.spanContext() - const traceparent = `00-${currentSpanContext.traceId}-${ - currentSpanContext.spanId - }-${currentSpanContext.traceFlags.toString(16).padStart(2, '0')}` + const traceparent = getTraceparentFromSpanContext(currentSpanContext) // Serialize the editor value after starting the span const editorValue = humanEditorRef.current?.getSerializedValue() diff --git a/vscode/webviews/utils/telemetry.ts b/vscode/webviews/utils/telemetry.ts index f403474c7d87..a2c1ea866eaa 100644 --- a/vscode/webviews/utils/telemetry.ts +++ b/vscode/webviews/utils/telemetry.ts @@ -1,5 +1,6 @@ import type { TelemetryRecorder } from '@sourcegraph/cody-shared' +import type { SpanContext } from '@opentelemetry/api' import { createContext, useContext } from 'react' import type { WebviewRecordEventParameters } from '../../src/chat/protocol' import type { ApiPostMessage } from '../Chat' @@ -41,3 +42,9 @@ export function useTelemetryRecorder(): TelemetryRecorder { } return telemetryRecorder } + +export function getTraceparentFromSpanContext(spanContext: SpanContext): string { + return `00-${spanContext.traceId}-${spanContext.spanId}-${spanContext.traceFlags + .toString(16) + .padStart(2, '0')}` +} From 6a6276818fe01cf7e48514cfe431a338bed5018a Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Mon, 25 Nov 2024 14:01:00 +0400 Subject: [PATCH 3/5] fixing function for start span --- vscode/webviews/Chat.tsx | 3 --- vscode/webviews/utils/spanManager.ts | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vscode/webviews/Chat.tsx b/vscode/webviews/Chat.tsx index 6c8432ac04db..c65c42f7019e 100644 --- a/vscode/webviews/Chat.tsx +++ b/vscode/webviews/Chat.tsx @@ -142,9 +142,6 @@ export const Chat: React.FunctionComponent sampled: true, }, }) - if (!span) { - throw new Error('Failed to start span for smartApplySubmit') - } span.setAttributes({ 'smartApply.id': id, }) diff --git a/vscode/webviews/utils/spanManager.ts b/vscode/webviews/utils/spanManager.ts index 9841644e6f36..fc685c4b76de 100644 --- a/vscode/webviews/utils/spanManager.ts +++ b/vscode/webviews/utils/spanManager.ts @@ -79,9 +79,9 @@ export class SpanManager { }) } - startSpan(name: string, options?: SpanManagerOptions): Span | undefined { + startSpan(name: string, options?: SpanManagerOptions): Span { if (this.spans.has(name)) { - return this.spans.get(name) + return this.spans.get(name)! } // Use provided context or fall back to active context From 9a274b31991dc1cc5c445426912af03c8ecfc066 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Mon, 25 Nov 2024 14:08:55 +0400 Subject: [PATCH 4/5] removing log --- vscode/src/edit/manager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/vscode/src/edit/manager.ts b/vscode/src/edit/manager.ts index 9ad5d87fe77f..3e3a80477c51 100644 --- a/vscode/src/edit/manager.ts +++ b/vscode/src/edit/manager.ts @@ -253,7 +253,6 @@ export class EditManager implements vscode.Disposable { } const extractedContext = propagation.extract(ROOT_CONTEXT, carrier, getter) - console.log('extractedContext', extractedContext) return context.with(extractedContext, async () => { await wrapInActiveSpan('edit.smart-apply', async span => { From 0c1d653eeab3e92547fc0ad891201b2a9b575a28 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Tue, 26 Nov 2024 20:43:38 +0400 Subject: [PATCH 5/5] Fixing edit metrics --- vscode/src/chat/chat-view/ChatController.ts | 2 +- vscode/src/edit/provider.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/vscode/src/chat/chat-view/ChatController.ts b/vscode/src/chat/chat-view/ChatController.ts index 20a037f59103..3c8060677756 100644 --- a/vscode/src/chat/chat-view/ChatController.ts +++ b/vscode/src/chat/chat-view/ChatController.ts @@ -139,7 +139,7 @@ import { getChatPanelTitle } from './chat-helpers' import { type HumanInput, getPriorityContext } from './context' import { DefaultPrompter, type PromptInfo } from './prompt' import { getPromptsMigrationInfo, startPromptsMigration } from './prompts-migration' -const { trace, context, propagation, ROOT_CONTEXT } = require('@opentelemetry/api') +const { context, propagation, ROOT_CONTEXT } = require('@opentelemetry/api') export interface ChatControllerOptions { extensionUri: vscode.Uri diff --git a/vscode/src/edit/provider.ts b/vscode/src/edit/provider.ts index d7e8de80945f..61c6ff3226b5 100644 --- a/vscode/src/edit/provider.ts +++ b/vscode/src/edit/provider.ts @@ -12,6 +12,7 @@ import { modelsService, posixFilePaths, telemetryRecorder, + tracer, uriBasename, wrapInActiveSpan, } from '@sourcegraph/cody-shared' @@ -55,6 +56,8 @@ export class EditProvider { public async startEdit(): Promise { return wrapInActiveSpan('command.edit.start', async span => { + span.setAttribute("sampled", true) + const mySpan = tracer.startSpan('cody.edit.provider.startTask') this.config.controller.startTask(this.config.task) const model = this.config.task.model const contextWindow = modelsService.getContextWindowByID(model) @@ -154,19 +157,30 @@ export class EditProvider { ) let textConsumed = 0 + let someTextConsumed = 0 for await (const message of stream) { switch (message.type) { case 'change': { + mySpan.addEvent('change') + if (textConsumed === 0 && responsePrefix) { void multiplexer.publish(responsePrefix) } + if (someTextConsumed === 0 && message.text.length > 1) { + mySpan.addEvent('complete') + mySpan.end() + someTextConsumed++ + } const text = message.text.slice(textConsumed) textConsumed += text.length void multiplexer.publish(text) break } case 'complete': { - await multiplexer.notifyTurnComplete() + wrapInActiveSpan('cody.edit.provider.complete', async span => { + span.setAttribute('sampled', true) + await multiplexer.notifyTurnComplete() + }) break } case 'error': {