mirror of
https://github.com/davidmarkclements/0x.git
synced 2025-12-08 19:06:01 +00:00
138 lines
3.7 KiB
JavaScript
138 lines
3.7 KiB
JavaScript
'use strict'
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const { spawn } = require('child_process')
|
|
const debug = require('debug')('0x')
|
|
const traceStacksToTicks = require('../lib/trace-stacks-to-ticks')
|
|
const { promisify } = require('util')
|
|
|
|
const {
|
|
getTargetFolder,
|
|
tidy,
|
|
pathTo
|
|
} = require('../lib/util')
|
|
|
|
module.exports = promisify(linux)
|
|
|
|
const SOFT_EXIT_SIGNALS = ['SIGINT', 'SIGTERM']
|
|
|
|
function linux (args, sudo, cb) {
|
|
const { status, outputDir, workingDir, name, onPort, pathToNodeBinary } = args
|
|
if (onPort) {
|
|
cb(Error('--on-port couldn\'t be used with Linux profiling. Run it in a separate terminal'))
|
|
return
|
|
}
|
|
|
|
const perf = pathTo('perf')
|
|
if (!perf) {
|
|
cb(Error('Unable to locate perf - make sure it\'s in your PATH'))
|
|
return
|
|
}
|
|
if (!sudo) {
|
|
status('Stacks are captured using perf(1), which requires sudo access\n')
|
|
return spawn('sudo', ['true'])
|
|
.on('exit', function () { linux(args, true, cb) })
|
|
}
|
|
|
|
const uid = parseInt(Math.random() * 1e9, 10).toString(36)
|
|
const perfdat = '/tmp/perf-' + uid + '.data'
|
|
const kernelTracingDebug = args.kernelTracingDebug
|
|
|
|
const proc = spawn('sudo', [
|
|
'-E',
|
|
'perf',
|
|
'record',
|
|
!kernelTracingDebug ? '-q' : '',
|
|
'-F 99',
|
|
'-g',
|
|
'-o',
|
|
perfdat,
|
|
'--',
|
|
pathToNodeBinary,
|
|
'--perf-basic-prof',
|
|
'-r', path.join(__dirname, '..', 'lib', 'preload', 'soft-exit.js')
|
|
].filter(Boolean).concat(args.argv), {
|
|
stdio: ['ignore', 'inherit', 'inherit', 'ignore', 'ignore', 'pipe']
|
|
}).on('exit', function (code) {
|
|
args.onProcessExit(code)
|
|
if (code !== null && code !== 0 && code !== 143 && code !== 130) {
|
|
tidy(args)
|
|
console.error('Tracing subprocess error, code: ' + code)
|
|
return
|
|
}
|
|
filterInternalFunctions(perfdat)
|
|
})
|
|
|
|
const folder = getTargetFolder({ outputDir, workingDir, name, pid: proc.pid })
|
|
|
|
status('Profiling')
|
|
|
|
const handleExit = () => {
|
|
spawn('sudo', ['kill', '-SIGINT', '' + proc.pid], {
|
|
stdio: 'inherit'
|
|
})
|
|
}
|
|
|
|
for (let i = 0; i < SOFT_EXIT_SIGNALS.length; i++) {
|
|
process.once(SOFT_EXIT_SIGNALS[i], handleExit)
|
|
}
|
|
|
|
function analyze (manual) {
|
|
if (analyze.called) { return }
|
|
analyze.called = true
|
|
|
|
if (!manual) {
|
|
debug('Caught SOFT_EXIT_SIGNAL, generating flamegraph')
|
|
status('Caught SOFT_EXIT_SIGNAL, generating flamegraph')
|
|
proc.on('exit', generate)
|
|
} else {
|
|
debug('Process exited, generating flamegraph')
|
|
status('Process exited, generating flamegraph')
|
|
generate()
|
|
}
|
|
|
|
function generate () {
|
|
const stacks = spawn('sudo', ['perf', 'script', '-i', perfdat], {
|
|
stdio: [
|
|
'ignore',
|
|
fs.openSync(folder + '/stacks.' + proc.pid + '.out', 'w'),
|
|
kernelTracingDebug ? process.stderr : 'ignore'
|
|
]
|
|
})
|
|
|
|
stacks.on('exit', function () {
|
|
cb(null, {
|
|
ticks: traceStacksToTicks(folder + '/stacks.' + proc.pid + '.out'),
|
|
pid: proc.pid,
|
|
folder,
|
|
// TODO: Inlined functions through linux_perf was not implemented yet
|
|
inlined: []
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
function filterInternalFunctions (perfFile) {
|
|
// Filtering out Node.js internal functions
|
|
const sed = spawn('sudo', [
|
|
'sed',
|
|
'-i',
|
|
'-e',
|
|
'/( __libc_start| LazyCompile | v8::internal::| Builtin:| Stub:| LoadIC:|[unknown]| LoadPolymorphicIC:)/d',
|
|
'-e',
|
|
's/ LazyCompile:[*~]?/ /',
|
|
perfFile
|
|
], {
|
|
stdio: ['ignore', 'inherit', 'inherit', 'ignore', 'ignore', 'pipe']
|
|
})
|
|
|
|
sed.on('exit', function (code) {
|
|
if (code !== null && code !== 0) {
|
|
console.error('`sed` subprocess error, code: ' + code)
|
|
return
|
|
}
|
|
analyze(true)
|
|
})
|
|
}
|
|
}
|