mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 23:35:54 +01:00
Merge pull request #299003 from microsoft/robo/add_tsserver_diagnostics
feat: support heap profile and snapshot capture for tsserver
This commit is contained in:
@@ -30,7 +30,9 @@
|
||||
"typescript.npm",
|
||||
"js/ts.tsserver.npm.path",
|
||||
"typescript.tsserver.nodePath",
|
||||
"js/ts.tsserver.node.path"
|
||||
"js/ts.tsserver.node.path",
|
||||
"js/ts.tsserver.diagnosticDir",
|
||||
"js/ts.tsserver.heapProfile"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -2556,6 +2558,16 @@
|
||||
"TypeScript"
|
||||
]
|
||||
},
|
||||
"js/ts.tsserver.diagnosticDir": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%configuration.tsserver.diagnosticDir%",
|
||||
"scope": "machine",
|
||||
"keywords": [
|
||||
"TypeScript",
|
||||
"diagnostic",
|
||||
"memory"
|
||||
]
|
||||
},
|
||||
"typescript.tsserver.maxTsServerMemory": {
|
||||
"type": "number",
|
||||
"default": 3072,
|
||||
@@ -2563,6 +2575,48 @@
|
||||
"markdownDeprecationMessage": "%configuration.tsserver.maxTsServerMemory.unifiedDeprecationMessage%",
|
||||
"scope": "window"
|
||||
},
|
||||
"js/ts.tsserver.heapSnapshot": {
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"minimum": 0,
|
||||
"markdownDescription": "%configuration.tsserver.heapSnapshot%",
|
||||
"scope": "window",
|
||||
"keywords": [
|
||||
"TypeScript",
|
||||
"memory",
|
||||
"diagnostics"
|
||||
]
|
||||
},
|
||||
"js/ts.tsserver.heapProfile": {
|
||||
"type": "object",
|
||||
"default": {
|
||||
"enabled": false
|
||||
},
|
||||
"markdownDescription": "%configuration.tsserver.heapProfile%",
|
||||
"scope": "machine",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%configuration.tsserver.heapProfile.enabled%"
|
||||
},
|
||||
"dir": {
|
||||
"type": "string",
|
||||
"description": "%configuration.tsserver.heapProfile.dir%"
|
||||
},
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"description": "%configuration.tsserver.heapProfile.interval%"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"TypeScript",
|
||||
"memory",
|
||||
"heap",
|
||||
"profile"
|
||||
]
|
||||
},
|
||||
"js/ts.tsserver.watchOptions": {
|
||||
"description": "%configuration.tsserver.watchOptions%",
|
||||
"scope": "window",
|
||||
|
||||
@@ -126,6 +126,12 @@
|
||||
"configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process. To use a memory limit greater than 4 GB, use `#js/ts.tsserver.node.path#` to run TS Server with a custom Node installation.",
|
||||
"configuration.tsserver.maxTsServerMemory.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.maxMemory#` instead.",
|
||||
"configuration.tsserver.maxMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process. To use a memory limit greater than 4 GB, use `#js/ts.tsserver.node.path#` to run TS Server with a custom Node installation.",
|
||||
"configuration.tsserver.diagnosticDir": "Directory where TypeScript server writes Node diagnostic output by passing `--diagnostic-dir`.",
|
||||
"configuration.tsserver.heapSnapshot": "Controls how many near-heap-limit snapshots TypeScript server writes by passing `--heapsnapshot-near-heap-limit`. Set to `0` to disable.",
|
||||
"configuration.tsserver.heapProfile": "Configures heap profiling for TypeScript server.",
|
||||
"configuration.tsserver.heapProfile.enabled": "Enable heap profiling for TypeScript server by passing `--heap-prof`.",
|
||||
"configuration.tsserver.heapProfile.dir": "Directory where TypeScript server writes heap profiles by passing `--heap-prof-dir`.",
|
||||
"configuration.tsserver.heapProfile.interval": "Sampling interval in bytes for TypeScript server heap profiling by passing `--heap-prof-interval`.",
|
||||
"configuration.tsserver.experimental.enableProjectDiagnostics": "Enables project wide error reporting.",
|
||||
"configuration.tsserver.experimental.enableProjectDiagnostics.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.experimental.enableProjectDiagnostics#` instead.",
|
||||
"typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.",
|
||||
|
||||
@@ -110,6 +110,12 @@ export class ImplicitProjectConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
export interface TsServerHeapProfileConfiguration {
|
||||
readonly enabled: boolean;
|
||||
readonly dir: string | undefined;
|
||||
readonly interval: number | undefined;
|
||||
}
|
||||
|
||||
export interface TypeScriptServiceConfiguration {
|
||||
readonly locale: string | null;
|
||||
readonly globalTsdk: string | null;
|
||||
@@ -126,6 +132,9 @@ export interface TypeScriptServiceConfiguration {
|
||||
readonly enableDiagnosticsTelemetry: boolean;
|
||||
readonly enableProjectDiagnostics: boolean;
|
||||
readonly maxTsServerMemory: number;
|
||||
readonly diagnosticDir: string | undefined;
|
||||
readonly heapSnapshot: number;
|
||||
readonly heapProfile: TsServerHeapProfileConfiguration;
|
||||
readonly enablePromptUseWorkspaceTsdk: boolean;
|
||||
readonly useVsCodeWatcher: boolean;
|
||||
readonly watchOptions: Proto.WatchOptions | undefined;
|
||||
@@ -168,6 +177,9 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu
|
||||
enableDiagnosticsTelemetry: this.readEnableDiagnosticsTelemetry(),
|
||||
enableProjectDiagnostics: this.readEnableProjectDiagnostics(),
|
||||
maxTsServerMemory: this.readMaxTsServerMemory(),
|
||||
diagnosticDir: this.readDiagnosticDir(),
|
||||
heapSnapshot: this.readHeapSnapshot(),
|
||||
heapProfile: this.readHeapProfileConfiguration(),
|
||||
enablePromptUseWorkspaceTsdk: this.readEnablePromptUseWorkspaceTsdk(),
|
||||
useVsCodeWatcher: this.readUseVsCodeWatcher(configuration),
|
||||
watchOptions: this.readWatchOptions(),
|
||||
@@ -288,6 +300,42 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu
|
||||
return Math.max(memoryInMB, minimumMaxMemory);
|
||||
}
|
||||
|
||||
protected readDiagnosticDir(): string | undefined {
|
||||
const diagnosticDir = readUnifiedConfig<string | undefined>('tsserver.diagnosticDir', undefined, { fallbackSection: 'typescript' });
|
||||
return typeof diagnosticDir === 'string' && diagnosticDir.length > 0 ? diagnosticDir : undefined;
|
||||
}
|
||||
|
||||
protected readHeapSnapshot(): number {
|
||||
const defaultNearHeapLimitSnapshotCount = 0;
|
||||
const nearHeapLimitSnapshotCount = readUnifiedConfig<number>('tsserver.heapSnapshot', defaultNearHeapLimitSnapshotCount, { fallbackSection: 'typescript' });
|
||||
if (!Number.isSafeInteger(nearHeapLimitSnapshotCount)) {
|
||||
return defaultNearHeapLimitSnapshotCount;
|
||||
}
|
||||
return Math.max(nearHeapLimitSnapshotCount, 0);
|
||||
}
|
||||
|
||||
private readHeapProfileConfiguration(): TsServerHeapProfileConfiguration {
|
||||
const defaultHeapProfileConfiguration: TsServerHeapProfileConfiguration = {
|
||||
enabled: false,
|
||||
dir: undefined,
|
||||
interval: undefined,
|
||||
};
|
||||
|
||||
const rawConfig = readUnifiedConfig<{ enabled?: unknown; dir?: unknown; interval?: unknown }>('tsserver.heapProfile', defaultHeapProfileConfiguration, { fallbackSection: 'typescript' });
|
||||
|
||||
const enabled = typeof rawConfig.enabled === 'boolean' ? rawConfig.enabled : false;
|
||||
const dir = typeof rawConfig.dir === 'string' && rawConfig.dir.length > 0 ? rawConfig.dir : undefined;
|
||||
const interval = typeof rawConfig.interval === 'number' && Number.isSafeInteger(rawConfig.interval) && rawConfig.interval > 0
|
||||
? rawConfig.interval
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
enabled,
|
||||
dir,
|
||||
interval,
|
||||
};
|
||||
}
|
||||
|
||||
protected readEnablePromptUseWorkspaceTsdk(): boolean {
|
||||
return readUnifiedConfig<boolean>('tsdk.promptToUseWorkspaceVersion', false, { fallbackSection: 'typescript', fallbackSubSectionNameOverride: 'enablePromptUseWorkspaceTsdk' });
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@ const contentLengthSize: number = Buffer.byteLength(contentLength, 'utf8');
|
||||
const blank: number = Buffer.from(' ', 'utf8')[0];
|
||||
const backslashR: number = Buffer.from('\r', 'utf8')[0];
|
||||
const backslashN: number = Buffer.from('\n', 'utf8')[0];
|
||||
const gracefulExitTimeout = 5000;
|
||||
const tsServerExitRequest: Proto.Request = {
|
||||
seq: 0,
|
||||
type: 'request',
|
||||
command: 'exit',
|
||||
};
|
||||
|
||||
class ProtocolBuffer {
|
||||
|
||||
@@ -162,6 +168,24 @@ function getExecArgv(kind: TsServerProcessKind, configuration: TypeScriptService
|
||||
args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
|
||||
}
|
||||
|
||||
if (configuration.diagnosticDir) {
|
||||
args.push(`--diagnostic-dir=${configuration.diagnosticDir}`);
|
||||
}
|
||||
|
||||
if (configuration.heapSnapshot > 0) {
|
||||
args.push(`--heapsnapshot-near-heap-limit=${configuration.heapSnapshot}`);
|
||||
}
|
||||
|
||||
if (configuration.heapProfile.enabled) {
|
||||
args.push('--heap-prof');
|
||||
if (configuration.heapProfile.dir) {
|
||||
args.push(`--heap-prof-dir=${configuration.heapProfile.dir}`);
|
||||
}
|
||||
if (configuration.heapProfile.interval) {
|
||||
args.push(`--heap-prof-interval=${configuration.heapProfile.interval}`);
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -189,10 +213,15 @@ function getTssDebugBrk(): string | undefined {
|
||||
}
|
||||
|
||||
class IpcChildServerProcess extends Disposable implements TsServerProcess {
|
||||
private _killTimeout: NodeJS.Timeout | undefined;
|
||||
private _isShuttingDown = false;
|
||||
|
||||
constructor(
|
||||
private readonly _process: child_process.ChildProcess,
|
||||
private readonly _useGracefulShutdown: boolean,
|
||||
) {
|
||||
super();
|
||||
this._process.once('exit', () => this.clearKillTimeout());
|
||||
}
|
||||
|
||||
write(serverRequest: Proto.Request): void {
|
||||
@@ -212,18 +241,47 @@ class IpcChildServerProcess extends Disposable implements TsServerProcess {
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
this._process.kill();
|
||||
if (!this._useGracefulShutdown) {
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
this._isShuttingDown = true;
|
||||
|
||||
try {
|
||||
this._process.send(tsServerExitRequest);
|
||||
} catch {
|
||||
this._process.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
this._killTimeout = setTimeout(() => this._process.kill(), gracefulExitTimeout);
|
||||
this._killTimeout.unref?.();
|
||||
}
|
||||
|
||||
private clearKillTimeout(): void {
|
||||
if (this._killTimeout) {
|
||||
clearTimeout(this._killTimeout);
|
||||
this._killTimeout = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StdioChildServerProcess extends Disposable implements TsServerProcess {
|
||||
private readonly _reader: Reader<Proto.Response>;
|
||||
private _killTimeout: NodeJS.Timeout | undefined;
|
||||
private _isShuttingDown = false;
|
||||
|
||||
constructor(
|
||||
private readonly _process: child_process.ChildProcess,
|
||||
private readonly _useGracefulShutdown: boolean,
|
||||
) {
|
||||
super();
|
||||
this._reader = this._register(new Reader<Proto.Response>(this._process.stdout!));
|
||||
this._process.once('exit', () => this.clearKillTimeout());
|
||||
}
|
||||
|
||||
write(serverRequest: Proto.Request): void {
|
||||
@@ -244,7 +302,39 @@ class StdioChildServerProcess extends Disposable implements TsServerProcess {
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
this._process.kill();
|
||||
if (!this._useGracefulShutdown) {
|
||||
this._process.kill();
|
||||
this._reader.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
this._isShuttingDown = true;
|
||||
|
||||
try {
|
||||
this._process.stdin?.write(JSON.stringify(tsServerExitRequest) + '\r\n', 'utf8');
|
||||
this._process.stdin?.end();
|
||||
} catch {
|
||||
this._process.kill();
|
||||
this._reader.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
this._killTimeout = setTimeout(() => {
|
||||
this._process.kill();
|
||||
this._reader.dispose();
|
||||
}, gracefulExitTimeout);
|
||||
this._killTimeout.unref?.();
|
||||
}
|
||||
|
||||
private clearKillTimeout(): void {
|
||||
if (this._killTimeout) {
|
||||
clearTimeout(this._killTimeout);
|
||||
this._killTimeout = undefined;
|
||||
}
|
||||
|
||||
this._reader.dispose();
|
||||
}
|
||||
}
|
||||
@@ -272,6 +362,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
|
||||
const env = generatePatchedEnv(process.env, tsServerPath, !!execPath);
|
||||
const runtimeArgs = [...args];
|
||||
const execArgv = getExecArgv(kind, configuration);
|
||||
const useGracefulShutdown = configuration.heapProfile.enabled;
|
||||
const useIpc = !execPath && version.apiVersion?.gte(API.v460);
|
||||
if (useIpc) {
|
||||
runtimeArgs.push('--useNodeIpc');
|
||||
@@ -291,6 +382,6 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory {
|
||||
stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
|
||||
});
|
||||
|
||||
return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess);
|
||||
return useIpc ? new IpcChildServerProcess(childProcess, useGracefulShutdown) : new StdioChildServerProcess(childProcess, useGracefulShutdown);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user