Skip to content

Commit 1b4e884

Browse files
committed
display fix + sysmetrics by default
1 parent 76a78ab commit 1b4e884

20 files changed

Lines changed: 685 additions & 1155 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
### Bug Fixes
55

66
- Fix `pm2 serve` returning 403 Forbidden on Windows — traversal guard used hardcoded `/` separator #6109
7-
- Fix `pm2 ls` table misalignment when a username exceeds the `user` column width — cli-tableau's `truncate()` miscounts ANSI bytes, so styled usernames were cut too short and leaked bold into the `watching` column
8-
- `pm2 ls` host-metrics line now only lists network interfaces that are actually carrying traffic (hides idle interfaces like utun, awdl, bridge, anpi, and unused en*)
9-
- `pm2 ls` host-metrics line: replaced `mem free` with `ram usage` (%), added GPU memory/temperature when a GPU reports it, and per-interface network errors/drops shown only when non-zero
10-
- `pm2 ls` now switches to the condensed layout based on the table's actual computed width vs. the terminal width (instead of a fixed column-count threshold that ignored the dynamic name column), and caps the `name` column at 40 chars so a long process name can't overflow the table
7+
- Fix `pm2 ls` table misalignment when a username exceeds the `user` column width — cli-tableau's `truncate()` miscounts ANSI bytes, leaking bold into the `watching` column
8+
- Fix long status lines (e.g. `Applying action … on app […]`) wrapping on narrow terminals — `Common.printOut` now ANSI-aware crops single-line TTY output to terminal width (piped output unaffected)
119

10+
### Features
11+
12+
- `pm2 ls` host-metrics line now shown by default`pm2 update`)
13+
- `pm2 ls` adaptive layout: picks the widest layout that fits the terminal — full → condensed → new ultra-compact `mini` (`id · name · status · cpu · mem`) — and caps the `name` column so long names can't overflow the table
14+
- `pm2 ls` host-metrics line only lists network interfaces carrying traffic (hides idle utun/awdl/bridge/anpi/unused en*)
15+
- `pm2 ls` host-metrics line: replaced `mem free` with `ram usage` (%), added GPU memory/temperature when reported, per-interface network errors/drops shown when non-zero
16+
17+
### Core Refactor
18+
19+
- Replace the bundled `pm2-sysmonit` module and `systeminformation` with `lib/tools/SysMetrics.js` (Linux/macOS); `pm2 slist`/`getSystemData` and the Docker metrics path now read this collector. Covered by `test/programmatic/sysmetrics.mocha.js`
1220

1321
## 7.0.1
1422

