Add support for capturing cpu profiles into tsc itself (#33586)
* Add support for capturing cpu profiles into tsc itself * Accept baseline for new compiler option in showConfig * Fix lints * Support profiling build mode, only ever have one live profiling session * Minor modification to enable/disable semaphore, accept re-cased baseline * Add pid into autognerated cpuprofile path * Rename to fix case * Sanitize filepaths in emitted cpuprofile for easier adoption by enterprise people, add inspector to browser field
This commit is contained in:
parent
8d7fd2e691
commit
558ece72cb
|
@ -127,7 +127,8 @@
|
|||
"crypto": false,
|
||||
"buffer": false,
|
||||
"@microsoft/typescript-etw": false,
|
||||
"source-map-support": false
|
||||
"source-map-support": false,
|
||||
"inspector": false
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,14 @@ namespace ts {
|
|||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Show_verbose_diagnostic_information
|
||||
},
|
||||
{
|
||||
name: "generateCpuProfile",
|
||||
type: "string",
|
||||
isFilePath: true,
|
||||
paramType: Diagnostics.FILE_OR_DIRECTORY,
|
||||
category: Diagnostics.Advanced_Options,
|
||||
description: Diagnostics.Generates_a_CPU_profile
|
||||
},
|
||||
{
|
||||
name: "incremental",
|
||||
shortName: "i",
|
||||
|
|
|
@ -4068,6 +4068,10 @@
|
|||
"category": "Message",
|
||||
"code": 6222
|
||||
},
|
||||
"Generates a CPU profile.": {
|
||||
"category": "Message",
|
||||
"code": 6223
|
||||
},
|
||||
|
||||
"Projects to reference": {
|
||||
"category": "Message",
|
||||
|
|
|
@ -667,6 +667,8 @@ namespace ts {
|
|||
createSHA256Hash?(data: string): string;
|
||||
getMemoryUsage?(): number;
|
||||
exit(exitCode?: number): void;
|
||||
/*@internal*/ enableCPUProfiler?(path: string, continuation: () => void): boolean;
|
||||
/*@internal*/ disableCPUProfiler?(continuation: () => void): boolean;
|
||||
realpath?(path: string): string;
|
||||
/*@internal*/ getEnvironmentVariable(name: string): string;
|
||||
/*@internal*/ tryEnableSourceMapsForHost?(): void;
|
||||
|
@ -694,6 +696,7 @@ namespace ts {
|
|||
declare const process: any;
|
||||
declare const global: any;
|
||||
declare const __filename: string;
|
||||
declare const __dirname: string;
|
||||
|
||||
export function getNodeMajorVersion(): number | undefined {
|
||||
if (typeof process === "undefined") {
|
||||
|
@ -744,8 +747,9 @@ namespace ts {
|
|||
const byteOrderMarkIndicator = "\uFEFF";
|
||||
|
||||
function getNodeSystem(): System {
|
||||
const _fs = require("fs");
|
||||
const _path = require("path");
|
||||
const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/;
|
||||
const _fs: typeof import("fs") = require("fs");
|
||||
const _path: typeof import("path") = require("path");
|
||||
const _os = require("os");
|
||||
// crypto can be absent on reduced node installations
|
||||
let _crypto: typeof import("crypto") | undefined;
|
||||
|
@ -755,6 +759,8 @@ namespace ts {
|
|||
catch {
|
||||
_crypto = undefined;
|
||||
}
|
||||
let activeSession: import("inspector").Session | "stopping" | undefined;
|
||||
let profilePath = "./profile.cpuprofile";
|
||||
|
||||
const Buffer: {
|
||||
new (input: string, encoding?: string): any;
|
||||
|
@ -843,8 +849,10 @@ namespace ts {
|
|||
return 0;
|
||||
},
|
||||
exit(exitCode?: number): void {
|
||||
process.exit(exitCode);
|
||||
disableCPUProfiler(() => process.exit(exitCode));
|
||||
},
|
||||
enableCPUProfiler,
|
||||
disableCPUProfiler,
|
||||
realpath,
|
||||
debugMode: some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
|
||||
tryEnableSourceMapsForHost() {
|
||||
|
@ -871,6 +879,92 @@ namespace ts {
|
|||
};
|
||||
return nodeSystem;
|
||||
|
||||
/**
|
||||
* Uses the builtin inspector APIs to capture a CPU profile
|
||||
* See https://nodejs.org/api/inspector.html#inspector_example_usage for details
|
||||
*/
|
||||
function enableCPUProfiler(path: string, cb: () => void) {
|
||||
if (activeSession) {
|
||||
cb();
|
||||
return false;
|
||||
}
|
||||
const inspector: typeof import("inspector") = require("inspector");
|
||||
if (!inspector || !inspector.Session) {
|
||||
cb();
|
||||
return false;
|
||||
}
|
||||
const session = new inspector.Session();
|
||||
session.connect();
|
||||
|
||||
session.post("Profiler.enable", () => {
|
||||
session.post("Profiler.start", () => {
|
||||
activeSession = session;
|
||||
profilePath = path;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips non-TS paths from the profile, so users with private projects shouldn't
|
||||
* need to worry about leaking paths by submitting a cpu profile to us
|
||||
*/
|
||||
function cleanupPaths(profile: import("inspector").Profiler.Profile) {
|
||||
let externalFileCounter = 0;
|
||||
const remappedPaths = createMap<string>();
|
||||
const normalizedDir = normalizeSlashes(__dirname);
|
||||
// Windows rooted dir names need an extra `/` prepended to be valid file:/// urls
|
||||
const fileUrlRoot = `file://${getRootLength(normalizedDir) === 1 ? "" : "/"}${normalizedDir}`;
|
||||
for (const node of profile.nodes) {
|
||||
if (node.callFrame.url) {
|
||||
const url = normalizeSlashes(node.callFrame.url);
|
||||
if (containsPath(fileUrlRoot, url, useCaseSensitiveFileNames)) {
|
||||
node.callFrame.url = getRelativePathToDirectoryOrUrl(fileUrlRoot, url, fileUrlRoot, createGetCanonicalFileName(useCaseSensitiveFileNames), /*isAbsolutePathAnUrl*/ true);
|
||||
}
|
||||
else if (!nativePattern.test(url)) {
|
||||
node.callFrame.url = (remappedPaths.has(url) ? remappedPaths : remappedPaths.set(url, `external${externalFileCounter}.js`)).get(url)!;
|
||||
externalFileCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
function disableCPUProfiler(cb: () => void) {
|
||||
if (activeSession && activeSession !== "stopping") {
|
||||
const s = activeSession;
|
||||
activeSession.post("Profiler.stop", (err, { profile }) => {
|
||||
if (!err) {
|
||||
try {
|
||||
if (_fs.statSync(profilePath).isDirectory()) {
|
||||
profilePath = _path.join(profilePath, `${(new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// do nothing and ignore fallible fs operation
|
||||
}
|
||||
try {
|
||||
_fs.mkdirSync(_path.dirname(profilePath), { recursive: true });
|
||||
}
|
||||
catch {
|
||||
// do nothing and ignore fallible fs operation
|
||||
}
|
||||
_fs.writeFileSync(profilePath, JSON.stringify(cleanupPaths(profile)));
|
||||
}
|
||||
activeSession = undefined;
|
||||
s.disconnect();
|
||||
cb();
|
||||
});
|
||||
activeSession = "stopping";
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
cb();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function bufferFrom(input: string, encoding?: string): Buffer {
|
||||
// See https://github.com/Microsoft/TypeScript/issues/25652
|
||||
return Buffer.from && (Buffer.from as Function) !== Int8Array.from
|
||||
|
|
|
@ -174,6 +174,7 @@ namespace ts {
|
|||
/* @internal */ diagnostics?: boolean;
|
||||
/* @internal */ extendedDiagnostics?: boolean;
|
||||
/* @internal */ locale?: string;
|
||||
/* @internal */ generateCpuProfile?: string;
|
||||
|
||||
[option: string]: CompilerOptionsValue | undefined;
|
||||
}
|
||||
|
|
|
@ -4805,6 +4805,7 @@ namespace ts {
|
|||
emitDecoratorMetadata?: boolean;
|
||||
experimentalDecorators?: boolean;
|
||||
forceConsistentCasingInFileNames?: boolean;
|
||||
/*@internal*/generateCpuProfile?: string;
|
||||
/*@internal*/help?: boolean;
|
||||
importHelpers?: boolean;
|
||||
/*@internal*/init?: boolean;
|
||||
|
|
|
@ -52,16 +52,7 @@ namespace ts {
|
|||
filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView);
|
||||
}
|
||||
|
||||
export function executeCommandLine(args: string[]): void {
|
||||
if (args.length > 0 && args[0].charCodeAt(0) === CharacterCodes.minus) {
|
||||
const firstOption = args[0].slice(args[0].charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase();
|
||||
if (firstOption === "build" || firstOption === "b") {
|
||||
return performBuild(args.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
const commandLine = parseCommandLine(args);
|
||||
|
||||
function executeCommandLineWorker(commandLine: ParsedCommandLine) {
|
||||
if (commandLine.options.build) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_build_must_be_the_first_command_line_argument));
|
||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
|
@ -174,6 +165,24 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
export function executeCommandLine(args: string[]): void {
|
||||
if (args.length > 0 && args[0].charCodeAt(0) === CharacterCodes.minus) {
|
||||
const firstOption = args[0].slice(args[0].charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase();
|
||||
if (firstOption === "build" || firstOption === "b") {
|
||||
return performBuild(args.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
const commandLine = parseCommandLine(args);
|
||||
|
||||
if (commandLine.options.generateCpuProfile && sys.enableCPUProfiler) {
|
||||
sys.enableCPUProfiler(commandLine.options.generateCpuProfile, () => executeCommandLineWorker(commandLine));
|
||||
}
|
||||
else {
|
||||
executeCommandLineWorker(commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
function reportWatchModeWithoutSysSupport() {
|
||||
if (!sys.watchFile || !sys.watchDirectory) {
|
||||
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
|
||||
|
@ -181,8 +190,7 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
|
||||
function performBuild(args: string[]) {
|
||||
const { buildOptions, projects, errors } = parseBuildCommand(args);
|
||||
function performBuildWorker(buildOptions: BuildOptions, projects: string[], errors: Diagnostic[]) {
|
||||
// Update to pretty if host supports it
|
||||
updateReportDiagnostic(buildOptions);
|
||||
|
||||
|
@ -229,6 +237,16 @@ namespace ts {
|
|||
return sys.exit(buildOptions.clean ? builder.clean() : builder.build());
|
||||
}
|
||||
|
||||
function performBuild(args: string[]) {
|
||||
const { buildOptions, projects, errors } = parseBuildCommand(args);
|
||||
if (buildOptions.generateCpuProfile && sys.enableCPUProfiler) {
|
||||
sys.enableCPUProfiler(buildOptions.generateCpuProfile, () => performBuildWorker(buildOptions, projects, errors));
|
||||
}
|
||||
else {
|
||||
performBuildWorker(buildOptions, projects, errors);
|
||||
}
|
||||
}
|
||||
|
||||
function createReportErrorSummary(options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined {
|
||||
return shouldBePretty(options) ?
|
||||
errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) :
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"generateCpuProfile": "./someString"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue