0x/platform/linux.js
Matteo Collina d986894c5d
updated linting
Signed-off-by: Matteo Collina <hello@matteocollina.com>
2025-06-17 17:57:29 +02:00

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)
})
}
}