Created
August 9, 2024 15:22
-
-
Save mizchi/a0a2727cfb46e408366beb4b2db40939 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// original https://github.com/vercel/next.js/blob/canary/scripts/trace-to-tree.mjs | |
// for deno script | |
import fs from 'node:fs' | |
import eventStream from 'npm:event-stream' | |
import pc from 'npm:picocolors' | |
const { bold, blue, cyan, green, magenta, red, yellow } = pc; | |
const file = fs.createReadStream(Deno.args[0]) | |
const sum = (...args) => args.reduce((a, b) => a + b, 0) | |
const aggregate = (event) => { | |
const isBuildModule = event.name.startsWith('build-module-') | |
event.range = event.timestamp + (event.duration || 0) | |
event.total = isBuildModule ? event.duration : 0 | |
if (isBuildModule) { | |
event.packageName = getPackageName(event.tags.name) | |
if (event.children) { | |
const queue = [...event.children] | |
event.children = [] | |
event.childrenTimings = {} | |
event.mergedChildren = 0 | |
for (const e of queue) { | |
if (!e.name.startsWith('build-module-')) { | |
event.childrenTimings[e.name] = | |
(event.childrenTimings[e.name] || 0) + e.duration | |
continue | |
} | |
const pkgName = getPackageName(e.tags.name) | |
if (!event.packageName || pkgName !== event.packageName) { | |
event.children.push(e) | |
} else { | |
event.duration += e.duration | |
event.mergedChildren++ | |
if (e.children) queue.push(...e.children) | |
} | |
} | |
} | |
} | |
if (event.children) { | |
event.children.forEach(aggregate) | |
event.children.sort((a, b) => a.timestamp - b.timestamp) | |
event.range = Math.max( | |
event.range, | |
...event.children.map((c) => c.range || event.timestamp) | |
) | |
event.total += isBuildModule | |
? sum(...event.children.map((c) => c.total || 0)) | |
: 0 | |
} | |
} | |
const formatDuration = (duration, isBold) => { | |
const color = isBold ? bold : (x) => x | |
if (duration < 1000) { | |
return color(`${duration} µs`) | |
} else if (duration < 10000) { | |
return color(`${Math.round(duration / 100) / 10} ms`) | |
} else if (duration < 100000) { | |
return color(`${Math.round(duration / 1000)} ms`) | |
} else if (duration < 1_000_000) { | |
return color(cyan(`${Math.round(duration / 1000)} ms`)) | |
} else if (duration < 10_000_000) { | |
return color(green(`${Math.round(duration / 100000) / 10} s`)) | |
} else if (duration < 20_000_000) { | |
return color(yellow(`${Math.round(duration / 1000000)} s`)) | |
} else if (duration < 100_000_000) { | |
return color(red(`${Math.round(duration / 1000000)} s`)) | |
} else { | |
return color('🔥' + red(`${Math.round(duration / 1000000)} s`)) | |
} | |
} | |
const formatTimes = (event) => { | |
const range = event.range - event.timestamp | |
const additionalInfo = [] | |
if (event.total && event.total !== range) | |
additionalInfo.push(`total ${formatDuration(event.total)}`) | |
if (event.duration !== range) | |
additionalInfo.push(`self ${formatDuration(event.duration, bold)}`) | |
return `${formatDuration(range, additionalInfo.length === 0)}${additionalInfo.length ? ` (${additionalInfo.join(', ')})` : '' | |
}` | |
} | |
const formatFilename = (filename) => { | |
return cleanFilename(filename).replace(/.+[\\/]node_modules[\\/]/, '') | |
} | |
const cleanFilename = (filename) => { | |
if (filename.includes('&absolutePagePath=')) { | |
filename = | |
'page ' + | |
decodeURIComponent( | |
filename.replace(/.+&absolutePagePath=/, '').slice(0, -1) | |
) | |
} | |
filename = filename.replace(/.+!(?!$)/, '') | |
return filename | |
} | |
const getPackageName = (filename) => { | |
const match = /.+[\\/]node_modules[\\/]((?:@[^\\/]+[\\/])?[^\\/]+)/.exec( | |
cleanFilename(filename) | |
) | |
return match && match[1] | |
} | |
const formatEvent = (event) => { | |
let head | |
switch (event.name) { | |
case 'webpack-compilation': | |
head = `${bold(`${event.tags.name} compilation`)} ${formatTimes(event)}` | |
break | |
case 'webpack-invalidated-client': | |
case 'webpack-invalidated-server': | |
head = `${bold(`${event.name.slice(-6)} recompilation`)} ${event.tags.trigger === 'manual' | |
? '(new page discovered)' | |
: `(${formatFilename(event.tags.trigger)})` | |
} ${formatTimes(event)}` | |
break | |
case 'add-entry': | |
head = `${blue('entry')} ${formatFilename(event.tags.request)}` | |
break | |
case 'hot-reloader': | |
head = `${bold(green(`hot reloader`))}` | |
break | |
case 'export-page': | |
head = `${event.name} ${event.tags.path} ${formatTimes(event)}` | |
break | |
default: | |
if (event.name.startsWith('build-module-')) { | |
const { mergedChildren, childrenTimings, packageName } = event | |
head = `${magenta('module')} ${packageName | |
? `${bold(cyan(packageName))} (${formatFilename(event.tags.name)}${mergedChildren ? ` + ${mergedChildren}` : '' | |
})` | |
: formatFilename(event.tags.name) | |
} ${formatTimes(event)}` | |
if (childrenTimings && Object.keys(childrenTimings).length) { | |
head += ` [${Object.keys(childrenTimings) | |
.map((key) => `${key} ${formatDuration(childrenTimings[key])}`) | |
.join(', ')}]` | |
} | |
} else { | |
head = `${event.name} ${formatTimes(event)}` | |
} | |
break | |
} | |
if (event.children && event.children.length) { | |
return head + '\n' + treeChildren(event.children.map(formatEvent)) | |
} else { | |
return head | |
} | |
} | |
const indentWith = (str, firstLinePrefix, otherLinesPrefix) => { | |
return firstLinePrefix + str.replace(/\n/g, '\n' + otherLinesPrefix) | |
} | |
const treeChildren = (items) => { | |
let str = '' | |
for (let i = 0; i < items.length; i++) { | |
if (i !== items.length - 1) { | |
str += indentWith(items[i], '├─ ', '│ ') + '\n' | |
} else { | |
str += indentWith(items[i], '└─ ', ' ') | |
} | |
} | |
return str | |
} | |
const tracesById = new Map() | |
file | |
.pipe(eventStream.split()) | |
.pipe( | |
eventStream.mapSync((data) => { | |
if (!data) return | |
const json = JSON.parse(data) | |
json.forEach((event) => { | |
tracesById.set(event.id, event) | |
}) | |
}) | |
) | |
.on('end', () => { | |
const rootEvents = [] | |
for (const event of tracesById.values()) { | |
if (event.parentId) { | |
event.parent = tracesById.get(event.parentId) | |
if (event.parent) { | |
if (!event.parent.children) event.parent.children = [] | |
event.parent.children.push(event) | |
} | |
} | |
if (!event.parent) rootEvents.push(event) | |
} | |
for (const event of rootEvents) { | |
aggregate(event) | |
} | |
console.log(`Explanation: | |
${formatEvent({ | |
name: 'build-module-js', | |
tags: { name: '/Users/next-user/src/magic-ui/pages/index.js' }, | |
duration: 163000, | |
timestamp: 0, | |
range: 24000000, | |
total: 33000000, | |
childrenTimings: { 'read-resource': 873, 'next-babel-turbo-loader': 135000 }, | |
})} | |
════════╤═══════════════════════════════════ ═╤═ ═╤═ ═╤════ ═══════════╤════════════════════════════════════════ | |
└─ name of the processed module │ │ │ └─ timings of nested steps | |
│ │ └─ building the module itself (including overlapping parallel actions) | |
│ └─ total build time of this modules and all nested ones (including overlapping parallel actions) | |
└─ how long until the module and all nested modules took compiling (wall time, without overlapping actions) | |
${formatEvent({ | |
name: 'build-module-js', | |
tags: { | |
name: '/Users/next-user/src/magic-ui/node_modules/lodash/camelCase.js', | |
}, | |
packageName: 'lodash', | |
duration: 958000, | |
timestamp: 0, | |
range: 295000, | |
childrenTimings: { 'read-resource': 936000 }, | |
mergedChildren: 281, | |
})} | |
═╤════ ══════╤════════════ ═╤═ | |
│ │ └─ number of modules that are merged into that line | |
│ └─ first module that is imported | |
└─ npm package name | |
`) | |
for (const event of rootEvents) { | |
console.log(formatEvent(event)) | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How to use