Add telemetry for open JS files (#23833)

* Add telemetry for open JS files

* Send event every time

* Keep stats even for closed files

* Remove tsCheckCountForOpenFilesTelemetry

* Use 'info.path'

* Update API
This commit is contained in:
Andy
2018-05-09 07:51:46 -07:00
committed by GitHub
parent fe7c82435c
commit 7fb7eecf2c
7 changed files with 109 additions and 14 deletions
+37 -2
View File
@@ -42,7 +42,7 @@ namespace ts.projectSystem {
const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
et.service.openClientFile(files[0].path);
et.assertProjectInfoTelemetryEvent({
fileStats: { ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 },
fileStats: fileStats({ ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 }),
compilerOptions,
include: true,
});
@@ -234,9 +234,44 @@ namespace ts.projectSystem {
languageServiceEnabled: false,
});
});
describe("open files telemetry", () => {
it("sends event for inferred project", () => {
const ajs = makeFile("/a.js", "// @ts-check\nconst x = 0;");
const bjs = makeFile("/b.js");
const et = new TestServerEventManager([ajs, bjs]);
et.service.openClientFile(ajs.path);
et.assertOpenFileTelemetryEvent({ checkJs: true });
et.service.openClientFile(bjs.path);
et.assertOpenFileTelemetryEvent({ checkJs: false });
// No repeated send for opening a file seen before.
et.service.openClientFile(bjs.path);
et.assertNoOpenFilesTelemetryEvent();
});
it("not for '.ts' file", () => {
const ats = makeFile("/a.ts", "");
const et = new TestServerEventManager([ats]);
et.service.openClientFile(ats.path);
et.assertNoOpenFilesTelemetryEvent();
});
it("even for project with 'ts-check' in config", () => {
const file = makeFile("/a.js");
const compilerOptions: CompilerOptions = { checkJs: true };
const jsconfig = makeFile("/jsconfig.json", { compilerOptions });
const et = new TestServerEventManager([jsconfig, file]);
et.service.openClientFile(file.path);
et.assertOpenFileTelemetryEvent({ checkJs: false });
});
});
});
function makeFile(path: string, content: {} = ""): File {
return { path, content: isString(content) ? "" : JSON.stringify(content) };
return { path, content: isString(content) ? content : JSON.stringify(content) };
}
}
+13 -6
View File
@@ -215,7 +215,7 @@ namespace ts.projectSystem {
}
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile?: string): void {
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(server.ProjectInfoTelemetryEvent), {
assert.deepEqual<server.ProjectInfoTelemetryEventData>(this.getEvent<server.ProjectInfoTelemetryEvent>(server.ProjectInfoTelemetryEvent), {
projectId: Harness.mockHash(configFile || "/tsconfig.json"),
fileStats: fileStats({ ts: 1 }),
compilerOptions: {},
@@ -236,6 +236,13 @@ namespace ts.projectSystem {
...partial,
});
}
assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void {
assert.deepEqual<server.OpenFileInfoTelemetryEventData>(this.getEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent), { info });
}
assertNoOpenFilesTelemetryEvent(): void {
this.hasZeroEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent);
}
}
class TestSession extends server.Session {
@@ -2755,7 +2762,7 @@ namespace ts.projectSystem {
const session = createSession(host, {
canUseEvents: true,
eventHandler: e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
@@ -2807,7 +2814,7 @@ namespace ts.projectSystem {
const session = createSession(host, {
canUseEvents: true,
eventHandler: e => {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) {
return;
}
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
@@ -6241,14 +6248,14 @@ namespace ts.projectSystem {
function verifyNoCall(callback: CalledMaps) {
const calledMap = calledMaps[callback];
assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`);
assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`);
}
function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map<number>) {
TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys);
}
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: string[], nTimes: number) {
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: ReadonlyArray<string>, nTimes: number) {
TestFSWithWatch.checkMultiMapEachKeyWithCount(callback, calledMaps[callback], expectedKeys, nTimes);
}
@@ -6256,7 +6263,7 @@ namespace ts.projectSystem {
iterateOnCalledMaps(key => verifyNoCall(key));
}
function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) {
function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: ReadonlyArray<string>) {
verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1);
verifyNoCall(CalledMapsWithSingleArg.directoryExists);
verifyNoCall(CalledMapsWithSingleArg.getDirectories);
+33 -1
View File
@@ -6,6 +6,7 @@ namespace ts.server {
export const ConfigFileDiagEvent = "configFileDiag";
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
export const ProjectInfoTelemetryEvent = "projectInfo";
export const OpenFileInfoTelemetryEvent = "openFileInfo";
// tslint:enable variable-name
export interface ProjectsUpdatedInBackgroundEvent {
@@ -55,6 +56,20 @@ namespace ts.server {
readonly version: string;
}
/**
* Info that we may send about a file that was just opened.
* Info about a file will only be sent once per session, even if the file changes in ways that might affect the info.
* Currently this is only sent for '.js' files.
*/
export interface OpenFileInfoTelemetryEvent {
readonly eventName: typeof OpenFileInfoTelemetryEvent;
readonly data: OpenFileInfoTelemetryEventData;
}
export interface OpenFileInfoTelemetryEventData {
readonly info: OpenFileInfo;
}
export interface ProjectInfoTypeAcquisitionData {
readonly enable: boolean;
// Actual values of include/exclude entries are scrubbed.
@@ -70,7 +85,11 @@ namespace ts.server {
readonly dts: number;
}
export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
export interface OpenFileInfo {
readonly checkJs: boolean;
}
export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
@@ -325,6 +344,9 @@ namespace ts.server {
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
// Set of all '.js' files ever opened.
private readonly allJsFilesForOpenFileTelemetry = createMap<true>();
/**
* Map to the real path of the infos
*/
@@ -2095,9 +2117,19 @@ namespace ts.server {
this.printProjects();
this.telemetryOnOpenFile(info);
return { configFileName, configFileErrors };
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (!this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;
}
const info: OpenFileInfo = { checkJs: !!scriptInfo.getDefaultProject().getSourceFile(scriptInfo.path).checkJsDirective };
this.eventHandler({ eventName: OpenFileInfoTelemetryEvent, data: { info } });
}
/**
* Close file whose contents is managed by the client
* @param filename is absolute pathname
+4 -1
View File
@@ -6,9 +6,12 @@ namespace ts.server {
External
}
/* @internal */
export type Mutable<T> = { -readonly [K in keyof T]: T[K]; };
/* @internal */
export function countEachFileTypes(infos: ScriptInfo[]): FileStats {
const result = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
const result: Mutable<FileStats> = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
for (const info of infos) {
switch (info.scriptKind) {
case ScriptKind.JS:
+1 -1
View File
@@ -11,7 +11,7 @@ namespace ts.server {
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
readonly globalTypingsCacheLocation: string;
readonly globalTypingsCacheLocation: string | undefined;
}
export const nullTypingsInstaller: ITypingsInstaller = {
+1 -1
View File
@@ -1,7 +1,7 @@
namespace ts {
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
const pragmaContext: PragmaContext = {
languageVersion: ScriptTarget.ES5, // controls weather the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
pragmas: undefined,
checkJsDirective: undefined,
referencedFiles: [],
+20 -2
View File
@@ -7734,7 +7734,7 @@ declare namespace ts.server {
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
attach(projectService: ProjectService): void;
onProjectClosed(p: Project): void;
readonly globalTypingsCacheLocation: string;
readonly globalTypingsCacheLocation: string | undefined;
}
const nullTypingsInstaller: ITypingsInstaller;
}
@@ -7971,6 +7971,7 @@ declare namespace ts.server {
const ConfigFileDiagEvent = "configFileDiag";
const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
const ProjectInfoTelemetryEvent = "projectInfo";
const OpenFileInfoTelemetryEvent = "openFileInfo";
interface ProjectsUpdatedInBackgroundEvent {
eventName: typeof ProjectsUpdatedInBackgroundEvent;
data: {
@@ -8019,6 +8020,18 @@ declare namespace ts.server {
/** TypeScript version used by the server. */
readonly version: string;
}
/**
* Info that we may send about a file that was just opened.
* Info about a file will only be sent once per session, even if the file changes in ways that might affect the info.
* Currently this is only sent for '.js' files.
*/
interface OpenFileInfoTelemetryEvent {
readonly eventName: typeof OpenFileInfoTelemetryEvent;
readonly data: OpenFileInfoTelemetryEventData;
}
interface OpenFileInfoTelemetryEventData {
readonly info: OpenFileInfo;
}
interface ProjectInfoTypeAcquisitionData {
readonly enable: boolean;
readonly include: boolean;
@@ -8031,7 +8044,10 @@ declare namespace ts.server {
readonly tsx: number;
readonly dts: number;
}
type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
interface OpenFileInfo {
readonly checkJs: boolean;
}
type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
interface SafeList {
[name: string]: {
@@ -8082,6 +8098,7 @@ declare namespace ts.server {
* Container of all known scripts
*/
private readonly filenameToScriptInfo;
private readonly allJsFilesForOpenFileTelemetry;
/**
* maps external project file name to list of config files that were the part of this project
*/
@@ -8286,6 +8303,7 @@ declare namespace ts.server {
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult;
private findExternalProjectContainingOpenScriptInfo;
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult;
private telemetryOnOpenFile;
/**
* Close file whose contents is managed by the client
* @param filename is absolute pathname