mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
153 lines
4.9 KiB
JavaScript
153 lines
4.9 KiB
JavaScript
// @ts-check
|
|
|
|
import { declare } from '@babel/helper-plugin-utils'
|
|
import { types as t } from '@babel/core'
|
|
|
|
// inlined from https://github.com/danielroe/errx
|
|
function captureStackTrace () {
|
|
const IS_ABSOLUTE_RE = /^[/\\](?![/\\])|^[/\\]{2}(?!\.)|^[a-z]:[/\\]/i
|
|
const LINE_RE = /^\s+at (?:(?<function>[^)]+) \()?(?<source>[^)]+)\)?$/u
|
|
const SOURCE_RE = /^(?<source>.+):(?<line>\d+):(?<column>\d+)$/u
|
|
|
|
if (!Error.captureStackTrace) {
|
|
return []
|
|
}
|
|
// eslint-disable-next-line unicorn/error-message
|
|
const stack = new Error()
|
|
Error.captureStackTrace(stack)
|
|
const trace = []
|
|
for (const line of stack.stack?.split('\n') || []) {
|
|
const parsed = LINE_RE.exec(line)?.groups
|
|
if (!parsed) {
|
|
continue
|
|
}
|
|
if (!parsed.source) {
|
|
continue
|
|
}
|
|
const parsedSource = SOURCE_RE.exec(parsed.source)?.groups
|
|
if (parsedSource) {
|
|
Object.assign(parsed, parsedSource)
|
|
}
|
|
if (IS_ABSOLUTE_RE.test(parsed.source)) {
|
|
parsed.source = `file://${parsed.source}`
|
|
}
|
|
if (parsed.source === import.meta.url) {
|
|
continue
|
|
}
|
|
for (const key of ['line', 'column']) {
|
|
if (parsed[key]) {
|
|
// @ts-expect-error
|
|
parsed[key] = Number(parsed[key])
|
|
}
|
|
}
|
|
trace.push(parsed)
|
|
}
|
|
return trace
|
|
}
|
|
|
|
export const leading = `
|
|
const ___captureStackTrace = ${captureStackTrace.toString()};
|
|
globalThis.___calls ||= {};
|
|
globalThis.___timings ||= {};
|
|
globalThis.___callers ||= {};`
|
|
|
|
function onExit () {
|
|
if (globalThis.___logged) { return }
|
|
globalThis.___logged = true
|
|
|
|
// worst by total time
|
|
const timings = Object.entries(globalThis.___timings)
|
|
|
|
const topFunctionsTotalTime = timings
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 10)
|
|
.map(([name, time]) => ({
|
|
name,
|
|
time: Number(Number(time).toFixed(2)),
|
|
calls: globalThis.___calls[name],
|
|
callers: globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).map(([name, count]) => `${name.trim()} (${count})`).join(', '),
|
|
}))
|
|
|
|
// eslint-disable-next-line no-console
|
|
console.log('Top 10 functions by total time:')
|
|
// eslint-disable-next-line no-console
|
|
console.table(topFunctionsTotalTime)
|
|
|
|
// worst by average time (excluding single calls)
|
|
const topFunctionsAverageTime = timings
|
|
.filter(([name]) => (globalThis.___calls[name] || 0) > 1)
|
|
.map(([name, time]) => [name, time / (globalThis.___calls[name] || 1)])
|
|
// @ts-expect-error
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 10)
|
|
.map(([name, time]) => ({
|
|
name,
|
|
time: Number(Number(time).toFixed(2)),
|
|
calls: name && globalThis.___calls[name],
|
|
callers: name && globalThis.___callers[name] && Object.entries(globalThis.___callers[name]).sort((a, b) => b[1] - a[1]).map(([name, count]) => `${name.trim()} (${count})`).join(', '),
|
|
}))
|
|
|
|
// eslint-disable-next-line no-console
|
|
console.log('Top 10 functions by average time:')
|
|
// eslint-disable-next-line no-console
|
|
console.table(topFunctionsAverageTime)
|
|
}
|
|
|
|
export const trailing = `process.on("exit", ${onExit.toString()})`
|
|
|
|
/** @param {string} functionName */
|
|
export function generateInitCode (functionName) {
|
|
return `
|
|
___calls.${functionName} = (___calls.${functionName} || 0) + 1;
|
|
___timings.${functionName} ||= 0;
|
|
const ___now = Date.now();`
|
|
}
|
|
|
|
/** @param {string} functionName */
|
|
export function generateFinallyCode (functionName) {
|
|
return `
|
|
___timings.${functionName} += Date.now() - ___now;
|
|
try {
|
|
const ___callee = ___captureStackTrace()[1]?.function;
|
|
if (___callee) {
|
|
___callers.${functionName} ||= {};
|
|
___callers.${functionName}[' ' + ___callee] = (___callers.${functionName}[' ' + ___callee] || 0) + 1;
|
|
}
|
|
} catch {}`
|
|
}
|
|
|
|
export default declare((api) => {
|
|
api.assertVersion(7)
|
|
|
|
return {
|
|
name: 'annotate-function-timings',
|
|
visitor: {
|
|
Program (path) {
|
|
path.unshiftContainer('body', t.expressionStatement(t.identifier(leading)))
|
|
path.pushContainer('body', t.expressionStatement(t.identifier(trailing)))
|
|
},
|
|
FunctionDeclaration (path) {
|
|
const functionName = path.node.id?.name
|
|
|
|
const start = path.get('body').get('body')[0]
|
|
const end = path.get('body').get('body').pop()
|
|
|
|
if (!functionName || ['createJiti', '___captureStackTrace', '_interopRequireDefault'].includes(functionName) || !start || !end) { return }
|
|
|
|
const initCode = generateInitCode(functionName)
|
|
const finallyCode = generateFinallyCode(functionName)
|
|
|
|
const originalCode = path.get('body').get('body').map(statement => statement.node)
|
|
path.get('body').get('body').forEach(statement => statement.remove())
|
|
|
|
path.get('body').unshiftContainer('body', t.expressionStatement(t.identifier(initCode)))
|
|
path.get('body').pushContainer('body', t.tryStatement(
|
|
t.blockStatement(originalCode),
|
|
t.catchClause(t.identifier('e'), t.blockStatement([])),
|
|
t.blockStatement([t.expressionStatement(t.identifier(finallyCode))]),
|
|
))
|
|
},
|
|
},
|
|
}
|
|
})
|