lib/API.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,6 @@ class API {
180180
if (meta.new_pm2_instance == false && that.daemon_mode === true)
181181
return cb(err, meta);
182182

183-
that.launchSysMonitoring(() => {})
184183
// If new pm2 instance has been popped
185184
// Launch all modules
186185
that.launchAll(that, function(err_mod) {
@@ -410,7 +409,6 @@ class API {
410409
that.Client.launchRPC(function() {
411410
that.resurrect(function() {
412411
Common.printOut(chalk.blue.bold('>>>>>>>>>> PM2 updated'));
413-
that.launchSysMonitoring(() => {})
414412
that.launchAll(that, function() {
415413
KMDaemon.launchAndInteract(that._conf, {
416414
pm2_version: pkg.version
@@ -1725,8 +1723,13 @@ class API {
17251723
if ((conf.PM2_PROGRAMMATIC && process.env.PM2_USAGE != 'CLI'))
17261724
return false;
17271725

1728-
return that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
1729-
doList(err, proc_list)
1726+
// Best-effort host metrics snapshot (daemon-side SysMetrics collector);
1727+
// absence just means the `host metrics` line is skipped.
1728+
return that.Client.executeRemote('getSystemData', {}, (serrr, sdata) => {
1729+
systemdata = sdata || null
1730+
that.Client.executeRemote('getMonitorData', {}, (err, proc_list) => {
1731+
doList(err, proc_list)
1732+
})
17301733
})
17311734

17321735
function doList(err, list) {
@@ -1756,7 +1759,7 @@ class API {
17561759
chalk.bold(that.gl_interact_infos.machine_name),
17571760
chalk.bold(dashboard_url))
17581761
}
1759-
UX.list(list, commander);
1762+
UX.list(list, commander, systemdata);
17601763
//Common.printOut(chalk.white.italic(' Use `pm2 show <id|name>` to get more details about an app'));
17611764
}
17621765

lib/API/Extra.js

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,35 +30,6 @@ module.exports = function(CLI) {
3030
});
3131
};
3232

33-
/**
34-
* Launch the bundled pm2-sysmonit module (modules/pm2-sysmonit)
35-
*/
36-
CLI.prototype.launchSysMonitoring = function(cb) {
37-
if ((this.pm2_configuration && this.pm2_configuration.sysmonit != 'true') ||
38-
process.env.TRAVIS ||
39-
global.it === 'function' ||
40-
cst.IS_WINDOWS === true)
41-
return cb ? cb(null) : null
42-
43-
// Pointing at the directory keeps the process name (basename) as `pm2-sysmonit`
44-
var filepath = path.resolve(__dirname, '../../modules/pm2-sysmonit')
45-
46-
if (!fs.existsSync(filepath))
47-
return cb ? cb(null) : null
48-
49-
this.start({
50-
script: filepath
51-
}, {
52-
started_as_module : true
53-
}, (err, res) => {
54-
if (err) {
55-
Common.printError(cst.PREFIX_MSG_ERR + 'Error while trying to serve : ' + err.message || err);
56-
return cb ? cb(err) : this.speedList(cst.ERROR_EXIT);
57-
}
58-
return cb ? cb(null) : this.speedList();
59-
});
60-
};
61-
6233
/**
6334
* Show application environment
6435
* @method env

lib/API/UX/pm2-ls.js

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -93,32 +93,60 @@ function listModulesAndAppsManaged(list, commander) {
9393
}
9494

9595
// cli-tableau renders at fixed widths and never shrinks to fit the
96-
// terminal; switch to the condensed layout when the full table (incl.
97-
// the dynamic name column) would overflow, rather than a fixed column
98-
// count threshold. process.stdout.columns is undefined when piped, so
99-
// the 300 fallback keeps the full table for non-TTY output.
100-
var full_keys = Object.keys(app_head)
101-
var full_width = full_keys.reduce((s, k) => s + app_head[k], 0) + full_keys.length + 1
102-
CONDENSED_MODE = (process.stdout.columns || 300) < full_width
103-
104-
if (CONDENSED_MODE) {
105-
app_head = {
106-
id: id_width,
107-
name: 20,
108-
mode: 10,
109-
'↺': 6,
110-
status: 11,
111-
cpu: 10,
112-
memory: 10
113-
}
96+
// terminal. Pick the widest layout that fits: full -> condensed -> mini.
97+
// process.stdout.columns is undefined when piped, so the 300 fallback
98+
// keeps the full table for non-TTY output.
99+
var term_cols = process.stdout.columns || 300
100+
var tableWidth = function (head) {
101+
var keys = Object.keys(head)
102+
return keys.reduce((s, k) => s + head[k], 0) + keys.length + 1
103+
}
114104

115-
mod_head = {
116-
id: id_width,
117-
name: 20,
118-
status: 10,
119-
cpu: 10,
120-
mem: 10
121-
}
105+
var condensed_app = {
106+
id: id_width,
107+
name: Math.min(name_col_size, 20),
108+
mode: 10,
109+
'↺': 6,
110+
status: 11,
111+
cpu: 10,
112+
memory: 10
113+
}
114+
var condensed_mod = {
115+
id: id_width,
116+
name: Math.min(name_col_size, 20),
117+
status: 10,
118+
cpu: 10,
119+
mem: 10
120+
}
121+
122+
var mini_app = {
123+
id: id_width,
124+
name: Math.min(name_col_size, 16),
125+
status: 11,
126+
cpu: 8,
127+
mem: 10
128+
}
129+
var mini_mod = {
130+
id: id_width,
131+
name: Math.min(name_col_size, 16),
132+
status: 11,
133+
cpu: 8,
134+
mem: 10
135+
}
136+
137+
var MINI_MODE = false
138+
139+
if (term_cols >= tableWidth(app_head)) {
140+
CONDENSED_MODE = false
141+
} else if (term_cols >= tableWidth(condensed_app)) {
142+
CONDENSED_MODE = true
143+
app_head = condensed_app
144+
mod_head = condensed_mod
145+
} else {
146+
CONDENSED_MODE = true
147+
MINI_MODE = true
148+
app_head = mini_app
149+
mod_head = mini_mod
122150
}
123151

124152
var app_table = new Table({
@@ -264,7 +292,8 @@ function listModulesAndAppsManaged(list, commander) {
264292
obj[key].push(l.pm2_env.version)
265293

266294
// Exec mode
267-
obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster'))
295+
if (!MINI_MODE)
296+
obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster'))
268297

269298
// PID
270299
if (!CONDENSED_MODE)
@@ -275,7 +304,8 @@ function listModulesAndAppsManaged(list, commander) {
275304
obj[key].push((l.pm2_env.pm_uptime && status == 'online') ? UxHelpers.timeSince(l.pm2_env.pm_uptime) : 0)
276305

277306
// Restart
278-
obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0)
307+
if (!MINI_MODE)
308+
obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0)
279309

280310
// Status
281311
obj[key].push(UxHelpers.colorStatus(status))
@@ -441,7 +471,7 @@ function miniMonitBar(sys_infos) {
441471
var sys_summary_line = `${chalk.bold.cyan('host metrics')} `
442472
sys_summary_line += `| ${chalk.bold('cpu')}: ${UxHelpers.colorizedMetric(cpu.value, 40, 70, '%')}`
443473

444-
let temp = sys_metrics['CPU Temperature'].value
474+
let temp = sys_metrics['CPU Temperature'] ? sys_metrics['CPU Temperature'].value : null
445475
if (temp && temp != '-1') {
446476
sys_summary_line += ` ${UxHelpers.colorizedMetric(temp, 50, 70, 'º')}`
447477
}
@@ -491,9 +521,9 @@ function miniMonitBar(sys_infos) {
491521
sys_summary_line += `${chalk.bold('drop')} ${UxHelpers.colorizedMetric(rx_drop + tx_drop, 1, 10, '/min')} `
492522
})
493523

494-
if (CONDENSED_MODE == false) {
524+
if (CONDENSED_MODE == false && sys_metrics['Disk Reads']) {
495525
let read = sys_metrics['Disk Reads'].value
496-
let write = sys_metrics['Disk Writes'].value
526+
let write = sys_metrics['Disk Writes'] ? sys_metrics['Disk Writes'].value : 0
497527

498528
sys_summary_line += `| ${chalk.bold('disk')}: ⇓ ${UxHelpers.colorizedMetric(read, 10, 20, 'mb/s')}`
499529
sys_summary_line += ` ⇑ ${UxHelpers.colorizedMetric(write, 10, 20, 'mb/s')} `
@@ -502,6 +532,7 @@ function miniMonitBar(sys_infos) {
502532
var disk_nb = 0
503533

504534
disks.forEach(fs => {
535+
if (!sys_metrics[`fs:use:${fs}`]) return
505536
let use = sys_metrics[`fs:use:${fs}`].value
506537
if (use > 60)
507538
sys_summary_line += `${chalk.gray(fs)} ${UxHelpers.colorizedMetric(use, 80, 90, '%')} `
@@ -516,19 +547,19 @@ function miniMonitBar(sys_infos) {
516547
* pm2 ls
517548
* @method dispAsTable
518549
* @param {Object} list
519-
* @param {Object} system informations (via pm2 sysmonit/pm2 sysinfos)
550+
* @param {Object} commander
551+
* @param {Object} systemdata host metrics snapshot (daemon SysMetrics, axm_monitor-shaped)
520552
*/
521-
module.exports = function(list, commander) {
553+
module.exports = function(list, commander, systemdata) {
522554
var pm2_conf = Configuration.getSync('pm2')
523555

524556
if (!list)
525557
return console.log('list empty')
526558

527559
listModulesAndAppsManaged(list, commander)
528560

529-
let sysmonit = list.filter(proc => proc.name == 'pm2-sysmonit')
530-
if (sysmonit && sysmonit[0])
531-
miniMonitBar(sysmonit[0])
561+
if (systemdata && Object.keys(systemdata).length > 0)
562+
miniMonitBar({ pm2_env: { axm_monitor: systemdata } })
532563

533564
// Disable warning message of process list not saved
534565
//checkIfProcessAreDumped(list)

lib/Common.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,35 @@ Common.logMod = function(msg) {
513513
return console.log(`${cst.PREFIX_MSG_MOD}${msg}`);
514514
}
515515

516+
var ANSI_RE = /\x1b\[[0-9;]*m/g;
517+
518+
/**
519+
* Crop a (possibly ANSI-colored) single line to `width` visible columns so
520+
* it does not wrap on narrow terminals. ANSI escapes are copied through
521+
* without counting toward the width, and a reset is appended after the
522+
* ellipsis so the truncation cannot leak color into following output.
523+
*/
524+
function cropToWidth(str, width) {
525+
var out = '', visible = 0, i = 0;
526+
while (i < str.length) {
527+
if (str[i] === '\x1b') {
528+
var m = str.slice(i).match(/^\x1b\[[0-9;]*m/);
529+
if (m) { out += m[0]; i += m[0].length; continue; }
530+
}
531+
if (visible >= width - 1)
532+
return out + '\u2026\x1b[0m';
533+
out += str[i]; visible++; i++;
534+
}
535+
return out;
536+
}
537+
516538
Common.printOut = function() {
517539
if (process.env.PM2_SILENT === 'true' || process.env.PM2_PROGRAMMATIC === 'true') return false;
518-
return console.log.apply(console, arguments);
540+
var msg = util.format.apply(util, arguments);
541+
var cols = process.stdout.columns;
542+
if (cols && msg.indexOf('\n') === -1 && msg.replace(ANSI_RE, '').length > cols)
543+
msg = cropToWidth(msg, cols);
544+
return console.log(msg);
519545
};
520546

521547

lib/Daemon.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ Daemon.prototype.innerStart = function(cb) {
218218
profileMEM : profile.bind(this, 'mem'),
219219
prepare : God.prepare,
220220
getMonitorData : God.getMonitorData,
221+
getSystemData : God.getSystemData,
221222

222223
startProcessId : God.startProcessId,
223224
stopProcessId : God.stopProcessId,

lib/Worker.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,24 @@ const pkg = require('../package.json');
1111

1212
var cst = require('../constants.js');
1313
var vCheck = require('./VersionCheck.js')
14+
var Configuration = require('./Configuration.js');
15+
var SysMetrics = require('./tools/SysMetrics.js');
1416

1517
module.exports = function(God) {
1618
var timer = null;
19+
var sysMetricsTimer = null;
1720

1821
God.CronJobs = new Map();
1922
God.Worker = {};
2023
God.Worker.is_running = false;
24+
God.system_infos = {};
25+
26+
/**
27+
* RPC: expose the latest host metrics snapshot (see SysMetrics.js).
28+
*/
29+
God.getSystemData = function(env, cb) {
30+
return cb(null, God.system_infos || {});
31+
};
2132

2233
God.getCronID = function(pm_id) {
2334
return `cron-${pm_id}`
@@ -149,9 +160,27 @@ module.exports = function(God) {
149160
};
150161

151162

163+
// Host metrics collection (replaces the external pm2-sysmonit process).
164+
// Enabled by default; disable with `pm2 set pm2:sysmonit false` + `pm2 update`.
165+
var sysMetricsTask = function() {
166+
try {
167+
SysMetrics.collect(function(data) {
168+
God.system_infos = data || {};
169+
});
170+
} catch (e) {
171+
debug('[PM2][WORKER] SysMetrics collection failed: %s', e && e.message);
172+
}
173+
};
174+
152175
God.Worker.start = function() {
153176
timer = setInterval(wrappedTasks, cst.WORKER_INTERVAL);
154177

178+
var pm2_conf = Configuration.getSync('pm2');
179+
if (!pm2_conf || pm2_conf.sysmonit != 'false') {
180+
sysMetricsTask();
181+
sysMetricsTimer = setInterval(sysMetricsTask, cst.WORKER_INTERVAL);
182+
}
183+
155184
if (!process.env.PM2_DISABLE_VERSION_CHECK) {
156185
setInterval(() => {
157186
vCheck({
@@ -165,5 +194,7 @@ module.exports = function(God) {
165194
God.Worker.stop = function() {
166195
if (timer !== null)
167196
clearInterval(timer);
197+
if (sysMetricsTimer !== null)
198+
clearInterval(sysMetricsTimer);
168199
};
169200
};

lib/binaries/CLI.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -809,12 +809,6 @@ commander.command('jlist')
809809
pm2.jlist()
810810
});
811811

812-
commander.command('sysmonit')
813-
.description('start system monitoring daemon')
814-
.action(function() {
815-
pm2.launchSysMonitoring()
816-
})
817-
818812
commander.command('slist')
819813
.alias('sysinfos')
820814
.option('-t --tree', 'show as tree')

0 commit comments

Comments
 (0)