pm2
Version:
Production process manager for Node.JS applications with a built-in load balancer.
828 lines (703 loc) • 24.2 kB
JavaScript
/**
* Copyright 2013 the PM2 project authors. All rights reserved.
* Use of this source code is governed by a license that
* can be found in the LICENSE file.
*/
var Table = require('cli-table-redemption');
var p = require('path');
var chalk = require('chalk');
var Common = require('../Common');
var Spinner = require('./Spinner.js');
var UX = module.exports = {};
var Passwd = require('../tools/passwd.js')
var Configuration = require('../Configuration.js')
/**
* Description
* @method miniDisplay
* @param {} list
* @return
*/
UX.miniDisplay = function(list) {
list.forEach(function(l) {
var mode = l.pm2_env.exec_mode.split('_mode')[0];
var status = l.pm2_env.status;
var key = l.pm2_env.name || p.basename(l.pm2_env.pm_exec_path.script);
console.log('+--- %s', key);
console.log('version : %s', l.pm2_env.version);
console.log('pid : %s', l.pid);
console.log('pm2 id : %s', l.pm2_env.pm_id);
console.log('status : %s', status);
console.log('mode : %s', mode);
console.log('restarted : %d', l.pm2_env.restart_time ? l.pm2_env.restart_time : 0);
console.log('uptime : %s', (l.pm2_env.pm_uptime && status == 'online') ? timeSince(l.pm2_env.pm_uptime) : 0);
console.log('memory usage : %s', l.monit ? UX.bytesToSize(l.monit.memory, 1) : '');
console.log('error log : %s', l.pm2_env.pm_err_log_path);
console.log('watching : %s', l.pm2_env.watch ? 'yes' : 'no');
console.log('PID file : %s\n', l.pm2_env.pm_pid_path);
});
};
UX.postModuleInfos = function(module_name, human_info) {
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
var disp = {};
human_info.unshift(['Module name', module_name]);
human_info.forEach(function(info) {
var obj = {};
obj[chalk.bold.cyan(info[0])] = info[1];
table.push(obj);
});
console.log();
console.log(chalk.bold.inverse(' Module %s infos '), module_name);
console.log(table.toString());
};
/**
* Description
* @method describeTable
* @param {} process
* @return
*/
UX.describeTable = function(proc) {
var table = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
var pm2_env = proc.pm2_env;
var created_at = 'N/A';
if (pm2_env.axm_options && pm2_env.axm_options.human_info) {
this.postModuleInfos(pm2_env.name, pm2_env.axm_options.human_info);
}
try {
if (pm2_env.created_at != null)
created_at = new Date(pm2_env.created_at).toISOString();
} catch (e) {
//throw new Error(pm2_env.created_at + ' is not a valid date: '+e.message, e.fileName, e.lineNumber);
}
console.log(chalk.bold.inverse(' Describing process with id %d - name %s '), pm2_env.pm_id, pm2_env.name);
safe_push(table,
{ 'status' : colorStatus(pm2_env.status) },
{ 'name': pm2_env.name },
{ 'version': pm2_env.version },
{ 'restarts' : pm2_env.restart_time },
{ 'uptime' : (pm2_env.pm_uptime && pm2_env.status == 'online') ? timeSince(pm2_env.pm_uptime) : 0 },
{ 'script path' : pm2_env.pm_exec_path },
{ 'script args' : pm2_env.args ? (typeof pm2_env.args == 'string' ? JSON.parse(pm2_env.args.replace(/'/g, '"')):pm2_env.args).join(' ') : null },
{ 'error log path' : pm2_env.pm_err_log_path },
{ 'out log path' : pm2_env.pm_out_log_path },
{ 'pid path' : pm2_env.pm_pid_path },
{ 'interpreter' : pm2_env.exec_interpreter },
{ 'interpreter args' : pm2_env.node_args.length != 0 ? pm2_env.node_args : null },
{ 'script id' : pm2_env.pm_id },
{ 'exec cwd' : pm2_env.pm_cwd },
{ 'exec mode' : pm2_env.exec_mode },
{ 'node.js version' : pm2_env.node_version },
{ 'node env': pm2_env.env.NODE_ENV },
{ 'watch & reload' : pm2_env.watch ? chalk.green.bold('✔') : '✘' },
{ 'unstable restarts' : pm2_env.unstable_restarts },
{ 'created at' : created_at }
);
if ('pm_log_path' in pm2_env){
table.splice(6, 0, {'entire log path': pm2_env.pm_log_path});
}
if ('cron_restart' in pm2_env){
table.splice(5, 0, {'cron restart': pm2_env.cron_restart});
}
console.log(table.toString());
/**
* Module conf display
*/
if (pm2_env.axm_options &&
pm2_env.axm_options.module_conf &&
Object.keys(pm2_env.axm_options.module_conf).length > 0) {
var table_conf = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
console.log('Process configuration');
Object.keys(pm2_env.axm_options.module_conf).forEach(function(key) {
var tmp = {};
tmp[key] = pm2_env.axm_options.module_conf[key];
safe_push(table_conf, tmp);
});
console.log(table_conf.toString());
}
/**
* Versioning metadata
*/
if (pm2_env.versioning) {
var table2 = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
console.log(chalk.inverse.bold(' Revision control metadata '));
safe_push(table2,
{ 'revision control' : pm2_env.versioning.type },
{ 'remote url' : pm2_env.versioning.url },
{ 'repository root' : pm2_env.versioning.repo_path },
{ 'last update' : pm2_env.versioning.update_time },
{ 'revision' : pm2_env.versioning.revision },
{ 'comment' : pm2_env.versioning.comment ? pm2_env.versioning.comment.trim().slice(0, 60) : '' },
{ 'branch' : pm2_env.versioning.branch }
);
console.log(table2.toString());
}
if (pm2_env.axm_actions && Object.keys(pm2_env.axm_actions).length > 0) {
var table_actions = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
console.log(chalk.inverse.bold(' Actions available '));
pm2_env.axm_actions.forEach(function(action_set) {
safe_push(table_actions, [action_set.action_name]);
});
console.log(table_actions.toString());
Common.printOut(chalk.white.italic(' Trigger via: pm2 trigger %s <action_name>\n'), pm2_env.name);
}
if (pm2_env.axm_monitor && Object.keys(pm2_env.axm_monitor).length > 0) {
var table_probes = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
console.log(chalk.inverse.bold(' Code metrics value '));
Object.keys(pm2_env.axm_monitor).forEach(function(key) {
var obj = {};
var metric_name = pm2_env.axm_monitor[key].hasOwnProperty("value") ? pm2_env.axm_monitor[key].value : pm2_env.axm_monitor[key]
var metric_unit = pm2_env.axm_monitor[key].hasOwnProperty("unit") ? pm2_env.axm_monitor[key].unit : ''
var value = `${metric_name} ${metric_unit}`;
obj[key] = value;
safe_push(table_probes, obj);
});
console.log(table_probes.toString());
}
var table_env = new Table({
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
console.log(chalk.inverse.bold(' Divergent env variables from local env '));
var _env = Common.safeExtend({}, pm2_env)
var diff_env = {}
Object.keys(process.env).forEach(k => {
if (!_env[k] || _env[k] != process.env[k]) {
diff_env[k] = process.env[k]
}
})
Object.keys(diff_env).forEach(function(key) {
var obj = {};
if (_env[key]) {
obj[key] = _env[key].slice(0, process.stdout.columns - 60);
safe_push(table_env, obj);
}
});
console.log(table_env.toString());
console.log()
Common.printOut(chalk.white.italic(' Add your own code metrics: http://bit.ly/code-metrics'));
Common.printOut(chalk.white.italic(' Use `pm2 logs %s [--lines 1000]` to display logs'), pm2_env.name);
Common.printOut(chalk.white.italic(' Use `pm2 env %s` to display environement variables'), pm2_env.pm_id);
Common.printOut(chalk.white.italic(' Use `pm2 monit` to monitor CPU and Memory usage'), pm2_env.name);
};
/**
* Description
* @method dispAsTable
* @param {} list
* @param {} commander
* @return
*/
UX.dispAsTable = function(list, sys_infos) {
var pm2_conf = Configuration.getSync('pm2')
var condensed_mode = (process.stdout.columns || 300) < 120
var app_head = {
id: 4,
name: 25,
version: 9,
mode: 9,
pid: 10,
uptime: 8,
'↺': 6,
status: 10,
cpu: 10,
mem: 10,
user: 10,
watching: 10
}
var mod_head = {
id: 4,
module: 39,
version: 20,
pid: 7,
status: 10,
'↺': 6,
cpu: 10,
mem: 10,
user: 10
}
if (condensed_mode) {
app_head = {
id: 4,
name: 20,
mode: 10,
'↺': 6,
status: 10,
cpu: 10,
memory: 10
}
mod_head = {
id: 4,
name: 20,
status: 10,
cpu: 10,
mem: 10
}
}
var proc_id = 0
var app_table = new Table({
head : Object.keys(app_head),
colWidths: Object.keys(app_head).map(k => app_head[k]),
colAligns : ['left'],
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
var module_table = new Table({
head : Object.keys(mod_head),
colWidths: Object.keys(mod_head).map(k => mod_head[k]),
colAligns : ['left'],
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
if (!list)
return console.log('list empty');
var sortField = 'name', sortOrder = 'asc', sort,
fields = {
name: 'pm2_env.name',
pid: 'pid',
id: 'pm_id',
cpu: 'monit.cpu',
memory: 'monit.memory',
uptime: 'pm2_env.pm_uptime',
status: 'pm2_env.status'
};
list.sort(function(a, b) {
var fieldA = getNestedProperty(fields[sortField], a);
var fieldB = getNestedProperty(fields[sortField], b);
if(sortOrder === 'desc') {
if (fieldA > fieldB)
return -1;
if (fieldA < fieldB)
return 1;
} else {
if (fieldA < fieldB)
return -1;
if (fieldA > fieldB)
return 1;
}
return 0;
});
list.forEach(function(l) {
var obj = {};
if (l.pm2_env.pm_id > proc_id) {
proc_id = l.pm2_env.pm_id
}
var mode = l.pm2_env.exec_mode;
var status = l.pm2_env.status;
var key = l.pm2_env.pm_id
key = chalk.bold.cyan(key);
if (l.pm2_env.axm_options) {
var is_tracing_enabled = false
if (l.pm2_env.axm_options.tracing &&
typeof(l.pm2_env.axm_options.tracing) == 'boolean' &&
l.pm2_env.axm_options.tracing == true)
is_tracing_enabled = true
if (l.pm2_env.axm_options.tracing &&
l.pm2_env.axm_options.tracing.enabled &&
typeof(l.pm2_env.axm_options.tracing.enabled) == 'boolean' &&
l.pm2_env.axm_options.tracing.enabled == true)
is_tracing_enabled = true
if (is_tracing_enabled == true)
l.pm2_env.name = chalk.green('☵') + ' ' + l.pm2_env.name
if (l.pm2_env._km_monitored)
l.pm2_env.name = chalk.bold.green('◉') + ' ' + l.pm2_env.name
}
if (l.pm2_env.pmx_module == true) {
// pm2 ls for Modules
obj[key] = [];
obj[key].push(l.name)
// Module version + PID
if (!condensed_mode) {
var pid = l.pm2_env.axm_options.pid ? l.pm2_env.axm_options.pid : l.pid
obj[key].push(l.pm2_env.version || 'N/A', pid);
}
// Status
obj[key].push(colorStatus(status));
// Restart
if (!condensed_mode)
obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0);
// CPU + Memory
obj[key].push(l.monit ? (l.monit.cpu + '%') : 'N/A', l.monit ? UX.bytesToSize(l.monit.memory, 1) : 'N/A' );
// User
if (!condensed_mode) {
if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') {
// Resolve user id to username
let users = Passwd.getUsers()
Object.keys(users).forEach(function(username) {
var user = users[username]
if (user.userId == l.pm2_env.uid) {
l.pm2_env.uid = user.name
}
})
}
obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username));
}
safe_push(module_table, obj);
}
else {
// pm2 ls for Applications
obj[key] = [];
// PM2 ID
obj[key].push(l.pm2_env.name);
// Version
if (!condensed_mode)
obj[key].push(l.pm2_env.version);
// Exec mode
obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster'));
// PID
if (!condensed_mode)
obj[key].push(l.pid);
// Uptime
if (!condensed_mode)
obj[key].push((l.pm2_env.pm_uptime && status == 'online') ? timeSince(l.pm2_env.pm_uptime) : 0);
// Restart
obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0);
// Status
obj[key].push(colorStatus(status));
// CPU
obj[key].push(l.monit ? l.monit.cpu + '%' : 'N/A');
// Memory
obj[key].push(l.monit ? UX.bytesToSize(l.monit.memory, 1) : 'N/A');
// User
if (!condensed_mode) {
if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') {
// Resolve user id to username
let users = Passwd.getUsers()
Object.keys(users).forEach(function(username) {
var user = users[username]
if (user.userId == l.pm2_env.uid) {
l.pm2_env.uid = user.name
}
})
}
obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username));
}
// Watch status
if (!condensed_mode)
obj[key].push(l.pm2_env.watch ? chalk.green.bold('enabled') : chalk.grey('disabled'));
safe_push(app_table, obj);
}
});
console.log(app_table.toString());
proc_id++
// Container display
if (sys_infos && sys_infos.containers && sys_infos.containers.length > 0 &&
(pm2_conf && pm2_conf.show_docker == "true")) {
var stacked_docker = (process.stdout.columns || 100) < 140
var docker_head = {
id: 4,
image: 50,
status: 10,
'↺': 6,
cpu: 10,
mem: 10,
'net I/O ⇵': 11,
'fs I/O ⇵': 11
}
if (stacked_docker) {
docker_head = {
id: 4,
image: 25,
status: 10,
cpu: 10,
mem: 10
}
}
var docker_table = new Table({
colWidths: Object.keys(docker_head).map(k => docker_head[k]),
head : Object.keys(docker_head),
colAligns : ['left'],
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
sys_infos.containers.forEach((c) => {
var cpu = c.stats.cpu_percent
var mem = c.stats.mem_percent == 0 ? '0' : c.stats.mem_percent
var id = chalk.bold.cyan(proc_id++)
var state = colorStatus(c.state)
if (stacked_docker)
docker_table.push([id, c.image, state, `${cpu}%`, `${mem}mb`])
else {
docker_table.push([
id,
c.image,
state,
c.restartCount,
`${cpu == 0 ? '0' : cpu}%`,
`${mem}mb`,
`${c.stats.netIO.rx}/${isNaN(c.stats.netIO.tx) == true ? '0.0' : c.stats.netIO.tx}`,
`${c.stats.blockIO.r}/${c.stats.blockIO.w}`
])
}
})
console.log(chalk.bold(`Container${sys_infos.containers.length > 1 ? 's' : ''}`))
console.log(docker_table.toString());
}
/**
* High resource processes
*/
if (sys_infos && sys_infos.processes) {
const CPU_MIN_SHOW = 60
const MEM_MIN_SHOW = 30
var sys_proc_head = ['id', 'cmd', 'pid', 'cpu', 'mem', 'uid']
var sys_proc_table = new Table({
colWidths: [4,77,10,10,10,8],
head : sys_proc_head,
colAligns : ['left'],
style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
});
if (sys_infos.processes.cpu_sorted || sys_infos.processes.mem_sorted) {
sys_infos.processes.cpu_sorted = sys_infos.processes.cpu_sorted.filter((proc) => {
return proc.cpu > CPU_MIN_SHOW && proc.cmd.includes('node') === false &&
proc.cmd.includes('God Daemon') === false
})
sys_infos.processes.cpu_sorted.forEach(proc => {
var cpu = `${colorizedMetric(proc.cpu, 40, 70, '%')}`
var mem = `${colorizedMetric(proc.memory, 40, 70, '%')}`
var cmd = proc.cmd
if (proc.cmd.length > 50)
cmd = '…' + proc.cmd.slice(proc.cmd.length - 48, proc.cmd.length)
sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid])
})
sys_infos.processes.mem_sorted = sys_infos.processes.mem_sorted.filter((proc) => {
return proc.memory > MEM_MIN_SHOW && proc.cmd.includes('node') == false
})
sys_infos.processes.mem_sorted.forEach((proc) => {
var cpu = `${colorizedMetric(proc.cpu, 40, 70, '%')}`
var mem = `${colorizedMetric(proc.memory, 40, 70, '%')}`
var cmd = proc.cmd
if (proc.cmd.length > 50)
cmd = '…' + proc.cmd.slice(proc.cmd.length - 48, proc.cmd.length)
sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid])
})
if (sys_infos.processes.cpu_sorted.length >= 1 || sys_infos.processes.mem_sorted.length >= 1) {
console.log(chalk.bold('Intensive Processes'))
console.log(sys_proc_table.toString())
}
}
}
/**
* Modules Display
*/
if (module_table.length > 0) {
console.log(chalk.bold(`Module${module_table.length > 1 ? 's' : ''}`));
console.log(module_table.toString());
}
/**
* Sys info line
*/
if (sys_infos && sys_infos.cpu && sys_infos.cpu.usage) {
var sys_summary_line = `${chalk.bold.cyan('host metrics')} `
sys_summary_line += `| ${chalk.bold('cpu')}: ${colorizedMetric(sys_infos.cpu.usage, 40, 70, '%')}`
if (sys_infos.cpu.temperature && sys_infos.cpu.temperature != '-1') {
sys_summary_line += ` ${colorizedMetric(sys_infos.cpu.temperature, 50, 70, 'º')}`
}
if (sys_infos.mem) {
var perc_mem_usage = (((sys_infos.mem.available) / sys_infos.mem.total) * 100).toFixed(1)
sys_summary_line += ` | ${chalk.bold('mem')}: ${colorizedMetric(perc_mem_usage, 80, 90, '%')} `
}
if (sys_infos.network) {
var latency = (sys_infos.network.latency).toFixed(1)
if (latency == -1) {
sys_summary_line += `| ${chalk.bold('net')}: ${chalk.red('offline')} `
}
else {
sys_summary_line += `| ${chalk.bold('net')}: `
sys_summary_line += `${colorizedMetric(latency, 100, 150, 'ms')} `
sys_summary_line += `⇓ ${colorizedMetric(sys_infos.network.rx_5, 10, 20, 'mb/s')} `
sys_summary_line += `⇑ ${colorizedMetric(sys_infos.network.tx_5, 10, 20, 'mb/s')} `
}
}
if (condensed_mode == false) {
if (sys_infos.storage) {
sys_summary_line += `| ${chalk.bold('disk')}: ⇓ ${colorizedMetric(sys_infos.storage.io.read, 10, 20, 'mb/s')}`
sys_summary_line += ` ⇑ ${colorizedMetric(sys_infos.storage.io.write, 10, 20, 'mb/s')} `
}
var disk_nb = 0
sys_infos.storage.filesystems.forEach(fs => {
disk_nb++
var perc_used = ((fs.used / fs.size) * 100).toFixed()
if (perc_used > 60)
sys_summary_line += `${chalk.grey(fs.fs)} ${colorizedMetric(perc_used, 80, 90, '%')} `
})
}
sys_summary_line += '|'
console.log(sys_summary_line)
}
};
UX.openEditor = function (file, opts, cb) {
var spawn = require('child_process').spawn;
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (!opts) opts = {};
var ed = /^win/.test(process.platform) ? 'notepad' : 'vim';
var editor = opts.editor || process.env.VISUAL || process.env.EDITOR || ed;
var args = editor.split(/\s+/);
var bin = args.shift();
var ps = spawn(bin, args.concat([ file ]), { stdio: 'inherit' });
ps.on('exit', function (code, sig) {
if (typeof cb === 'function') cb(code, sig)
});
};
UX.dispKeys = function(kv, target_module) {
Object.keys(kv).forEach(function(key) {
if (target_module != null && target_module != key)
return false;
if (typeof(kv[key]) == 'object') {
var obj = {};
console.log(chalk.bold('Module: ') + chalk.bold.blue(key));
Object.keys(kv[key]).forEach(function(sub_key) {
console.log(`$ pm2 set ${key}:${sub_key} ${kv[key][sub_key]}`);
});
}
})
}
UX.processing = {
current_spinner : null,
start : function(text) {
this.current_spinner = new Spinner(text || 'Connecting...');
this.current_spinner.start();
},
stop : function() {
if (!this.current_spinner) return;
this.current_spinner.stop();
this.current_spinner = null;
}
};
/**
* Description
* @method bytesToSize
* @param {} bytes
* @param {} precision
* @return
*/
UX.bytesToSize = function(bytes, precision) {
var kilobyte = 1024;
var megabyte = kilobyte * 1024;
var gigabyte = megabyte * 1024;
var terabyte = gigabyte * 1024;
if ((bytes >= 0) && (bytes < kilobyte)) {
return bytes + 'b ';
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
return (bytes / kilobyte).toFixed(precision) + 'kb ';
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
return (bytes / megabyte).toFixed(precision) + 'mb ';
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
return (bytes / gigabyte).toFixed(precision) + 'gb ';
} else if (bytes >= terabyte) {
return (bytes / terabyte).toFixed(precision) + 'tb ';
} else {
return bytes + 'b ';
}
};
/**
* Description
* @method colorStatus
* @param {} status
* @return
*/
function colorStatus(status) {
switch (status) {
case 'online':
return chalk.green.bold('online');
break;
case 'running':
return chalk.green.bold('online');
break;
case 'restarting':
return chalk.yellow.bold('restart');
break;
case 'created':
return chalk.yellow.bold('created');
break;
case 'launching':
return chalk.blue.bold('launching');
break;
default:
return chalk.red.bold(status);
}
}
var safe_push = function() {
var argv = arguments;
var table = argv[0];
for (var i = 1; i < argv.length; ++i) {
var elem = argv[i];
if (elem[Object.keys(elem)[0]] === undefined
|| elem[Object.keys(elem)[0]] === null) {
elem[Object.keys(elem)[0]] = 'N/A';
}
else if (Array.isArray(elem[Object.keys(elem)[0]])) {
elem[Object.keys(elem)[0]].forEach(function(curr, j) {
if (curr === undefined || curr === null)
elem[Object.keys(elem)[0]][j] = 'N/A';
});
}
table.push(elem);
}
};
/**
* Description
* @method timeSince
* @param {} date
* @return BinaryExpression
*/
function timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = Math.floor(seconds / 31536000);
if (interval > 1) {
return interval + 'Y';
}
interval = Math.floor(seconds / 2592000);
if (interval > 1) {
return interval + 'M';
}
interval = Math.floor(seconds / 86400);
if (interval > 1) {
return interval + 'D';
}
interval = Math.floor(seconds / 3600);
if (interval > 1) {
return interval + 'h';
}
interval = Math.floor(seconds / 60);
if (interval > 1) {
return interval + 'm';
}
return Math.floor(seconds) + 's';
}
function colorizedMetric(value, warn, alert, prefix) {
if (!prefix) prefix = ''
if (isNaN(value) === true)
return 'N/A'
if (value == 0)
return 0 + prefix
if (value < warn)
return chalk.green(value + prefix)
if (value >= warn && value <= alert)
return chalk.bold.yellow(value + prefix)
return chalk.bold.red(value + prefix)
}
/**
* Get nested property
*
* @param {String} propertyName
* @param {Object} obj
* @returns {String} property value
*/
function getNestedProperty(propertyName, obj) {
var parts = propertyName.split('.'),
length = parts.length,
property = obj || {};
for ( var i = 0; i < length; i++ ) {
property = property[parts[i]];
}
return property;
}