-
Notifications
You must be signed in to change notification settings - Fork 37
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
add ls method to list egg runing app #31
base: master
Are you sure you want to change the base?
Changes from 1 commit
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'; | ||
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 => { | ||
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. 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. 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; | ||
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; | ||
}); | ||
|
||
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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
'use strict'; | ||
// code copy from pm2 | ||
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) { | ||
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) { | ||
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 || | ||
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; | ||
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 | ||
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 ? | ||
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,85 @@ | ||
'use strict'; | ||
// code copy from pm2 | ||
|
||
const pidusage = require('pidusage'); | ||
|
||
exports.getMonitorData = function getMonitorData(processs, cb) { | ||
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. don't use callback 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. ok, now without callback |
||
|
||
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); | ||
}); | ||
}; |
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?