Skip to content

Commit 7fb7eec

Browse files
author
Andy
authored
Add telemetry for open JS files (microsoft#23833)
* Add telemetry for open JS files * Send event every time * Keep stats even for closed files * Remove tsCheckCountForOpenFilesTelemetry * Use 'info.path' * Update API
1 parent fe7c824 commit 7fb7eec

File tree

7 files changed

+109
-14
lines changed

7 files changed

+109
-14
lines changed

src/harness/unittests/telemetry.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace ts.projectSystem {
4242
const et = new TestServerEventManager([...files, notIncludedFile, tsconfig]);
4343
et.service.openClientFile(files[0].path);
4444
et.assertProjectInfoTelemetryEvent({
45-
fileStats: { ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 },
45+
fileStats: fileStats({ ts: 2, tsx: 1, js: 1, jsx: 1, dts: 1 }),
4646
compilerOptions,
4747
include: true,
4848
});
@@ -234,9 +234,44 @@ namespace ts.projectSystem {
234234
languageServiceEnabled: false,
235235
});
236236
});
237+
238+
describe("open files telemetry", () => {
239+
it("sends event for inferred project", () => {
240+
const ajs = makeFile("/a.js", "// @ts-check\nconst x = 0;");
241+
const bjs = makeFile("/b.js");
242+
const et = new TestServerEventManager([ajs, bjs]);
243+
244+
et.service.openClientFile(ajs.path);
245+
et.assertOpenFileTelemetryEvent({ checkJs: true });
246+
247+
et.service.openClientFile(bjs.path);
248+
et.assertOpenFileTelemetryEvent({ checkJs: false });
249+
250+
// No repeated send for opening a file seen before.
251+
et.service.openClientFile(bjs.path);
252+
et.assertNoOpenFilesTelemetryEvent();
253+
});
254+
255+
it("not for '.ts' file", () => {
256+
const ats = makeFile("/a.ts", "");
257+
const et = new TestServerEventManager([ats]);
258+
259+
et.service.openClientFile(ats.path);
260+
et.assertNoOpenFilesTelemetryEvent();
261+
});
262+
263+
it("even for project with 'ts-check' in config", () => {
264+
const file = makeFile("/a.js");
265+
const compilerOptions: CompilerOptions = { checkJs: true };
266+
const jsconfig = makeFile("/jsconfig.json", { compilerOptions });
267+
const et = new TestServerEventManager([jsconfig, file]);
268+
et.service.openClientFile(file.path);
269+
et.assertOpenFileTelemetryEvent({ checkJs: false });
270+
});
271+
});
237272
});
238273

239274
function makeFile(path: string, content: {} = ""): File {
240-
return { path, content: isString(content) ? "" : JSON.stringify(content) };
275+
return { path, content: isString(content) ? content : JSON.stringify(content) };
241276
}
242277
}

src/harness/unittests/tsserverProjectSystem.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ namespace ts.projectSystem {
215215
}
216216

