Skip to content

Commit 7c92057

Browse files
authored
enable sending telemetry events to tsserver client (#12035)
enable sending telemetry events
1 parent dbf69b7 commit 7c92057

15 files changed

+214
-56
lines changed

Jakefile.js

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

128128
var serverCoreSources = [
129129
"types.d.ts",
130+
"shared.ts",
130131
"utilities.ts",
131132
"scriptVersionCache.ts",
132133
"typingsCache.ts",
@@ -149,6 +150,7 @@ var cancellationTokenSources = [
149150

150151
var typingsInstallerSources = [
151152
"../types.d.ts",
153+
"../shared.ts",
152154
"typingsInstaller.ts",
153155
"nodeTypingsInstaller.ts"
154156
].map(function (f) {

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ namespace ts.projectSystem {
5454
throttleLimit: number,
5555
installTypingHost: server.ServerHost,
5656
readonly typesRegistry = createMap<void>(),
57+
telemetryEnabled?: boolean,
5758
log?: TI.Log) {
58-
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, log);
59+
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, telemetryEnabled, log);
5960
}
6061

6162
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",
@@ -782,7 +784,7 @@ namespace ts.projectSystem {
782784
const host = createServerHost([f1, packageJson]);
783785
const installer = new (class extends Installer {
784786
constructor() {
785-
super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
787+
super(host, { globalTypingsCacheLocation: "/tmp" }, /*telemetryEnabled*/ false, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
786788
}
787789
installWorker(requestId: number, args: string[], cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
788790
assert(false, "runCommand should not be invoked");
@@ -795,4 +797,50 @@ namespace ts.projectSystem {
795797
assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name");
796798
});
797799
});
800+
801+
describe("telemetry events", () => {
802+
it ("should be received", () => {
803+
const f1 = {
804+
path: "/a/app.js",
805+
content: ""
806+
};
807+
const package = {
808+
path: "/a/package.json",
809+
content: JSON.stringify({ dependencies: { "commander": "1.0.0" } })
810+
};
811+
const cachePath = "/a/cache/";
812+
const commander = {
813+
path: cachePath + "node_modules/@types/commander/index.d.ts",
814+
content: "export let x: number"
815+
};
816+
const host = createServerHost([f1, package]);
817+
let seenTelemetryEvent = false;
818+
const installer = new (class extends Installer {
819+
constructor() {
820+
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }, /*telemetryEnabled*/ true);
821+
}
822+
installWorker(requestId: number, args: string[], cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
823+
const installedTypings = ["@types/commander"];
824+
const typingFiles = [commander];
825+
executeCommand(this, host, installedTypings, typingFiles, cb);
826+
}
827+
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.TypingsInstallEvent) {
828+
if (response.kind === server.EventInstall) {
829+
assert.deepEqual(response.packagesToInstall, ["@types/commander"]);
830+
seenTelemetryEvent = true;
831+
return;
832+
}
833+
super.sendResponse(response);
834+
}
835+
})();
836+
const projectService = createProjectService(host, { typingsInstaller: installer });
837+
projectService.openClientFile(f1.path);
838+
839+
installer.installAll(/*expectedCount*/ 1);
840+
841+
assert.isTrue(seenTelemetryEvent);
842+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
843+
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
844+
});
845+
});
798846
}

src/server/editorServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,11 +284,11 @@ namespace ts.server {
284284
return;
285285
}
286286
switch (response.kind) {
287-
case "set":
287+
case ActionSet:
288288
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings);
289289
project.updateGraph();
290290
break;
291-
case "invalidate":
291+
case ActionInvalidate:
292292
this.typingsCache.invalidateCachedTypingsForProject(project);
293293
break;
294294
}

src/server/protocol.ts

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

