@typescript/analyze-trace
Version:
Analyze the output of tsc --generatetrace
188 lines • 9.24 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.reportHighlights = void 0;
const console_1 = require("console");
const chalk = require("chalk");
const treeify = require("treeify");
const fs = require("fs");
const path = require("path");
const getTypeTree = require("./get-type-tree");
const parse_trace_file_1 = require("./parse-trace-file");
const analyze_trace_utilities_1 = require("./analyze-trace-utilities");
async function reportHighlights(tracePath, typesPath, thresholdDuration, minDuration, minPercentage, importExpressionThreshold) {
const parseResult = await (0, parse_trace_file_1.parse)(tracePath, minDuration);
const root = (0, analyze_trace_utilities_1.buildHotPathsTree)(parseResult, thresholdDuration, minPercentage);
const unclosedStack = parseResult.unclosedStack;
if (unclosedStack.length) {
console.log("Trace ended unexpectedly");
while (unclosedStack.length) {
const event = unclosedStack.pop();
console.log(`> ${event.name}: ${JSON.stringify(event.args)}`);
}
console.log();
}
const sawHotspots = await printHotStacks(root, importExpressionThreshold, typesPath);
console.log();
const sawDuplicates = await printDuplicateNodeModules(parseResult.nodeModulePaths);
return sawHotspots || sawDuplicates;
}
exports.reportHighlights = reportHighlights;
async function printDuplicateNodeModules(nodeModulePaths) {
const tree = {};
let sawDuplicate = false;
const sorted = Array.from(nodeModulePaths.entries()).sort(([n1, p1], [n2, p2]) => p2.length - p1.length || n1.localeCompare(n2));
for (const [packageName, packagePaths] of sorted) {
if (packagePaths.length < 2)
continue;
sawDuplicate = true;
const packageTree = {};
for (const packagePath of packagePaths.sort((p1, p2) => p1.localeCompare(p2))) {
const version = await (0, analyze_trace_utilities_1.getPackageVersion)(packagePath);
packageTree[`${version ? `Version ${version}` : `Unknown version`} from ${packagePath}`] = {};
}
tree[packageName] = packageTree;
}
if (sawDuplicate) {
console.log("Duplicate packages");
console.log(treeify.asTree(tree, /*showValues*/ false, /*hideFunctions*/ true).trimEnd());
}
else {
console.log("No duplicate packages found");
}
return sawDuplicate;
}
async function printHotStacks(root, importExpressionThreshold, typesPath) {
const relatedTypes = typesPath ? await (0, analyze_trace_utilities_1.getRelatedTypes)(root, typesPath, /*leafOnly*/ true) : undefined;
const positionMap = await (0, analyze_trace_utilities_1.getNormalizedPositions)(root, relatedTypes);
const tree = await makePrintableTree(root, /*currentFile*/ undefined, positionMap, relatedTypes, importExpressionThreshold);
const sawHotspots = Object.entries(tree).length > 0;
if (sawHotspots) {
console.log("Hot Spots");
console.log(treeify.asTree(tree, /*showValues*/ false, /*hideFunctions*/ true).trimEnd());
}
else {
console.log("No hot spots found");
}
return sawHotspots;
}
async function makePrintableTree(curr, currentFile, positionMap, relatedTypes, importExpressionThreshold) {
var _a, _b;
let childTree = {};
let showCurrentFile = false;
if (((_a = curr.event) === null || _a === void 0 ? void 0 : _a.cat) === "check") {
const path = curr.event.args.path;
if (path) {
showCurrentFile = path !== currentFile;
currentFile = path;
}
else {
(0, console_1.assert)(((_b = curr.event) === null || _b === void 0 ? void 0 : _b.name) !== "checkSourceFile", "checkSourceFile should have a path");
}
}
if (curr.children.length) {
// Sort slow to fast
const sortedChildren = curr.children.sort((a, b) => (b.end - b.start) - (a.end - a.start));
for (const child of sortedChildren) {
Object.assign(childTree, await makePrintableTree(child, currentFile, positionMap, relatedTypes, importExpressionThreshold));
}
}
if (curr.event) {
const eventStr = await eventToString();
if (eventStr) {
let result = {};
result[`${eventStr} (${Math.round((curr.end - curr.start) / 1000)}ms)`] = childTree;
return result;
}
}
return childTree;
async function eventToString() {
const event = curr.event;
switch (event.name) {
// TODO (https://github.com/microsoft/typescript-analyze-trace/issues/2)
// case "findSourceFile":
// return `Load file ${event.args!.fileName}`;
case "emitDeclarationFileOrBundle":
const dtsPath = event.args.declarationFilePath;
if (!dtsPath || !fs.existsSync(dtsPath)) {
return undefined;
}
try {
const emittedImports = await (0, analyze_trace_utilities_1.getEmittedImports)(dtsPath, importExpressionThreshold);
if (emittedImports.length === 0) {
return undefined;
}
for (const { name, count } of emittedImports) {
// Directly modifying childTree is pretty hacky
childTree[`Consider adding \`${chalk.cyan(`import ${chalk.cyan(name)}`)}\` which is used in ${count} places`] = {};
}
return `Emit declarations file ${formatPath(dtsPath)}`;
}
catch (_a) {
return undefined;
}
case "checkSourceFile":
return `Check file ${formatPath(currentFile)}`;
case "structuredTypeRelatedTo":
const args = event.args;
if (relatedTypes && curr.children.length === 0) {
const typeTree = Object.assign(Object.assign({}, getTypeTree(args.sourceId, relatedTypes)), getTypeTree(args.targetId, relatedTypes));
// Directly modifying childTree is pretty hacky
Object.assign(childTree, updateTypeTreePositions(typeTree));
}
return `Compare types ${args.sourceId} and ${args.targetId}`;
case "getVariancesWorker":
if (relatedTypes && curr.children.length === 0) {
const typeTree = getTypeTree(event.args.id, relatedTypes);
// Directly modifying childTree is pretty hacky
Object.assign(childTree, updateTypeTreePositions(typeTree));
}
return `Determine variance of type ${event.args.id}`;
default:
if (event.cat === "check" && event.args && event.args.pos && event.args.end) {
const currentFileClause = showCurrentFile
? ` in ${formatPath(currentFile)}`
: "";
if (positionMap.has(currentFile)) {
const updatedPos = positionMap.get(currentFile).get(event.args.pos.toString());
const updatedEnd = positionMap.get(currentFile).get(event.args.end.toString());
return `${(0, analyze_trace_utilities_1.unmangleCamelCase)(event.name)}${currentFileClause} from (line ${updatedPos[0]}, char ${updatedPos[1]}) to (line ${updatedEnd[0]}, char ${updatedEnd[1]})`;
}
else {
return `${(0, analyze_trace_utilities_1.unmangleCamelCase)(event.name)}${currentFileClause} from offset ${event.args.pos} to offset ${event.args.end}`;
}
}
return undefined;
}
}
function updateTypeTreePositions(typeTree) {
let newTree = {};
for (let typeString in typeTree) {
const subtree = typeTree[typeString];
const type = JSON.parse(typeString);
if (type.location) {
const path = type.location.path;
if (positionMap.has(path)) {
const updatedPosition = positionMap.get(path).get((0, analyze_trace_utilities_1.getLineCharMapKey)(type.location.line, type.location.char));
[type.location.line, type.location.char] = updatedPosition;
typeString = JSON.stringify(type);
}
typeString = typeString.replace(path, formatPath(path));
}
newTree[typeString] = updateTypeTreePositions(subtree);
}
return newTree;
}
}
function formatPath(p) {
if (/node_modules/.test(p)) {
p = p.replace(/\/node_modules\/([^@][^/]+)\//g, `/node_modules/${chalk.cyan("$1")}/`);
p = p.replace(/\/node_modules\/(@[^/]+\/[^/]+)/g, `/node_modules/${chalk.cyan("$1")}/`);
}
else {
p = path.join(path.dirname(p), chalk.cyan(path.basename(p)));
}
return chalk.magenta(path.normalize(p));
}
//# sourceMappingURL=print-trace-analysis-text.js.map