Skip to content

Commit df75f81

Browse files
committed
Enhance AI generation telemetry tracking
1 parent c686f26 commit df75f81

File tree

5 files changed

+92
-9
lines changed

5 files changed

+92
-9
lines changed

workspaces/ballerina/ballerina-extension/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ export async function activate(context: ExtensionContext) {
113113

114114
// Wait for the ballerina extension to be ready
115115
await StateMachine.initialize();
116-
117116
// Then return the ballerina extension context
117+
118118
return { ballerinaExtInstance: extension.ballerinaExtInstance, projectPath: StateMachine.context().projectPath };
119119
}
120120

workspaces/ballerina/ballerina-extension/src/features/ai/service/design/design.ts

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { createConnectorGeneratorTool, CONNECTOR_GENERATOR_TOOL } from "../libs/
3535
import { LangfuseExporter } from 'langfuse-vercel';
3636
import { NodeSDK } from '@opentelemetry/sdk-node';
3737
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
38-
import { sendTelemetryEvent, TM_EVENT_BALLERINA_AI_QUERY_SUBMIT, TM_EVENT_BALLERINA_AI_GENERATION_FINISHED, CMP_BALLERINA_AI } from "../../../telemetry";
38+
import { sendTelemetryEvent, sendTelemetryException, TM_EVENT_BALLERINA_AI_QUERY_SUBMIT, TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED, TM_EVENT_BALLERINA_AI_GENERATION_ERROR, TM_EVENT_BALLERINA_AI_GENERATION_ABORTED, TM_EVENT_BALLERINA_AI_DIAGNOSTICS, CMP_BALLERINA_AI } from "../../../telemetry";
3939
import { extension } from "../../../../BalExtensionContext";
4040

4141
const LANGFUSE_SECRET = process.env.LANGFUSE_SECRET;
@@ -64,11 +64,17 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
6464

6565
const cacheOptions = await getProviderCacheControl();
6666

67+
// Get state machine context for telemetry
68+
const stateContext = AIChatStateMachine.context();
69+
6770
// Send telemetry when the user submits a query
6871
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_QUERY_SUBMIT, CMP_BALLERINA_AI, {
72+
projectId: stateContext.projectId || 'unknown',
6973
messageId: messageId,
7074
command: Command.Design,
7175
operationType: params.operationType,
76+
isPlanMode: isPlanModeEnabled.toString(),
77+
approvalMode: stateContext.autoApproveEnabled ? 'auto' : 'manual',
7278
});
7379

7480
const modifiedFiles: string[] = [];
@@ -127,15 +133,36 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
127133
let accumulatedMessages: any[] = [];
128134
let currentAssistantContent: any[] = [];
129135