1943+
export type TelemetryEventName = "telemetry";
1944+
1945+
export interface TelemetryEvent extends Event {
1946+
event: TelemetryEventName;
1947+
body: TelemetryEventBody;
1948+
}
1949+
1950+
export interface TelemetryEventBody {
1951+
telemetryEventName: string;
1952+
payload: any;
1953+
}
1954+
1955+
export type TypingsInstalledTelemetryEventName = "typingsInstalled";
1956+
1957+
export interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody {
1958+
telemetryEventName: TypingsInstalledTelemetryEventName;
1959+
payload: TypingsInstalledTelemetryEventPayload;
1960+
}
1961+
1962+
export interface TypingsInstalledTelemetryEventPayload {
1963+
/**
1964+
* Comma separated list of installed typing packages
1965+
*/
1966+
installedPackages: string;
1967+
}
1968+
19431969
export interface NavBarResponse extends Response {
19441970
body?: NavigationBarItem[];
19451971
}

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) {
@@ -189,8 +189,10 @@ namespace ts.server {
189189
private installer: NodeChildProcess;
190190
private socket: NodeSocket;
191191
private projectService: ProjectService;
192+
private telemetrySender: EventSender;
192193

193194
constructor(
195+
private readonly telemetryEnabled: boolean,
194196
private readonly logger: server.Logger,
195197
private readonly eventPort: number,
196198
readonly globalTypingsCacheLocation: string,
@@ -202,15 +204,22 @@ namespace ts.server {
202204
}
203205
}
204206

207+
setTelemetrySender(telemetrySender: EventSender) {
208+
this.telemetrySender = telemetrySender;
209+
}
210+
205211
attach(projectService: ProjectService) {
206212
this.projectService = projectService;
207213
if (this.logger.hasLevel(LogLevel.requestTime)) {
208214
this.logger.info("Binding...");
209215
}
210216

211-
const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
217+
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
218+
if (this.telemetryEnabled) {
219+
args.push(Arguments.EnableTelemetry);
220+
}
212221
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
213-
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
222+
args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
214223
}
215224
const execArgv: string[] = [];
216225
{
@@ -247,12 +256,25 @@ namespace ts.server {
247256
this.installer.send(request);
248257
}
249258

250-
private handleMessage(response: SetTypings | InvalidateCachedTypings) {
259+
private handleMessage(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent) {
251260
if (this.logger.hasLevel(LogLevel.verbose)) {
252261
this.logger.info(`Received response: ${JSON.stringify(response)}`);
253262
}
263+
if (response.kind === EventInstall) {
264+
if (this.telemetrySender) {
265+
const body: protocol.TypingsInstalledTelemetryEventBody = {
266+
telemetryEventName: "typingsInstalled",
267+
payload: {
268+
installedPackages: response.packagesToInstall.join(",")
269+
}
270+
};
271+
const eventName: protocol.TelemetryEventName = "telemetry";
272+
this.telemetrySender.event(body, eventName);
273+
}
274+
return;
275+
}
254276
this.projectService.updateTypingsForProject(response);
255-
if (response.kind == "set" && this.socket) {
277+
if (response.kind == ActionSet && this.socket) {
256278
this.socket.write(formatMessage({ seq: 0, type: "event", message: response }, this.logger, Buffer.byteLength, this.newLine), "utf8");
257279
}
258280
}
@@ -267,18 +289,25 @@ namespace ts.server {
267289
useSingleInferredProject: boolean,
268290
disableAutomaticTypingAcquisition: boolean,
269291
globalTypingsCacheLocation: string,
292+
telemetryEnabled: boolean,
270293
logger: server.Logger) {
271-
super(
272-
host,
273-
cancellationToken,
274-
useSingleInferredProject,
275-
disableAutomaticTypingAcquisition
276-
? nullTypingsInstaller
277-
: new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine),
278-
Buffer.byteLength,
279-
process.hrtime,
280-
logger,
281-
canUseEvents);
294+
const typingsInstaller = disableAutomaticTypingAcquisition
295+
? undefined
296+
: new NodeTypingsInstaller(telemetryEnabled, logger, installerEventPort, globalTypingsCacheLocation, host.newLine);
297+
298+
super(
299+
host,
300+
cancellationToken,
301+
useSingleInferredProject,
302+
typingsInstaller || nullTypingsInstaller,
303+
Buffer.byteLength,
304+
process.hrtime,
305+
logger,
306+
canUseEvents);
307+
308+
if (telemetryEnabled && typingsInstaller) {
309+
typingsInstaller.setTelemetrySender(this);
310+
}
282311
}
283312

284313
exit() {
@@ -505,17 +534,17 @@ namespace ts.server {
505534

506535
let eventPort: number;
507536
{
508-
const index = sys.args.indexOf("--eventPort");
509-
if (index >= 0 && index < sys.args.length - 1) {
510-
const v = parseInt(sys.args[index + 1]);
511-
if (!isNaN(v)) {
512-
eventPort = v;
513-
}
537+
const str = findArgument("--eventPort");
538+
const v = str && parseInt(str);
539+
if (!isNaN(v)) {
540+
eventPort = v;
514541
}
515542
}
516543

517-
const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
518-
const disableAutomaticTypingAcquisition = sys.args.indexOf("--disableAutomaticTypingAcquisition") >= 0;
544+
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
545+
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
546+
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
547+
519548
const ioSession = new IOSession(
520549
sys,
521550
cancellationToken,
@@ -524,6 +553,7 @@ namespace ts.server {
524553
useSingleInferredProject,
525554
disableAutomaticTypingAcquisition,
526555
getGlobalTypingsCacheLocation(),
556+
telemetryEnabled,
527557
logger);
528558
process.on("uncaughtException", function (err: Error) {
529559
ioSession.logError(err, "unknown");

src/server/session.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ namespace ts.server {
6161
project: Project;
6262
}
6363

64+
export interface EventSender {
65+
event(payload: any, eventName: string): void;
66+
}
67+
6468
function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
6569
for (const edit of edits) {
6670
if (textSpanEnd(edit.span) >= pos) {
@@ -148,7 +152,7 @@ namespace ts.server {
148152
return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`;
149153
}
150154

151-
export class Session {
155+
export class Session implements EventSender {
152156
private readonly gcTimer: GcTimer;
153157
protected projectService: ProjectService;
154158
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
@@ -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",

src/server/tsconfig.library.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"files": [
1313
"../services/shims.ts",
1414
"../services/utilities.ts",
15+
"shared.ts",
1516
"utilities.ts",
1617
"scriptVersionCache.ts",
1718
"scriptInfo.ts",

0 commit comments

Comments
 (0)