From ec89e8aea94264928859f34a479e60f15d22ee80 Mon Sep 17 00:00:00 2001 From: buzai <783243077@qq.com> Date: Sun, 30 Sep 2018 22:03:18 +0800 Subject: [PATCH 1/2] add ls method to list egg runing app --- index.js | 1 + lib/cmd/ls.js | 77 ++++++++++++++++++++++++++ lib/display.js | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/helper.js | 72 ++++++++++++++++++++++++ lib/monitor.js | 85 +++++++++++++++++++++++++++++ package.json | 3 + 6 files changed, 383 insertions(+) create mode 100644 lib/cmd/ls.js create mode 100644 lib/display.js create mode 100644 lib/monitor.js diff --git a/index.js b/index.js index 5fd74e0..90aa4d3 100644 --- a/index.js +++ b/index.js @@ -17,3 +17,4 @@ module.exports = exports = EggScripts; exports.Command = Command; exports.StartCommand = require('./lib/cmd/start'); exports.StopCommand = require('./lib/cmd/stop'); +exports.LsCommand = require('./lib/cmd/ls'); diff --git a/lib/cmd/ls.js b/lib/cmd/ls.js new file mode 100644 index 0000000..40dfa22 --- /dev/null +++ b/lib/cmd/ls.js @@ -0,0 +1,77 @@ +'use strict'; + +const path = require('path'); +const Command = require('../command'); +const isWin = process.platform === 'win32'; +const osRelated = { + titlePrefix: isWin ? '\\"title\\":\\"' : '"title":"', + appWorkerPath: isWin ? 'egg-cluster\\lib\\app_worker.js' : 'egg-cluster/lib/app_worker.js', + agentWorkerPath: isWin ? 'egg-cluster\\lib\\agent_worker.js' : 'egg-cluster/lib/agent_worker.js', +}; + +class LsCommand extends Command { + + constructor(rawArgv) { + super(rawArgv); + this.usage = 'Usage: egg-scripts ls [--title=example]'; + this.serverBin = path.join(__dirname, '../start-cluster'); + this.options = { + title: { + description: 'process title description, use for find app', + type: 'string', + }, + }; + } + + get description() { + return 'ls app'; + } + + * run(context) { + const { argv } = context; + this.logger.info(`list egg application ${argv.title ? `with --title=${argv.title}` : ''}`); + + const processList = yield this.helper.findNodeProcessWithPpid(item => { + const cmd = item.cmd; + + item.isMaster = false; + item.isAgent = false; + item.isWorker = false; + + let tileFlag = true; + if (argv.title) { + tileFlag = cmd.includes(argv.title); + } + + if (cmd.includes(osRelated.appWorkerPath) && tileFlag) { + item.isWorker = true; + item.mode = 'Worker'; + return true; + } + + if (cmd.includes('start-cluster') && tileFlag) { + item.isMaster = true; + item.mode = 'Master'; + return true; + } + + if (cmd.includes(osRelated.agentWorkerPath) && tileFlag) { + item.isAgent = true; + item.mode = 'Agent'; + return true; + } + return false; + }); + + this.helper.getMonitorData(processList, (err, list) => { + if (err) { + console.log('getMonitorData error', err); + this.exit(1); + } + this.helper.dispAsTable(list); + this.exit(0); + }); + } +} + +module.exports = LsCommand; diff --git a/lib/display.js b/lib/display.js new file mode 100644 index 0000000..14b1f94 --- /dev/null +++ b/lib/display.js @@ -0,0 +1,145 @@ +'use strict'; +// code copy from pm2 + +const Table = require('cli-table-redemption'); +const chalk = require('chalk'); + +exports.dispAsTable = function(list) { + const stacked = (process.stdout.columns || 90) < 90; + const app_head = stacked ? [ 'Name', 'ppid', 'pid', 'mode', 'cpu', 'memory' ] : [ 'App name', 'ppid', 'pid', 'mode', 'elapsed', 'cpu', 'mem', 'user', 'port' ]; + const app_table = new Table({ + head: app_head, + colAligns: [ 'left' ], + style: { + 'padding-left': 1, + head: [ 'cyan', 'bold' ], + compact: true, + }, + }); + if (!list) { + return console.log('list empty'); + } + + list.forEach(function(l) { + const obj = {}; + + const key = chalk.bold.cyan(l.name); + + // ls for Applications + obj[key] = []; + + // ppid + obj[key].push(l.ppid); + + // pid + obj[key].push(l.pid); + + // Exec mode + obj[key].push(colorModels(l.mode)); + + // elapsed + if (!stacked) { + obj[key].push(l.monit ? timeSince(l.monit.elapsed) : 'N/A'); + } + + // CPU + obj[key].push(l.monit ? l.monit.cpu + '%' : 'N/A'); + + // Memory + obj[key].push(l.monit ? bytesToSize(l.monit.memory, 1) : 'N/A'); + + // User + if (!stacked) { + const user = l.user ? l.user : 'N/A'; + obj[key].push(chalk.bold(user)); + } + + // port + if (!stacked) { + const port = l.port ? l.port : 'N/A'; + obj[key].push(chalk.bold(port)); + } + + safe_push(app_table, obj); + + }); + + console.log(app_table.toString()); +}; + +function safe_push() { + const argv = arguments; + const table = argv[0]; + + for (let i = 1; i < argv.length; ++i) { + const 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); + } +} + + +function timeSince(date) { + const seconds = Math.floor(date / 1000); + let 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 bytesToSize(bytes, precision) { + const kilobyte = 1024; + const megabyte = kilobyte * 1024; + const gigabyte = megabyte * 1024; + const 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 '; + } + return bytes + ' B '; +} + +function colorModels(model) { + switch (model) { + case 'Master': + return chalk.green.bold('Master'); + case 'Worker': + return chalk.blue.bold('Worker'); + default: + return chalk.red.bold(model); + } +} diff --git a/lib/helper.js b/lib/helper.js index 0f42f6c..3866eea 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -4,6 +4,14 @@ const runScript = require('runscript'); const isWin = process.platform === 'win32'; const REGEX = isWin ? /^(.*)\s+(\d+)\s*$/ : /^\s*(\d+)\s+(.*)/; +// ls +const REGEXPPID = isWin ? /^\s*(\d+)\s*(\d+)\s+(.*)/ : /^\s*(\d+)\s*(\d+)\s*(\S+)\s+(.*)/; +const getMonitorData = require('./monitor').getMonitorData; +const dispAsTable = require('./display').dispAsTable; + +exports.getMonitorData = getMonitorData; +exports.dispAsTable = dispAsTable; + exports.findNodeProcess = function* (filterFn) { const command = isWin ? 'wmic Path win32_process Where "Name = \'node.exe\'" Get CommandLine,ProcessId' : @@ -38,3 +46,67 @@ exports.kill = function(pids, signal) { } }); }; + +// ps process func with user ppid +exports.findNodeProcessWithPpid = function* (filterFn) { + const command = isWin ? + 'wmic Path win32_process Where "Name = \'node.exe\'" Get ParentProcessId,,ProcessId,CommandLine' : + // command, cmd are alias of args, not POSIX standard, so we use args + 'ps -eo "ppid,pid,user,args"'; + const stdio = yield runScript(command, { stdio: 'pipe' }); + + const processList = stdio.stdout.toString().split('\n') + .reduce((arr, line) => { + if (!!line && !line.includes('/bin/sh') && line.includes('node')) { + const m = line.match(REGEXPPID); + /* istanbul ignore else */ + if (m) { + // TODO: just test in osx + const item = isWin ? { ppid: m[1], pid: m[2], user: '', cmd: m[3] } : + { ppid: m[1], pid: m[2], user: m[3], cmd: m[4] }; + if (!filterFn || filterFn(item)) { + item.port = getPort(item.cmd); + item.name = getName(item.cmd); + arr.push(item); + } + } + } + return arr; + }, []); + return processList; +}; + + +// get port string, it is not perfect +function getPort(cmd) { + + // default value + let port = '7001(default)'; + + // find in cmd , when set port option in package.json, it will be find in cmd + const cmdArr = cmd.split(' '); + const options = JSON.parse(cmdArr[2]); + if (options.port) { + port = options.port; + return port; + } + + // when set port in config , the process require the config file with runtime env + // but how easy to know the process env. eg:"local prod .." + // const baseDir = options.baseDir; + + return port; +} + + +// get tile string in the script, tile as the project name ? +function getName(cmd) { + let title = ''; + const cmdArr = cmd.split(' '); + + const options = JSON.parse(cmdArr[2]); + if (options.title) { + title = options.title; + } + return title; +} diff --git a/lib/monitor.js b/lib/monitor.js new file mode 100644 index 0000000..8677cd6 --- /dev/null +++ b/lib/monitor.js @@ -0,0 +1,85 @@ +'use strict'; +// code copy from pm2 + +const pidusage = require('pidusage'); + +exports.getMonitorData = function getMonitorData(processs, cb) { + + if (processs.length === 0) { + return cb(null, processs.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + })); + } + + const pids = processs.map(pro => { + return pro.pid; + }); + + if (pids.length === 0) { + return cb(null, pids.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + })); + } + + pidusage(pids, function retPidUsage(err, statistics) { + // Just log, we'll set empty statistics + if (err) { + console.error('Error caught while calling pidusage'); + console.error(err); + + return cb(err, processs.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + })); + } + + if (!statistics) { + console.error('Statistics is not defined!'); + return cb(err, processs.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + })); + } + + processs = processs.map(function(pro) { + const stat = statistics[pro.pid]; + + if (!stat) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + + return pro; + } + + pro.monit = { + memory: stat.memory, + cpu: Math.round(stat.cpu * 10) / 10, + elapsed: stat.elapsed, + }; + + return pro; + }); + return cb(err, processs); + }); +}; diff --git a/package.json b/package.json index 23bafdb..f697394 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "eggctl": "bin/egg-scripts.js" }, "dependencies": { + "chalk": "^2.4.1", + "cli-table-redemption": "^1.0.1", "common-bin": "^2.7.1", "debug": "^3.1.0", "egg-utils": "^2.3.0", @@ -15,6 +17,7 @@ "mz": "^2.7.0", "mz-modules": "^2.0.0", "node-homedir": "^1.1.0", + "pidusage": "^2.0.17", "runscript": "^1.3.0", "source-map-support": "^0.5.4", "zlogger": "^1.1.0" From e3681ee1855ea1ff3d07b631441488abe2c092e4 Mon Sep 17 00:00:00 2001 From: buzai <783243077@qq.com> Date: Mon, 1 Oct 2018 00:33:16 +0800 Subject: [PATCH 2/2] replace callback with promise --- lib/cmd/ls.js | 14 ++--- lib/monitor.js | 135 ++++++++++++++++++++++++------------------------- 2 files changed, 73 insertions(+), 76 deletions(-) diff --git a/lib/cmd/ls.js b/lib/cmd/ls.js index 40dfa22..2a73b6d 100644 --- a/lib/cmd/ls.js +++ b/lib/cmd/ls.js @@ -62,15 +62,15 @@ class LsCommand extends Command { } return false; }); - - this.helper.getMonitorData(processList, (err, list) => { - if (err) { - console.log('getMonitorData error', err); - this.exit(1); - } + try { + const list = yield this.helper.getMonitorData(processList); this.helper.dispAsTable(list); this.exit(0); - }); + } catch (e) { + console.log('getMonitorData error', e); + this.exit(1); + } + } } diff --git a/lib/monitor.js b/lib/monitor.js index 8677cd6..838f7da 100644 --- a/lib/monitor.js +++ b/lib/monitor.js @@ -3,83 +3,80 @@ const pidusage = require('pidusage'); -exports.getMonitorData = function getMonitorData(processs, cb) { - - if (processs.length === 0) { - return cb(null, processs.map(function(pro) { - pro.monit = { - memory: 0, - cpu: 0, - elapsed: 0, - }; - return pro; - })); - } - - const pids = processs.map(pro => { - return pro.pid; - }); - - if (pids.length === 0) { - return cb(null, pids.map(function(pro) { - pro.monit = { - memory: 0, - cpu: 0, - elapsed: 0, - }; - return pro; - })); - } - - pidusage(pids, function retPidUsage(err, statistics) { - // Just log, we'll set empty statistics - if (err) { - console.error('Error caught while calling pidusage'); - console.error(err); - - return cb(err, processs.map(function(pro) { - pro.monit = { - memory: 0, - cpu: 0, - elapsed: 0, - }; - return pro; - })); +exports.getMonitorData = function* getMonitorData(processs) { + + return new Promise(function(resolve, reject) { + + if (processs.length === 0) { + resolve(processs); + return; } - if (!statistics) { - console.error('Statistics is not defined!'); - return cb(err, processs.map(function(pro) { - pro.monit = { - memory: 0, - cpu: 0, - elapsed: 0, - }; - return pro; - })); + const pids = processs.map(pro => { + return pro.pid; + }); + + if (pids.length === 0) { + resolve(pids); + return; } + pidusage(pids, function(err, statistics) { + // Just log, we'll set empty statistics + if (err) { + console.error('Error caught while calling pidusage'); + console.error(err); - processs = processs.map(function(pro) { - const stat = statistics[pro.pid]; + processs.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + }); + reject(err); + return; + } + + if (!statistics) { + console.error('Statistics is not defined!'); + processs.map(function(pro) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + return pro; + }); + reject(err); + return; + } + + processs = processs.map(function(pro) { + const stat = statistics[pro.pid]; + + if (!stat) { + pro.monit = { + memory: 0, + cpu: 0, + elapsed: 0, + }; + + return pro; + } - if (!stat) { pro.monit = { - memory: 0, - cpu: 0, - elapsed: 0, + memory: stat.memory, + cpu: Math.round(stat.cpu * 10) / 10, + elapsed: stat.elapsed, }; return pro; - } + }); + resolve(processs); - pro.monit = { - memory: stat.memory, - cpu: Math.round(stat.cpu * 10) / 10, - elapsed: stat.elapsed, - }; + }); // pidusage end - return pro; - }); - return cb(err, processs); - }); -}; + }); // Promise end + +}; // getMonitorData end