Skip to content

Commit de77dc8

Browse files
Add API to track lsp perf on language client level (#2996)
* Add API to track lsp perf on language client level
1 parent e3778eb commit de77dc8

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

src/TracingLanguageClient.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { performance } from "perf_hooks";
2+
import { Event, EventEmitter } from "vscode";
3+
import { CancellationToken, LanguageClient, LanguageClientOptions, ProtocolRequestType, ProtocolRequestType0, RequestType, RequestType0, ServerOptions } from "vscode-languageclient/node";
4+
import { TraceEvent } from "./extension.api";
5+
6+
const requestEventEmitter = new EventEmitter<TraceEvent>();
7+
export const onDidRequestEnd: Event<TraceEvent> = requestEventEmitter.event;
8+
9+
export class TracingLanguageClient extends LanguageClient {
10+
private isStarted: boolean = false;
11+
12+
constructor(id: string, name: string, serverOptions: ServerOptions, clientOptions: LanguageClientOptions, forceDebug?: boolean) {
13+
super(id, name, serverOptions, clientOptions, forceDebug);
14+
}
15+
16+
start(): Promise<void> {
17+
const isFirstTimeStart: boolean = !this.isStarted;
18+
this.isStarted = true;
19+
const startAt: number = performance.now();
20+
return super.start().then(value => {
21+
if (isFirstTimeStart) {
22+
this.fireTraceEvent("initialize", startAt);
23+
}
24+
return value;
25+
}, reason => {
26+
if (isFirstTimeStart) {
27+
this.fireTraceEvent("initialize", startAt, reason);
28+
}
29+
throw reason;
30+
});
31+
}
32+
33+
stop(timeout?: number): Promise<void> {
34+
this.isStarted = false;
35+
return super.stop(timeout);
36+
}
37+
38+
sendRequest<R, PR, E, RO>(type: ProtocolRequestType0<R, PR, E, RO>, token?: CancellationToken): Promise<R>;
39+
sendRequest<P, R, PR, E, RO>(type: ProtocolRequestType<P, R, PR, E, RO>, params: P, token?: CancellationToken): Promise<R>;
40+
sendRequest<R, E>(type: RequestType0<R, E>, token?: CancellationToken): Promise<R>;
41+
sendRequest<P, R, E>(type: RequestType<P, R, E>, params: P, token?: CancellationToken): Promise<R>;
42+
sendRequest<R>(method: string, token?: CancellationToken): Promise<R>;
43+
sendRequest<R>(method: string, param: any, token?: CancellationToken): Promise<R>;
44+
sendRequest(method: any, ...args) {
45+
const startAt: number = performance.now();
46+
const requestType: string = this.getRequestType(method, ...args);
47+
return this.sendRequest0(method, ...args).then(value => {
48+
this.fireTraceEvent(requestType, startAt);
49+
return value;
50+
}, reason => {
51+
this.fireTraceEvent(requestType, startAt, reason);
52+
throw reason;
53+
});
54+
}
55+
56+
private sendRequest0(method: any, ...args) {
57+
if (!args || !args.length) {
58+
return super.sendRequest(method);
59+
}
60+
61+
const first = args[0];
62+
const last = args[args.length - 1];
63+
if (CancellationToken.is(last)) {
64+
if (first === last) {
65+
return super.sendRequest(method, last);
66+
} else {
67+
return super.sendRequest(method, first, last);
68+
}
69+
}
70+
71+
return super.sendRequest(method, first);
72+
}
73+
74+
private getRequestType(method: any, ...args): string {
75+
let requestType: string;
76+
if (typeof method === 'string' || method instanceof String) {
77+
requestType = String(method);
78+
} else {
79+
requestType = method?.method;
80+
}
81+
82+
if (requestType === "workspace/executeCommand") {
83+
if (args?.[0]?.command) {
84+
requestType = `workspace/executeCommand/${args[0].command}`;
85+
}
86+
}
87+
88+
return requestType;
89+
}
90+
91+
private fireTraceEvent(type: string, startAt: number, reason?: any): void {
92+
const duration: number = performance.now() - startAt;
93+
requestEventEmitter.fire({
94+
type,
95+
duration,
96+
error: reason,
97+
});
98+
}
99+
}

src/apiManager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Commands } from "./commands";
99
import { Emitter } from "vscode-languageclient";
1010
import { ServerMode } from "./settings";
1111
import { registerHoverCommand } from "./hoverAction";
12+
import { onDidRequestEnd } from "./TracingLanguageClient";
1213

1314
class ApiManager {
1415

@@ -60,6 +61,7 @@ class ApiManager {
6061
onDidServerModeChange,
6162
onDidProjectsImport,
6263
serverReady,
64+
onDidRequestEnd,
6365
};
6466
}
6567

src/extension.api.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,22 @@ export enum ClientStatus {
7373
stopping = "Stopping",
7474
}
7575

76-
export const extensionApiVersion = '0.7';
76+
export interface TraceEvent {
77+
/**
78+
* Request type.
79+
*/
80+
type: string;
81+
/**
82+
* Time (in milliseconds) taken to process a request.
83+
*/
84+
duration: number;
85+
/**
86+
* Error that occurs while processing a request.
87+
*/
88+
error?: any;
89+
}
90+
91+
export const extensionApiVersion = '0.8';
7792

7893
export interface ExtensionAPI {
7994
readonly apiVersion: string;
@@ -118,4 +133,11 @@ export interface ExtensionAPI {
118133
* @since extension version 1.7.0
119134
*/
120135
readonly serverReady: () => Promise<boolean>;
136+
137+
/**
138+
* An event that's fired when a request has been responded.
139+
* @since API version 0.8
140+
* @since extension version 1.16.0
141+
*/
142+
readonly onDidRequestEnd: Event<TraceEvent>;
121143
}

src/standardLanguageClient.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { excludeProjectSettingsFiles, ServerMode, setGradleWrapperChecksum } fro
3333
import { snippetCompletionProvider } from "./snippetCompletionProvider";
3434
import * as sourceAction from './sourceAction';
3535
import { askForProjects, projectConfigurationUpdate, upgradeGradle } from "./standardLanguageClientUtils";
36+
import { TracingLanguageClient } from './TracingLanguageClient';
3637
import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol";
3738
import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree";
3839
import { getAllJavaProjects, getJavaConfig, getJavaConfiguration } from "./utils";
@@ -103,7 +104,7 @@ export class StandardLanguageClient {
103104
}
104105

105106
// Create the language client and start the client.
106-
this.languageClient = new LanguageClient('java', extensionName, serverOptions, clientOptions);
107+
this.languageClient = new TracingLanguageClient('java', extensionName, serverOptions, clientOptions);
107108

108109
this.registerCommandsForStandardServer(context, jdtEventEmitter);
109110
fileEventHandler.registerFileEventHandlers(this.languageClient, context);

0 commit comments

Comments
 (0)