Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
77 changes: 77 additions & 0 deletions lib/cmd/ls.js
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 = {
Copy link
Member

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?

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';
Copy link
Member

Choose a reason for hiding this comment

The 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 => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move the filter into findNodeProcessWithPpid.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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;
Copy link
Member

Choose a reason for hiding this comment

The 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 {
Copy link
Member

Choose a reason for hiding this comment

The 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;
145 changes: 145 additions & 0 deletions lib/display.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
'use strict';
// code copy from pm2
Copy link
Member

Choose a reason for hiding this comment

The 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) {
Copy link
Member

Choose a reason for hiding this comment

The 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) {
Copy link
Member

Choose a reason for hiding this comment

The 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 ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract Object.keys(elem)[0]

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);
}
}
72 changes: 72 additions & 0 deletions lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The 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' :
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another way maybe could read sth like run/application_config.json

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 ?
Copy link
Member

Choose a reason for hiding this comment

The 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;
}
82 changes: 82 additions & 0 deletions lib/monitor.js
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) {
Copy link
Member

Choose a reason for hiding this comment

The 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 => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const pids = processs.map(p => p.pid)

return pro.pid;
});

if (pids.length === 0) {
resolve(pids);
return;
}
pidusage(pids, function(err, statistics) {
Copy link
Member

Choose a reason for hiding this comment

The 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) {
Copy link
Member

Choose a reason for hiding this comment

The 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
Loading