[typescript-language-features] Add diagnostics performance telemetry (#220127)

* WIP

* invalidate diagnostics in range

* check whether should use region based diagnostics

* add ts-expect-errors

* make region opt off by default

* bump to expected 5.6

* update comments to refer to 5.6

* make region diagnostics on by default for insiders

* add telemetry for diagnostics performance

* add file line count

* remove comment

* use block on case

* add ts-expect-error

* declare interface earlier in file

* address review comments
This commit is contained in:
Gabriela Araujo Britto
2024-07-12 10:03:45 -07:00
committed by GitHub
parent a0599a9e2f
commit 14cf5001ec
5 changed files with 74 additions and 4 deletions

View File

@@ -11,6 +11,8 @@ import { ResourceMap } from '../utils/resourceMap';
import { TelemetryReporter } from '../logging/telemetry';
import { TypeScriptServiceConfiguration } from '../configuration/configuration';
import { equals } from '../utils/objects';
// @ts-expect-error until ts 5.6
import { DiagnosticPerformanceData as TsDiagnosticPerformanceData } from '../tsServer/protocol/protocol';
function diagnosticsEquals(a: vscode.Diagnostic, b: vscode.Diagnostic): boolean {
if (a === b) {
@@ -173,6 +175,10 @@ class DiagnosticSettings {
}
}
interface DiagnosticPerformanceData extends TsDiagnosticPerformanceData {
fileLineCount?: number;
}
class DiagnosticsTelemetryManager extends Disposable {
private readonly _diagnosticCodesMap = new Map<number, number>();
@@ -194,6 +200,37 @@ class DiagnosticsTelemetryManager extends Disposable {
this._registerTelemetryEventEmitter();
}
public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
for (const data of performanceData) {
/* __GDPR__
"diagnostics.performance" : {
"owner": "mjbvz",
"syntaxDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"semanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"suggestionDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"regionSemanticDiagDuration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"fileLineCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"${include}": [
"${TypeScriptCommonProperties}"
]
}
*/
this._telemetryReporter.logTelemetry('diagnostics.performance',
{
// @ts-expect-error until ts 5.6
syntaxDiagDuration: data.syntaxDiag,
// @ts-expect-error until ts 5.6
semanticDiagDuration: data.semanticDiag,
// @ts-expect-error until ts 5.6
suggestionDiagDuration: data.suggestionDiag,
// @ts-expect-error until ts 5.6
regionSemanticDiagDuration: data.regionSemanticDiag,
fileLineCount: data.fileLineCount,
},
);
}
}
private _updateAllDiagnosticCodesAfterTimeout() {
clearTimeout(this._timeout);
this._timeout = setTimeout(() => this._updateDiagnosticCodes(), 5000);
@@ -257,6 +294,8 @@ export class DiagnosticsManager extends Disposable {
private readonly _updateDelay = 50;
private readonly _diagnosticsTelemetryManager: DiagnosticsTelemetryManager | undefined;
constructor(
owner: string,
configuration: TypeScriptServiceConfiguration,
@@ -270,7 +309,7 @@ export class DiagnosticsManager extends Disposable {
this._currentDiagnostics = this._register(vscode.languages.createDiagnosticCollection(owner));
// Here we are selecting only 1 user out of 1000 to send telemetry diagnostics
if (Math.random() * 1000 <= 1 || configuration.enableDiagnosticsTelemetry) {
this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics));
this._diagnosticsTelemetryManager = this._register(new DiagnosticsTelemetryManager(telemetryReporter, this._currentDiagnostics));
}
}
@@ -349,6 +388,10 @@ export class DiagnosticsManager extends Disposable {
return this._currentDiagnostics.get(file) || [];
}
public logDiagnosticsPerformanceTelemetry(performanceData: DiagnosticPerformanceData[]): void {
this._diagnosticsTelemetryManager?.logDiagnosticsPerformanceTelemetry(performanceData);
}
private scheduleDiagnosticsUpdate(file: vscode.Uri) {
if (!this._pendingUpdates.has(file)) {
this._pendingUpdates.set(file, setTimeout(() => this.updateCurrentDiagnostics(file), this._updateDelay));

View File

@@ -662,6 +662,10 @@ export default class BufferSyncSupport extends Disposable {
this.synchronizer.beforeCommand(command);
}
public lineCount(resource: vscode.Uri): number | undefined {
return this.syncedBuffers.get(resource)?.lineCount;
}
private onDidCloseTextDocument(document: vscode.TextDocument): void {
this.closeResource(document.uri);
}

View File

@@ -92,6 +92,7 @@ export enum EventName {
createFileWatcher = 'createFileWatcher',
createDirectoryWatcher = 'createDirectoryWatcher',
closeFileWatcher = 'closeFileWatcher',
requestCompleted = 'requestCompleted',
}
export enum OrganizeImportsMode {

View File

@@ -166,6 +166,10 @@ export class SingleTsServer extends Disposable implements ITypeScriptServer {
this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
callback.onSuccess(undefined);
}
// @ts-expect-error until ts 5.6
if ((event as Proto.RequestCompletedEvent).body.performanceData) {
this._onEvent.fire(event);
}
} else {
this._tracer.traceEvent(this._serverId, event);
this._onEvent.fire(event);

View File

@@ -141,7 +141,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
constructor(
private readonly context: vscode.ExtensionContext,
onCaseInsenitiveFileSystem: boolean,
onCaseInsensitiveFileSystem: boolean,
services: {
pluginManager: PluginManager;
logDirectoryProvider: ILogDirectoryProvider;
@@ -191,7 +191,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
this.restartTsServer();
}));
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem);
this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsensitiveFileSystem);
this.onReady(() => { this.bufferSyncSupport.listen(); });
this.bufferSyncSupport.onDelete(resource => {
@@ -232,7 +232,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.apiVersion.fullVersionString;
});
this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsenitiveFileSystem);
this.diagnosticsManager = new DiagnosticsManager('typescript', this._configuration, this.telemetryReporter, onCaseInsensitiveFileSystem);
this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory);
this._register(this.pluginManager.onDidUpdateConfig(update => {
@@ -1038,6 +1038,24 @@ export default class TypeScriptServiceClient extends Disposable implements IType
case EventName.closeFileWatcher:
this.closeFileSystemWatcher(event.body.id);
break;
case EventName.requestCompleted: {
// @ts-expect-error until ts 5.6
const diagnosticsDuration = (event.body as Proto.RequestCompletedEventBody).performanceData?.diagnosticsDuration;
if (diagnosticsDuration) {
this.diagnosticsManager.logDiagnosticsPerformanceTelemetry(
// @ts-expect-error until ts 5.6
diagnosticsDuration.map(fileData => {
const resource = this.toResource(fileData.file);
return {
...fileData,
lineCount: this.bufferSyncSupport.lineCount(resource),
};
})
);
}
break;
}
}
}