diff --git a/.gitignore b/.gitignore index c2795f601..c28b0b9a5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ yarn.lock *.tar.gz e2e_time unit_time +*.heapprofile diff --git a/bin/pm2 b/bin/pm2 index 9eaea2c84..fc1c216cd 100755 --- a/bin/pm2 +++ b/bin/pm2 @@ -373,19 +373,19 @@ commander.command('scale ') // // snapshot PM2 // -commander.command('snapshot') - .description('snapshot PM2 memory') - .action(function() { - pm2.snapshotPM2(); +commander.command('profile:mem [time]') + .description('Sample PM2 heap memory') + .action(function(time) { + pm2.profile('mem', time); }); // // snapshot PM2 // -commander.command('profile ') - .description('profile CPU') - .action(function(command) { - pm2.profilePM2(command); +commander.command('profile:cpu [time]') + .description('Profile PM2 cpu') + .action(function(time) { + pm2.profile('cpu', time); }); // diff --git a/lib/API/Extra.js b/lib/API/Extra.js index d2b694c88..22d5efa60 100644 --- a/lib/API/Extra.js +++ b/lib/API/Extra.js @@ -168,64 +168,48 @@ module.exports = function(CLI) { return cb(null, pids); }) } + /** * Create PM2 memory snapshot * @method getVersion * @callback cb */ - CLI.prototype.snapshotPM2 = function(cb) { + CLI.prototype.profile = function(type, time, cb) { var that = this; var moment = require('moment'); - var file = path.join(process.cwd(), moment().format('dd-HH:mm:ss') + '.heapsnapshot'); + var cmd + + if (type == 'cpu') { + cmd = { + ext: '.cpuprofile', + action: 'profileCPU' + } + } + if (type == 'mem') { + cmd = { + ext: '.heapprofile', + action: 'profileMEM' + } + } + + var file = path.join(process.cwd(), moment().format('dd-HH:mm:ss') + cmd.ext); + time = time || 10000 - that.Client.executeRemote('snapshotPM2', { - pwd : file + console.log(`Starting ${cmd.action} profiling for ${time}ms...`) + that.Client.executeRemote(cmd.action, { + pwd : file, + timeout: time }, function(err) { if (err) { console.error(err); return that.exitCli(1); } - console.log('Heapdump in %s', file); + console.log(`Profile done in ${file}`) return cb ? cb.apply(null, arguments) : that.exitCli(cst.SUCCESS_EXIT); }); }; - /** - * Create PM2 memory snapshot - * @method getVersion - * @callback cb - */ - CLI.prototype.profilePM2 = function(command, cb) { - var that = this; - var moment = require('moment'); - var file = path.join(process.cwd(), moment().format('dd-HH:mm:ss') + '.cpuprofile'); - - if (command == 'start') { - that.Client.executeRemote('profileStart', { - }, function(err) { - if (err) { - console.error(err); - return that.exitCli(1); - } - console.log('CPU profiling started, type pm2 profile stop once finished'); - return cb ? cb.apply(null, arguments) : that.exitCli(cst.SUCCESS_EXIT); - }); - } - else if (command == 'stop') { - that.Client.executeRemote('profileStop', { - pwd : file - }, function(err) { - if (err) { - console.error(err); - return that.exitCli(1); - } - console.log('CPU profile in %s', file); - return cb ? cb.apply(null, arguments) : that.exitCli(cst.SUCCESS_EXIT); - }); - } - }; - /** * Description * @method sendLineToStdin diff --git a/lib/Daemon.js b/lib/Daemon.js index b6f9eb88f..d9f57dbda 100644 --- a/lib/Daemon.js +++ b/lib/Daemon.js @@ -151,80 +151,69 @@ Daemon.prototype.innerStart = function(cb) { that.sendReady(cb); }); - var profiler; - - try { - profiler = semver.satisfies(process.version, '>= 10.0.0') ? require('inspector') : null; - } catch(e) { - profiler = null; - } /** * Memory Snapshot */ - function snapshotPM2(msg, cb) { - if (profiler == null) { - console.log('Heap snapshot is not available (node 10+)'); - return cb(new Error('Heap snapshot is not available (node 10+)')); + function profile(type, msg, cb) { + if (semver.satisfies(process.version, '< 8')) + return cb(null, { error: 'Node.js is not on right version' }) + + var cmd + + if (type === 'cpu') { + cmd = { + enable: 'Profiler.enable', + start: 'Profiler.start', + stop: 'Profiler.stop', + disable: 'Profiler.disable' + } + } + if (type == 'mem') { + cmd = { + enable: 'HeapProfiler.enable', + start: 'HeapProfiler.startSampling', + stop: 'HeapProfiler.stopSampling', + disable: 'HeapProfiler.disable' + } } - const session = new profiler.Session() - session.connect() - session.post('HeapProfiler.enable') - - const chunks = [] - session.on('HeapProfiler.addHeapSnapshotChunk', (data) => { - chunks.push(data.params.chunk) - }) - - session.post('HeapProfiler.takeHeapSnapshot', { - reportProgress: false - }, (err, data) => { - if (err) return cb(err) - - fs.writeFile(msg.pwd, chunks.join(''), function() { - session.post('Profiler.disable') - session.disconnect() + const inspector = require('inspector') + var session = new inspector.Session() - return cb(null, {file : msg.pwd}); - }); - }) - } + session.connect() - function startProfilingPM2(msg, cb) { - if (profiler == null) { - console.log('v8-profiler is not available'); - return cb(new Error('v8-profiler is not available')); - } + var timeout = msg.timeout || 5000 - profiler.startProfiling('cpu'); + session.post(cmd.enable, (err, data) => { + if (err) return cb(null, { error: err.message || err }) - process.nextTick(function() { - return cb(null, {msg : 'profiling started'}); - }); - } + console.log(`Starting ${cmd.start}`) + session.post(cmd.start, (err, data) => { + if (err) return cb(null, { error: err.message || err }) - function stopProfilingPM2(msg, cb) { - if (profiler == null) { - console.log('v8-profiler is not available'); - return cb(new Error('v8-profiler is not available')); - } + setTimeout(() => { + session.post(cmd.stop, (err, data) => { + if (err) return cb(null, { error: err.message || err }) + const profile = data.profile - var profile1 = profiler.stopProfiling('cpu'); + console.log(`Stopping ${cmd.stop}`) + session.post(cmd.disable) - profile1.export() - .pipe(fs.createWriteStream(msg.pwd)) - .on('finish', function() { - profile1.delete(); - return cb(null, {file : msg.pwd}); - }); + fs.writeFile(msg.pwd, JSON.stringify(profile), (err) => { + if (err) return cb(null, { error: err.message || err }) + return cb(null, { file : msg.pwd }) + }) + }) + }, timeout) + }) + }) } server.expose({ killMe : that.close.bind(this), - snapshotPM2 : snapshotPM2, - profileStart : startProfilingPM2, - profileStop : stopProfilingPM2, + profileCPU : profile.bind(this, 'cpu'), + profileMEM : profile.bind(this, 'mem'), prepare : God.prepare, getMonitorData : God.getMonitorData, getSystemData : God.getSystemData,