Skip to content

Commit f03e04a

Browse files
authored
enable sending telemetry events to tsserver client (#12034) (#12051)
enable sending telemetry events
1 parent afe36be commit f03e04a

15 files changed

+218
-58
lines changed

Jakefile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ var servicesSources = [
171171

172172
var serverCoreSources = [
173173
"types.d.ts",
174+
"shared.ts",
174175
"utilities.ts",
175176
"scriptVersionCache.ts",
176177
"typingsCache.ts",
@@ -193,6 +194,7 @@ var cancellationTokenSources = [
193194

194195
var typingsInstallerSources = [
195196
"../types.d.ts",
197+
"../shared.ts",
196198
"typingsInstaller.ts",
197199
"nodeTypingsInstaller.ts"
198200
].map(function (f) {

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ namespace ts.projectSystem {
5151
throttleLimit: number,
5252
installTypingHost: server.ServerHost,
5353
readonly typesRegistry = createMap<void>(),
54+
telemetryEnabled?: boolean,
5455
log?: TI.Log) {
55-
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, log);
56+
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, telemetryEnabled, log);
5657
}
5758

5859
safeFileList = safeList.path;

src/harness/unittests/typingsInstaller.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ namespace ts.projectSystem {
2020
}
2121

2222
class Installer extends TestTypingsInstaller {
23-
constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) {
23+
constructor(host: server.ServerHost, p?: InstallerParams, telemetryEnabled?: boolean, log?: TI.Log) {
2424
super(
2525
(p && p.globalTypingsCacheLocation) || "/a/data",
2626
(p && p.throttleLimit) || 5,
2727
host,
2828
(p && p.typesRegistry),
29+
telemetryEnabled,
2930
log);
3031
}
3132

@@ -35,15 +36,16 @@ namespace ts.projectSystem {
3536
}
3637
}
3738

39+
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
40+
self.addPostExecAction(installedTypings, success => {
41+
for (const file of typingFiles) {
42+
host.createFileOrFolder(file, /*createParentDirectory*/ true);
43+
}
44+
cb(success);
45+
});
46+
}
47+
3848
describe("typingsInstaller", () => {
39-
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
40-
self.addPostExecAction(installedTypings, success => {
41-
for (const file of typingFiles) {
42-
host.createFileOrFolder(file, /*createParentDirectory*/ true);
43-
}
44-
cb(success);
45-
});
46-
}
4749
it("configured projects (typings installed) 1", () => {
4850
const file1 = {
4951
path: "/a/b/app.js",
@@ -905,7 +907,7 @@ namespace ts.projectSystem {
905907
const host = createServerHost([f1, packageJson]);
906908
const installer = new (class extends Installer {
907909
constructor() {
908-
super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
910+
super(host, { globalTypingsCacheLocation: "/tmp" }, /*telemetryEnabled*/ false, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
909911
}
910912
installWorker(_requestId: number, _args: string[], _cwd: string, _cb: server.typingsInstaller.RequestCompletedAction) {
911913
assert(false, "runCommand should not be invoked");
@@ -949,4 +951,50 @@ namespace ts.projectSystem {
949951
assert.deepEqual(result.newTypingNames, ["bar"]);
950952
});
951953
});
954+
955+
describe("telemetry events", () => {
956+
it ("should be received", () => {
957+
const f1 = {
958+
path: "/a/app.js",
959+
content: ""
960+
};
961+
const package = {
962+
path: "/a/package.json",
963+
content: JSON.stringify({ dependencies: { "commander": "1.0.0" } })
964+
};
965+
const cachePath = "/a/cache/";
966+
const commander = {
967+
path: cachePath + "node_modules/@types/commander/index.d.ts",
968+
content: "export let x: number"
969+
};
970+
const host = createServerHost([f1, package]);
971+
let seenTelemetryEvent = false;
972+
const installer = new (class extends Installer {
973+
constructor() {
974+
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }, /*telemetryEnabled*/ true);
975+
}
976+
installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
977+
const installedTypings = ["@types/commander"];
978+
const typingFiles = [commander];
979+
executeCommand(this, host, installedTypings, typingFiles, cb);
980+
}
981+
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.TypingsInstallEvent) {
982+
if (response.kind === server.EventInstall) {
983+
assert.deepEqual(response.packagesToInstall, ["@types/commander"]);
984+
seenTelemetryEvent = true;
985+
return;
986+
}
987+
super.sendResponse(response);
988+
}
989+
})();
990+
const projectService = createProjectService(host, { typingsInstaller: installer });
991+
projectService.openClientFile(f1.path);
992+
993+
installer.installAll(/*expectedCount*/ 1);
994+
995+
assert.isTrue(seenTelemetryEvent);
996+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
997+
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
998+
});
999+
});
9521000
}

src/server/editorServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,10 @@ namespace ts.server {
286286
return;
287287
}
288288
switch (response.kind) {
289-
case "set":
289+
case ActionSet:
290290
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.unresolvedImports, response.typings);
291291
break;
292-
case "invalidate":
292+
case ActionInvalidate:
293293
this.typingsCache.deleteTypingsForProject(response.projectName);
294294
break;
295295
}

src/server/protocol.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,32 @@ namespace ts.server.protocol {
20572057
childItems?: NavigationTree[];
20582058
}
20592059

2060+
export type TelemetryEventName = "telemetry";
2061+
2062+
export interface TelemetryEvent extends Event {
2063+
event: TelemetryEventName;
2064+
body: TelemetryEventBody;
2065+
}
2066+
2067+
export interface TelemetryEventBody {
2068+
telemetryEventName: string;
2069+
payload: any;
2070+
}
2071+
2072+
export type TypingsInstalledTelemetryEventName = "typingsInstalled";
2073+
2074+
export interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody {
2075+
telemetryEventName: TypingsInstalledTelemetryEventName;
2076+
payload: TypingsInstalledTelemetryEventPayload;
2077+
}
2078+
2079+
export interface TypingsInstalledTelemetryEventPayload {
2080+
/**
2081+
* Comma separated list of installed typing packages
2082+
*/
2083+
installedPackages: string;
2084+
}
2085+
20602086
export interface NavBarResponse extends Response {
20612087
body?: NavigationBarItem[];
20622088
}

src/server/server.ts

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/// <reference types="node" />
2+
/// <reference path="shared.ts" />
23
/// <reference path="session.ts" />
34
// used in fs.writeSync
45
/* tslint:disable:no-null-keyword */
@@ -17,7 +18,6 @@ namespace ts.server {
1718
homedir(): string
1819
} = require("os");
1920

20-
2121
function getGlobalTypingsCacheLocation() {
2222
let basePath: string;
2323
switch (process.platform) {
@@ -184,8 +184,10 @@ namespace ts.server {
184184
private socket: NodeSocket;
185185
private projectService: ProjectService;
186186
private throttledOperations: ThrottledOperations;
187+
private telemetrySender: EventSender;
187188

188189
constructor(
190+
private readonly telemetryEnabled: boolean,
189191
private readonly logger: server.Logger,
190192
host: ServerHost,
191193
eventPort: number,
@@ -214,15 +216,22 @@ namespace ts.server {
214216
this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8");
215217
}
216218

219+
setTelemetrySender(telemetrySender: EventSender) {
220+
this.telemetrySender = telemetrySender;
221+
}
222+
217223
attach(projectService: ProjectService) {
218224
this.projectService = projectService;
219225
if (this.logger.hasLevel(LogLevel.requestTime)) {
220226
this.logger.info("Binding...");
221227
}
222228

223-
const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
229+
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
230+
if (this.telemetryEnabled) {
231+
args.push(Arguments.EnableTelemetry);
232+
}
224233
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
225-
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
234+
args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
226235
}
227236
const execArgv: string[] = [];
228237
{
@@ -268,12 +277,25 @@ namespace ts.server {
268277
});
269278
}
270279

271-
private handleMessage(response: SetTypings | InvalidateCachedTypings) {
280+
private handleMessage(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent) {
272281
if (this.logger.hasLevel(LogLevel.verbose)) {
273282
this.logger.info(`Received response: ${JSON.stringify(response)}`);
274283
}
284+
if (response.kind === EventInstall) {
285+
if (this.telemetrySender) {
286+
const body: protocol.TypingsInstalledTelemetryEventBody = {
287+
telemetryEventName: "typingsInstalled",
288+
payload: {
289+
installedPackages: response.packagesToInstall.join(",")
290+
}
291+
};
292+
const eventName: protocol.TelemetryEventName = "telemetry";
293+
this.telemetrySender.event(body, eventName);
294+
}
295+
return;
296+
}
275297
this.projectService.updateTypingsForProject(response);
276-
if (response.kind == "set" && this.socket) {
298+
if (response.kind == ActionSet && this.socket) {
277299
this.sendEvent(0, "setTypings", response);
278300
}
279301
}
@@ -288,18 +310,25 @@ namespace ts.server {
288310
useSingleInferredProject: boolean,
289311
disableAutomaticTypingAcquisition: boolean,
290312
globalTypingsCacheLocation: string,
313+
telemetryEnabled: boolean,
291314
logger: server.Logger) {
292-
super(
293-
host,
294-
cancellationToken,
295-
useSingleInferredProject,
296-
disableAutomaticTypingAcquisition
297-
? nullTypingsInstaller
298-
: new NodeTypingsInstaller(logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine),
299-
Buffer.byteLength,
300-
process.hrtime,
301-
logger,
302-
canUseEvents);
315+
const typingsInstaller = disableAutomaticTypingAcquisition
316+
? undefined
317+
: new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine);
318+
319+
super(
320+
host,
321+
cancellationToken,
322+
useSingleInferredProject,
323+
typingsInstaller || nullTypingsInstaller,
324+
Buffer.byteLength,
325+
process.hrtime,
326+
logger,
327+
canUseEvents);
328+
329+
if (telemetryEnabled && typingsInstaller) {
330+
typingsInstaller.setTelemetrySender(this);
331+
}
303332
}
304333

305334
exit() {
@@ -526,17 +555,17 @@ namespace ts.server {
526555

527556
let eventPort: number;
528557
{
529-
const index = sys.args.indexOf("--eventPort");
530-
if (index >= 0 && index < sys.args.length - 1) {
531-
const v = parseInt(sys.args[index + 1]);
532-
if (!isNaN(v)) {
533-
eventPort = v;
534-
}
558+
const str = findArgument("--eventPort");
559+
const v = str && parseInt(str);
560+
if (!isNaN(v)) {
561+
eventPort = v;
535562
}
536563
}
537564

538-
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
539-
const disableAutomaticTypingAcquisition = sys.args.indexOf("--disableAutomaticTypingAcquisition") >= 0;
565+
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
566+
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
567+
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
568+
540569
const ioSession = new IOSession(
541570
sys,
542571
cancellationToken,
@@ -545,6 +574,7 @@ namespace ts.server {
545574
useSingleInferredProject,
546575
disableAutomaticTypingAcquisition,
547576
getGlobalTypingsCacheLocation(),
577+
telemetryEnabled,
548578
logger);
549579
process.on("uncaughtException", function (err: Error) {
550580
ioSession.logError(err, "unknown");

src/server/session.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ namespace ts.server {
7373
project: Project;
7474
}
7575

76+
export interface EventSender {
77+
event(payload: any, eventName: string): void;
78+
}
79+
7680
function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
7781
for (const edit of edits) {
7882
if (textSpanEnd(edit.span) >= pos) {
@@ -165,7 +169,7 @@ namespace ts.server {
165169
return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`;
166170
}
167171

168-
export class Session {
172+
export class Session implements EventSender {
169173
private readonly gcTimer: GcTimer;
170174
protected projectService: ProjectService;
171175
private errorTimer: any; /*NodeJS.Timer | number*/

src/server/shared.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path="types.d.ts" />
2+
3+
namespace ts.server {
4+
export const ActionSet: ActionSet = "action::set";
5+
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
6+
export const EventInstall: EventInstall = "event::install";
7+
8+
export namespace Arguments {
9+
export const GlobalCacheLocation = "--globalTypingsCacheLocation";
10+
export const LogFile = "--logFile";
11+
export const EnableTelemetry = "--enableTelemetry";
12+
}
13+
14+
export function hasArgument(argumentName: string) {
15+
return sys.args.indexOf(argumentName) >= 0;
16+
}
17+
18+
export function findArgument(argumentName: string) {
19+
const index = sys.args.indexOf(argumentName);
20+
return index >= 0 && index < sys.args.length - 1
21+
? sys.args[index + 1]
22+
: undefined;
23+
}
24+
}

src/server/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"files": [
1919
"../services/shims.ts",
2020
"../services/utilities.ts",
21+
"shared.ts",
2122
"utilities.ts",
2223
"scriptVersionCache.ts",
2324
"scriptInfo.ts",

src/server/tsconfig.library.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"files": [
1616
"../services/shims.ts",
1717
"../services/utilities.ts",
18+
"shared.ts",
1819
"utilities.ts",
1920
"scriptVersionCache.ts",
2021
"scriptInfo.ts",

0 commit comments

Comments
 (0)