Skip to content

Commit c47e0a1

Browse files
committed
Add performance hooks
1 parent 8733992 commit c47e0a1

8 files changed

Lines changed: 176 additions & 12 deletions

File tree

src/CompilerOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface TypeScriptToLuaOptions {
3737
sourceMapTraceback?: boolean;
3838
tstlVerbose?: boolean;
3939
lua51AllowTryCatchInAsyncAwait?: boolean;
40+
measurePerformance?: boolean;
4041
}
4142

4243
export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> &

src/cli/parse.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ export const optionDeclarations: CommandLineOption[] = [
9999
description: "Always allow try/catch in async/await functions for Lua 5.1.",
100100
type: "boolean",
101101
},
102+
{
103+
name: "measurePerformance",
104+
description: "Measure performance of the tstl compiler.",
105+
type: "boolean",
106+
},
102107
];
103108

104109
export function updateParsedConfigFile(parsedConfigFile: ts.ParsedCommandLine): ParsedCommandLine {

src/lualib/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"tstl": {
1313
"luaLibImport": "none",
1414
"noHeader": true,
15-
"luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }]
15+
"luaPlugins": [{ "name": "../../dist/lualib-build/plugin.js" }],
16+
"measurePerformance": true,
1617
},
1718
"include": [".", "../../language-extensions/index.d.ts"]
1819
}

src/performance.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { performance } from "perf_hooks";
2+
3+
// We use our own performance hooks implementation for easier use, but also call node's performance hooks, so it shows up in the profiler.
4+
5+
let enabled = false;
6+
const marks = new Map<string, number>();
7+
const durations = new Map<string, number>();
8+
9+
function timestamp() {
10+
return performance.now();
11+
}
12+
13+
/**
14+
* Marks a performance event, with the given markName.
15+
*/
16+
function mark(markName: string) {
17+
if (enabled) {
18+
marks.set(markName, timestamp());
19+
performance.mark(markName);
20+
}
21+
}
22+
23+
/**
24+
* Adds a performance measurement with the specified name.
25+
*
26+
* @param measureName The name of the performance measurement.
27+
* @param startMarkName The name of the starting mark
28+
* @param endMarkName The name of the ending mark
29+
*/
30+
function measure(measureName: string, startMarkName: string, endMarkName: string) {
31+
if (enabled) {
32+
const end = marks.get(endMarkName) ?? timestamp();
33+
const start = marks.get(startMarkName) ?? performance.timeOrigin;
34+
const previousDuration = durations.get(measureName) ?? 0;
35+
durations.set(measureName, previousDuration + (end - start));
36+
performance.measure(measureName, startMarkName, endMarkName);
37+
}
38+
}
39+
40+
/**
41+
* Starts a performance measurement section.
42+
* @param name name of the measurement
43+
*/
44+
export function startSection(name: string) {
45+
mark("start " + name);
46+
}
47+
48+
/**
49+
* Ends a performance measurement section.
50+
* @param name name of the measurement
51+
*/
52+
export function endSection(name: string) {
53+
mark("end " + name);
54+
measure(name, "start " + name, "end " + name);
55+
}
56+
57+
export function isEnabled() {
58+
return enabled;
59+
}
60+
61+
export function enable() {
62+
if (!enabled) {
63+
enabled = true;
64+
}
65+
}
66+
export function disable() {
67+
if (enabled) {
68+
enabled = false;
69+
marks.clear();
70+
durations.clear();
71+
}
72+
}
73+
74+
export function forEachMeasure(callback: (measureName: string, duration: number) => void) {
75+
durations.forEach((duration, measureName) => callback(measureName, duration));
76+
}
77+
78+
export function getTotalDuration() {
79+
let total = 0;
80+
forEachMeasure((_, duration) => (total += duration));
81+
return total;
82+
}

