Skip to content

Commit

Permalink
Add API to track lsp perf on language client level (#2996)
Browse files Browse the repository at this point in the history
* Add API to track lsp perf on language client level
  • Loading branch information
testforstephen authored Mar 15, 2023
1 parent e3778eb commit de77dc8
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 2 deletions.
99 changes: 99 additions & 0 deletions src/TracingLanguageClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { performance } from "perf_hooks";
import { Event, EventEmitter } from "vscode";
import { CancellationToken, LanguageClient, LanguageClientOptions, ProtocolRequestType, ProtocolRequestType0, RequestType, RequestType0, ServerOptions } from "vscode-languageclient/node";
import { TraceEvent } from "./extension.api";

const requestEventEmitter = new EventEmitter<TraceEvent>();
export const onDidRequestEnd: Event<TraceEvent> = requestEventEmitter.event;

export class TracingLanguageClient extends LanguageClient {
private isStarted: boolean = false;

constructor(id: string, name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean) {
super(id, name, serverOptions, clientOptions, forceDebug);
}

start(): Promise<void> {
const isFirstTimeStart: boolean = !this.isStarted;
this.isStarted = true;
const startAt: number = performance.now();
return super.start().then(value => {
if (isFirstTimeStart) {
this.fireTraceEvent("initialize", startAt);
}
return value;
}, reason => {
if (isFirstTimeStart) {
this.fireTraceEvent("initialize", startAt, reason);
}
throw reason;
});
}

stop(timeout?: number): Promise<void> {
this.isStarted = false;
return super.stop(timeout);
}

sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, token?: CancellationToken): Promise<R>;
sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: P, token?: CancellationToken): Promise<R>;
sendRequest<R, E>(type: RequestType0<R, E>, token?: CancellationToken): Promise<R>;
sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Promise<R>;
sendRequest<R>(method: string, token?: CancellationToken): Promise<R>;
sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>;
sendRequest(method: any, ...args) {
const startAt: number = performance.now();
const requestType: string = this.getRequestType(method, ...args);
return this.sendRequest0(method, ...args).then(value => {
this.fireTraceEvent(requestType, startAt);
return value;
}, reason => {
this.fireTraceEvent(requestType, startAt, reason);
throw reason;
});
}

private sendRequest0(method: any, ...args) {
if (!args || !args.length) {
return super.sendRequest(method);
}

const first = args[0];
const last = args[args.length - 1];
if (CancellationToken.is(last)) {
if (first === last) {
return super.sendRequest(method, last);
} else {
return super.sendRequest(method, first, last);
}
}

return super.sendRequest(method, first);
}

private getRequestType(method: any, ...args): string {
let requestType: string;
if (typeof method === 'string' || method instanceof String) {
requestType = String(method);
} else {
requestType = method?.method;
}

if (requestType === "workspace/executeCommand") {
if (args?.[0]?.command) {
requestType = `workspace/executeCommand/${args[0].command}`;
}
}

return requestType;
}

private fireTraceEvent(type: string, startAt: number, reason?: any): void {
const duration: number = performance.now() - startAt;
requestEventEmitter.fire({
type,
duration,
error: reason,
});
}
}
2 changes: 2 additions & 0 deletions src/apiManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Commands } from "./commands";
import { Emitter } from "vscode-languageclient";
import { ServerMode } from "./settings";
import { registerHoverCommand } from "./hoverAction";
import { onDidRequestEnd } from "./TracingLanguageClient";

class ApiManager {

Expand Down Expand Up @@ -60,6 +61,7 @@ class ApiManager {
onDidServerModeChange,
onDidProjectsImport,
serverReady,
onDidRequestEnd,
};
}

Expand Down
24 changes: 23 additions & 1 deletion src/extension.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,22 @@ export enum ClientStatus {
stopping = "Stopping",
}

export const extensionApiVersion = '0.7';
export interface TraceEvent {
/**
* Request type.
*/
type: string;
/**
* Time (in milliseconds) taken to process a request.
*/
duration: number;
/**
* Error that occurs while processing a request.
*/
error?: any;
}

export const extensionApiVersion = '0.8';

export interface ExtensionAPI {
readonly apiVersion: string;
Expand Down Expand Up @@ -118,4 +133,11 @@ export interface ExtensionAPI {
* @since extension version 1.7.0
*/
readonly serverReady: () => Promise<boolean>;

/**
* An event that's fired when a request has been responded.
* @since API version 0.8
* @since extension version 1.16.0
*/
readonly onDidRequestEnd: Event<TraceEvent>;
}
3 changes: 2 additions & 1 deletion src/standardLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { excludeProjectSettingsFiles, ServerMode, setGradleWrapperChecksum } fro
import { snippetCompletionProvider } from "./snippetCompletionProvider";
import * as sourceAction from './sourceAction';
import { askForProjects, projectConfigurationUpdate, upgradeGradle } from "./standardLanguageClientUtils";
import { TracingLanguageClient } from './TracingLanguageClient';
import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol";
import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree";
import { getAllJavaProjects, getJavaConfig, getJavaConfiguration } from "./utils";
Expand Down Expand Up @@ -103,7 +104,7 @@ export class StandardLanguageClient {
}

// Create the language client and start the client.
this.languageClient = new LanguageClient('java', extensionName, serverOptions, clientOptions);
this.languageClient = new TracingLanguageClient('java', extensionName, serverOptions, clientOptions);

this.registerCommandsForStandardServer(context, jdtEventEmitter);
fileEventHandler.registerFileEventHandlers(this.languageClient, context);
Expand Down

0 comments on commit de77dc8

Please sign in to comment.