123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- /**
- * Copyright 2013-2022 the PM2 project authors. All rights reserved.
- * Use of this source code is governed by a license that
- * can be found in the LICENSE file.
- */
- var os = require('os');
- var p = require('path');
- var blessed = require('blessed');
- var debug = require('debug')('pm2:monit');
- var printf = require('sprintf-js').sprintf;
- // Total memory
- const totalMem = os.totalmem();
- var Dashboard = {};
- var DEFAULT_PADDING = {
- top : 0,
- left : 1,
- right : 1
- };
- var WIDTH_LEFT_PANEL = 30;
- /**
- * Synchronous Dashboard init method
- * @method init
- * @return this
- */
- Dashboard.init = function() {
- // Init Screen
- this.screen = blessed.screen({
- smartCSR: true,
- fullUnicode: true
- });
- this.screen.title = 'PM2 Dashboard';
- this.logLines = {}
- this.list = blessed.list({
- top: '0',
- left: '0',
- width: WIDTH_LEFT_PANEL + '%',
- height: '70%',
- padding: 0,
- scrollbar: {
- ch: ' ',
- inverse: false
- },
- border: {
- type: 'line'
- },
- keys: true,
- autoCommandKeys: true,
- tags: true,
- style: {
- selected: {
- bg: 'blue',
- fg: 'white'
- },
- scrollbar: {
- bg: 'blue',
- fg: 'black'
- },
- fg: 'white',
- border: {
- fg: 'blue'
- },
- header: {
- fg: 'blue'
- }
- }
- });
- this.list.on('select item', (item, i) => {
- this.logBox.clearItems()
- })
- this.logBox = blessed.list({
- label: ' Logs ',
- top: '0',
- left: WIDTH_LEFT_PANEL + '%',
- width: 100 - WIDTH_LEFT_PANEL + '%',
- height: '70%',
- padding: DEFAULT_PADDING,
- scrollable: true,
- scrollbar: {
- ch: ' ',
- inverse: false
- },
- keys: true,
- autoCommandKeys: true,
- tags: true,
- border: {
- type: 'line'
- },
- style: {
- fg: 'white',
- border: {
- fg: 'white'
- },
- scrollbar: {
- bg: 'blue',
- fg: 'black'
- }
- }
- });
- this.metadataBox = blessed.box({
- label: ' Metadata ',
- top: '70%',
- left: WIDTH_LEFT_PANEL + '%',
- width: 100 - WIDTH_LEFT_PANEL + '%',
- height: '26%',
- padding: DEFAULT_PADDING,
- scrollable: true,
- scrollbar: {
- ch: ' ',
- inverse: false
- },
- keys: true,
- autoCommandKeys: true,
- tags: true,
- border: {
- type: 'line'
- },
- style: {
- fg: 'white',
- border: {
- fg: 'white'
- },
- scrollbar: {
- bg: 'blue',
- fg: 'black'
- }
- }
- });
- this.metricsBox = blessed.list({
- label: ' Custom Metrics ',
- top: '70%',
- left: '0%',
- width: WIDTH_LEFT_PANEL + '%',
- height: '26%',
- padding: DEFAULT_PADDING,
- scrollbar: {
- ch: ' ',
- inverse: false
- },
- keys: true,
- autoCommandKeys: true,
- tags: true,
- border: {
- type: 'line'
- },
- style: {
- fg: 'white',
- border: {
- fg: 'white'
- },
- scrollbar: {
- bg: 'blue',
- fg: 'black'
- }
- }
- });
- this.box4 = blessed.text({
- content: ' left/right: switch boards | up/down/mouse: scroll | Ctrl-C: exit{|} {cyan-fg}{bold}To go further check out https://pm2.io/{/} ',
- left: '0%',
- top: '95%',
- width: '100%',
- height: '6%',
- valign: 'middle',
- tags: true,
- style: {
- fg: 'white'
- }
- });
- this.list.focus();
- this.screen.append(this.list);
- this.screen.append(this.logBox);
- this.screen.append(this.metadataBox);
- this.screen.append(this.metricsBox);
- this.screen.append(this.box4);
- this.list.setLabel(' Process List ');
- this.screen.render();
- var that = this;
- var i = 0;
- var boards = ['list', 'logBox', 'metricsBox', 'metadataBox'];
- this.screen.key(['left', 'right'], function(ch, key) {
- (key.name === 'left') ? i-- : i++;
- if (i == 4)
- i = 0;
- if (i == -1)
- i = 3;
- that[boards[i]].focus();
- that[boards[i]].style.border.fg = 'blue';
- if (key.name === 'left') {
- if (i == 3)
- that[boards[0]].style.border.fg = 'white';
- else
- that[boards[i + 1]].style.border.fg = 'white';
- }
- else {
- if (i == 0)
- that[boards[3]].style.border.fg = 'white';
- else
- that[boards[i - 1]].style.border.fg = 'white';
- }
- });
- this.screen.key(['escape', 'q', 'C-c'], function(ch, key) {
- this.screen.destroy();
- process.exit(0);
- });
- // async refresh of the ui
- setInterval(function () {
- that.screen.render();
- }, 300);
- return this;
- }
- /**
- * Refresh dashboard
- * @method refresh
- * @param {} processes
- * @return this
- */
- Dashboard.refresh = function(processes) {
- debug('Monit refresh');
- if(!processes) {
- this.list.setItem(0, 'No process available');
- return;
- }
- if (processes.length != this.list.items.length) {
- this.list.clearItems();
- }
- // Total of processes memory
- var mem = 0;
- processes.forEach(function(proc) {
- mem += proc.monit.memory;
- })
- // Sort process list
- processes.sort(function(a, b) {
- if (a.pm2_env.name < b.pm2_env.name)
- return -1;
- if (a.pm2_env.name > b.pm2_env.name)
- return 1;
- return 0;
- });
- // Loop to get process infos
- for (var i = 0; i < processes.length; i++) {
- // Percent of memory use by one process in all pm2 processes
- var memPercent = (processes[i].monit.memory / mem) * 100;
- // Status of process
- var status = processes[i].pm2_env.status == 'online' ? '{green-fg}' : '{red-fg}';
- status = status + '{bold}' + processes[i].pm2_env.status + '{/}';
- var name = processes[i].pm2_env.name || p.basename(processes[i].pm2_env.pm_exec_path);
- // Line of list
- var item = printf('[%2s] %s {|} Mem: {bold}{%s-fg}%3d{/} MB CPU: {bold}{%s-fg}%2d{/} %s %s',
- processes[i].pm2_env.pm_id,
- name,
- gradient(memPercent, [255, 0, 0], [0, 255, 0]),
- (processes[i].monit.memory / 1048576).toFixed(2),
- gradient(processes[i].monit.cpu, [255, 0, 0], [0, 255, 0]),
- processes[i].monit.cpu,
- "%",
- status);
- // Check if item exist
- if (this.list.getItem(i)) {
- this.list.setItem(i, item);
- }
- else {
- this.list.pushItem(item);
- }
- var proc = processes[this.list.selected];
- // render the logBox
- let process_id = proc.pm_id
- let logs = this.logLines[process_id];
- if(typeof(logs) !== "undefined"){
- this.logBox.setItems(logs)
- if (!this.logBox.focused) {
- this.logBox.setScrollPerc(100);
- }
- }else{
- this.logBox.clearItems();
- }
- this.logBox.setLabel(` ${proc.pm2_env.name} Logs `)
- this.metadataBox.setLine(0, 'App Name ' + '{bold}' + proc.pm2_env.name + '{/}');
- this.metadataBox.setLine(1, 'Namespace ' + '{bold}' + proc.pm2_env.namespace + '{/}');
- this.metadataBox.setLine(2, 'Version ' + '{bold}' + proc.pm2_env.version + '{/}');
- this.metadataBox.setLine(3, 'Restarts ' + proc.pm2_env.restart_time);
- this.metadataBox.setLine(4, 'Uptime ' + ((proc.pm2_env.pm_uptime && proc.pm2_env.status == 'online') ? timeSince(proc.pm2_env.pm_uptime) : 0));
- this.metadataBox.setLine(5, 'Script path ' + proc.pm2_env.pm_exec_path);
- this.metadataBox.setLine(6, 'Script args ' + (proc.pm2_env.args ? (typeof proc.pm2_env.args == 'string' ? JSON.parse(proc.pm2_env.args.replace(/'/g, '"')):proc.pm2_env.args).join(' ') : 'N/A'));
- this.metadataBox.setLine(7, 'Interpreter ' + proc.pm2_env.exec_interpreter);
- this.metadataBox.setLine(8, 'Interpreter args ' + (proc.pm2_env.node_args.length != 0 ? proc.pm2_env.node_args : 'N/A'));
- this.metadataBox.setLine(9, 'Exec mode ' + (proc.pm2_env.exec_mode == 'fork_mode' ? '{bold}fork{/}' : '{blue-fg}{bold}cluster{/}'));
- this.metadataBox.setLine(10, 'Node.js version ' + proc.pm2_env.node_version);
- this.metadataBox.setLine(11, 'watch & reload ' + (proc.pm2_env.watch ? '{green-fg}{bold}✔{/}' : '{red-fg}{bold}✘{/}'));
- this.metadataBox.setLine(12, 'Unstable restarts ' + proc.pm2_env.unstable_restarts);
- this.metadataBox.setLine(13, 'Comment ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.comment : 'N/A'));
- this.metadataBox.setLine(14, 'Revision ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.revision : 'N/A'));
- this.metadataBox.setLine(15, 'Branch ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.branch : 'N/A'));
- this.metadataBox.setLine(16, 'Remote url ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.url : 'N/A'));
- this.metadataBox.deleteLine(17)
- this.metadataBox.setLine(17, 'Last update ' + ((proc.pm2_env.versioning) ? proc.pm2_env.versioning.update_time : 'N/A'));
- if (Object.keys(proc.pm2_env.axm_monitor).length != this.metricsBox.items.length) {
- this.metricsBox.clearItems();
- }
- var j = 0;
- for (var key in proc.pm2_env.axm_monitor) {
- var metric_name = proc.pm2_env.axm_monitor[key].hasOwnProperty('value') ? proc.pm2_env.axm_monitor[key].value : proc.pm2_env.axm_monitor[key]
- var metric_unit = proc.pm2_env.axm_monitor[key].hasOwnProperty('unit') ? proc.pm2_env.axm_monitor[key].unit : null
- var probe = `{bold}${key}{/} {|} ${metric_name}${metric_unit == null ? '' : ' ' + metric_unit}`
- if (this.metricsBox.getItem(j)) {
- this.metricsBox.setItem(j, probe);
- }
- else {
- this.metricsBox.pushItem(probe);
- }
- j++;
- }
- this.screen.render();
- }
- return this;
- }
- /**
- * Put Log
- * @method log
- * @param {} data
- * @return this
- */
- Dashboard.log = function(type, data) {
- var that = this;
- if(typeof(this.logLines[data.process.pm_id]) == "undefined"){
- this.logLines[data.process.pm_id]=[];
- }
- // Logs colors
- switch (type) {
- case 'PM2':
- var color = '{blue-fg}';
- break;
- case 'out':
- var color = '{green-fg}';
- break;
- case 'err':
- var color = '{red-fg}';
- break;
- default:
- var color = '{white-fg}';
- }
- var logs = data.data.split('\n')
- logs.forEach((log) => {
- if (log.length > 0) {
- this.logLines[data.process.pm_id].push(color + data.process.name + '{/} > ' + log)
- //removing logs if longer than limit
- let count = 0;
- let max_count = 0;
- let leading_process_id = -1;
- for(var process_id in this.logLines){
- count += this.logLines[process_id].length;
- if( this.logLines[process_id].length > max_count){
- leading_process_id = process_id;
- max_count = this.logLines[process_id].length;
- }
- }
- if (count > 200) {
- this.logLines[leading_process_id].shift()
- }
- }
- })
- return this;
- }
- module.exports = Dashboard;
- function timeSince(date) {
- var seconds = Math.floor((new Date() - date) / 1000);
- var 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';
- }
- /* Args :
- * p : Percent 0 - 100
- * rgb_ : Array of rgb [255, 255, 255]
- * Return :
- * Hexa #FFFFFF
- */
- function gradient(p, rgb_beginning, rgb_end) {
- var w = (p / 100) * 2 - 1;
- var w1 = (w + 1) / 2.0;
- var w2 = 1 - w1;
- var rgb = [parseInt(rgb_beginning[0] * w1 + rgb_end[0] * w2),
- parseInt(rgb_beginning[1] * w1 + rgb_end[1] * w2),
- parseInt(rgb_beginning[2] * w1 + rgb_end[2] * w2)];
- return "#" + ((1 << 24) + (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]).toString(16).slice(1);
- }
|