src/transpilation/plugins.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CompilerOptions } from "../CompilerOptions";
44
import { Printer } from "../LuaPrinter";
55
import { Visitors } from "../transformation/context";
66
import { EmitFile, getConfigDirectory, ProcessedFile, resolvePlugin } from "./utils";
7+
import * as performance from "../performance";
78

89
export interface Plugin {
910
/**
@@ -47,6 +48,7 @@ export interface Plugin {
4748
}
4849

4950
export function getPlugins(program: ts.Program): { diagnostics: ts.Diagnostic[]; plugins: Plugin[] } {
51+
performance.startSection("getPlugins");
5052
const diagnostics: ts.Diagnostic[] = [];
5153
const pluginsFromOptions: Plugin[] = [];
5254
const options = program.getCompilerOptions() as CompilerOptions;
@@ -73,5 +75,7 @@ export function getPlugins(program: ts.Program): { diagnostics: ts.Diagnostic[];
7375
console.log(`Loaded ${pluginsFromOptions.length} plugins`);
7476
}
7577

78+
performance.endSection("getPlugins");
79+
7680
return { diagnostics, plugins: pluginsFromOptions };
7781
}

src/transpilation/transpile.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { isNonNull } from "../utils";
77
import { Plugin } from "./plugins";
88
import { getTransformers } from "./transformers";
99
import { EmitHost, ProcessedFile } from "./utils";
10+
import * as performance from "../performance";
1011

1112
export interface TranspileOptions {
1213
program: ts.Program;
@@ -25,6 +26,8 @@ export function getProgramTranspileResult(
2526
writeFileResult: ts.WriteFileCallback,
2627
{ program, sourceFiles: targetSourceFiles, customTransformers = {}, plugins = [] }: TranspileOptions
2728
): TranspileResult {
29+
performance.startSection("beforeTransform");
30+
2831
const options = program.getCompilerOptions() as CompilerOptions;
2932

3033
if (options.tstlVerbose) {
@@ -56,6 +59,7 @@ export function getProgramTranspileResult(
5659
}
5760

5861
if (preEmitDiagnostics.length > 0) {
62+
performance.endSection("beforeTransform");
5963
return { diagnostics: preEmitDiagnostics, transpiledFiles };
6064
}
6165
}
@@ -69,15 +73,21 @@ export function getProgramTranspileResult(
6973

7074
const visitorMap = createVisitorMap(plugins.map(p => p.visitors).filter(isNonNull));
7175
const printer = createPrinter(plugins.map(p => p.printer).filter(isNonNull));
76+
7277
const processSourceFile = (sourceFile: ts.SourceFile) => {
7378
if (options.tstlVerbose) {
7479
console.log(`Transforming ${sourceFile.fileName}`);
7580
}
7681

77-
const { file, diagnostics: transformDiagnostics } = transformSourceFile(program, sourceFile, visitorMap);
82+
performance.startSection("transpile");
7883

84+
const { file, diagnostics: transformDiagnostics } = transformSourceFile(program, sourceFile, visitorMap);
7985
diagnostics.push(...transformDiagnostics);
86+
87+
performance.endSection("transpile");
88+
8089
if (!options.noEmit && !options.emitDeclarationOnly) {
90+
performance.startSection("print");
8191
if (options.tstlVerbose) {
8292
console.log(`Printing ${sourceFile.fileName}`);
8393
}
@@ -89,6 +99,7 @@ export function getProgramTranspileResult(
8999
luaAst: file,
90100
...printResult,
91101
});
102+
performance.endSection("print");
92103
}
93104
};
94105

@@ -109,6 +120,8 @@ export function getProgramTranspileResult(
109120
}
110121
};
111122

123+
performance.endSection("beforeTransform");
124+
112125
if (targetSourceFiles) {
113126
for (const file of targetSourceFiles) {
114127
if (isEmittableJsonFile(file)) {
@@ -124,6 +137,8 @@ export function getProgramTranspileResult(
124137
program.getSourceFiles().filter(isEmittableJsonFile).forEach(processSourceFile);
125138
}
126139

140+
performance.startSection("afterPrint");
141+
127142
options.noEmit = oldNoEmit;
128143

129144
if (options.noEmit || (options.noEmitOnError && diagnostics.length > 0)) {
@@ -137,5 +152,7 @@ export function getProgramTranspileResult(
137152
}
138153
}
139154

155+
performance.endSection("afterPrint");
156+
140157
return { diagnostics, transpiledFiles };
141158
}

src/transpilation/transpiler.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { CompilerOptions, isBundleEnabled } from "../CompilerOptions";
44
import { getLuaLibBundle } from "../LuaLib";
55
import { normalizeSlashes, trimExtension } from "../utils";
66
import { getBundleResult } from "./bundle";
7-
import { getPlugins } from "./plugins";
7+
import { getPlugins, Plugin } from "./plugins";
88
import { resolveDependencies } from "./resolve";
99
import { getProgramTranspileResult, TranspileOptions } from "./transpile";
1010
import { EmitFile, EmitHost, ProcessedFile } from "./utils";
11+
import * as performance from "../performance";
1112

1213
export interface TranspilerOptions {
1314
emitHost?: EmitHost;
@@ -24,28 +25,52 @@ export interface EmitResult {
2425

2526
export class Transpiler {
2627
protected emitHost: EmitHost;
28+
2729
constructor({ emitHost = ts.sys }: TranspilerOptions = {}) {
2830
this.emitHost = emitHost;
2931
}
3032

3133
public emit(emitOptions: EmitOptions): EmitResult {
3234
const { program, writeFile = this.emitHost.writeFile, plugins: optionsPlugins = [] } = emitOptions;
33-
const options = program.getCompilerOptions() as CompilerOptions;
3435

3536
const { diagnostics: getPluginsDiagnostics, plugins: configPlugins } = getPlugins(program);
3637
const plugins = [...optionsPlugins, ...configPlugins];
3738

38-
const { diagnostics, transpiledFiles: freshFiles } = getProgramTranspileResult(this.emitHost, writeFile, {
39-
...emitOptions,
40-
plugins,
41-
});
39+
const { diagnostics: transpileDiagnostics, transpiledFiles: freshFiles } = getProgramTranspileResult(
40+
this.emitHost,
41+
writeFile,
42+
{
43+
...emitOptions,
44+
plugins,
45+
}
46+
);
47+
48+
const { emitPlan } = this.getEmitPlan(program, transpileDiagnostics, freshFiles);
4249

43-
const { emitPlan } = this.getEmitPlan(program, diagnostics, freshFiles);
50+
const emitDiagnostics = this.emitFiles(program, plugins, emitPlan, writeFile);
51+
52+
return {
53+
diagnostics: getPluginsDiagnostics.concat(transpileDiagnostics, emitDiagnostics),
54+
emitSkipped: emitPlan.length === 0,
55+
};
56+
}
57+
58+
private emitFiles(
59+
program: ts.Program,
60+
plugins: Plugin[],
61+
emitPlan: EmitFile[],
62+
writeFile: ts.WriteFileCallback
63+
): ts.Diagnostic[] {
64+
performance.startSection("emit");
65+
66+
const options = program.getCompilerOptions() as CompilerOptions;
4467

4568
if (options.tstlVerbose) {
4669
console.log("Emitting output");
4770
}
4871

72+
const diagnostics: ts.Diagnostic[] = [];
73+
4974
for (const plugin of plugins) {
5075
if (plugin.beforeEmit) {
5176
const beforeEmitPluginDiagnostics = plugin.beforeEmit(program, options, this.emitHost, emitPlan) ?? [];
@@ -69,14 +94,17 @@ export class Transpiler {
6994
console.log("Emit finished!");
7095
}
7196

72-
return { diagnostics: getPluginsDiagnostics.concat(diagnostics), emitSkipped: emitPlan.length === 0 };
97+
performance.endSection("emit");
98+
99+
return diagnostics;
73100
}
74101

75102
protected getEmitPlan(
76103
program: ts.Program,
77104
diagnostics: ts.Diagnostic[],
78105
files: ProcessedFile[]
79106
): { emitPlan: EmitFile[] } {
107+
performance.startSection("getEmitPlan");
80108
const options = program.getCompilerOptions() as CompilerOptions;
81109

82110
if (options.tstlVerbose) {
@@ -113,6 +141,8 @@ export class Transpiler {
113141
}));
114142
}
115143

144+
performance.endSection("getEmitPlan");
145+
116146
return { emitPlan };
117147
}
118148
}

src/tstl.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { parseCommandLine } from "./cli/parse";
77
import { createDiagnosticReporter } from "./cli/report";
88
import { createConfigFileUpdater, locateConfigFile, parseConfigFileWithSystem } from "./cli/tsconfig";
99
import { isBundleEnabled } from "./CompilerOptions";
10+
import * as performance from "./performance";
1011

1112
const shouldBePretty = ({ pretty }: ts.CompilerOptions = {}) =>
1213
pretty !== undefined ? (pretty as boolean) : ts.sys.writeOutputIsTTY?.() ?? false;
@@ -95,20 +96,27 @@ function performCompilation(
9596
options: tstl.CompilerOptions,
9697
configFileParsingDiagnostics?: readonly ts.Diagnostic[]
9798
): void {
99+
if (options.measurePerformance) performance.enable();
100+
101+
performance.startSection("createProgram");
102+
98103
const program = ts.createProgram({
99104
rootNames,
100105
options,
101106
projectReferences,
102107
configFileParsingDiagnostics,
103108
});
104-
105109
const preEmitDiagnostics = ts.getPreEmitDiagnostics(program);
106110

111+
performance.endSection("createProgram");
112+
107113
const { diagnostics: transpileDiagnostics, emitSkipped } = new tstl.Transpiler().emit({ program });
108114

109115
const diagnostics = ts.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics]);
110-
111116
diagnostics.forEach(reportDiagnostic);
117+
118+
if (options.measurePerformance) reportPerformance();
119+
112120
const exitCode =
113121
diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length === 0
114122
? ts.ExitStatus.Success
@@ -158,6 +166,9 @@ function updateWatchCompilationHost(
158166
host.afterProgramCreate = builderProgram => {
159167
const program = builderProgram.getProgram();
160168
const options = builderProgram.getCompilerOptions() as tstl.CompilerOptions;
169+
170+
if (options.measurePerformance) performance.enable();
171+
161172
const configFileParsingDiagnostics: ts.Diagnostic[] = updateConfigFile(options);
162173

163174
let sourceFiles: ts.SourceFile[] | undefined;
@@ -188,13 +199,26 @@ function updateWatchCompilationHost(
188199

189200
diagnostics.forEach(reportDiagnostic);
190201

202+
if (options.measurePerformance) reportPerformance();
203+
191204
const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error);
192205
hadErrorLastTime = errors.length > 0;
193206

194207
host.onWatchStatusChange!(cliDiagnostics.watchErrorSummary(errors.length), host.getNewLine(), options);
195208
};
196209
}
197210

211+
function reportPerformance() {
212+
if (performance.isEnabled()) {
213+
console.log("Performance measurements: ");
214+
performance.forEachMeasure((name, duration) => {
215+
console.log(` ${name}: ${duration.toFixed(2)}ms`);
216+
});
217+
console.log(`Total: ${performance.getTotalDuration().toFixed(2)}ms`);
218+
performance.disable();
219+
}
220+
}
221+
198222
function checkNodeVersion(): void {
199223
const [major, minor] = process.version.slice(1).split(".").map(Number);
200224
const isValid = major > 12 || (major === 12 && minor >= 13);

0 commit comments

Comments
 (0)