pm2-ls.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. const cst = require('../../../constants')
  2. const Common = require('../../Common')
  3. const Configuration = require('../../Configuration')
  4. const UxHelpers = require('./helpers.js')
  5. const chalk = require('chalk')
  6. const Table = require('cli-tableau')
  7. const Passwd = require('../../tools/passwd.js')
  8. const List = {}
  9. const CONDENSED_MODE = (process.stdout.columns || 300) < 120
  10. /**
  11. * Check if dump file contains same apps that the one managed by PM2
  12. */
  13. function checkIfProcessAreDumped(list) {
  14. try {
  15. var dump_raw = require('fs').readFileSync(cst.DUMP_FILE_PATH)
  16. var dump = JSON.parse(dump_raw)
  17. var apps_dumped = dump.map(proc => proc.name)
  18. var apps_running = list
  19. .filter(proc => proc.pm2_env.pmx_module != true)
  20. .map(proc => proc.name)
  21. var diff = apps_dumped.filter(a => !apps_running.includes(a))
  22. if (diff.length > 0) {
  23. Common.warn(`Current process list is not synchronized with saved list. App ${chalk.bold(diff.join(' '))} differs. Type 'pm2 save' to synchronize.`)
  24. }
  25. else if (apps_dumped.length != apps_running.length) {
  26. Common.warn(`Current process list is not synchronized with saved list. Type 'pm2 save' to synchronize.`)
  27. }
  28. } catch(e) {
  29. }
  30. }
  31. var proc_id = 0
  32. /**
  33. * List Applications and Modules managed by PM2
  34. */
  35. function listModulesAndAppsManaged(list, commander) {
  36. var name_col_size = 11
  37. if (list && list.length > 0)
  38. name_col_size = (list.reduce((p, c) => (p.name.length > c.name.length) ? p : c)).name.length + 5
  39. var app_head = {
  40. id: 5,
  41. name: name_col_size,
  42. namespace: 13,
  43. version: 9,
  44. mode: 9,
  45. pid: 10,
  46. uptime: 8,
  47. '↺': 6,
  48. status: 11,
  49. cpu: 10,
  50. mem: 10,
  51. user: 10,
  52. watching: 10
  53. }
  54. var mod_head = {
  55. id: 4,
  56. module: 30,
  57. version: 15,
  58. pid: 10,
  59. status: 10,
  60. '↺': 6,
  61. cpu: 10,
  62. mem: 10,
  63. user: 10
  64. }
  65. if (CONDENSED_MODE) {
  66. app_head = {
  67. id: 4,
  68. name: 20,
  69. mode: 10,
  70. '↺': 6,
  71. status: 11,
  72. cpu: 10,
  73. memory: 10
  74. }
  75. mod_head = {
  76. id: 4,
  77. name: 20,
  78. status: 10,
  79. cpu: 10,
  80. mem: 10
  81. }
  82. }
  83. var app_table = new Table({
  84. head : Object.keys(app_head),
  85. colWidths: Object.keys(app_head).map(k => app_head[k]),
  86. colAligns : ['left'],
  87. style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
  88. })
  89. var module_table = new Table({
  90. head : Object.keys(mod_head),
  91. colWidths: Object.keys(mod_head).map(k => mod_head[k]),
  92. colAligns : ['left'],
  93. style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
  94. })
  95. var sortField = 'name', sortOrder = 'asc', sort,
  96. fields = {
  97. name: 'pm2_env.name',
  98. namespace: 'pm2_env.namespace',
  99. pid: 'pid',
  100. id: 'pm_id',
  101. cpu: 'monit.cpu',
  102. memory: 'monit.memory',
  103. uptime: 'pm2_env.pm_uptime',
  104. status: 'pm2_env.status'
  105. }
  106. if (commander && commander.sort) {
  107. sort = commander.sort.split(':');
  108. if(fields[sort[0].toLowerCase()]) {
  109. sortField = sort[0].toLowerCase();
  110. sortOrder = sort.length === 2 ? sort[1] : 'asc';
  111. }
  112. }
  113. list.sort(function(a, b) {
  114. var fieldA = UxHelpers.getNestedProperty(fields[sortField], a)
  115. var fieldB = UxHelpers.getNestedProperty(fields[sortField], b)
  116. if (sortOrder === 'desc') {
  117. if (fieldA > fieldB)
  118. return -1
  119. if (fieldA < fieldB)
  120. return 1
  121. } else {
  122. if (fieldA < fieldB)
  123. return -1
  124. if (fieldA > fieldB)
  125. return 1
  126. }
  127. return 0
  128. })
  129. list.forEach(function(l) {
  130. var obj = {}
  131. if (l.pm2_env.pm_id > proc_id) {
  132. proc_id = l.pm2_env.pm_id
  133. }
  134. var mode = l.pm2_env.exec_mode
  135. var status = l.pm2_env.status
  136. var key = l.pm2_env.pm_id
  137. key = chalk.bold.cyan(key)
  138. if (l.pm2_env.axm_options) {
  139. var is_tracing_enabled = false
  140. if (l.pm2_env.axm_options.tracing &&
  141. typeof(l.pm2_env.axm_options.tracing) == 'boolean' &&
  142. l.pm2_env.axm_options.tracing == true)
  143. is_tracing_enabled = true
  144. if (l.pm2_env.axm_options.tracing &&
  145. l.pm2_env.axm_options.tracing.enabled &&
  146. typeof(l.pm2_env.axm_options.tracing.enabled) == 'boolean' &&
  147. l.pm2_env.axm_options.tracing.enabled == true)
  148. is_tracing_enabled = true
  149. if (is_tracing_enabled == true)
  150. l.pm2_env.name = chalk.green('☵') + ' ' + l.pm2_env.name
  151. if (l.pm2_env._km_monitored)
  152. l.pm2_env.name = chalk.bold.green('◉') + ' ' + l.pm2_env.name
  153. }
  154. if (l.pm2_env.pmx_module == true) {
  155. if (l.pm2_env.name == 'pm2-sysmonit') return
  156. // pm2 ls for Modules
  157. obj[key] = []
  158. obj[key].push(l.name)
  159. // Module version + PID
  160. if (!CONDENSED_MODE) {
  161. var pid = l.pm2_env.axm_options.pid ? l.pm2_env.axm_options.pid : l.pid
  162. obj[key].push(l.pm2_env.version || 'N/A', pid)
  163. }
  164. // Status
  165. obj[key].push(UxHelpers.colorStatus(status))
  166. // Restart
  167. if (!CONDENSED_MODE)
  168. obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0)
  169. // CPU + Memory
  170. obj[key].push(l.monit ? (l.monit.cpu + '%') : 'N/A', l.monit ? UxHelpers.bytesToSize(l.monit.memory, 1) : 'N/A' )
  171. // User
  172. if (!CONDENSED_MODE) {
  173. if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') {
  174. // Resolve user id to username
  175. let users = Passwd.getUsers()
  176. Object.keys(users).forEach(function(username) {
  177. var user = users[username]
  178. if (user.userId == l.pm2_env.uid) {
  179. l.pm2_env.uid = user.username
  180. }
  181. })
  182. }
  183. obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username))
  184. }
  185. UxHelpers.safe_push(module_table, obj)
  186. }
  187. else {
  188. // pm2 ls for Applications
  189. obj[key] = []
  190. // PM2 ID
  191. obj[key].push(l.pm2_env.name)
  192. // Namespace
  193. if (!CONDENSED_MODE)
  194. obj[key].push(l.pm2_env.namespace)
  195. // Version
  196. if (!CONDENSED_MODE)
  197. obj[key].push(l.pm2_env.version)
  198. // Exec mode
  199. obj[key].push(mode == 'fork_mode' ? chalk.inverse.bold('fork') : chalk.blue.bold('cluster'))
  200. // PID
  201. if (!CONDENSED_MODE)
  202. obj[key].push(l.pid)
  203. // Uptime
  204. if (!CONDENSED_MODE)
  205. obj[key].push((l.pm2_env.pm_uptime && status == 'online') ? UxHelpers.timeSince(l.pm2_env.pm_uptime) : 0)
  206. // Restart
  207. obj[key].push(l.pm2_env.restart_time ? l.pm2_env.restart_time : 0)
  208. // Status
  209. obj[key].push(UxHelpers.colorStatus(status))
  210. // CPU
  211. obj[key].push(l.monit ? l.monit.cpu + '%' : 'N/A')
  212. // Memory
  213. obj[key].push(l.monit ? UxHelpers.bytesToSize(l.monit.memory, 1) : 'N/A')
  214. // User
  215. if (!CONDENSED_MODE) {
  216. if (l.pm2_env.uid && typeof(l.pm2_env.uid) == 'number') {
  217. // Resolve user id to username
  218. let users = Passwd.getUsers()
  219. Object.keys(users).forEach(function(username) {
  220. var user = users[username]
  221. if (user.userId == l.pm2_env.uid) {
  222. l.pm2_env.uid = user.username
  223. }
  224. })
  225. }
  226. obj[key].push(chalk.bold(l.pm2_env.uid || l.pm2_env.username))
  227. }
  228. // Watch status
  229. if (!CONDENSED_MODE)
  230. obj[key].push(l.pm2_env.watch ? chalk.green.bold('enabled') : chalk.grey('disabled'))
  231. UxHelpers.safe_push(app_table, obj)
  232. }
  233. })
  234. // Print Applications Managed
  235. console.log(app_table.toString())
  236. // Print Modules Managed
  237. if (module_table.length > 0) {
  238. console.log(chalk.bold(`Module${module_table.length > 1 ? 's' : ''}`))
  239. console.log(module_table.toString())
  240. }
  241. proc_id++
  242. }
  243. // Container display
  244. function containersListing(sys_infos) {
  245. var stacked_docker = (process.stdout.columns || 100) < 140
  246. var docker_head = {
  247. id: 4,
  248. image: 50,
  249. status: 10,
  250. '↺': 6,
  251. cpu: 10,
  252. mem: 10,
  253. 'net I/O ⇵': 11,
  254. 'fs I/O ⇵': 11
  255. }
  256. if (stacked_docker) {
  257. docker_head = {
  258. id: 4,
  259. image: 25,
  260. status: 10,
  261. cpu: 10,
  262. mem: 10
  263. }
  264. }
  265. var docker_table = new Table({
  266. colWidths: Object.keys(docker_head).map(k => docker_head[k]),
  267. head : Object.keys(docker_head),
  268. colAligns : ['left'],
  269. style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
  270. })
  271. sys_infos.containers.forEach((c) => {
  272. var cpu = c.stats.cpu_percent
  273. var mem = c.stats.mem_percent == 0 ? '0' : c.stats.mem_percent
  274. var id = chalk.bold.cyan(proc_id++)
  275. var state = UxHelpers.colorStatus(c.state)
  276. if (stacked_docker)
  277. docker_table.push([id, c.image, state, `${cpu}%`, `${mem}mb`])
  278. else {
  279. docker_table.push([
  280. id,
  281. c.image,
  282. state,
  283. c.restartCount,
  284. `${cpu == 0 ? '0' : cpu}%`,
  285. `${mem}mb`,
  286. `${c.stats.netIO.rx}/${isNaN(c.stats.netIO.tx) == true ? '0.0' : c.stats.netIO.tx}`,
  287. `${c.stats.blockIO.r}/${c.stats.blockIO.w}`
  288. ])
  289. }
  290. })
  291. console.log(chalk.bold(`Container${sys_infos.containers.length > 1 ? 's' : ''}`))
  292. console.log(docker_table.toString())
  293. }
  294. /**
  295. * High resource processes
  296. */
  297. function listHighResourcesProcesses(sys_infos) {
  298. const CPU_MIN_SHOW = 60
  299. const MEM_MIN_SHOW = 30
  300. var sys_proc_head = ['id', 'cmd', 'pid', 'cpu', 'mem', 'uid']
  301. var sys_proc_table = new Table({
  302. colWidths: [4, CONDENSED_MODE ? 29 : 77, 10, 10, 10, 8],
  303. head : sys_proc_head,
  304. colAligns : ['left'],
  305. style : {'padding-left' : 1, head : ['cyan', 'bold'], compact : true}
  306. })
  307. sys_infos.processes.cpu_sorted = sys_infos.processes.cpu_sorted.filter((proc) => {
  308. return proc.cpu > CPU_MIN_SHOW && proc.cmd.includes('node') === false &&
  309. proc.cmd.includes('God Daemon') === false
  310. })
  311. sys_infos.processes.cpu_sorted.forEach(proc => {
  312. var cpu = `${UxHelpers.colorizedMetric(proc.cpu, 40, 70, '%')}`
  313. var mem = `${UxHelpers.colorizedMetric(proc.memory, 40, 70, '%')}`
  314. var cmd = proc.cmd
  315. sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid])
  316. })
  317. sys_infos.processes.mem_sorted = sys_infos.processes.mem_sorted.filter((proc) => {
  318. return proc.memory > MEM_MIN_SHOW && proc.cmd.includes('node') == false
  319. })
  320. sys_infos.processes.mem_sorted.forEach((proc) => {
  321. var cpu = `${UxHelpers.colorizedMetric(proc.cpu, 40, 70, '%')}`
  322. var mem = `${UxHelpers.colorizedMetric(proc.memory, 40, 70, '%')}`
  323. var cmd = proc.cmd
  324. // if (proc.cmd.length > 50)
  325. // cmd = '…' + proc.cmd.slice(proc.cmd.length - 48, proc.cmd.length)
  326. sys_proc_table.push([chalk.bold.cyan(proc_id++), cmd, proc.pid, cpu, mem, proc.uid])
  327. })
  328. if (sys_infos.processes.cpu_sorted.length >= 1 || sys_infos.processes.mem_sorted.length >= 1) {
  329. console.log(chalk.bold('Intensive Processes'))
  330. console.log(sys_proc_table.toString())
  331. }
  332. }
  333. /**
  334. * Sys info line
  335. */
  336. function miniMonitBar(sys_infos) {
  337. let sys_metrics = sys_infos.pm2_env.axm_monitor
  338. let cpu = sys_metrics['CPU Usage']
  339. if (typeof(cpu) == 'undefined') return
  340. var sys_summary_line = `${chalk.bold.cyan('host metrics')} `
  341. sys_summary_line += `| ${chalk.bold('cpu')}: ${UxHelpers.colorizedMetric(cpu.value, 40, 70, '%')}`
  342. let temp = sys_metrics['CPU Temperature'].value
  343. if (temp && temp != '-1') {
  344. sys_summary_line += ` ${UxHelpers.colorizedMetric(temp, 50, 70, 'º')}`
  345. }
  346. let mem_total = sys_metrics['RAM Total'].value
  347. let mem_available = sys_metrics['RAM Available'].value
  348. if (mem_total) {
  349. var perc_mem_usage = (((mem_available) / mem_total) * 100).toFixed(1)
  350. sys_summary_line += ` | ${chalk.bold('mem free')}: ${UxHelpers.colorizedMetric(perc_mem_usage, 30, 10, '%')} `
  351. }
  352. let interfaces = Object.keys(sys_metrics).filter(m => m.includes('net') && m != 'net:default').map(i => i.split(':')[2]).filter((iface, i, self) => self.indexOf(iface) === i)
  353. interfaces.forEach(iface => {
  354. if (!sys_metrics[`net:rx_5:${iface}`]) return
  355. sys_summary_line += `| ${chalk.bold(iface)}: `
  356. sys_summary_line += `⇓ ${UxHelpers.colorizedMetric(sys_metrics[`net:rx_5:${iface}`].value, 10, 20, 'mb/s')} `
  357. sys_summary_line += `⇑ ${UxHelpers.colorizedMetric(sys_metrics[`net:tx_5:${iface}`].value, 10, 20, 'mb/s')} `
  358. })
  359. if (CONDENSED_MODE == false) {
  360. let read = sys_metrics['Disk Reads'].value
  361. let write = sys_metrics['Disk Writes'].value
  362. sys_summary_line += `| ${chalk.bold('disk')}: ⇓ ${UxHelpers.colorizedMetric(read, 10, 20, 'mb/s')}`
  363. sys_summary_line += ` ⇑ ${UxHelpers.colorizedMetric(write, 10, 20, 'mb/s')} `
  364. let disks = Object.keys(sys_metrics).filter(m => m.includes('fs:')).map(i => i.split(':')[2]).filter((iface, i, self) => self.indexOf(iface) === i)
  365. var disk_nb = 0
  366. disks.forEach(fs => {
  367. let use = sys_metrics[`fs:use:${fs}`].value
  368. if (use > 60)
  369. sys_summary_line += `${chalk.grey(fs)} ${UxHelpers.colorizedMetric(use, 80, 90, '%')} `
  370. })
  371. }
  372. sys_summary_line += '|'
  373. console.log(sys_summary_line)
  374. }
  375. /**
  376. * pm2 ls
  377. * @method dispAsTable
  378. * @param {Object} list
  379. * @param {Object} system informations (via pm2 sysmonit/pm2 sysinfos)
  380. */
  381. module.exports = function(list, commander) {
  382. var pm2_conf = Configuration.getSync('pm2')
  383. if (!list)
  384. return console.log('list empty')
  385. listModulesAndAppsManaged(list, commander)
  386. let sysmonit = list.filter(proc => proc.name == 'pm2-sysmonit')
  387. if (sysmonit && sysmonit[0])
  388. miniMonitBar(sysmonit[0])
  389. checkIfProcessAreDumped(list)
  390. }