Add telemetry trace support to TypeScript

This commit is contained in:
Dirk Baeumer
2025-04-15 11:46:39 +02:00
parent 72a9615f9d
commit 0c129cf983
5 changed files with 58 additions and 7 deletions

View File

@@ -16,6 +16,7 @@ function isCancellationToken(value: any): value is vscode.CancellationToken {
interface RequestArgs {
readonly file?: unknown;
readonly $traceId?: unknown;
}
export class TSServerRequestCommand implements Command {
@@ -31,11 +32,18 @@ export class TSServerRequestCommand implements Command {
}
if (args && typeof args === 'object' && !Array.isArray(args)) {
const requestArgs = args as RequestArgs;
let newArgs: any = undefined;
if (requestArgs.file instanceof vscode.Uri) {
newArgs = { ...args };
const client = this.lazyClientHost.value.serviceClient;
newArgs.file = client.toOpenTsFilePath(requestArgs.file);
const hasFile = requestArgs.file instanceof vscode.Uri;
const hasTraceId = typeof requestArgs.$traceId === 'string';
if (hasFile || hasTraceId) {
const newArgs = { ...args };
if (hasFile) {
const client = this.lazyClientHost.value.serviceClient;
newArgs.file = client.toOpenTsFilePath(requestArgs.file);
}
if (hasTraceId) {
const telemetryReporter = this.lazyClientHost.value.serviceClient.telemetryReporter;
telemetryReporter.logTraceEvent('TSServerRequestCommand.execute', requestArgs.$traceId, JSON.stringify({ command }));
}
args = newArgs;
}
}

View File

@@ -11,6 +11,7 @@ export interface TelemetryProperties {
export interface TelemetryReporter {
logTelemetry(eventName: string, properties?: TelemetryProperties): void;
logTraceEvent(tracePoint: string, correlationId: string, command?: string): void;
}
export class VSCodeTelemetryReporter implements TelemetryReporter {
@@ -34,4 +35,27 @@ export class VSCodeTelemetryReporter implements TelemetryReporter {
reporter.postEventObj(eventName, properties);
}
public logTraceEvent(point: string, id: string, data?: string): void {
const event: { point: string; id: string; data?: string | undefined } = {
point,
id
};
if (data) {
event.data = data;
}
/* __GDPR__
"tsServerRequest.trace" : {
"owner": "dirkb",
"${include}": [
"${TypeScriptCommonProperties}"
],
"point" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", comment: "The trace point." },
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", comment: "The traceId is used to correlate the request with other trace points." },
"data": { "classification": "SystemMetaData", "purpose": "FeatureInsight", comment: "Additional data" }
}
*/
this.logTelemetry('typeScriptExtension.trace', event);
}
}

View File

@@ -18,6 +18,7 @@ import { nulToken } from '../../utils/cancellation';
const NoopTelemetryReporter = new class implements TelemetryReporter {
logTelemetry(): void { /* noop */ }
logTraceEvent(): void { /* noop */ }
dispose(): void { /* noop */ }
};

View File

@@ -11,6 +11,7 @@ export interface CallbackItem<R> {
readonly onError: (err: Error) => void;
readonly queuingStartTime: number;
readonly isAsync: boolean;
readonly traceId?: string | undefined;
}
export class CallbackMap<R extends Proto.Response> {
@@ -43,6 +44,10 @@ export class CallbackMap<R extends Proto.Response> {
return callback;
}
public peek(seq: number): CallbackItem<ServerResponse.Response<R> | undefined> | undefined {
return this._callbacks.get(seq) ?? this._asyncCallbacks.get(seq);
}
private delete(seq: number) {
if (!this._callbacks.delete(seq)) {
this._asyncCallbacks.delete(seq);

View File

@@ -185,6 +185,10 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
private tryCancelRequest(request: Proto.Request, command: string): boolean {
const seq = request.seq;
const callback = this._callbacks.peek(seq);
if (callback?.traceId !== undefined) {
this._telemetryReporter.logTraceEvent('TSServerRequest.cancel', callback.traceId, JSON.stringify({ command, cancelled: true }));
}
try {
if (this._requestQueue.tryDeletePendingRequest(seq)) {
this.logTrace(`Canceled request with sequence number ${seq}`);
@@ -206,7 +210,9 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
if (!callback) {
return;
}
if (callback.traceId !== undefined) {
this._telemetryReporter.logTraceEvent('TSServerRequest.response', callback.traceId, JSON.stringify({ command: response.command, success: response.success, performanceData: response.performanceData }));
}
this._tracer.traceResponse(this._serverId, response, callback);
if (response.success) {
callback.onSuccess(response);
@@ -229,7 +235,7 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
let result: Promise<ServerResponse.Response<Proto.Response>> | undefined;
if (executeInfo.expectsResult) {
result = new Promise<ServerResponse.Response<Proto.Response>>((resolve, reject) => {
this._callbacks.add(request.seq, { onSuccess: resolve as () => ServerResponse.Response<Proto.Response> | undefined, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync);
this._callbacks.add(request.seq, { onSuccess: resolve as () => ServerResponse.Response<Proto.Response> | undefined, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync, traceId: request.arguments?.$traceId }, executeInfo.isAsync);
if (executeInfo.token) {
@@ -263,6 +269,10 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
}
this._requestQueue.enqueue(requestInfo);
if (typeof args.$traceId === 'string') {
const queueLength = this._requestQueue.length - 1;
this._telemetryReporter.logTraceEvent('TSServerRequest.enqueue', args.$traceId, JSON.stringify({ command, queueLength }));
}
this.sendNextRequests();
return [result];
@@ -287,6 +297,9 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
try {
this.write(serverRequest);
if (typeof serverRequest.arguments?.$traceId === 'string') {
this._telemetryReporter.logTraceEvent('TSServerRequest.send', serverRequest.arguments.$traceId, JSON.stringify({ command: serverRequest.command }));
}
} catch (err) {
const callback = this.fetchCallback(serverRequest.seq);
callback?.onError(err);