From 7fec270bedbc6839b04920f7d86a14a87e56e04b Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 17 Nov 2025 16:30:12 +0800 Subject: [PATCH 1/5] feat: promise queue to send telemetry data --- src/copilot/utils.ts | 123 +++++++++++++++++++++++++++++++++---------- test/simple/.project | 39 +++++++++----- 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/src/copilot/utils.ts b/src/copilot/utils.ts index 4a2b424a..e822788d 100644 --- a/src/copilot/utils.ts +++ b/src/copilot/utils.ts @@ -8,6 +8,31 @@ import { type ContextProvider, } from '@github/copilot-language-server'; import { sendInfo } from "vscode-extension-telemetry-wrapper"; + +/** + * TelemetryQueue - Asynchronous telemetry queue to avoid blocking main thread + * Based on the PromiseQueue pattern from copilot-client + */ +class TelemetryQueue { + private promises = new Set>(); + + register(promise: Promise): void { + this.promises.add(promise); + // Use void to avoid blocking - the key pattern from PromiseQueue + void promise.finally(() => this.promises.delete(promise)); + } + + async flush(): Promise { + await Promise.allSettled(this.promises); + } + + get size(): number { + return this.promises.size; + } +} + +// Global telemetry queue instance +const globalTelemetryQueue = new TelemetryQueue(); /** * Error classes for Copilot context provider cancellation handling */ @@ -211,14 +236,61 @@ export class ContextProviderResolverError extends Error { } /** - * Send consolidated telemetry data for Java context resolution - * This is the centralized function for sending context resolution telemetry + * Asynchronously send telemetry data preparation and sending + * This function prepares telemetry data and handles the actual sending asynchronously + */ +async function _sendContextResolutionTelemetry( + request: ResolveRequest, + start: number, + items: SupportedContextItem[], + status: string, + error?: string, + dependenciesEmptyReason?: string, + importsEmptyReason?: string, + dependenciesCount?: number, + importsCount?: number +): Promise { + try { + const duration = Math.round(performance.now() - start); + const tokenCount = JavaContextProviderUtils.calculateTokenCount(items); + const telemetryData: any = { + "action": "resolveJavaContext", + "completionId": request.completionId, + "duration": duration, + "itemCount": items.length, + "tokenCount": tokenCount, + "status": status, + "dependenciesCount": dependenciesCount ?? 0, + "importsCount": importsCount ?? 0 + }; + + // Add empty reasons if present + if (dependenciesEmptyReason) { + telemetryData.dependenciesEmptyReason = dependenciesEmptyReason; + } + if (importsEmptyReason) { + telemetryData.importsEmptyReason = importsEmptyReason; + } + if (error) { + telemetryData.error = error; + } + + // Actual telemetry sending - this is synchronous but network is async + sendInfo("", telemetryData); + } catch (telemetryError) { + // Silently ignore telemetry errors to not affect main functionality + console.error('Failed to send Java context resolution telemetry:', telemetryError); + } +} + +/** + * Send consolidated telemetry data for Java context resolution asynchronously + * This function immediately returns and sends telemetry in the background without blocking * * @param request The resolve request from Copilot * @param start Performance timestamp when resolution started * @param items The resolved context items * @param status Status of the resolution ("succeeded", "cancelled_by_copilot", "cancelled_internally", "error_partial_results") - * @param sendInfo The sendInfo function from vscode-extension-telemetry-wrapper * @param error Optional error message * @param dependenciesEmptyReason Optional reason why dependencies were empty * @param importsEmptyReason Optional reason why imports were empty @@ -236,29 +308,26 @@ export function sendContextResolutionTelemetry( dependenciesCount?: number, importsCount?: number ): void { - const duration = Math.round(performance.now() - start); - const tokenCount = JavaContextProviderUtils.calculateTokenCount(items); - const telemetryData: any = { - "action": "resolveJavaContext", - "completionId": request.completionId, - "duration": duration, - "itemCount": items.length, - "tokenCount": tokenCount, - "status": status, - "dependenciesCount": dependenciesCount ?? 0, - "importsCount": importsCount ?? 0 - }; - - // Add empty reasons if present - if (dependenciesEmptyReason) { - telemetryData.dependenciesEmptyReason = dependenciesEmptyReason; - } - if (importsEmptyReason) { - telemetryData.importsEmptyReason = importsEmptyReason; - } - if (error) { - telemetryData.error = error; - } + // Register the telemetry promise for non-blocking execution + // This follows the PromiseQueue pattern from copilot-client + globalTelemetryQueue.register( + _sendContextResolutionTelemetry( + request, + start, + items, + status, + error, + dependenciesEmptyReason, + importsEmptyReason, + dependenciesCount, + importsCount + ) + ); +} - sendInfo("", telemetryData); +/** + * Get the global telemetry queue instance (useful for testing and monitoring) + */ +export function getTelemetryQueue(): TelemetryQueue { + return globalTelemetryQueue; } \ No newline at end of file diff --git a/test/simple/.project b/test/simple/.project index e86159b9..efdddca1 100644 --- a/test/simple/.project +++ b/test/simple/.project @@ -1,17 +1,28 @@ - 1.helloworld - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - + 1.helloworld + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + 1763367965791 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + From 36079b234f813334d5db75dde3bec51f5a234eff Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 17 Nov 2025 16:35:02 +0800 Subject: [PATCH 2/5] ci: revert useless java --- test/simple/.project | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/test/simple/.project b/test/simple/.project index efdddca1..e86159b9 100644 --- a/test/simple/.project +++ b/test/simple/.project @@ -1,28 +1,17 @@ - 1.helloworld - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - - - 1763367965791 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - + 1.helloworld + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + From 1cebd9a6562f09b002e0b7986e9333f967725b27 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 17 Nov 2025 16:50:17 +0800 Subject: [PATCH 3/5] ci: revert useless java --- src/copilot/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/copilot/utils.ts b/src/copilot/utils.ts index e822788d..a694dca5 100644 --- a/src/copilot/utils.ts +++ b/src/copilot/utils.ts @@ -279,7 +279,6 @@ async function _sendContextResolutionTelemetry( sendInfo("", telemetryData); } catch (telemetryError) { // Silently ignore telemetry errors to not affect main functionality - console.error('Failed to send Java context resolution telemetry:', telemetryError); } } From 410acfe18dafba8a7ad4a051bc9f57e7c2ed399a Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Mon, 17 Nov 2025 19:29:59 +0800 Subject: [PATCH 4/5] fix: update code --- src/copilot/contextProvider.ts | 12 ++++++++---- src/copilot/utils.ts | 9 ++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/copilot/contextProvider.ts b/src/copilot/contextProvider.ts index 6640ee13..2e6cfc63 100644 --- a/src/copilot/contextProvider.ts +++ b/src/copilot/contextProvider.ts @@ -103,9 +103,10 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode items.push(...importsResult.items); } catch (error: any) { if (error instanceof CopilotCancellationError) { + const duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, - start, + duration, items, "cancelled_by_copilot", undefined, @@ -117,9 +118,10 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode throw error; } if (error instanceof vscode.CancellationError || error.message === CancellationError.CANCELED) { + const duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, - start, + duration, items, "cancelled_internally", undefined, @@ -132,9 +134,10 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode } // Send telemetry for general errors (but continue with partial results) + const duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, - start, + duration, items, "error_partial_results", error.message || "unknown_error", @@ -149,9 +152,10 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode } // Send telemetry data once at the end for success case + const duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, - start, + duration, items, "succeeded", undefined, diff --git a/src/copilot/utils.ts b/src/copilot/utils.ts index a694dca5..f45eb907 100644 --- a/src/copilot/utils.ts +++ b/src/copilot/utils.ts @@ -241,7 +241,7 @@ export class ContextProviderResolverError extends Error { */ async function _sendContextResolutionTelemetry( request: ResolveRequest, - start: number, + duration: number, items: SupportedContextItem[], status: string, error?: string, @@ -251,7 +251,6 @@ async function _sendContextResolutionTelemetry( importsCount?: number ): Promise { try { - const duration = Math.round(performance.now() - start); const tokenCount = JavaContextProviderUtils.calculateTokenCount(items); const telemetryData: any = { "action": "resolveJavaContext", @@ -287,7 +286,7 @@ async function _sendContextResolutionTelemetry( * This function immediately returns and sends telemetry in the background without blocking * * @param request The resolve request from Copilot - * @param start Performance timestamp when resolution started + * @param duration Duration of the resolution in milliseconds * @param items The resolved context items * @param status Status of the resolution ("succeeded", "cancelled_by_copilot", "cancelled_internally", "error_partial_results") * @param error Optional error message @@ -298,7 +297,7 @@ async function _sendContextResolutionTelemetry( */ export function sendContextResolutionTelemetry( request: ResolveRequest, - start: number, + duration: number, items: SupportedContextItem[], status: string, error?: string, @@ -312,7 +311,7 @@ export function sendContextResolutionTelemetry( globalTelemetryQueue.register( _sendContextResolutionTelemetry( request, - start, + duration, items, status, error, From b999cacf93a0b5f47782a9f4b188b8245b1c74fc Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 18 Nov 2025 09:30:46 +0800 Subject: [PATCH 5/5] fix: update --- src/copilot/contextProvider.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/copilot/contextProvider.ts b/src/copilot/contextProvider.ts index 2e6cfc63..2c41defd 100644 --- a/src/copilot/contextProvider.ts +++ b/src/copilot/contextProvider.ts @@ -74,6 +74,7 @@ function createJavaContextResolver(): ContextResolverFunction { async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise { const items: SupportedContextItem[] = []; const start = performance.now(); + let duration: number; let dependenciesResult: CopilotHelper.IResolveResult | undefined; let importsResult: CopilotHelper.IResolveResult | undefined; @@ -103,7 +104,7 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode items.push(...importsResult.items); } catch (error: any) { if (error instanceof CopilotCancellationError) { - const duration = Math.round(performance.now() - start); + duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, duration, @@ -118,7 +119,7 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode throw error; } if (error instanceof vscode.CancellationError || error.message === CancellationError.CANCELED) { - const duration = Math.round(performance.now() - start); + duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, duration, @@ -134,7 +135,7 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode } // Send telemetry for general errors (but continue with partial results) - const duration = Math.round(performance.now() - start); + duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, duration, @@ -152,7 +153,7 @@ async function resolveJavaContext(request: ResolveRequest, copilotCancel: vscode } // Send telemetry data once at the end for success case - const duration = Math.round(performance.now() - start); + duration = Math.round(performance.now() - start); sendContextResolutionTelemetry( request, duration,