UNPKG

@typescript/analyze-trace

Version:
188 lines 9.24 kB
"use strict"; // 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