-
Notifications
You must be signed in to change notification settings - Fork 37
add ls method to list egg runing app #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list egg process |
||
| } | ||
|
|
||
| * run(context) { | ||
| const { argv } = context; | ||
| this.logger.info(`list egg application ${argv.title ? `with --title=${argv.title}` : ''}`); | ||
|
|
||
| const processList = yield this.helper.findNodeProcessWithPpid(item => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can move the filter into findNodeProcessWithPpid.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @popomore This filter function use a title variable , so I think it's more appropriate to put it here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this could extract to a utils function, and share with stop cmd |
||
| const cmd = item.cmd; | ||
|
|
||
| item.isMaster = false; | ||
| item.isAgent = false; | ||
| item.isWorker = false; | ||
|
|
||
| let tileFlag = true; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tileFlag -> titleFlag |
||
| 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; | ||
| }); | ||
| try { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. new line |
||
| const list = yield this.helper.getMonitorData(processList); | ||
| this.helper.dispAsTable(list); | ||
| this.exit(0); | ||
| } catch (e) { | ||
| console.log('getMonitorData error', e); | ||
| this.exit(1); | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| module.exports = LsCommand; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| 'use strict'; | ||
| // code copy from pm2 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PM2 是 AGPL 协议的,这里还是自己实现吧,不要复制,也不要参考太多。 |
||
|
|
||
| const Table = require('cli-table-redemption'); | ||
| const chalk = require('chalk'); | ||
|
|
||
| exports.dispAsTable = function(list) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dispAsTable -> displayAsTable |
||
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. arraw style |
||
| 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 || | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extract |
||
| 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); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. display |
||
|
|
||
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. another way maybe could read sth like |
||
| 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 ? | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. title |
||
| function getName(cmd) { | ||
| let title = ''; | ||
| const cmdArr = cmd.split(' '); | ||
|
|
||
| const options = JSON.parse(cmdArr[2]); | ||
| if (options.title) { | ||
| title = options.title; | ||
| } | ||
| return title; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| 'use strict'; | ||
| // code copy from pm2 | ||
|
|
||
| const pidusage = require('pidusage'); | ||
|
|
||
| exports.getMonitorData = function* getMonitorData(processs) { | ||
|
|
||
| return new Promise(function(resolve, reject) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer arrow style |
||
|
|
||
| if (processs.length === 0) { | ||
| resolve(processs); | ||
| return; | ||
| } | ||
|
|
||
| const pids = processs.map(pro => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return pro.pid; | ||
| }); | ||
|
|
||
| if (pids.length === 0) { | ||
| resolve(pids); | ||
| return; | ||
| } | ||
| pidusage(pids, function(err, statistics) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer arrow style |
||
| // Just log, we'll set empty statistics | ||
| if (err) { | ||
| console.error('Error caught while calling pidusage'); | ||
| console.error(err); | ||
|
|
||
| processs.map(function(pro) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer arrow style |
||
| 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; | ||
| } | ||
|
|
||
| pro.monit = { | ||
| memory: stat.memory, | ||
| cpu: Math.round(stat.cpu * 10) / 10, | ||
| elapsed: stat.elapsed, | ||
| }; | ||
|
|
||
| return pro; | ||
| }); | ||
| resolve(processs); | ||
|
|
||
| }); // pidusage end | ||
|
|
||
| }); // Promise end | ||
|
|
||
| }; // getMonitorData end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's duplication with stop cmd, extract this to helper or?