Skip to content

Commit 3b98ae1

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

File tree

5 files changed

+94
-8
lines changed

5 files changed

+94
-8
lines changed

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

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

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

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

Lines changed: 83 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,18 @@ 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+
// Send telemetry for generation completion
365+
sendTelemetryEvent(extension.ballerinaExtInstance, TM_EVENT_BALLERINA_AI_GENERATION_COMPLETED, CMP_BALLERINA_AI, {
366+
projectId: stateContext.projectId || 'unknown',
293367
messageId: messageId,
294368
finishReason: finishReason,
295369
modifiedFilesCount: modifiedFiles.length.toString(),
370+
generationStartTime: generationStartTime?.toString() || 'unknown',
371+
generationEndTime: generationEndTime.toString(),
372+
firstTokenTime: firstTokenTime?.toString() || 'unknown',
373+
lastTokenTime: lastTokenTime?.toString() || 'unknown',
374+
isPlanMode: isPlanModeEnabled.toString(),
375+
approvalMode: stateContext.autoApproveEnabled ? 'auto' : 'manual',
296376
});
297377

298378
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export async function sendTelemetryException(extension: BallerinaExtension, erro
5959
export async function getTelemetryProperties(extension: BallerinaExtension, component: string, params: { [key: string]: string; } = {})
6060
: Promise<{ [key: string]: string; }> {
6161

62-
const loginType = await getLoginMethod();
62+
const userType = await getLoginMethod();
6363
const biIntelId = await getBiIntelId();
6464

6565
return {
@@ -74,7 +74,7 @@ export async function getTelemetryProperties(extension: BallerinaExtension, comp
7474
'component': CHOREO_COMPONENT_ID,
7575
'project': CHOREO_PROJECT_ID,
7676
'org': CHOREO_ORG_ID,
77-
'loginType': loginType,
77+
'userType': userType,
7878
'biIntelId': biIntelId,
7979
}
8080
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ 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 { sendTelemetryEvent, TM_EVENT_BALLERINA_AI_REVERT, CMP_BALLERINA_AI } from '../../features/telemetry';
28+
import { StateMachine } from '../../stateMachine';
2929

3030
// Extracted utilities
3131
import { generateProjectId, generateSessionId } from './idGenerators';
3232
import { addUserMessage, updateChatMessage, convertChatHistoryToModelMessages, convertChatHistoryToUIMessages } from './chatHistoryUtils';
3333
import { saveChatState, loadChatState, clearChatState, clearChatStateAction, getAllProjectIds, clearAllChatStates, getChatStateMetadata } from './chatStatePersistence';
3434
import { normalizeCodeContext } from './codeContextUtils';
3535

36+
import { sendTelemetryEvent, TM_EVENT_BALLERINA_AI_REVERT, CMP_BALLERINA_AI } from '../../features/telemetry';
37+
38+
3639
const cleanupOldCheckpoints = (checkpoints: Checkpoint[]): Checkpoint[] => {
3740
const config = getCheckpointConfig();
3841
if (checkpoints.length <= config.maxCount) {

0 commit comments

Comments
 (0)