add trace-to-tree script to allow to visualize traces on the commandline (#29942)
![image](https://user-images.githubusercontent.com/1365881/137498172-18e20f48-77df-4a26-9004-9baeb44893fb.png)
This commit is contained in:
parent
32029ef5e9
commit
b5d7a91f6c
1 changed files with 238 additions and 0 deletions
238
scripts/trace-to-tree.mjs
Normal file
238
scripts/trace-to-tree.mjs
Normal file
|
@ -0,0 +1,238 @@
|
|||
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
|
||||
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))
|
||||
}
|
||||
})
|
Loading…
Reference in a new issue