136+
// Timing metrics for telemetry
137+
let generationStartTime: number | undefined;
138+
let firstTokenTime: number | undefined;
139+
let lastTokenTime: number | undefined;
140+
130141
for await (const part of fullStream) {
131142
switch (part.type) {
132143
case "text-delta": {
144+
// Capture first token time
145+
if (!firstTokenTime) {
146+
firstTokenTime = Date.now();
147+
generationStartTime = firstTokenTime;
148+
}
149+
// Update last token time
150+
lastTokenTime = Date.now();
151+
133152
const textPart = part.text;
134153
eventHandler({ type: "content_block", content: textPart });
135154
accumulateTextContent(currentAssistantContent, textPart);
136155
break;
137156
}
138157
case "tool-call": {
158+
// Capture first token time for tool calls as well
159+
if (!firstTokenTime) {
160+
firstTokenTime = Date.now();
161+
generationStartTime = firstTokenTime;
162+
}
163+
// Update last token time
164+
lastTokenTime = Date.now();
165+
139166
const toolName = part.toolName;
140167
accumulateToolCall(currentAssistantContent, part);
141168

@@ -207,6 +234,20 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
207234
toolOutput: { success: true, action }
208235
});
209236
} else if (toolName === DIAGNOSTICS_TOOL_NAME) {
237+
// Send telemetry for diagnostics
238+
const diagnosticsResult = result as any;
239+
const hasErrors = diagnosticsResult?.diagnostics?.some((d: any) => d.severity === 'Error') || false;
240+
const errorCount = diagnosticsResult?.diagnostics?.filter((d: any) => d.severity === 'Error').length || 0;
241+
const warningCount = diagnosticsResult?.diagnostics?.filter((d: any) => d.severity === 'Warning').length || 0;
242+
243+
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_DIAGNOSTICS, CMP_BALLERINA_AI, {
244+
projectId: stateContext.projectId || 'unknown',
245+
messageId: messageId,
246+
hasErrors: hasErrors.toString(),
247+
errorCount: errorCount.toString(),
248+
warningCount: warningCount.toString(),
249+
});
250+
210251
eventHandler({
211252
type: "tool_result",
212253
toolName,
@@ -220,8 +261,26 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
220261
case "error": {
221262
const error = part.error;
222263
console.error("[Design] Error:", error);
264+
265+
// Send telemetry for generation error
266+
const errorObj = error instanceof Error ? error : new Error(String(error));
267+
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_GENERATION_ERROR, CMP_BALLERINA_AI, {
268+
projectId: stateContext.projectId || 'unknown',
269+
messageId: messageId,
270+
errorMessage: getErrorMessage(error),
271+
errorType: errorObj.name || 'Unknown',
272+
generationStartTime: generationStartTime?.toString() || 'unknown',
273+
errorTime: Date.now().toString(),
274+
});
275+
276+
sendTelemetryException(extension.ballerinaExtInstance, errorObj, CMP_BALLERINA_AI, {
277+
projectId: stateContext.projectId || 'unknown',
278+
messageId: messageId,
279+
});
280+
223281
// Cleanup temp project on error
224282
cleanupTempProject(tempProjectPath);
283+
225284
eventHandler({ type: "error", content: getErrorMessage(error) });
226285
break;
227286
}
@@ -231,6 +290,19 @@ export async function generateDesignCore(params: GenerateAgentCodeRequest, event
231290
}
232291
case "abort": {
233292
console.log("[Design] Aborted by user.");
293+
const abortTime = Date.now();
294+
295+
// Send telemetry for generation abort
296+
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_GENERATION_ABORTED, CMP_BALLERINA_AI, {
297+
projectId: stateContext.projectId || 'unknown',
298+
messageId: messageId,
299+
generationStartTime: generationStartTime?.toString() || 'unknown',
300+
abortTime: abortTime.toString(),
301+
firstTokenTime: firstTokenTime?.toString() || 'unknown',
302+
lastTokenTime: lastTokenTime?.toString() || 'unknown',
303+
modifiedFilesCount: modifiedFiles.length.toString(),
304+
});
305+
234306
let messagesToSave: any[] = [];
235307
try {
236308
const partialResponse = await response;
@@ -272,6 +344,7 @@ Generation stopped by user. The last in-progress task was not saved. Files have
272344
const finishReason = part.finishReason;
273345
const finalResponse = await response;
274346
const assistantMessages = finalResponse.messages || [];
347+
const generationEndTime = Date.now();
275348

276349
console.log(`[Design] Finished with reason: ${finishReason}`);
277350

@@ -288,11 +361,17 @@ Generation stopped by user. The last in-progress task was not saved. Files have
288361
updateAndSaveChat(messageId, userMessageContent, assistantMessages, eventHandler);
289362
eventHandler({ type: "stop", command: Command.Design });
290363

291-
// Send telemetry after generation
292-
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_GENERATION_FINISHED, CMP_BALLERINA_AI, {
364+
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED, CMP_BALLERINA_AI, {
365+
projectId: stateContext.projectId || 'unknown',
293366
messageId: messageId,
294367
finishReason: finishReason,
295368
modifiedFilesCount: modifiedFiles.length.toString(),
369+
generationStartTime: generationStartTime?.toString() || 'unknown',
370+
generationEndTime: generationEndTime.toString(),
371+
firstTokenTime: firstTokenTime?.toString() || 'unknown',
372+
lastTokenTime: lastTokenTime?.toString() || 'unknown',
373+
isPlanMode: isPlanModeEnabled.toString(),
374+
approvalMode: stateContext.autoApproveEnabled ? 'auto' : 'manual',
296375
});
297376

298377
AIChatStateMachine.sendEvent({

workspaces/ballerina/ballerina-extension/src/features/telemetry/events.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,8 @@ export const TM_EVENT_OPEN_REPO_SAME_FOLDER = "vscode.open.exist.repo.same.folde
125125

126126
// events for AI features
127127
export const TM_EVENT_BALLERINA_AI_QUERY_SUBMIT = "ballerina.ai.query.submit";
128-
export const TM_EVENT_BALLERINA_AI_GENERATION_FINISHED = "ballerina.ai.generation.finished";
128+
export const TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED = "ballerina.ai.generation.completed";
129+
export const TM_EVENT_BALLERINA_AI_GENERATION_ERROR = "ballerina.ai.generation.error";
130+
export const TM_EVENT_BALLERINA_AI_GENERATION_ABORTED = "ballerina.ai.generation.aborted";
131+
export const TM_EVENT_BALLERINA_AI_DIAGNOSTICS = "ballerina.ai.diagnostics";
129132
export const TM_EVENT_BALLERINA_AI_REVERT = "ballerina.ai.code.revert";

workspaces/ballerina/ballerina-extension/src/features/telemetry/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import { BallerinaExtension } from "../../core";
2121
import { getLoginMethod, getBiIntelId } from "../../utils/ai/auth";
2222

2323
//Ballerina-VSCode-Extention repo key as default
24-
const DEFAULT_KEY = "3a82b093-5b7b-440c-9aa2-3b8e8e5704e7";
24+
// const DEFAULT_KEY = "3a82b093-5b7b-440c-9aa2-3b8e8e5704e7";
25+
const DEFAULT_KEY = "ff7807cf-db47-4e8b-bd70-03fdfc2576b7";
2526
const INSTRUMENTATION_KEY = process.env.CODE_SERVER_ENV && process.env.VSCODE_CHOREO_INSTRUMENTATION_KEY ? process.env.VSCODE_CHOREO_INSTRUMENTATION_KEY : DEFAULT_KEY;
2627
const isWSO2User = process.env.VSCODE_CHOREO_USER_EMAIL ? process.env.VSCODE_CHOREO_USER_EMAIL.endsWith('@wso2.com') : false;
2728
const isAnonymous = process.env.VSCODE_CHOREO_USER_EMAIL ? process.env.VSCODE_CHOREO_USER_EMAIL.endsWith('@choreo.dev') : false;
@@ -59,7 +60,7 @@ export async function sendTelemetryException(extension: BallerinaExtension, erro
5960
export async function getTelemetryProperties(extension: BallerinaExtension, component: string, params: { [key: string]: string; } = {})
6061
: Promise<{ [key: string]: string; }> {
6162

62-
const loginType = await getLoginMethod();
63+
const userType = await getLoginMethod();
6364
const biIntelId = await getBiIntelId();
6465

6566
return {
@@ -74,7 +75,7 @@ export async function getTelemetryProperties(extension: BallerinaExtension, comp
7475
'component': CHOREO_COMPONENT_ID,
7576
'project': CHOREO_PROJECT_ID,
7677
'org': CHOREO_ORG_ID,
77-
'loginType': loginType,
78+
'userType': userType,
7879
'biIntelId': biIntelId,
7980
}
8081
}

workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiChatMachine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import { generateDesign } from '../../features/ai/service/design/design';
2525
import { captureWorkspaceSnapshot, restoreWorkspaceSnapshot } from './checkpoint/checkpointUtils';
2626
import { getCheckpointConfig } from './checkpoint/checkpointConfig';
2727
import { notifyCheckpointCaptured } from '../../RPCLayer';
28+
import { StateMachine } from '../../stateMachine';
2829
import { sendTelemetryEvent, TM_EVENT_BALLERINA_AI_REVERT, CMP_BALLERINA_AI } from '../../features/telemetry';
29-
3030
// Extracted utilities
3131
import { generateProjectId, generateSessionId } from './idGenerators';
3232
import { addUserMessage, updateChatMessage, convertChatHistoryToModelMessages, convertChatHistoryToUIMessages } from './chatHistoryUtils';

0 commit comments

Comments
 (0)