mirror of
https://github.com/davidmarkclements/0x.git
synced 2026-01-18 14:38:23 +00:00
164 lines
4.4 KiB
JavaScript
164 lines
4.4 KiB
JavaScript
'use strict'
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const spawn = require('child_process').spawn
|
|
const pump = require('pump')
|
|
const sym = require('perf-sym')
|
|
const debug = require('debug')('0x')
|
|
const traceStacksToTicks = require('../lib/trace-stacks-to-ticks')
|
|
const { promisify } = require('util')
|
|
const {
|
|
getTargetFolder,
|
|
tidy,
|
|
pathTo,
|
|
spawnOnPort,
|
|
when
|
|
} = require('../lib/util')
|
|
|
|
module.exports = promisify(sun)
|
|
|
|
function sun (args, sudo, binary, cb) {
|
|
const { status, outputDir, workingDir, name, onPort } = args
|
|
|
|
var dtrace = pathTo('dtrace')
|
|
var profile = require.resolve('perf-sym/profile_1ms.d')
|
|
if (!dtrace) return void cb(Error('Unable to locate dtrace - make sure it\'s in your PATH'))
|
|
if (!sudo) {
|
|
status('Stacks are captured using DTrace, which requires sudo access\n')
|
|
return spawn('sudo', ['true'])
|
|
.on('exit', function () { sun(args, true, binary, cb) })
|
|
}
|
|
var node = !binary || binary === 'node' ? pathTo('node') : binary
|
|
var kernelTracingDebug = args.kernelTracingDebug
|
|
|
|
args = Object.assign([
|
|
'--perf-basic-prof',
|
|
'-r', path.join(__dirname, '..', 'lib', 'preload', 'soft-exit'),
|
|
...(onPort ? ['-r', path.join(__dirname, '..', 'lib', 'preload', 'detect-port.js')] : [])
|
|
].concat(args.argv), args)
|
|
|
|
var proc = spawn(node, args, {
|
|
stdio: ['ignore', 'inherit', 'inherit', 'ignore', 'ignore', 'pipe']
|
|
}).on('exit', function (code) {
|
|
if (code !== 0) {
|
|
tidy()
|
|
const err = Error('Target subprocess error, code: ' + code)
|
|
err.code = code
|
|
cb(err)
|
|
return
|
|
}
|
|
analyze(true)
|
|
})
|
|
var folder
|
|
var prof
|
|
var profExited = false
|
|
|
|
function start () {
|
|
prof = spawn('sudo', [profile, '-p', proc.pid])
|
|
|
|
if (kernelTracingDebug) { prof.stderr.pipe(process.stderr) }
|
|
|
|
folder = getTargetFolder({outputDir, workingDir, name, pid: proc.pid})
|
|
|
|
prof.on('exit', function (code) {
|
|
profExited = true
|
|
})
|
|
|
|
pump(
|
|
prof.stdout,
|
|
fs.createWriteStream(path.join(folder, '.stacks.' + proc.pid + '.out')),
|
|
function (err) {
|
|
if (err) {
|
|
cb(err)
|
|
return
|
|
}
|
|
debug('dtrace out closed')
|
|
})
|
|
}
|
|
|
|
if (onPort) status('Profiling\n')
|
|
else status('Profiling')
|
|
|
|
start()
|
|
|
|
if (onPort) when(proc.stdio[5], 'data').then((port) => {
|
|
const whenPort = spawnOnPort(onPort, port)
|
|
whenPort.then(() => proc.kill('SIGINT'))
|
|
whenPort.catch((err) => {
|
|
proc.kill()
|
|
cb(err)
|
|
})
|
|
})
|
|
|
|
process.once('SIGINT', analyze)
|
|
|
|
function analyze (manual) {
|
|
if (analyze.called) { return }
|
|
analyze.called = true
|
|
|
|
if (!prof) {
|
|
debug('Profiling not begun')
|
|
status('No stacks, profiling had not begun\n')
|
|
tidy()
|
|
cb(Error('Profiling not begun'))
|
|
return
|
|
}
|
|
|
|
if (!manual) {
|
|
debug('Caught SIGINT, generating flamegraph')
|
|
status('Caught SIGINT, generating flamegraph')
|
|
}
|
|
|
|
try { process.kill(proc.pid, 'SIGINT') } catch (e) {}
|
|
try { process.kill(prof.pid, 'SIGINT') } catch (e) {}
|
|
|
|
capture(10)
|
|
function capture (attempts) {
|
|
if (!profExited) {
|
|
if (attempts) {
|
|
if (attempts < 5) {
|
|
// desperate, killing prof process
|
|
try { process.kill(prof.pid, 'SIGKILL') } catch (e) {}
|
|
}
|
|
setTimeout(capture, 300, attempts - 1)
|
|
} else {
|
|
status('Unable to find map file!\n')
|
|
debug('Unable to find map file after multiple attempts')
|
|
tidy()
|
|
cb(Error('0x: Unable to find map file'))
|
|
return
|
|
}
|
|
return
|
|
}
|
|
var translate = sym({silent: true, pid: proc.pid})
|
|
|
|
if (!translate) {
|
|
debug('unable to find map file')
|
|
if (attempts) {
|
|
status('Unable to find map file - waiting 300ms and retrying\n')
|
|
debug('retrying')
|
|
setTimeout(capture, 300, attempts--)
|
|
return
|
|
}
|
|
debug('Unable to find map file after multiple attempts')
|
|
tidy()
|
|
cb(Error('Unable to find map file'))
|
|
return
|
|
}
|
|
pump(
|
|
fs.createReadStream(folder + '/.stacks.' + proc.pid + '.out'),
|
|
translate,
|
|
fs.createWriteStream(folder + '/stacks.' + proc.pid + '.out'),
|
|
(err) => {
|
|
if (err) return void cb(err)
|
|
cb(null, {
|
|
ticks: traceStacksToTicks(folder + '/stacks.' + proc.pid + '.out'),
|
|
pid: proc.pid,
|
|
folder: folder
|
|
})
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|