Skip to content

Commit 377cfc2

Browse files
committed
perf: throttling telemetry event
1 parent 8c231a2 commit 377cfc2

File tree

3 files changed

+130
-62
lines changed

3 files changed

+130
-62
lines changed

src/copilot/contextProvider.ts

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
ContextResolverFunction,
1919
CopilotApi,
2020
ContextProviderRegistrationError,
21-
ContextProviderResolverError
21+
ContextProviderResolverError,
22+
sendContextResolutionTelemetry
2223
} from './utils';
2324

2425
export async function registerCopilotContextProviders(
@@ -70,70 +71,99 @@ function createJavaContextResolver(): ContextResolverFunction {
7071
};
7172
}
7273

73-
/**
74-
* Send telemetry data for Java context resolution
75-
*/
76-
function sendContextTelemetry(request: ResolveRequest, start: number, items: SupportedContextItem[], status: string, error?: string) {
77-
const duration = Math.round(performance.now() - start);
78-
const tokenCount = JavaContextProviderUtils.calculateTokenCount(items);
79-
const telemetryData: any = {
80-
"action": "resolveJavaContext",
81-
"completionId": request.completionId,
82-
"duration": duration,
83-
"itemCount": items.length,
84-
"tokenCount": tokenCount,
85-
"status": status
86-
};
87-
if (error) {
88-
telemetryData.error = error;
89-
}
90-
sendInfo("", telemetryData);
91-
}
92-
9374
async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise<SupportedContextItem[]> {
9475
const items: SupportedContextItem[] = [];
9576
const start = performance.now();
77+
78+
let dependenciesResult: CopilotHelper.IResolveResult | undefined;
79+
let importsResult: CopilotHelper.IResolveResult | undefined;
80+
9681
try {
9782
// Check for cancellation before starting
9883
JavaContextProviderUtils.checkCancellation(copilotCancel);
84+
9985
// Resolve project dependencies and convert to context items
100-
const projectDependencyItems = await CopilotHelper.resolveAndConvertProjectDependencies(
86+
dependenciesResult = await CopilotHelper.resolveAndConvertProjectDependencies(
10187
vscode.window.activeTextEditor,
10288
copilotCancel,
10389
JavaContextProviderUtils.checkCancellation
10490
);
10591
JavaContextProviderUtils.checkCancellation(copilotCancel);
106-
items.push(...projectDependencyItems);
92+
items.push(...dependenciesResult.items);
10793

10894
JavaContextProviderUtils.checkCancellation(copilotCancel);
10995

11096
// Resolve local imports and convert to context items
111-
const localImportItems = await CopilotHelper.resolveAndConvertLocalImports(
97+
importsResult = await CopilotHelper.resolveAndConvertLocalImports(
11298
vscode.window.activeTextEditor,
11399
copilotCancel,
114100
JavaContextProviderUtils.checkCancellation
115101
);
116102
JavaContextProviderUtils.checkCancellation(copilotCancel);
117-
items.push(...localImportItems);
103+
items.push(...importsResult.items);
118104
} catch (error: any) {
119105
if (error instanceof CopilotCancellationError) {
120-
sendContextTelemetry(request, start, items, "cancelled_by_copilot");
106+
sendContextResolutionTelemetry(
107+
request,
108+
start,
109+
items,
110+
"cancelled_by_copilot",
111+
sendInfo,
112+
undefined,
113+
dependenciesResult?.emptyReason,
114+
importsResult?.emptyReason,
115+
dependenciesResult?.itemCount,
116+
importsResult?.itemCount
117+
);
121118
throw error;
122119
}
123120
if (error instanceof vscode.CancellationError || error.message === CancellationError.CANCELED) {
124-
sendContextTelemetry(request, start, items, "cancelled_internally");
121+
sendContextResolutionTelemetry(
122+
request,
123+
start,
124+
items,
125+
"cancelled_internally",
126+
sendInfo,
127+
undefined,
128+
dependenciesResult?.emptyReason,
129+
importsResult?.emptyReason,
130+
dependenciesResult?.itemCount,
131+
importsResult?.itemCount
132+
);
125133
throw new InternalCancellationError();
126134
}
127135

128136
// Send telemetry for general errors (but continue with partial results)
129-
sendContextTelemetry(request, start, items, "error_partial_results", error.message || "unknown_error");
137+
sendContextResolutionTelemetry(
138+
request,
139+
start,
140+
items,
141+
"error_partial_results",
142+
sendInfo,
143+
error.message || "unknown_error",
144+
dependenciesResult?.emptyReason,
145+
importsResult?.emptyReason,
146+
dependenciesResult?.itemCount,
147+
importsResult?.itemCount
148+
);
130149

131150
// Return partial results and log completion for error case
132151
return items;
133152
}
134153

135154
// Send telemetry data once at the end for success case
136-
sendContextTelemetry(request, start, items, "succeeded");
155+
sendContextResolutionTelemetry(
156+
request,
157+
start,
158+
items,
159+
"succeeded",
160+
sendInfo,
161+
undefined,
162+
dependenciesResult?.emptyReason,
163+
importsResult?.emptyReason,
164+
dependenciesResult?.itemCount,
165+
importsResult?.itemCount
166+
);
137167

138168
return items;
139169
}

src/copilot/copilotHelper.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Licensed under the MIT license.
33

44
import { commands, Uri, CancellationToken } from "vscode";
5-
import { sendError, sendInfo } from "vscode-extension-telemetry-wrapper";
6-
import { GetImportClassContentError, GetProjectDependenciesError, sendContextOperationTelemetry, JavaContextProviderUtils } from "./utils";
5+
import { sendError } from "vscode-extension-telemetry-wrapper";
6+
import { GetImportClassContentError, GetProjectDependenciesError, JavaContextProviderUtils } from "./utils";
77
import { Commands } from '../commands';
88

99
/**
@@ -259,27 +259,35 @@ export namespace CopilotHelper {
259259
}
260260
}
261261

262+
/**
263+
* Result interface for dependency resolution with diagnostic information
264+
*/
265+
export interface IResolveResult {
266+
items: any[];
267+
emptyReason?: string;
268+
itemCount: number;
269+
}
270+
262271
/**
263272
* Resolves project dependencies and converts them to context items with cancellation support
264-
* @param workspaceFolders The workspace folders, or undefined if none
273+
* @param activeEditor The active text editor, or undefined if none
265274
* @param copilotCancel Cancellation token from Copilot
266275
* @param checkCancellation Function to check for cancellation
267-
* @returns Array of context items for project dependencies, or empty array if no workspace folders
276+
* @returns Result object containing context items and diagnostic information
268277
*/
269278
export async function resolveAndConvertProjectDependencies(
270279
activeEditor: { document: { uri: Uri; languageId: string } } | undefined,
271280
copilotCancel: CancellationToken,
272281
checkCancellation: (token: CancellationToken) => void
273-
): Promise<{ name: string; value: string; importance: number }[]> {
282+
): Promise<IResolveResult> {
274283
const items: any[] = [];
284+
275285
// Check if workspace folders exist
276286
if (!activeEditor) {
277-
sendContextOperationTelemetry("resolveLocalImports", "ContextEmpty", sendInfo, EmptyReason.NoActiveEditor);
278-
return items;
287+
return { items: [], emptyReason: EmptyReason.NoActiveEditor, itemCount: 0 };
279288
}
280289
if (activeEditor.document.languageId !== 'java') {
281-
sendContextOperationTelemetry("resolveLocalImports", "ContextEmpty", sendInfo, EmptyReason.NotJavaFile);
282-
return items;
290+
return { items: [], emptyReason: EmptyReason.NotJavaFile, itemCount: 0 };
283291
}
284292
const documentUri = activeEditor.document.uri;
285293

@@ -289,9 +297,9 @@ export namespace CopilotHelper {
289297
// Check for cancellation after dependency resolution
290298
checkCancellation(copilotCancel);
291299

292-
// Send telemetry if result is empty
300+
// Return empty result with reason if no dependencies found
293301
if (projectDependenciesResult.isEmpty && projectDependenciesResult.emptyReason) {
294-
sendContextOperationTelemetry("resolveProjectDependencies", "ContextEmpty", sendInfo, projectDependenciesResult.emptyReason);
302+
return { items: [], emptyReason: projectDependenciesResult.emptyReason, itemCount: 0 };
295303
}
296304

297305
// Check for cancellation after telemetry
@@ -306,31 +314,29 @@ export namespace CopilotHelper {
306314
items.push(...contextItems);
307315
}
308316

309-
return items;
317+
return { items, itemCount: items.length };
310318
}
311319

312320
/**
313321
* Resolves local imports and converts them to context items with cancellation support
314322
* @param activeEditor The active text editor, or undefined if none
315323
* @param copilotCancel Cancellation token from Copilot
316324
* @param checkCancellation Function to check for cancellation
317-
* @param createContextItems Function to create context items from imports
318-
* @returns Array of context items for local imports, or empty array if no valid editor
325+
* @returns Result object containing context items and diagnostic information
319326
*/
320327
export async function resolveAndConvertLocalImports(
321328
activeEditor: { document: { uri: Uri; languageId: string } } | undefined,
322329
copilotCancel: CancellationToken,
323330
checkCancellation: (token: CancellationToken) => void
324-
): Promise<any[]> {
331+
): Promise<IResolveResult> {
325332
const items: any[] = [];
333+
326334
// Check if there's an active editor with a Java document
327335
if (!activeEditor) {
328-
sendContextOperationTelemetry("resolveLocalImports", "ContextEmpty", sendInfo, EmptyReason.NoActiveEditor);
329-
return items;
336+
return { items: [], emptyReason: EmptyReason.NoActiveEditor, itemCount: 0 };
330337
}
331338
if (activeEditor.document.languageId !== 'java') {
332-
sendContextOperationTelemetry("resolveLocalImports", "ContextEmpty", sendInfo, EmptyReason.NotJavaFile);
333-
return items;
339+
return { items: [], emptyReason: EmptyReason.NotJavaFile, itemCount: 0 };
334340
}
335341

336342
const documentUri = activeEditor.document.uri;
@@ -343,10 +349,11 @@ export namespace CopilotHelper {
343349
// Check for cancellation after resolution
344350
checkCancellation(copilotCancel);
345351

346-
// Send telemetry if result is empty
352+
// Return empty result with reason if no imports found
347353
if (importClassResult.isEmpty && importClassResult.emptyReason) {
348-
sendContextOperationTelemetry("resolveLocalImports", "ContextEmpty", sendInfo, importClassResult.emptyReason);
354+
return { items: [], emptyReason: importClassResult.emptyReason, itemCount: 0 };
349355
}
356+
350357
// Check for cancellation before processing results
351358
checkCancellation(copilotCancel);
352359
if (importClassResult.classInfoList && importClassResult.classInfoList.length > 0) {
@@ -357,6 +364,6 @@ export namespace CopilotHelper {
357364
items.push(...contextItems);
358365
}
359366

360-
return items;
367+
return { items, itemCount: items.length };
361368
}
362369
}

src/copilot/utils.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,24 +210,55 @@ export class ContextProviderResolverError extends Error {
210210
}
211211

212212
/**
213-
* Send telemetry data for context operations (like resolveProjectDependencies, resolveLocalImports)
214-
* @param action The action being performed
215-
* @param status The status of the action (e.g., "ContextEmpty", "succeeded")
216-
* @param reason Optional reason for empty context
213+
* Send consolidated telemetry data for Java context resolution
214+
* This is the centralized function for sending context resolution telemetry
215+
*
216+
* @param request The resolve request from Copilot
217+
* @param start Performance timestamp when resolution started
218+
* @param items The resolved context items
219+
* @param status Status of the resolution ("succeeded", "cancelled_by_copilot", "cancelled_internally", "error_partial_results")
217220
* @param sendInfo The sendInfo function from vscode-extension-telemetry-wrapper
221+
* @param error Optional error message
222+
* @param dependenciesEmptyReason Optional reason why dependencies were empty
223+
* @param importsEmptyReason Optional reason why imports were empty
224+
* @param dependenciesCount Number of dependency items resolved
225+
* @param importsCount Number of import items resolved
218226
*/
219-
export function sendContextOperationTelemetry(
220-
action: string,
227+
export function sendContextResolutionTelemetry(
228+
request: ResolveRequest,
229+
start: number,
230+
items: SupportedContextItem[],
221231
status: string,
222232
sendInfo: (eventName: string, properties?: any) => void,
223-
reason?: string
233+
error?: string,
234+
dependenciesEmptyReason?: string,
235+
importsEmptyReason?: string,
236+
dependenciesCount?: number,
237+
importsCount?: number
224238
): void {
239+
const duration = Math.round(performance.now() - start);
240+
const tokenCount = JavaContextProviderUtils.calculateTokenCount(items);
225241
const telemetryData: any = {
226-
"action": action,
227-
"status": status
242+
"action": "resolveJavaContext",
243+
"completionId": request.completionId,
244+
"duration": duration,
245+
"itemCount": items.length,
246+
"tokenCount": tokenCount,
247+
"status": status,
248+
"dependenciesCount": dependenciesCount ?? 0,
249+
"importsCount": importsCount ?? 0
228250
};
229-
if (reason) {
230-
telemetryData.ContextEmptyReason = reason;
251+
252+
// Add empty reasons if present
253+
if (dependenciesEmptyReason) {
254+
telemetryData.dependenciesEmptyReason = dependenciesEmptyReason;
231255
}
256+
if (importsEmptyReason) {
257+
telemetryData.importsEmptyReason = importsEmptyReason;
258+
}
259+
if (error) {
260+
telemetryData.error = error;
261+
}
262+
232263
sendInfo("", telemetryData);
233264
}

0 commit comments

Comments
 (0)