Skip to content

Commit 642803c

Browse files
authored
Merge pull request #28559 from amcasey/FileSize
Expose aggregate file sizes in FileStats
2 parents e0dca0b + 5c1c34a commit 642803c

File tree

7 files changed

+190
-21
lines changed

7 files changed

+190
-21
lines changed

src/server/editorServices.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,22 @@ namespace ts.server {
123123

124124
export interface FileStats {
125125
readonly js: number;
126+
readonly jsSize?: number;
127+
126128
readonly jsx: number;
129+
readonly jsxSize?: number;
130+
127131
readonly ts: number;
132+
readonly tsSize?: number;
133+
128134
readonly tsx: number;
135+
readonly tsxSize?: number;
136+
129137
readonly dts: number;
138+
readonly dtsSize?: number;
139+
130140
readonly deferred: number;
141+
readonly deferredSize?: number;
131142
}
132143

133144
export interface OpenFileInfo {
@@ -1600,7 +1611,7 @@ namespace ts.server {
16001611
setProjectOptionsUsed(project);
16011612
const data: ProjectInfoTelemetryEventData = {
16021613
projectId: this.host.createSHA256Hash(project.projectName),
1603-
fileStats: countEachFileTypes(project.getScriptInfos()),
1614+
fileStats: countEachFileTypes(project.getScriptInfos(), /*includeSizes*/ true),
16041615
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilationSettings()),
16051616
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),
16061617
extends: projectOptions && projectOptions.configHasExtendsProperty,

src/server/project.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,43 @@ namespace ts.server {
1010
export type Mutable<T> = { -readonly [K in keyof T]: T[K]; };
1111

1212
/* @internal */
13-
export function countEachFileTypes(infos: ScriptInfo[]): FileStats {
14-
const result: Mutable<FileStats> = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0, deferred: 0 };
13+
export function countEachFileTypes(infos: ScriptInfo[], includeSizes = false): FileStats {
14+
const result: Mutable<FileStats> = {
15+
js: 0, jsSize: 0,
16+
jsx: 0, jsxSize: 0,
17+
ts: 0, tsSize: 0,
18+
tsx: 0, tsxSize: 0,
19+
dts: 0, dtsSize: 0,
20+
deferred: 0, deferredSize: 0,
21+
};
1522
for (const info of infos) {
23+
const fileSize = includeSizes ? info.getTelemetryFileSize() : 0;
1624
switch (info.scriptKind) {
1725
case ScriptKind.JS:
1826
result.js += 1;
27+
result.jsSize! += fileSize;
1928
break;
2029
case ScriptKind.JSX:
2130
result.jsx += 1;
31+
result.jsxSize! += fileSize;
2232
break;
2333
case ScriptKind.TS:
24-
fileExtensionIs(info.fileName, Extension.Dts)
25-
? result.dts += 1
26-
: result.ts += 1;
34+
if (fileExtensionIs(info.fileName, Extension.Dts)) {
35+
result.dts += 1;
36+
result.dtsSize! += fileSize;
37+
}
38+
else {
39+
result.ts += 1;
40+
result.tsSize! += fileSize;
41+
}
2742
break;
2843
case ScriptKind.TSX:
2944
result.tsx += 1;
45+
result.tsxSize! += fileSize;
3046
break;
3147
case ScriptKind.Deferred:
3248
result.deferred += 1;
49+
result.deferredSize! += fileSize;
3350
break;
3451
}
3552
}

src/server/scriptInfo.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ namespace ts.server {
2525
*/
2626
private lineMap: number[] | undefined;
2727

28+
/**
29+
* When a large file is loaded, text will artificially be set to "".
30+
* In order to be able to report correct telemetry, we store the actual
31+
* file size in this case. (In other cases where text === "", e.g.
32+
* for mixed content or dynamic files, fileSize will be undefined.)
33+
*/
34+
private fileSize: number | undefined;
35+
2836
/**
2937
* True if the text is for the file thats open in the editor
3038
*/
@@ -56,10 +64,12 @@ namespace ts.server {
5664
this.switchToScriptVersionCache();
5765
}
5866

67+
/** Public for testing */
5968
public useText(newText?: string) {
6069
this.svc = undefined;
6170
this.text = newText;
6271
this.lineMap = undefined;
72+
this.fileSize = undefined;
6373
this.version.text++;
6474
}
6575

@@ -68,13 +78,14 @@ namespace ts.server {
6878
this.ownFileText = false;
6979
this.text = undefined;
7080
this.lineMap = undefined;
81+
this.fileSize = undefined;
7182
}
7283

7384
/**
7485
* Set the contents as newText
7586
* returns true if text changed
7687
*/
77-
public reload(newText: string) {
88+
public reload(newText: string): boolean {
7889
Debug.assert(newText !== undefined);
7990

8091
// Reload always has fresh content
@@ -91,14 +102,18 @@ namespace ts.server {
91102
this.ownFileText = false;
92103
return true;
93104
}
105+
106+
return false;
94107
}
95108

96109
/**
97110
* Reads the contents from tempFile(if supplied) or own file and sets it as contents
98111
* returns true if text changed
99112
*/
100113
public reloadWithFileText(tempFileName?: string) {
101-
const reloaded = this.reload(this.getFileText(tempFileName));
114+
const { text: newText, fileSize } = this.getFileTextAndSize(tempFileName);
115+
const reloaded = this.reload(newText);
116+
this.fileSize = fileSize; // NB: after reload since reload clears it
102117
this.ownFileText = !tempFileName || tempFileName === this.fileName;
103118
return reloaded;
104119
}
@@ -118,6 +133,23 @@ namespace ts.server {
118133
this.pendingReloadFromDisk = true;
119134
}
120135

136+
/**
137+
* For telemetry purposes, we would like to be able to report the size of the file.
138+
* However, we do not want telemetry to require extra file I/O so we report a size
139+
* that may be stale (e.g. may not reflect change made on disk since the last reload).
140+
* NB: Will read from disk if the file contents have never been loaded because
141+
* telemetry falsely indicating size 0 would be counter-productive.
142+
*/
143+
public getTelemetryFileSize(): number {
144+
return !!this.fileSize
145+
? this.fileSize
146+
: !!this.text // Check text before svc because its length is cheaper
147+
? this.text.length // Could be wrong if this.pendingReloadFromDisk
148+
: !!this.svc
149+
? this.svc.getSnapshot().getLength() // Could be wrong if this.pendingReloadFromDisk
150+
: this.getSnapshot().getLength(); // Should be strictly correct
151+
}
152+
121153
public getSnapshot(): IScriptSnapshot {
122154
return this.useScriptVersionCacheIfValidOrOpen()
123155
? this.svc!.getSnapshot()
@@ -161,7 +193,7 @@ namespace ts.server {
161193
return this.svc!.positionToLineOffset(position);
162194
}
163195

164-
private getFileText(tempFileName?: string) {
196+
private getFileTextAndSize(tempFileName?: string): { text: string, fileSize?: number } {
165197
let text: string;
166198
const fileName = tempFileName || this.fileName;
167199
const getText = () => text === undefined ? (text = this.host.readFile(fileName) || "") : text;
@@ -173,10 +205,10 @@ namespace ts.server {
173205
const service = this.info.containingProjects[0].projectService;
174206
service.logger.info(`Skipped loading contents of large file ${fileName} for info ${this.info.fileName}: fileSize: ${fileSize}`);
175207
this.info.containingProjects[0].projectService.sendLargeFileReferencedEvent(fileName, fileSize);
176-
return "";
208+
return { text: "", fileSize };
177209
}
178210
}
179-
return getText();
211+
return { text: getText() };
180212
}
181213

182214
private switchToScriptVersionCache(): ScriptVersionCache {
@@ -276,6 +308,11 @@ namespace ts.server {
276308
return this.textStorage.version;
277309
}
278310

311+
/*@internal*/
312+
getTelemetryFileSize() {
313+
return this.textStorage.getTelemetryFileSize();
314+
}
315+
279316
/*@internal*/
280317
public isDynamicOrHasMixedContent() {
281318
return this.hasMixedContent || this.isDynamic;

src/testRunner/unittests/telemetry.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,36 @@ namespace ts.projectSystem {
211211
}, "/jsconfig.json");
212212
});
213213

214+
it("sends telemetry for file sizes", () => {
215+
const jsFile = makeFile("/a.js", "1");
216+
const tsFile = makeFile("/b.ts", "12");
217+
const tsconfig = makeFile("/jsconfig.json", {
218+
compilerOptions: autoJsCompilerOptions
219+
});
220+
const et = new TestServerEventManager([tsconfig, jsFile, tsFile]);
221+
et.service.openClientFile(jsFile.path);
222+
et.assertProjectInfoTelemetryEvent({
223+
fileStats: fileStats({ js: 1, jsSize: 1, ts: 1, tsSize: 2 }),
224+
compilerOptions: autoJsCompilerOptions,
225+
typeAcquisition: {
226+
enable: true,
227+
include: false,
228+
exclude: false,
229+
},
230+
configFileName: "jsconfig.json",
231+
}, "/jsconfig.json");
232+
});
233+
214234
it("detects whether language service was disabled", () => {
215235
const file = makeFile("/a.js");
216236
const tsconfig = makeFile("/jsconfig.json", {});
217237
const et = new TestServerEventManager([tsconfig, file]);
218-
et.host.getFileSize = () => server.maxProgramSizeForNonTsFiles + 1;
238+
const fileSize = server.maxProgramSizeForNonTsFiles + 1;
239+
et.host.getFileSize = () => fileSize;
219240
et.service.openClientFile(file.path);
220241
et.getEvent<server.ProjectLanguageServiceStateEvent>(server.ProjectLanguageServiceStateEvent);
221242
et.assertProjectInfoTelemetryEvent({
222-
fileStats: fileStats({ js: 1 }),
243+
fileStats: fileStats({ js: 1, jsSize: fileSize }),
223244
compilerOptions: autoJsCompilerOptions,
224245
configFileName: "jsconfig.json",
225246
typeAcquisition: {

src/testRunner/unittests/textStorage.ts

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,20 @@ namespace ts.textStorage {
2929
for (let offset = 0; offset < end - start; offset++) {
3030
const pos1 = ts1.lineOffsetToPosition(line + 1, offset + 1);
3131
const pos2 = ts2.lineOffsetToPosition(line + 1, offset + 1);
32-
assert.isTrue(pos1 === pos2, `lineOffsetToPosition ${line + 1}-${offset + 1}: expected ${pos1} to equal ${pos2}`);
32+
assert.strictEqual(pos1, pos2, `lineOffsetToPosition ${line + 1}-${offset + 1}: expected ${pos1} to equal ${pos2}`);
3333
}
3434

3535
const {start: start1, length: length1 } = ts1.lineToTextSpan(line);
3636
const {start: start2, length: length2 } = ts2.lineToTextSpan(line);
37-
assert.isTrue(start1 === start2, `lineToTextSpan ${line}::start:: expected ${start1} to equal ${start2}`);
38-
assert.isTrue(length1 === length2, `lineToTextSpan ${line}::length:: expected ${length1} to equal ${length2}`);
37+
assert.strictEqual(start1, start2, `lineToTextSpan ${line}::start:: expected ${start1} to equal ${start2}`);
38+
assert.strictEqual(length1, length2, `lineToTextSpan ${line}::length:: expected ${length1} to equal ${length2}`);
3939
}
4040

4141
for (let pos = 0; pos < f.content.length; pos++) {
4242
const { line: line1, offset: offset1 } = ts1.positionToLineOffset(pos);
4343
const { line: line2, offset: offset2 } = ts2.positionToLineOffset(pos);
44-
assert.isTrue(line1 === line2, `positionToLineOffset ${pos}::line:: expected ${line1} to equal ${line2}`);
45-
assert.isTrue(offset1 === offset2, `positionToLineOffset ${pos}::offset:: expected ${offset1} to equal ${offset2}`);
44+
assert.strictEqual(line1, line2, `positionToLineOffset ${pos}::line:: expected ${line1} to equal ${line2}`);
45+
assert.strictEqual(offset1, offset2, `positionToLineOffset ${pos}::offset:: expected ${offset1} to equal ${offset2}`);
4646
}
4747
});
4848

@@ -52,16 +52,93 @@ namespace ts.textStorage {
5252
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
5353

5454
ts1.getSnapshot();
55-
assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1");
55+
assert.isFalse(ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1");
5656

5757
ts1.edit(0, 5, " ");
5858
assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 1");
5959

6060
ts1.useText();
61-
assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2");
61+
assert.isFalse(ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 2");
6262

6363
ts1.getLineInfo(0);
6464
assert.isTrue(ts1.hasScriptVersionCache_TestOnly(), "have script version cache - 2");
6565
});
66+
67+
it("should be able to return the file size immediately after construction", () => {
68+
const host = projectSystem.createServerHost([f]);
69+
// Since script info is not used in these tests, just cheat by passing undefined
70+
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
71+
72+
assert.strictEqual(f.content.length, ts1.getTelemetryFileSize());
73+
});
74+
75+
it("should be able to return the file size when backed by text", () => {
76+
const host = projectSystem.createServerHost([f]);
77+
// Since script info is not used in these tests, just cheat by passing undefined
78+
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
79+
80+
ts1.useText(f.content);
81+
assert.isFalse(ts1.hasScriptVersionCache_TestOnly());
82+
83+
assert.strictEqual(f.content.length, ts1.getTelemetryFileSize());
84+
});
85+
86+
it("should be able to return the file size when backed by a script version cache", () => {
87+
const host = projectSystem.createServerHost([f]);
88+
// Since script info is not used in these tests, just cheat by passing undefined
89+
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
90+
91+
ts1.useScriptVersionCache_TestOnly();
92+
assert.isTrue(ts1.hasScriptVersionCache_TestOnly());
93+
94+
assert.strictEqual(f.content.length, ts1.getTelemetryFileSize());
95+
});
96+
97+
it("should be able to return the file size when a JS file is too large to load into text", () => {
98+
const largeFile = {
99+
path: "/a/large.js",
100+
content: " ".repeat(server.maxFileSize + 1)
101+
};
102+
103+
const host = projectSystem.createServerHost([largeFile]);
104+
105+
// The large-file handling requires a ScriptInfo with a containing project
106+
const projectService = projectSystem.createProjectService(host);
107+
projectService.openClientFile(largeFile.path);
108+
const scriptInfo = projectService.getScriptInfo(largeFile.path);
109+
110+
const ts1 = new server.TextStorage(host, server.asNormalizedPath(largeFile.path), /*initialVersion*/ undefined, scriptInfo!);
111+
112+
assert.isTrue(ts1.reloadFromDisk());
113+
assert.isFalse(ts1.hasScriptVersionCache_TestOnly());
114+
115+
assert.strictEqual(largeFile.content.length, ts1.getTelemetryFileSize());
116+
});
117+
118+
it("should return the file size without reloading the file", () => {
119+
const oldText = "hello";
120+
const newText = "goodbye";
121+
122+
const changingFile = {
123+
path: "/a/changing.ts",
124+
content: oldText
125+
};
126+
127+
const host = projectSystem.createServerHost([changingFile]);
128+
// Since script info is not used in these tests, just cheat by passing undefined
129+
const ts1 = new server.TextStorage(host, server.asNormalizedPath(changingFile.path), /*initialVersion*/ undefined, /*info*/undefined!);
130+
131+
assert.isTrue(ts1.reloadFromDisk());
132+
133+
// Refresh the file and notify TextStorage
134+
host.writeFile(changingFile.path, newText);
135+
ts1.delayReloadFromFileIntoText();
136+
137+
assert.strictEqual(oldText.length, ts1.getTelemetryFileSize());
138+
139+
assert.isTrue(ts1.reloadWithFileText());
140+
141+
assert.strictEqual(newText.length, ts1.getTelemetryFileSize());
142+
});
66143
});
67144
}

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ namespace ts.projectSystem {
183183
}
184184

185185
export function fileStats(nonZeroStats: Partial<server.FileStats>): server.FileStats {
186-
return { ts: 0, tsx: 0, dts: 0, js: 0, jsx: 0, deferred: 0, ...nonZeroStats };
186+
return { ts: 0, tsSize: 0, tsx: 0, tsxSize: 0, dts: 0, dtsSize: 0, js: 0, jsSize: 0, jsx: 0, jsxSize: 0, deferred: 0, deferredSize: 0, ...nonZeroStats };
187187
}
188188

189189
export interface ConfigFileDiagnostic {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8437,11 +8437,17 @@ declare namespace ts.server {
84378437
}
84388438
interface FileStats {
84398439
readonly js: number;
8440+
readonly jsSize?: number;
84408441
readonly jsx: number;
8442+
readonly jsxSize?: number;
84418443
readonly ts: number;
8444+
readonly tsSize?: number;
84428445
readonly tsx: number;
8446+
readonly tsxSize?: number;
84438447
readonly dts: number;
8448+
readonly dtsSize?: number;
84448449
readonly deferred: number;
8450+
readonly deferredSize?: number;
84458451
}
84468452
interface OpenFileInfo {
84478453
readonly checkJs: boolean;

0 commit comments

Comments
 (0)