rsnext/scripts/trace-to-tree.mjs

242 lines
8.1 KiB
JavaScript
Raw Normal View History

import fs from 'fs'
import eventStream from 'event-stream'
import chalk from 'chalk'
const file = fs.createReadStream(process.argv[2])
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, bold) => {
const color = bold ? chalk.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(chalk.cyan(`${Math.round(duration / 1000)} ms`))
} else if (duration < 10_000_000) {
return color(chalk.green(`${Math.round(duration / 100000) / 10}s`))
} else if (duration < 20_000_000) {
return color(chalk.yellow(`${Math.round(duration / 1000000)}s`))
} else if (duration < 100_000_000) {
return color(chalk.red(`${Math.round(duration / 1000000)}s`))
} else {
return color('🔥' + chalk.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, chalk.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 = `${chalk.bold(`${event.tags.name} compilation`)} ${formatTimes(
event
)}`
break
case 'webpack-invalidated-client':
case 'webpack-invalidated-server':
head = `${chalk.bold(`${event.name.slice(-6)} recompilation`)} ${
event.tags.trigger === 'manual'
? '(new page discovered)'
: `(${formatFilename(event.tags.trigger)})`
} ${formatTimes(event)}`
break
case 'add-entry':
head = `${chalk.blueBright('entry')} ${formatFilename(
event.tags.request
)}`
break
case 'hot-reloader':
head = `${chalk.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 = `${chalk.magentaBright('module')} ${
packageName
? `${chalk.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))
}
})