mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
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:
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user