feat: add pm2 profile:cpu / pm2 profile:mem

This commit is contained in:
Unitech 2019-02-18 22:18:52 +01:00
parent 2a01e5c4e3
commit f57a6aeeed
4 changed files with 83 additions and 109 deletions

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ yarn.lock
*.tar.gz
e2e_time
unit_time
*.heapprofile

16
bin/pm2
View File

@ -373,19 +373,19 @@ commander.command('scale <app_name> <number>')
//
// 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 <command>')
.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);
});
//

View File

@ -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
that.Client.executeRemote('snapshotPM2', {
pwd : file
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
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

View File

@ -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()
const inspector = require('inspector')
var session = new inspector.Session()
session.connect()
session.post('HeapProfiler.enable')
const chunks = []
session.on('HeapProfiler.addHeapSnapshotChunk', (data) => {
chunks.push(data.params.chunk)
var timeout = msg.timeout || 5000
session.post(cmd.enable, (err, data) => {
if (err) return cb(null, { error: err.message || err })
console.log(`Starting ${cmd.start}`)
session.post(cmd.start, (err, data) => {
if (err) return cb(null, { error: err.message || err })
setTimeout(() => {
session.post(cmd.stop, (err, data) => {
if (err) return cb(null, { error: err.message || err })
const profile = data.profile
console.log(`Stopping ${cmd.stop}`)
session.post(cmd.disable)
fs.writeFile(msg.pwd, JSON.stringify(profile), (err) => {
if (err) return cb(null, { error: err.message || err })
return cb(null, { file : msg.pwd })
})
})
}, timeout)
})
})
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()
return cb(null, {file : msg.pwd});
});
})
}
function startProfilingPM2(msg, cb) {
if (profiler == null) {
console.log('v8-profiler is not available');
return cb(new Error('v8-profiler is not available'));
}
profiler.startProfiling('cpu');
process.nextTick(function() {
return cb(null, {msg : 'profiling started'});
});
}
function stopProfilingPM2(msg, cb) {
if (profiler == null) {
console.log('v8-profiler is not available');
return cb(new Error('v8-profiler is not available'));
}
var profile1 = profiler.stopProfiling('cpu');
profile1.export()
.pipe(fs.createWriteStream(msg.pwd))
.on('finish', function() {
profile1.delete();
return cb(null, {file : msg.pwd});
});
}
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,