guvnor
Version:
A node process manager that isn't spanners all the way down
226 lines (177 loc) • 7.61 kB
JavaScript
var Autowire = require('wantsit').Autowire
var util = require('util')
var Actions = require('./Actions')
var Table = require('./Table')
var path = require('path')
var Processes = function () {
Actions.call(this)
this._moment = Autowire
this._formatMemory = Autowire
this._fs = Autowire
}
util.inherits(Processes, Actions)
Processes.prototype.list = function (options) {
this._do(options, function (guvnor) {
guvnor.listProcesses(function (error, processes) {
if (error) {
throw error
}
var table = new Table('No running processes')
table.addHeader(['PID', 'User', 'Group', 'Name', 'Uptime', 'Restarts', 'CPU', 'RSS', 'Heap size', 'Heap used', 'Status', 'Type'])
var addProcessToTable = function (proc, type) {
if (!proc) {
return table.addRow(['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', type])
}
var pid = proc.pid == null ? '?' : proc.pid
var user = proc.user == null ? '?' : proc.user
var group = proc.group == null ? '?' : proc.group
var name = proc.name == null ? '?' : proc.name
var uptime = proc.uptime == null || isNaN(proc.uptime) ? '?' : this._moment.duration(proc.uptime * 1000).humanize()
var restarts = proc.restarts == null ? '?' : proc.restarts
var rss = proc.residentSize == null || isNaN(proc.residentSize) ? '?' : this._formatMemory(proc.residentSize, true)
var heapTotal = proc.heapTotal == null || isNaN(proc.heapTotal) ? '?' : this._formatMemory(proc.heapTotal, true)
var heapUsed = proc.heapUsed == null || isNaN(proc.heapUsed) ? '?' : this._formatMemory(proc.heapUsed, true)
var cpu = proc.cpu == null || isNaN(proc.cpu) ? '?' : proc.cpu.toFixed(2)
var status = proc.status == null ? '?' : proc.status
table.addRow([pid, user, group, name, uptime, restarts, cpu, rss, heapTotal, heapUsed, status, type])
}.bind(this)
processes.forEach(function (proc) {
addProcessToTable(proc, proc.cluster ? 'Manager' : 'Process')
if (proc.cluster && proc.workers) {
proc.workers.forEach(function (worker) {
addProcessToTable(worker, 'Worker')
})
}
})
table.print(console.info)
guvnor.disconnect()
}.bind(this))
}.bind(this))
}
Processes.prototype.start = function (scriptOrAppName, options) {
var script = path.resolve(scriptOrAppName)
if (!this._fs.existsSync(script)) {
script = scriptOrAppName
}
var opts = this._parseStartProcessOpts(options)
var method = this._do.bind(this)
var operation = 'startProcess'
if (options.user && options.user !== this._user.name) {
this._logger.debug('Using admin socket to start process because', options.user, '!=', this._user.name)
operation = 'startProcessAsUser'
method = this._doAdmin.bind(this, operation)
}
method(options, function (guvnor) {
this._logger.debug('Starting process', script, opts)
guvnor[operation](script, opts, function (error, processInfo) {
if (error) {
this._logger.error('Failed to start %s', script)
this._logger.error(error)
return guvnor.disconnect()
}
if (processInfo.status === 'paused') {
this._logger.warn('%s%s has been started in debug mode.', processInfo.cluster ? 'The cluster manager for' : '', processInfo.name)
this._logger.warn('It is paused and listening on port %d for a debugger to attach before continuing.', processInfo.debugPort)
this._logger.warn('Please connect a debugger to this port (e.g. node-inspector or node-debugger).')
}
processInfo.once('process:uncaughtexception', function (error) {
this._logger.warn('%s encountered %s', processInfo.name, error.message || error.stack)
}.bind(this))
processInfo.once('cluster:online', function () {
this._logger.debug('%s started with pid %d', script, processInfo.pid)
guvnor.disconnect()
}.bind(this))
processInfo.once('process:ready', function () {
this._logger.debug('%s started with pid %d', script, processInfo.pid)
guvnor.disconnect()
}.bind(this))
processInfo.once('process:aborted', function () {
this._logger.error('%s failed to start', processInfo.name)
guvnor.disconnect()
}.bind(this))
processInfo.once('process:errored', function (error) {
this._logger.error(error.stack ? error.stack : error.message)
})
}.bind(this))
}.bind(this))
}
Processes.prototype.stop = function (pidOrNames, options, callback) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Killing remote process', managedProcess.name)
managedProcess.kill(function (error) {
if (!error) {
return done()
}
if (error && error.message === 'No socket defined') {
guvnor.stopProcess(managedProcess.id, function (error) {
if (error && error.code === 'EPERM') {
this._logger.error(error.message)
}
done(error)
}.bind(this))
} else {
done(error)
}
}.bind(this))
}.bind(this), callback)
}
Processes.prototype.restart = function (pidOrNames, options) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Restarting remote process', managedProcess.name)
managedProcess.restart(done)
}.bind(this))
}
Processes.prototype.remove = function (pidOrNames, options) {
// first stop any running processes
this.stop(pidOrNames, options, function () {
// then remove them
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Removing remote process ' + managedProcess.name)
guvnor.removeProcess(managedProcess.id, done)
}.bind(this))
}.bind(this))
}
Processes.prototype.send = function (pidOrNames, event, args, options) {
if (!args) {
args = []
}
args = [event].concat(args)
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Sending event to remote process', args)
managedProcess.send.apply(managedProcess, args.concat(done))
}.bind(this))
}
Processes.prototype.signal = function (pidOrNames, signal, options) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Sending signal %s to %d', signal, pidOrNames)
managedProcess.signal(signal, done)
}.bind(this))
}
Processes.prototype.heapdump = function (pidOrNames, options) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Writing heap dump')
managedProcess.dumpHeap(function (error, snapshot) {
if (!error) {
console.info('Written heap dump to %s', snapshot.path)
}
done(error)
})
}.bind(this))
}
Processes.prototype.gc = function (pidOrNames, options) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Garbage collecting')
managedProcess.forceGc(done)
}.bind(this))
}
Processes.prototype.startWebMonitor = function (options) {
options.name = 'guvnor-web'
this.start(path.resolve(__dirname + '/../web'), options)
}
Processes.prototype.write = function (pidOrNames, string, options) {
this._withEach(pidOrNames, options, function (managedProcess, guvnor, done) {
this._logger.debug('Writing string', string)
managedProcess.write(string, done)
}.bind(this))
}
module.exports = Processes