Skip to content

Commit ffb7eaf

Browse files
authored
Merge pull request #6223 from dibarbet/project_load_telemetry
Implement project load telemetry support
2 parents 677fa52 + 08e17c0 commit ffb7eaf

File tree

10 files changed

+151
-79
lines changed

10 files changed

+151
-79
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838
},
3939
"defaults": {
40-
"roslyn": "4.8.0-2.23426.1",
40+
"roslyn": "4.8.0-2.23428.2",
4141
"omniSharp": "1.39.7",
4242
"razor": "7.0.0-preview.23423.3",
4343
"razorOmnisharp": "7.0.0-preview.23363.1"

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ import { RoslynLanguageClient } from './roslynLanguageClient';
6363
import { registerUnitTestingCommands } from './unitTesting';
6464
import SerializableSimplifyMethodParams from '../razor/src/simplify/serializableSimplifyMethodParams';
6565
import { TextEdit } from 'vscode-html-languageservice';
66+
import { reportProjectConfigurationEvent } from '../shared/projectConfiguration';
67+
import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
6668
import { registerLanguageServerOptionChanges } from './optionChanges';
6769
import { Observable } from 'rxjs';
70+
import { DotnetInfo } from '../shared/utils/dotnetInfo';
6871

6972
let _languageServer: RoslynLanguageServer;
7073
let _channel: vscode.OutputChannel;
@@ -135,7 +138,7 @@ export class RoslynLanguageServer {
135138
* Resolves server options and starts the dotnet language server process. The process is started asynchronously and this method will not wait until
136139
* the process is launched.
137140
*/
138-
public start(): void {
141+
public async start(): Promise<void> {
139142
const options = this.optionProvider.GetLatestOptions();
140143
const logLevel = options.languageServerOptions.logLevel;
141144
const languageClientTraceLevel = this.GetTraceLevel(logLevel);
@@ -206,6 +209,22 @@ export class RoslynLanguageServer {
206209
);
207210
});
208211

212+
// Store the dotnet info outside of the notification so we're not running dotnet --info every time the project changes.
213+
let dotnetInfo: DotnetInfo | undefined = undefined;
214+
this._languageClient.onNotification(RoslynProtocol.ProjectConfigurationNotification.type, async (params) => {
215+
if (!dotnetInfo) {
216+
dotnetInfo = await getDotnetInfo([]);
217+
}
218+
reportProjectConfigurationEvent(
219+
this.telemetryReporter,
220+
params,
221+
this.platformInfo,
222+
dotnetInfo,
223+
this._solutionFile?.fsPath,
224+
true
225+
);
226+
});
227+
209228
this._languageClient.onNotification(RoslynProtocol.ShowToastNotification.type, async (notification) => {
210229
const messageOptions: vscode.MessageOptions = {
211230
modal: false,
@@ -282,7 +301,7 @@ export class RoslynLanguageServer {
282301
*/
283302
public async restart(): Promise<void> {
284303
await this.stop();
285-
this.start();
304+
await this.start();
286305
}
287306

288307
/**
@@ -847,7 +866,7 @@ export async function activateRoslynLanguageServer(
847866
});
848867

849868
// Start the language server.
850-
_languageServer.start();
869+
await _languageServer.start();
851870

852871
return _languageServer;
853872

src/lsptoolshost/roslynProtocol.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Command } from 'vscode';
77
import * as lsp from 'vscode-languageserver-protocol';
8+
import { ProjectConfigurationMessage } from '../shared/projectConfiguration';
89

910
export interface WorkspaceDebugConfigurationParams {
1011
/**
@@ -161,6 +162,12 @@ export namespace ProjectInitializationCompleteNotification {
161162
export const type = new lsp.NotificationType(method);
162163
}
163164

165+
export namespace ProjectConfigurationNotification {
166+
export const method = 'workspace/projectConfigurationTelemetry';
167+
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.serverToClient;
168+
export const type = new lsp.NotificationType<ProjectConfigurationMessage>(method);
169+
}
170+
164171
export namespace ShowToastNotification {
165172
export const method = 'window/_roslyn_showToast';
166173
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.serverToClient;

src/observers/telemetryObserver.ts

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import * as crypto from 'crypto';
7-
import { machineIdSync } from 'node-machine-id';
86
import { PlatformInformation } from '../shared/platform';
97
import {
108
BaseEvent,
@@ -20,25 +18,13 @@ import { PackageError } from '../packageManager/packageError';
2018
import { EventType } from '../omnisharp/eventType';
2119
import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
2220
import { DotnetInfo } from '../shared/utils/dotnetInfo';
23-
24-
export interface ITelemetryReporter {
25-
sendTelemetryEvent(
26-
eventName: string,
27-
properties?: { [key: string]: string },
28-
measures?: { [key: string]: number }
29-
): void;
30-
sendTelemetryErrorEvent(
31-
eventName: string,
32-
properties?: { [key: string]: string },
33-
measures?: { [key: string]: number },
34-
errorProps?: string[]
35-
): void;
36-
}
21+
import { ITelemetryReporter, getTelemetryProps } from '../shared/telemetryReporter';
22+
import { reportProjectConfigurationEvent } from '../shared/projectConfiguration';
3723

3824
export class TelemetryObserver {
3925
private reporter: ITelemetryReporter;
4026
private platformInfo: PlatformInformation;
41-
private solutionId?: string;
27+
private solutionPath?: string;
4228
private dotnetInfo?: DotnetInfo;
4329
private useModernNet: boolean;
4430

@@ -49,7 +35,7 @@ export class TelemetryObserver {
4935
}
5036

5137
public post = (event: BaseEvent) => {
52-
const telemetryProps = this.getTelemetryProps();
38+
const telemetryProps = getTelemetryProps(this.platformInfo);
5339
switch (event.type) {
5440
case EventType.OmnisharpInitialisation:
5541
this.handleOmnisharpInitialisation(<OmnisharpInitialisation>event);
@@ -90,7 +76,7 @@ export class TelemetryObserver {
9076
break;
9177
}
9278
case EventType.ProjectConfigurationReceived:
93-
this.handleProjectConfigurationReceived(<ProjectConfiguration>event, telemetryProps);
79+
this.handleProjectConfigurationReceived(<ProjectConfiguration>event);
9480
break;
9581
}
9682
};
@@ -101,7 +87,7 @@ export class TelemetryObserver {
10187

10288
private async handleOmnisharpInitialisation(event: OmnisharpInitialisation) {
10389
this.dotnetInfo = await getDotnetInfo(event.dotNetCliPaths);
104-
this.solutionId = this.createSolutionId(event.solutionPath);
90+
this.solutionPath = event.solutionPath;
10591
}
10692

10793
private handleInstallationSuccess(telemetryProps: { [key: string]: string }) {
@@ -129,45 +115,15 @@ export class TelemetryObserver {
129115
}
130116
}
131117

132-
private handleProjectConfigurationReceived(event: ProjectConfiguration, telemetryProps: { [key: string]: string }) {
118+
private handleProjectConfigurationReceived(event: ProjectConfiguration) {
133119
const projectConfig = event.projectConfiguration;
134-
telemetryProps['SolutionId'] = this.solutionId ?? '';
135-
telemetryProps['ProjectId'] = projectConfig.ProjectId;
136-
telemetryProps['SessionId'] = projectConfig.SessionId;
137-
telemetryProps['OutputType'] = projectConfig.OutputKind?.toString() ?? '';
138-
telemetryProps['ProjectCapabilities'] = projectConfig.ProjectCapabilities?.join(' ') ?? '';
139-
telemetryProps['TargetFrameworks'] = projectConfig.TargetFrameworks.join('|');
140-
telemetryProps['References'] = projectConfig.References.join('|');
141-
telemetryProps['FileExtensions'] = projectConfig.FileExtensions.join('|');
142-
telemetryProps['FileCounts'] = projectConfig.FileCounts?.join('|') ?? '';
143-
telemetryProps['NetSdkVersion'] = this.dotnetInfo?.Version ?? '';
144-
telemetryProps['useModernNet'] = this.useModernNet.toString();
145-
telemetryProps['sdkStyleProject'] = projectConfig.SdkStyleProject.toString();
146-
this.reporter.sendTelemetryEvent('ProjectConfiguration', telemetryProps);
147-
}
148-
149-
private getTelemetryProps() {
150-
const telemetryProps: { [key: string]: string } = {
151-
'platform.architecture': this.platformInfo.architecture,
152-
'platform.platform': this.platformInfo.platform,
153-
};
154-
155-
if (this.platformInfo.distribution) {
156-
telemetryProps['platform.distribution'] = this.platformInfo.distribution.toTelemetryString();
157-
}
158-
159-
return telemetryProps;
160-
}
161-
162-
private createSolutionId(solutionPath: string) {
163-
const solutionHash = crypto.createHash('sha256').update(solutionPath).digest('hex');
164-
165-
const machineId = machineIdSync();
166-
const machineHash = crypto.createHash('sha256').update(machineId).digest('hex');
167-
168-
return crypto
169-
.createHash('sha256')
170-
.update(solutionHash + machineHash)
171-
.digest('hex');
120+
reportProjectConfigurationEvent(
121+
this.reporter,
122+
projectConfig,
123+
this.platformInfo,
124+
this.dotnetInfo,
125+
this.solutionPath ?? '',
126+
this.useModernNet
127+
);
172128
}
173129
}

src/omnisharp/loggingEvents.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Request } from './requestQueue';
88
import * as protocol from './protocol';
99
import { LaunchTarget } from '../shared/launchTarget';
1010
import { EventType } from './eventType';
11+
import { ProjectConfigurationMessage } from '../shared/projectConfiguration';
1112

1213
export interface BaseEvent {
1314
type: EventType;
@@ -165,7 +166,7 @@ export class OmnisharpOnMultipleLaunchTargets implements BaseEvent {
165166

166167
export class ProjectConfiguration implements BaseEvent {
167168
type = EventType.ProjectConfigurationReceived;
168-
constructor(public projectConfiguration: protocol.ProjectConfigurationMessage) {}
169+
constructor(public projectConfiguration: ProjectConfigurationMessage) {}
169170
}
170171

171172
export class WorkspaceInformationUpdated implements BaseEvent {

src/omnisharp/protocol.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -464,18 +464,6 @@ export interface UnresolvedDependenciesMessage {
464464
UnresolvedDependencies: PackageDependency[];
465465
}
466466

467-
export interface ProjectConfigurationMessage {
468-
ProjectId: string;
469-
SessionId: string;
470-
OutputKind: number;
471-
ProjectCapabilities: string[];
472-
TargetFrameworks: string[];
473-
References: string[];
474-
FileExtensions: string[];
475-
FileCounts: number[];
476-
SdkStyleProject: boolean;
477-
}
478-
479467
export interface PackageDependency {
480468
Name: string;
481469
Version: string;

src/omnisharp/server.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { validateRequirements } from './requirementCheck';
3333
import { Advisor } from '../features/diagnosticsProvider';
3434
import TestManager from '../features/dotnetTest';
3535
import { findLaunchTargets } from './launcher';
36+
import { ProjectConfigurationMessage } from '../shared/projectConfiguration';
3637

3738
enum ServerState {
3839
Starting,
@@ -397,7 +398,7 @@ export class OmniSharpServer {
397398
);
398399

399400
disposables.add(
400-
this.onProjectConfigurationReceived((message: protocol.ProjectConfigurationMessage) => {
401+
this.onProjectConfigurationReceived((message: ProjectConfigurationMessage) => {
401402
this.eventStream.post(new ObservableEvents.ProjectConfiguration(message));
402403
})
403404
);
@@ -611,7 +612,7 @@ export class OmniSharpServer {
611612
);
612613
}
613614

614-
private onProjectConfigurationReceived(listener: (e: protocol.ProjectConfigurationMessage) => void) {
615+
private onProjectConfigurationReceived(listener: (e: ProjectConfigurationMessage) => void) {
615616
return this._addListener(Events.ProjectConfiguration, listener);
616617
}
617618

src/shared/projectConfiguration.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as crypto from 'crypto';
7+
import { machineIdSync } from 'node-machine-id';
8+
import { PlatformInformation } from './platform';
9+
import { ITelemetryReporter, getTelemetryProps } from './telemetryReporter';
10+
import { DotnetInfo } from './utils/dotnetInfo';
11+
12+
export interface ProjectConfigurationMessage {
13+
ProjectId: string;
14+
SessionId: string;
15+
OutputKind: number;
16+
ProjectCapabilities: string[];
17+
TargetFrameworks: string[];
18+
References: string[];
19+
FileExtensions: string[];
20+
FileCounts: number[];
21+
SdkStyleProject: boolean;
22+
}
23+
24+
export function reportProjectConfigurationEvent(
25+
reporter: ITelemetryReporter,
26+
projectConfig: ProjectConfigurationMessage,
27+
platformInfo: PlatformInformation,
28+
dotnetInfo: DotnetInfo | undefined,
29+
solutionPath?: string,
30+
useModernNet?: boolean
31+
) {
32+
let solutionId = '';
33+
if (solutionPath) {
34+
solutionId = createSolutionId(solutionPath);
35+
}
36+
37+
const telemetryProps = getTelemetryProps(platformInfo);
38+
telemetryProps['SolutionId'] = solutionId;
39+
telemetryProps['ProjectId'] = projectConfig.ProjectId;
40+
telemetryProps['SessionId'] = projectConfig.SessionId;
41+
telemetryProps['OutputType'] = projectConfig.OutputKind?.toString() ?? '';
42+
telemetryProps['ProjectCapabilities'] = projectConfig.ProjectCapabilities?.join(' ') ?? '';
43+
telemetryProps['TargetFrameworks'] = projectConfig.TargetFrameworks.join('|');
44+
telemetryProps['References'] = projectConfig.References.join('|');
45+
telemetryProps['FileExtensions'] = projectConfig.FileExtensions.join('|');
46+
telemetryProps['FileCounts'] = projectConfig.FileCounts?.join('|') ?? '';
47+
telemetryProps['NetSdkVersion'] = dotnetInfo?.Version ?? '';
48+
telemetryProps['sdkStyleProject'] = projectConfig.SdkStyleProject.toString();
49+
50+
if (useModernNet) {
51+
telemetryProps['useModernNet'] = useModernNet.toString();
52+
}
53+
54+
reporter.sendTelemetryEvent('ProjectConfiguration', telemetryProps);
55+
}
56+
57+
function createSolutionId(solutionPath: string) {
58+
const solutionHash = crypto.createHash('sha256').update(solutionPath).digest('hex');
59+
60+
const machineId = machineIdSync();
61+
const machineHash = crypto.createHash('sha256').update(machineId).digest('hex');
62+
63+
return crypto
64+
.createHash('sha256')
65+
.update(solutionHash + machineHash)
66+
.digest('hex');
67+
}

src/shared/telemetryReporter.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { PlatformInformation } from './platform';
7+
8+
export interface ITelemetryReporter {
9+
sendTelemetryEvent(
10+
eventName: string,
11+
properties?: { [key: string]: string },
12+
measures?: { [key: string]: number }
13+
): void;
14+
sendTelemetryErrorEvent(
15+
eventName: string,
16+
properties?: { [key: string]: string },
17+
measures?: { [key: string]: number },
18+
errorProps?: string[]
19+
): void;
20+
}
21+
22+
export function getTelemetryProps(platformInfo: PlatformInformation) {
23+
const telemetryProps: { [key: string]: string } = {
24+
'platform.architecture': platformInfo.architecture,
25+
'platform.platform': platformInfo.platform,
26+
};
27+
28+
if (platformInfo.distribution) {
29+
telemetryProps['platform.distribution'] = platformInfo.distribution.toTelemetryString();
30+
}
31+
32+
return telemetryProps;
33+
}

test/unitTests/fakes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as vscode from '../../src/vscodeAdapter';
77
import * as protocol from '../../src/omnisharp/protocol';
8-
import { ITelemetryReporter } from '../../src/observers/telemetryObserver';
8+
import { ITelemetryReporter } from '../../src/shared/telemetryReporter';
99
import { MSBuildDiagnosticsMessage } from '../../src/omnisharp/protocol';
1010
import {
1111
OmnisharpServerMsBuildProjectDiagnostics,

0 commit comments

Comments
 (0)