217217
assertProjectInfoTelemetryEvent(partial: Partial<server.ProjectInfoTelemetryEventData>, configFile?: string): void {
218-
assert.deepEqual(this.getEvent<server.ProjectInfoTelemetryEvent>(server.ProjectInfoTelemetryEvent), {
218+
assert.deepEqual<server.ProjectInfoTelemetryEventData>(this.getEvent<server.ProjectInfoTelemetryEvent>(server.ProjectInfoTelemetryEvent), {
219219
projectId: Harness.mockHash(configFile || "/tsconfig.json"),
220220
fileStats: fileStats({ ts: 1 }),
221221
compilerOptions: {},
@@ -236,6 +236,13 @@ namespace ts.projectSystem {
236236
...partial,
237237
});
238238
}
239+
240+
assertOpenFileTelemetryEvent(info: server.OpenFileInfo): void {
241+
assert.deepEqual<server.OpenFileInfoTelemetryEventData>(this.getEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent), { info });
242+
}
243+
assertNoOpenFilesTelemetryEvent(): void {
244+
this.hasZeroEvent<server.OpenFileInfoTelemetryEvent>(server.OpenFileInfoTelemetryEvent);
245+
}
239246
}
240247

241248
class TestSession extends server.Session {
@@ -2755,7 +2762,7 @@ namespace ts.projectSystem {
27552762
const session = createSession(host, {
27562763
canUseEvents: true,
27572764
eventHandler: e => {
2758-
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
2765+
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectsUpdatedInBackgroundEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) {
27592766
return;
27602767
}
27612768
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
@@ -2807,7 +2814,7 @@ namespace ts.projectSystem {
28072814
const session = createSession(host, {
28082815
canUseEvents: true,
28092816
eventHandler: e => {
2810-
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent) {
2817+
if (e.eventName === server.ConfigFileDiagEvent || e.eventName === server.ProjectInfoTelemetryEvent || e.eventName === server.OpenFileInfoTelemetryEvent) {
28112818
return;
28122819
}
28132820
assert.equal(e.eventName, server.ProjectLanguageServiceStateEvent);
@@ -6241,22 +6248,22 @@ namespace ts.projectSystem {
62416248

62426249
function verifyNoCall(callback: CalledMaps) {
62436250
const calledMap = calledMaps[callback];
6244-
assert.equal(calledMap.size, 0, `${callback} shouldnt be called: ${arrayFrom(calledMap.keys())}`);
6251+
assert.equal(calledMap.size, 0, `${callback} shouldn't be called: ${arrayFrom(calledMap.keys())}`);
62456252
}
62466253

62476254
function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map<number>) {
62486255
TestFSWithWatch.checkMultiMapKeyCount(callback, calledMaps[callback], expectedKeys);
62496256
}
62506257

6251-
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: string[], nTimes: number) {
6258+
function verifyCalledOnEachEntryNTimes(callback: CalledMaps, expectedKeys: ReadonlyArray<string>, nTimes: number) {
62526259
TestFSWithWatch.checkMultiMapEachKeyWithCount(callback, calledMaps[callback], expectedKeys, nTimes);
62536260
}
62546261

62556262
function verifyNoHostCalls() {
62566263
iterateOnCalledMaps(key => verifyNoCall(key));
62576264
}
62586265

6259-
function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: string[]) {
6266+
function verifyNoHostCallsExceptFileExistsOnce(expectedKeys: ReadonlyArray<string>) {
62606267
verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, expectedKeys, 1);
62616268
verifyNoCall(CalledMapsWithSingleArg.directoryExists);
62626269
verifyNoCall(CalledMapsWithSingleArg.getDirectories);

src/server/editorServices.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace ts.server {
66
export const ConfigFileDiagEvent = "configFileDiag";
77
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
88
export const ProjectInfoTelemetryEvent = "projectInfo";
9+
export const OpenFileInfoTelemetryEvent = "openFileInfo";
910
// tslint:enable variable-name
1011

1112
export interface ProjectsUpdatedInBackgroundEvent {
@@ -55,6 +56,20 @@ namespace ts.server {
5556
readonly version: string;
5657
}
5758

59+
/**
60+
* Info that we may send about a file that was just opened.
61+
* Info about a file will only be sent once per session, even if the file changes in ways that might affect the info.
62+
* Currently this is only sent for '.js' files.
63+
*/
64+
export interface OpenFileInfoTelemetryEvent {
65+
readonly eventName: typeof OpenFileInfoTelemetryEvent;
66+
readonly data: OpenFileInfoTelemetryEventData;
67+
}
68+
69+
export interface OpenFileInfoTelemetryEventData {
70+
readonly info: OpenFileInfo;
71+
}
72+
5873
export interface ProjectInfoTypeAcquisitionData {
5974
readonly enable: boolean;
6075
// Actual values of include/exclude entries are scrubbed.
@@ -70,7 +85,11 @@ namespace ts.server {
7085
readonly dts: number;
7186
}
7287

73-
export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
88+
export interface OpenFileInfo {
89+
readonly checkJs: boolean;
90+
}
91+
92+
export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
7493

7594
export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
7695

@@ -325,6 +344,9 @@ namespace ts.server {
325344
* Container of all known scripts
326345
*/
327346
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
347+
// Set of all '.js' files ever opened.
348+
private readonly allJsFilesForOpenFileTelemetry = createMap<true>();
349+
328350
/**
329351
* Map to the real path of the infos
330352
*/
@@ -2095,9 +2117,19 @@ namespace ts.server {
20952117

20962118
this.printProjects();
20972119

2120+
this.telemetryOnOpenFile(info);
20982121
return { configFileName, configFileErrors };
20992122
}
21002123

2124+
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
2125+
if (!this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
2126+
return;
2127+
}
2128+
2129+
const info: OpenFileInfo = { checkJs: !!scriptInfo.getDefaultProject().getSourceFile(scriptInfo.path).checkJsDirective };
2130+
this.eventHandler({ eventName: OpenFileInfoTelemetryEvent, data: { info } });
2131+
}
2132+
21012133
/**
21022134
* Close file whose contents is managed by the client
21032135
* @param filename is absolute pathname

src/server/project.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ namespace ts.server {
66
External
77
}
88

9+
/* @internal */
10+
export type Mutable<T> = { -readonly [K in keyof T]: T[K]; };
11+
912
/* @internal */
1013
export function countEachFileTypes(infos: ScriptInfo[]): FileStats {
11-
const result = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
14+
const result: Mutable<FileStats> = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
1215
for (const info of infos) {
1316
switch (info.scriptKind) {
1417
case ScriptKind.JS:

src/server/typingsCache.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace ts.server {
1111
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
1212
attach(projectService: ProjectService): void;
1313
onProjectClosed(p: Project): void;
14-
readonly globalTypingsCacheLocation: string;
14+
readonly globalTypingsCacheLocation: string | undefined;
1515
}
1616

1717
export const nullTypingsInstaller: ITypingsInstaller = {

src/services/preProcess.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace ts {
22
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
33
const pragmaContext: PragmaContext = {
4-
languageVersion: ScriptTarget.ES5, // controls weather the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
4+
languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
55
pragmas: undefined,
66
checkJsDirective: undefined,
77
referencedFiles: [],

tests/baselines/reference/api/tsserverlibrary.d.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -7734,7 +7734,7 @@ declare namespace ts.server {
77347734
enqueueInstallTypingsRequest(p: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>): void;
77357735
attach(projectService: ProjectService): void;
77367736
onProjectClosed(p: Project): void;
7737-
readonly globalTypingsCacheLocation: string;
7737+
readonly globalTypingsCacheLocation: string | undefined;
77387738
}
77397739
const nullTypingsInstaller: ITypingsInstaller;
77407740
}
@@ -7971,6 +7971,7 @@ declare namespace ts.server {
79717971
const ConfigFileDiagEvent = "configFileDiag";
79727972
const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
79737973
const ProjectInfoTelemetryEvent = "projectInfo";
7974+
const OpenFileInfoTelemetryEvent = "openFileInfo";
79747975
interface ProjectsUpdatedInBackgroundEvent {
79757976
eventName: typeof ProjectsUpdatedInBackgroundEvent;
79767977
data: {
@@ -8019,6 +8020,18 @@ declare namespace ts.server {
80198020
/** TypeScript version used by the server. */
80208021
readonly version: string;
80218022
}
8023+
/**
8024+
* Info that we may send about a file that was just opened.
8025+
* Info about a file will only be sent once per session, even if the file changes in ways that might affect the info.
8026+
* Currently this is only sent for '.js' files.
8027+
*/
8028+
interface OpenFileInfoTelemetryEvent {
8029+
readonly eventName: typeof OpenFileInfoTelemetryEvent;
8030+
readonly data: OpenFileInfoTelemetryEventData;
8031+
}
8032+
interface OpenFileInfoTelemetryEventData {
8033+
readonly info: OpenFileInfo;
8034+
}
80228035
interface ProjectInfoTypeAcquisitionData {
80238036
readonly enable: boolean;
80248037
readonly include: boolean;
@@ -8031,7 +8044,10 @@ declare namespace ts.server {
80318044
readonly tsx: number;
80328045
readonly dts: number;
80338046
}
8034-
type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent;
8047+
interface OpenFileInfo {
8048+
readonly checkJs: boolean;
8049+
}
8050+
type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
80358051
type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
80368052
interface SafeList {
80378053
[name: string]: {
@@ -8082,6 +8098,7 @@ declare namespace ts.server {
80828098
* Container of all known scripts
80838099
*/
80848100
private readonly filenameToScriptInfo;
8101+
private readonly allJsFilesForOpenFileTelemetry;
80858102
/**
80868103
* maps external project file name to list of config files that were the part of this project
80878104
*/
@@ -8286,6 +8303,7 @@ declare namespace ts.server {
82868303
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind, projectRootPath?: string): OpenConfiguredProjectResult;
82878304
private findExternalProjectContainingOpenScriptInfo;
82888305
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult;
8306+
private telemetryOnOpenFile;
82898307
/**
82908308
* Close file whose contents is managed by the client
82918309
* @param filename is absolute pathname

0 commit comments

Comments
 (0)