Skip to content

Commit

Permalink
enable sending telemetry events to tsserver client (#12034)
Browse files Browse the repository at this point in the history
enable sending telemetry events
  • Loading branch information
vladima committed Nov 4, 2016
1 parent afe36be commit 93e435c
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 58 deletions.
2 changes: 2 additions & 0 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ var servicesSources = [

var serverCoreSources = [
"types.d.ts",
"shared.ts",
"utilities.ts",
"scriptVersionCache.ts",
"typingsCache.ts",
Expand All @@ -193,6 +194,7 @@ var cancellationTokenSources = [

var typingsInstallerSources = [
"../types.d.ts",
"../shared.ts",
"typingsInstaller.ts",
"nodeTypingsInstaller.ts"
].map(function (f) {
Expand Down
3 changes: 2 additions & 1 deletion src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ namespace ts.projectSystem {
throttleLimit: number,
installTypingHost: server.ServerHost,
readonly typesRegistry = createMap<void>(),
telemetryEnabled?: boolean,
log?: TI.Log) {
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, log);
super(installTypingHost, globalTypingsCacheLocation, safeList.path, throttleLimit, telemetryEnabled, log);
}

safeFileList = safeList.path;
Expand Down
68 changes: 58 additions & 10 deletions src/harness/unittests/typingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ namespace ts.projectSystem {
}

class Installer extends TestTypingsInstaller {
constructor(host: server.ServerHost, p?: InstallerParams, log?: TI.Log) {
constructor(host: server.ServerHost, p?: InstallerParams, telemetryEnabled?: boolean, log?: TI.Log) {
super(
(p && p.globalTypingsCacheLocation) || "/a/data",
(p && p.throttleLimit) || 5,
host,
(p && p.typesRegistry),
telemetryEnabled,
log);
}

Expand All @@ -35,15 +36,16 @@ namespace ts.projectSystem {
}
}

function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
self.addPostExecAction(installedTypings, success => {
for (const file of typingFiles) {
host.createFileOrFolder(file, /*createParentDirectory*/ true);
}
cb(success);
});
}

describe("typingsInstaller", () => {
function executeCommand(self: Installer, host: TestServerHost, installedTypings: string[], typingFiles: FileOrFolder[], cb: TI.RequestCompletedAction): void {
self.addPostExecAction(installedTypings, success => {
for (const file of typingFiles) {
host.createFileOrFolder(file, /*createParentDirectory*/ true);
}
cb(success);
});
}
it("configured projects (typings installed) 1", () => {
const file1 = {
path: "/a/b/app.js",
Expand Down Expand Up @@ -905,7 +907,7 @@ namespace ts.projectSystem {
const host = createServerHost([f1, packageJson]);
const installer = new (class extends Installer {
constructor() {
super(host, { globalTypingsCacheLocation: "/tmp" }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
super(host, { globalTypingsCacheLocation: "/tmp" }, /*telemetryEnabled*/ false, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
}
installWorker(_requestId: number, _args: string[], _cwd: string, _cb: server.typingsInstaller.RequestCompletedAction) {
assert(false, "runCommand should not be invoked");
Expand Down Expand Up @@ -949,4 +951,50 @@ namespace ts.projectSystem {
assert.deepEqual(result.newTypingNames, ["bar"]);
});
});

describe("telemetry events", () => {
it ("should be received", () => {
const f1 = {
path: "/a/app.js",
content: ""
};
const package = {
path: "/a/package.json",
content: JSON.stringify({ dependencies: { "commander": "1.0.0" } })
};
const cachePath = "/a/cache/";
const commander = {
path: cachePath + "node_modules/@types/commander/index.d.ts",
content: "export let x: number"
};
const host = createServerHost([f1, package]);
let seenTelemetryEvent = false;
const installer = new (class extends Installer {
constructor() {
super(host, { globalTypingsCacheLocation: cachePath, typesRegistry: createTypesRegistry("commander") }, /*telemetryEnabled*/ true);
}
installWorker(_requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
const installedTypings = ["@types/commander"];
const typingFiles = [commander];
executeCommand(this, host, installedTypings, typingFiles, cb);
}
sendResponse(response: server.SetTypings | server.InvalidateCachedTypings | server.TypingsInstallEvent) {
if (response.kind === server.EventInstall) {
assert.deepEqual(response.packagesToInstall, ["@types/commander"]);
seenTelemetryEvent = true;
return;
}
super.sendResponse(response);
}
})();
const projectService = createProjectService(host, { typingsInstaller: installer });
projectService.openClientFile(f1.path);

installer.installAll(/*expectedCount*/ 1);

assert.isTrue(seenTelemetryEvent);
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(projectService.inferredProjects[0], [f1.path, commander.path]);
});
});
}
4 changes: 2 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,10 @@ namespace ts.server {
return;
}
switch (response.kind) {
case "set":
case ActionSet:
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.unresolvedImports, response.typings);
break;
case "invalidate":
case ActionInvalidate:
this.typingsCache.deleteTypingsForProject(response.projectName);
break;
}
Expand Down
26 changes: 26 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2057,6 +2057,32 @@ namespace ts.server.protocol {
childItems?: NavigationTree[];
}

export type TelemetryEventName = "telemetry";

export interface TelemetryEvent extends Event {
event: TelemetryEventName;
body: TelemetryEventBody;
}

export interface TelemetryEventBody {
telemetryEventName: string;
payload: any;
}

export type TypingsInstalledTelemetryEventName = "typingsInstalled";

export interface TypingsInstalledTelemetryEventBody extends TelemetryEventBody {
telemetryEventName: TypingsInstalledTelemetryEventName;
payload: TypingsInstalledTelemetryEventPayload;
}

export interface TypingsInstalledTelemetryEventPayload {
/**
* Comma separated list of installed typing packages
*/
installedPackages: string;
}

export interface NavBarResponse extends Response {
body?: NavigationBarItem[];
}
Expand Down
78 changes: 54 additions & 24 deletions src/server/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// <reference types="node" />
/// <reference path="shared.ts" />
/// <reference path="session.ts" />
// used in fs.writeSync
/* tslint:disable:no-null-keyword */
Expand All @@ -17,7 +18,6 @@ namespace ts.server {
homedir(): string
} = require("os");


function getGlobalTypingsCacheLocation() {
let basePath: string;
switch (process.platform) {
Expand Down Expand Up @@ -184,8 +184,10 @@ namespace ts.server {
private socket: NodeSocket;
private projectService: ProjectService;
private throttledOperations: ThrottledOperations;
private telemetrySender: EventSender;

constructor(
private readonly telemetryEnabled: boolean,
private readonly logger: server.Logger,
host: ServerHost,
eventPort: number,
Expand Down Expand Up @@ -214,15 +216,22 @@ namespace ts.server {
this.socket.write(formatMessage({ seq, type: "event", event, body }, this.logger, Buffer.byteLength, this.newLine), "utf8");
}

setTelemetrySender(telemetrySender: EventSender) {
this.telemetrySender = telemetrySender;
}

attach(projectService: ProjectService) {
this.projectService = projectService;
if (this.logger.hasLevel(LogLevel.requestTime)) {
this.logger.info("Binding...");
}

const args: string[] = ["--globalTypingsCacheLocation", this.globalTypingsCacheLocation];
const args: string[] = [Arguments.GlobalCacheLocation, this.globalTypingsCacheLocation];
if (this.telemetryEnabled) {
args.push(Arguments.EnableTelemetry);
}
if (this.logger.loggingEnabled() && this.logger.getLogFileName()) {
args.push("--logFile", combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
args.push(Arguments.LogFile, combinePaths(getDirectoryPath(normalizeSlashes(this.logger.getLogFileName())), `ti-${process.pid}.log`));
}
const execArgv: string[] = [];
{
Expand Down Expand Up @@ -268,12 +277,25 @@ namespace ts.server {
});
}

private handleMessage(response: SetTypings | InvalidateCachedTypings) {
private handleMessage(response: SetTypings | InvalidateCachedTypings | TypingsInstallEvent) {
if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Received response: ${JSON.stringify(response)}`);
}
if (response.kind === EventInstall) {
if (this.telemetrySender) {
const body: protocol.TypingsInstalledTelemetryEventBody = {
telemetryEventName: "typingsInstalled",
payload: {
installedPackages: response.packagesToInstall.join(",")
}
};
const eventName: protocol.TelemetryEventName = "telemetry";
this.telemetrySender.event(body, eventName);
}
return;
}
this.projectService.updateTypingsForProject(response);
if (response.kind == "set" && this.socket) {
if (response.kind == ActionSet && this.socket) {
this.sendEvent(0, "setTypings", response);
}
}
Expand All @@ -288,18 +310,25 @@ namespace ts.server {
useSingleInferredProject: boolean,
disableAutomaticTypingAcquisition: boolean,
globalTypingsCacheLocation: string,
telemetryEnabled: boolean,
logger: server.Logger) {
super(
host,
cancellationToken,
useSingleInferredProject,
disableAutomaticTypingAcquisition
? nullTypingsInstaller
: new NodeTypingsInstaller(logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine),
Buffer.byteLength,
process.hrtime,
logger,
canUseEvents);
const typingsInstaller = disableAutomaticTypingAcquisition
? undefined
: new NodeTypingsInstaller(telemetryEnabled, logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine);

super(
host,
cancellationToken,
useSingleInferredProject,
typingsInstaller || nullTypingsInstaller,
Buffer.byteLength,
process.hrtime,
logger,
canUseEvents);

if (telemetryEnabled && typingsInstaller) {
typingsInstaller.setTelemetrySender(this);
}
}

exit() {
Expand Down Expand Up @@ -526,17 +555,17 @@ namespace ts.server {

let eventPort: number;
{
const index = sys.args.indexOf("--eventPort");
if (index >= 0 && index < sys.args.length - 1) {
const v = parseInt(sys.args[index + 1]);
if (!isNaN(v)) {
eventPort = v;
}
const str = findArgument("--eventPort");
const v = str && parseInt(str);
if (!isNaN(v)) {
eventPort = v;
}
}

const useSingleInferredProject = sys.args.indexOf("--useSingleInferredProject") >= 0;
const disableAutomaticTypingAcquisition = sys.args.indexOf("--disableAutomaticTypingAcquisition") >= 0;
const useSingleInferredProject = hasArgument("--useSingleInferredProject");
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);

const ioSession = new IOSession(
sys,
cancellationToken,
Expand All @@ -545,6 +574,7 @@ namespace ts.server {
useSingleInferredProject,
disableAutomaticTypingAcquisition,
getGlobalTypingsCacheLocation(),
telemetryEnabled,
logger);
process.on("uncaughtException", function (err: Error) {
ioSession.logError(err, "unknown");
Expand Down
6 changes: 5 additions & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ namespace ts.server {
project: Project;
}

export interface EventSender {
event(payload: any, eventName: string): void;
}

function allEditsBeforePos(edits: ts.TextChange[], pos: number) {
for (const edit of edits) {
if (textSpanEnd(edit.span) >= pos) {
Expand Down Expand Up @@ -165,7 +169,7 @@ namespace ts.server {
return `Content-Length: ${1 + len}\r\n\r\n${json}${newLine}`;
}

export class Session {
export class Session implements EventSender {
private readonly gcTimer: GcTimer;
protected projectService: ProjectService;
private errorTimer: any; /*NodeJS.Timer | number*/
Expand Down
24 changes: 24 additions & 0 deletions src/server/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// <reference path="types.d.ts" />

namespace ts.server {
export const ActionSet: ActionSet = "action::set";
export const ActionInvalidate: ActionInvalidate = "action::invalidate";
export const EventInstall: EventInstall = "event::install";

export namespace Arguments {
export const GlobalCacheLocation = "--globalTypingsCacheLocation";
export const LogFile = "--logFile";
export const EnableTelemetry = "--enableTelemetry";
}

export function hasArgument(argumentName: string) {
return sys.args.indexOf(argumentName) >= 0;
}

export function findArgument(argumentName: string) {
const index = sys.args.indexOf(argumentName);
return index >= 0 && index < sys.args.length - 1
? sys.args[index + 1]
: undefined;
}
}
1 change: 1 addition & 0 deletions src/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"files": [
"../services/shims.ts",
"../services/utilities.ts",
"shared.ts",
"utilities.ts",
"scriptVersionCache.ts",
"scriptInfo.ts",
Expand Down
1 change: 1 addition & 0 deletions src/server/tsconfig.library.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"files": [
"../services/shims.ts",
"../services/utilities.ts",
"shared.ts",
"utilities.ts",
"scriptVersionCache.ts",
"scriptInfo.ts",
Expand Down
Loading

0 comments on commit 93e435c

Please sign in to comment.