0x/platform/linux.js
Matteo Collina eeb0f2d2c6
Updated deps and added CI (#246)
* Updated deps and added CI

* fixes
2021-12-30 12:31:29 +01:00

141 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,
spawnOnPort,
when
} = require('../lib/util')
module.exports = promisify(linux)
function linux (args, sudo, cb) {
const { status, outputDir, workingDir, name, onPort, pathToNodeBinary } = args
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'),
...(onPort ? ['-r', path.join(__dirname, '..', 'lib', 'preload', 'detect-port.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 })
if (onPort) status('Profiling\n')
else status('Profiling')
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', () => {
spawn('sudo', ['kill', '-SIGINT', '' + proc.pid], {
stdio: 'inherit'
})
})
function analyze (manual) {
if (analyze.called) { return }
analyze.called = true
if (!manual) {
debug('Caught SIGINT, generating flamegraph')
status('Caught SIGINT, 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: folder,
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)
})
}
}