mmon
Version:
micro monitor - CLI system monitor
435 lines (381 loc) • 17.5 kB
JavaScript
;
// ==================================================================================
// index.js
// ----------------------------------------------------------------------------------
// Description: mmon - micro monitor - System Information CLI tool
// based on Node.js
// Copyright: (c) 2016 - 2017
// Author: Sebastian Hildebrandt
// ----------------------------------------------------------------------------------
// Contributors: -
// ----------------------------------------------------------------------------------
// License: MIT
// ==================================================================================
// ------------------------------------------------
// dependenciea
// ------------------------------------------------
let options = require('./lib/args');
let draw = require('./lib/draw');
let cols = require('./lib/cols');
let si = require('systeminformation');
let version = require('./package.json').version;
let _ = require('./lib/libObj');
let time = require('./lib/libTime');
let staticData = {};
let dynamicData = {};
let dockerData = [];
let primaryNet = {};
// ------------------------------------------------
// Params
// ------------------------------------------------
let interval = (options.i || options.interval || 3) * 1000;
if (interval < 1000) interval = 1000;
let dockerinterval = interval < 4000 ? interval * 2 : interval;
// ------------------------------------------------
// Calc + put together lines
// ------------------------------------------------
function header() {
let line = draw.strLeft(' mmon - micro monitor', 110);
line = draw.strAddRight(line, 'Version ' + version + ' ');
return line;
}
function footer() {
let line = draw.strLeft(' SI-Version: ' + si.version() + ' Node: ' + process.versions.node + ' V8: ' + process.versions.v8, 110);
line = draw.strAddRight(line, '(c) ' + new Date().getFullYear() + ' Sebastian Hildebrandt ');
return line;
}
function machine_os() {
let line = draw.strLeft((staticData.system.manufacturer + ' ' + staticData.system.model).trim(), 110);
line = draw.strAddRight(line, staticData.os.distro + ' - ' + staticData.os.release + ' - Kernel: ' + staticData.os.kernel);
return line;
}
function cpu_host() {
let line = draw.strLeft(staticData.cpu.manufacturer + ' ' + staticData.cpu.brand + ' - ' + staticData.cpu.speed + ' GHz - ' + staticData.cpu.cores + ' Cores', 110);
line = draw.strAddRight(line, 'Host: ' + staticData.os.hostname + (dynamicData && dynamicData.time && dynamicData.time.uptime ? ' - Uptime: ' + time.uptime(dynamicData.time.uptime) : ''));
return line;
}
function calc_primary_net() {
let iface_name = '';
let iface_ip4 = '';
let iface_ip6 = '';
staticData.net.forEach(iface => {
if (dynamicData.networkStats && dynamicData.networkStats[0] && iface.iface === dynamicData.networkStats[0].iface) {
iface_name = iface_name || iface.iface;
iface_ip4 = iface_ip4 || iface.ip4;
iface_ip6 = iface_ip6 || iface.ip6;
}
});
return {
iface: iface_name,
ip4: iface_ip4,
ip6: iface_ip6
};
}
function calc_fs() {
let size = 0;
let used = 0;
dynamicData.fsSize.forEach(fs => {
size += fs.size;
used += fs.used;
});
return {
size: size,
used: used,
free: ((size && (size > used)) ? size - used : 0),
use: (size ? 100.0 * used / size : 0)
};
}
function calc_nwconn() {
let all = dynamicData.networkConnections.length;
let established = 0;
let listen = 0;
dynamicData.networkConnections.forEach(nwconn => {
if (nwconn.state == 'LISTEN') listen++;
if (nwconn.state == 'ESTABLISHED') established++;
});
return {
all: all,
established: established,
listen: listen
};
}
// ------------------------------------------------
// Outputs
// ------------------------------------------------
function startScreen() {
draw.clear();
console.log(cols.log(header(), 'black', 'gray'));
console.log(machine_os());
console.log(cpu_host());
console.log('\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
console.log(' STARTING ...');
console.log('\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
console.log(cols.log(footer(), 'black', 'gray'));
draw.hide();
}
function displayAll(first) {
// if (!primaryNet.iface) {
primaryNet = calc_primary_net();
// }
if (!first) draw.up(39);
console.log(cols.log(header(), 'black', 'gray'));
console.log(machine_os());
console.log(cpu_host());
console.log();
let fssize = calc_fs();
let nwconn = calc_nwconn();
// console.log(cols.log(' CPU MEM FS DiskIO', 'white'));
console.log('CPU: ' + draw.progress(dynamicData.currentLoad.currentLoad, 37, true, true) + ' ' + cols.log('CPU: ', 'white') + draw.fmtNum(dynamicData.currentLoad.currentLoad, 2, 6, 70, 85) + ' % ' + cols.log('MEM: ', 'white') + draw.fmtNum(dynamicData.mem.used / dynamicData.mem.total * 100, 2, 6, 70, 85) + ' % ' + cols.log('FS: ', 'white') + draw.fmtNum(fssize.use, 2, 6, 70, 85) + ' %');
console.log('MEM: ' + draw.progress(dynamicData.mem.used / dynamicData.mem.total * 100, 37, true, true) + ' Speed:' + ' ' + draw.fmtNum(dynamicData.cpuCurrentSpeed.avg, 2, 4) + 'GHz ' + 'Total: ' + draw.fmtNum(dynamicData.mem.total / 1073741824.0, 2, 6) + 'GB ' + 'Total:' + draw.fmtNum(fssize.size / 1073741824.0, 2, 9) + 'GB');
console.log('FS: ' + draw.progress(fssize.use, 37, true, true) + ' Temp:' + (dynamicData.temp && dynamicData.temp.main && dynamicData.temp.main > 0 ? draw.fmtNum(dynamicData.temp.main, 2, 7, 70, 90) : ' -.--') + ' °C ' + 'Free: ' + draw.fmtNum(dynamicData.mem.free / 1073741824.0, 2, 6) + 'GB ' + 'Free: ' + draw.fmtNum(fssize.free / 1073741824.0, 2, 8) + 'GB');
console.log();
let lines = [];
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines[0] = lines[0] + cols.log('FS Stats', 'white');
lines[1] = lines[1] + 'RX: ' + (dynamicData.fsStats.rx_sec != null ? draw.fmtNum(dynamicData.fsStats.rx_sec, 2, 14, 500000, 1000000) + ' B/s' : ' - ');
lines[2] = lines[2] + 'WX: ' + (dynamicData.fsStats.wx_sec != null ? draw.fmtNum(dynamicData.fsStats.wx_sec, 2, 14, 500000, 1000000) + ' B/s' : ' - ');
lines[3] = lines[3] + 'TX: ' + (dynamicData.fsStats.tx_sec != null ? draw.fmtNum(dynamicData.fsStats.tx_sec, 2, 14, 500000, 1000000) + ' B/s' : ' - ');
lines[0] = lines[0] + ' ' + cols.log('IOPS', 'white');
lines[1] = lines[1] + ' ' + 'rIO: ' + (dynamicData.disksIO.rIO_sec != null ? draw.fmtNum(dynamicData.disksIO.rIO_sec, 2, 10, 200, 500) + ' per s' : ' - ');
lines[2] = lines[2] + ' ' + 'wIO: ' + (dynamicData.disksIO.wIO_sec != null ? draw.fmtNum(dynamicData.disksIO.wIO_sec, 2, 10, 200, 500) + ' per s' : ' - ');
lines[3] = lines[3] + ' ' + 'tIO: ' + (dynamicData.disksIO.tIO_sec != null ? draw.fmtNum(dynamicData.disksIO.tIO_sec, 2, 10, 200, 500) + ' per s' : ' - ');
lines[0] = lines[0] + ' ' + cols.log('NET: ', 'white') + (' ' + primaryNet.iface).substr(-12);
lines[1] = lines[1] + ' ' + 'IP: ' + (' ' + primaryNet.ip4).substr(-15);
lines[2] = lines[2] + ' ' + 'RX: ' + (dynamicData.networkStats && dynamicData.networkStats[0] && dynamicData.networkStats[0].rx_sec != null ? draw.fmtNum(dynamicData.networkStats[0].rx_sec, 2, 11, 100000, 200000) + ' B/s' : ' - ');
lines[3] = lines[3] + ' ' + 'TX: ' + (dynamicData.networkStats && dynamicData.networkStats[0] && dynamicData.networkStats[0].rx_sec != null ? draw.fmtNum(dynamicData.networkStats[0].tx_sec, 2, 11, 100000, 200000) + ' B/s' : ' - ');
lines[0] = lines[0] + ' ' + cols.log('NW-Connect. ', 'white');
lines[1] = lines[1] + ' All: ' + draw.fmtNum(nwconn.all, 0, 4);
lines[2] = lines[2] + ' Establ: ' + draw.fmtNum(nwconn.established, 0, 4);
lines[3] = lines[3] + ' Listen: ' + draw.fmtNum(nwconn.listen, 0, 4);
lines[0] = lines[0] + ' ' + cols.log('Processes', 'white');
lines[1] = lines[1] + ' All: ' + draw.fmtNum(dynamicData.processes.all, 0, 5);
lines[2] = lines[2] + ' Running: ' + draw.fmtNum(dynamicData.processes.running, 0, 5);
lines[3] = lines[3] + ' Blocked: ' + draw.fmtNum(dynamicData.processes.blocked, 0, 5);
lines.forEach(line => {
console.log(line);
});
// Raster FS und UserUser
lines = [];
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
// File System
lines[0] = lines[0] + cols.log('File System', 'white', 'darkgray') + cols.log(' Montpoint Used %', 'lightgray', 'darkgray') + ' ';
for (let i = 1; i <= 5; i++) {
//console.log(dynamicData.fsSize[i-1]);
if (i <= dynamicData.fsSize.length) {
if (i < 5 || dynamicData.fsSize.length == 5) {
lines[i] = lines[i] + draw.strLeft(dynamicData.fsSize[i - 1].fs, 23) + ' ' + draw.strLeft(dynamicData.fsSize[i - 1].mount, 17) + ' ' + draw.fmtNum(dynamicData.fsSize[i - 1].use, 2, 6, 70, 85) + '% ';
} else {
lines[i] = lines[i] + '+' + draw.fmtNum(dynamicData.fsSize.length - 4, 0, 2) + ' more Mounts... ';
}
} else {
lines[i] = lines[i] + ' '.repeat(55);
}
}
// Users
lines[0] = lines[0] + cols.log('Users online', 'white', 'darkgray') + cols.log(' TTY IP DATE', 'lightgray', 'darkgray');
for (let i = 1; i <= 5; i++) {
if (i <= dynamicData.users.length) {
if (i < 5 || dynamicData.users.length == 5) {
lines[i] = lines[i] + draw.strLeft(dynamicData.users[i - 1].user, 16) + ' ' + draw.strLeft(dynamicData.users[i - 1].tty, 10) + ' ' + draw.strLeft(dynamicData.users[i - 1].ip, 15) + ' ' + draw.strRight(dynamicData.users[i - 1].date, 11);
} else {
lines[i] = lines[i] + '+' + draw.fmtNum(dynamicData.users.length - 4, 0, 2) + ' more users online...';
}
} else {
lines[i] = lines[i] + ' '.repeat(55);
}
}
console.log();
lines.forEach(line => {
console.log(line);
});
// Raster Processes
lines = [];
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
// Processes
lines[0] = lines[0] + cols.log('PID Top 5 Processes', 'white', 'darkgray') + cols.log(' State TTY User CPU% MEM%', 'lightgray', 'darkgray');
// top 5 processes
let topProcesses = dynamicData.processes.list.sort(function (a, b) { return ((b.pcpu - a.pcpu) * 100 + b.pmem - a.pmem); }).splice(0, 5);
for (let i = 1; i <= 5; i++) {
if (i <= topProcesses.length) {
lines[i] = lines[i] +
draw.strLeft(topProcesses[i - 1].pid + ' ', 5) + ' ' +
draw.strLeft(topProcesses[i - 1].command, 48) + ' ' +
draw.strLeft(topProcesses[i - 1].state, 10) + ' ' +
draw.strLeft(topProcesses[i - 1].tty, 11) + ' ' +
draw.strLeft(topProcesses[i - 1].user, 16) + ' ' +
draw.fmtNum(topProcesses[i - 1].cpu < 100 ? topProcesses[i - 1].cpu : 100, 2, 6, 70, 85) + ' ' +
draw.fmtNum(topProcesses[i - 1].mem, 2, 6, 70, 85);
} else {
lines[i] = ' '.repeat(110);
}
}
console.log();
lines.forEach(line => {
console.log(line);
});
// Raster Docker
lines = [];
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
lines.push('');
// Docker
lines[0] = lines[0] + cols.log('Docker Container', 'white', 'darkgray') + cols.log(' ID Image PORTS CPU% MEM%', 'lightgray', 'darkgray');
for (let i = 1; i <= 5; i++) {
if (i <= dockerData.length) {
if (i < 5 || dockerData.length == 5) {
let ports = '';
dockerData[i - 1].ports.forEach(port => {
ports = ports + (port.PrivatePort ? port.PrivatePort : '?') + ':' + (port.PublicPort ? port.PublicPort : '?') + ' ';
});
// lines[i] = lines[i] + draw.strLeft(dockerData[i - 1].name, 25) + ' ' + draw.strLeft(dockerData[i - 1].id, 10) + ' ' + draw.strLeft(dockerData[i - 1].image, 25) + ' ' + draw.strLeft(ports, 20) + ' ' + draw.fmtNum(dockerData[i - 1].cpu_percent, 2, 6, 70, 85) + '% ' + ' ' + draw.fmtNum(dockerData[i - 1].mem_percent, 2, 6, 70, 85) + '% ';
lines[i] = lines[i] + draw.strLeft(dockerData[i - 1].name, 28) + ' ' + draw.strLeft(dockerData[i - 1].id, 12) + ' ' + draw.strLeft(dockerData[i - 1].image, 22) + ' ' + draw.strLeft(ports, 29) + ' ' + (dockerData[i - 1].state == 'running' ? draw.fmtNum(dockerData[i - 1].cpu_percent < 100 ? dockerData[i - 1].cpu_percent : 100, 2, 6, 70, 85) + ' ' + draw.fmtNum(dockerData[i - 1].mem_percent, 2, 6, 70, 85) : draw.strRight(dockerData[i - 1].state, 13));
} else {
lines[i] = lines[i] + '+' + draw.fmtNum(dockerData.length - 4, 0, 2) + ' more Docker Containers...';
}
} else {
lines[i] = ' '.repeat(110);
}
}
if (dockerData.length == 0) {
lines[3] = cols.log(' No Docker Containers found ...', 'darkgray');
}
console.log();
lines.forEach(line => {
console.log(line);
});
lines = [];
lines.push('');
lines.push('');
lines[0] = lines[0] + cols.log('MISC: ', 'white', 'darkgray');
lines[1] = lines[1] + 'Internet Latency : ' + (dynamicData.inetLatency >= 0 ? draw.fmtNum(dynamicData.inetLatency, 2, 8, 2000, 5000) + ' ms ' : ' - ');
lines[1] = lines[1] + 'Battery Level : ' + (dynamicData.battery.hasbattery ? draw.fmtNum(dynamicData.battery.percent, 1, 5) + '%' + (dynamicData.battery.ischarging ? cols.log(' ++', 'green') : ' ') : ' ---- ');
console.log();
lines.forEach(line => {
console.log(line);
});
console.log();
console.log(cols.log(footer(), 'black', 'gray'));
draw.hide();
}
// ------------------------------------------------
// handle exits
// ------------------------------------------------
function getStaticData(callback) {
return new Promise((resolve) => {
process.nextTick(() => {
let data = {};
data.version = si.version();
Promise.all([
si.system(),
// system.bios(),
// system.baseboard(),
// system.chassis(),
si.osInfo(),
// osInfo.uuid(),
// osInfo.versions(),
si.cpu(),
// cpu.cpuFlags(),
// graphics.graphics(),
si.networkInterfaces(),
// memory.memLayout(),
// filesystem.diskLayout()
]).then(res => {
data.system = res[0];
// data.bios = res[1];
// data.baseboard = res[2];
// data.chassis = res[3];
data.os = res[1];
// data.uuid = res[5];
// data.versions = res[6];
data.cpu = res[2];
// data.cpu.flags = res[8];
// data.graphics = res[9];
data.net = res[3];
// data.memLayout = res[11];
// data.diskLayout = res[12];
if (callback) { callback(data); }
resolve(data);
});
});
});
}
// ------------------------------------------------
// handle exits
// ------------------------------------------------
function exitHandler(options, err) {
if (options.cleanup) { }
if (err) { console.log(err.stack); } //;
if (options.exit) {
// draw.show();
// draw.clear();
// if (err) console.log('Terminated with error ...');
process.exit();
}
}
process.on('exit', exitHandler.bind(null, { cleanup: true })); // do something when app is closing
process.on('SIGINT', exitHandler.bind(null, { exit: true })); // catches ctrl+c event
process.on('uncaughtException', exitHandler.bind(null, { exit: true, err: true })); // catches uncaught exceptions
//process.on('exit', function () { });
// ------------------------------------------------
// handle key stroke
// ------------------------------------------------
let stdin = process.stdin;
stdin.setRawMode(true);
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', function (key) {
// q OR ctrl-c ( end of text )
if (key === 'q' || key === '\u0003') {
draw.show();
draw.clear();
process.exit();
}
// write the key to stdout all normal like
//process.stdout.write(key);
});
// ------------------------------------------------
// main loop
// ------------------------------------------------
getStaticData().then(resultStatic => {
staticData = resultStatic;
startScreen();
si.getDynamicData().then(resultDynamic => {
dynamicData = resultDynamic;
si.dockerAll().then(resultDocker => {
dockerData = resultDocker;
});
draw.up(20);
draw.clearline();
draw.up(20);
//draw.clear()
displayAll(true);
setInterval(function () {
si.getDynamicData().then(resultDynamic => {
_.merge(dynamicData, resultDynamic);
displayAll(false);
});
}, interval);
setInterval(function () {
si.dockerAll().then(resultDocker => {
dockerData = resultDocker;
});
}, dockerinterval);
});
});