pm2-perfmonitor 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +59 -55
- package/lib/alert.js +29 -29
- package/lib/app.js +516 -499
- package/lib/defaults.js +123 -104
- package/lib/execa-helper.js +17 -17
- package/lib/job-conf.js +39 -39
- package/lib/message.js +35 -35
- package/lib/perf-sampler.js +241 -239
- package/lib/pm2-extra.js +54 -54
- package/lib/utils.js +62 -62
- package/lib/zombie-check.js +65 -0
- package/package.json +11 -2
package/lib/utils.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
const pidusage = require('pidusage')
|
|
2
|
-
|
|
3
|
-
const parseParamToArray = (value, defaultVal = []) => {
|
|
4
|
-
if (Array.isArray(value)) return value
|
|
5
|
-
|
|
6
|
-
if (typeof value === 'string') {
|
|
7
|
-
return value.split(',').map((v) => v.trim())
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return defaultVal
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const parseParamToNumber = (value) => {
|
|
14
|
-
if (typeof value === 'number') return value
|
|
15
|
-
if (!value) return 0
|
|
16
|
-
|
|
17
|
-
if (typeof value === 'string') {
|
|
18
|
-
return Number(value)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return 0
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const parseBool = (value, defaultVal = false) => {
|
|
25
|
-
if (typeof value === 'boolean') return value
|
|
26
|
-
|
|
27
|
-
if (value === 'true') return true
|
|
28
|
-
if (value === 'false') return false
|
|
29
|
-
|
|
30
|
-
return defaultVal
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param { number} duration - sleep duration (ms)
|
|
35
|
-
*/
|
|
36
|
-
const sleepAsync = (duration = 0) => {
|
|
37
|
-
return new Promise((resolve) => {
|
|
38
|
-
setTimeout(resolve, duration)
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 获取指定进程的CPU使用率
|
|
44
|
-
* @param {string| number} pid
|
|
45
|
-
* @returns { Promise<number> } CPU 使用率
|
|
46
|
-
*/
|
|
47
|
-
const getSysCpuUsageByPid = async (pid) => {
|
|
48
|
-
try {
|
|
49
|
-
const stats = await pidusage(pid)
|
|
50
|
-
return stats.cpu
|
|
51
|
-
} catch (err) {
|
|
52
|
-
console.error('Call pidusage error:', err)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
module.exports = {
|
|
57
|
-
parseParamToArray,
|
|
58
|
-
parseParamToNumber,
|
|
59
|
-
parseBool,
|
|
60
|
-
sleepAsync,
|
|
61
|
-
getSysCpuUsageByPid,
|
|
62
|
-
}
|
|
1
|
+
const pidusage = require('pidusage')
|
|
2
|
+
|
|
3
|
+
const parseParamToArray = (value, defaultVal = []) => {
|
|
4
|
+
if (Array.isArray(value)) return value
|
|
5
|
+
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
return value.split(',').map((v) => v.trim())
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return defaultVal
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const parseParamToNumber = (value) => {
|
|
14
|
+
if (typeof value === 'number') return value
|
|
15
|
+
if (!value) return 0
|
|
16
|
+
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
return Number(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const parseBool = (value, defaultVal = false) => {
|
|
25
|
+
if (typeof value === 'boolean') return value
|
|
26
|
+
|
|
27
|
+
if (value === 'true') return true
|
|
28
|
+
if (value === 'false') return false
|
|
29
|
+
|
|
30
|
+
return defaultVal
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param { number} duration - sleep duration (ms)
|
|
35
|
+
*/
|
|
36
|
+
const sleepAsync = (duration = 0) => {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
setTimeout(resolve, duration)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取指定进程的CPU使用率
|
|
44
|
+
* @param {string| number} pid
|
|
45
|
+
* @returns { Promise<number> } CPU 使用率
|
|
46
|
+
*/
|
|
47
|
+
const getSysCpuUsageByPid = async (pid) => {
|
|
48
|
+
try {
|
|
49
|
+
const stats = await pidusage(pid)
|
|
50
|
+
return stats.cpu
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('Call pidusage error:', err)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
parseParamToArray,
|
|
58
|
+
parseParamToNumber,
|
|
59
|
+
parseBool,
|
|
60
|
+
sleepAsync,
|
|
61
|
+
getSysCpuUsageByPid,
|
|
62
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { getExeca } = require('./execa-helper')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Linux/macOS 专属:判定进程是否为原生僵尸进程(仅靠系统状态,无CPU检测)
|
|
5
|
+
* @param {number} pid 进程PID(必须为正整数)
|
|
6
|
+
* @returns {Promise<{
|
|
7
|
+
* isZombie: boolean, // 是否为僵尸进程(state=Z/<defunct>)
|
|
8
|
+
* exists: boolean // 进程是否存在
|
|
9
|
+
* failed?: boolean // 是否获取进程状态失败
|
|
10
|
+
* }>}
|
|
11
|
+
*/
|
|
12
|
+
const isZombieStateProcess = async (pid) => {
|
|
13
|
+
// 1. 基础参数校验
|
|
14
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
15
|
+
return { isZombie: false, exists: false }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// 2. 执行ps命令(Linux/macOS通用,仅获取PID、状态、进程名)
|
|
20
|
+
const cmdArgs =
|
|
21
|
+
process.platform === 'linux'
|
|
22
|
+
? ['-o', 'pid,state,comm', '-p', pid]
|
|
23
|
+
: ['-o', 'pid,state,command', '-p', pid] // macOS 调整进程名字段
|
|
24
|
+
|
|
25
|
+
const execa = await getExeca()
|
|
26
|
+
|
|
27
|
+
const { stdout } = await execa('ps', cmdArgs, {
|
|
28
|
+
timeout: 3000, // 3秒超时保护
|
|
29
|
+
reject: false, // 进程不存在时不抛异常,自行解析
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// 3. 解析输出(格式:PID S COMM → 1234 Z node)
|
|
33
|
+
const lines = stdout.trim().split('\n')
|
|
34
|
+
if (lines.length < 2) {
|
|
35
|
+
return { isZombie: false, exists: false } // 进程不存在
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [pidStr, state, name] = lines[1].trim().split(/\s+/)
|
|
39
|
+
const processExists = Number(pidStr) === pid
|
|
40
|
+
// 4. 核心判定:state=Z 或 进程名包含<defunct>
|
|
41
|
+
const isZombie =
|
|
42
|
+
processExists && (state === 'Z' || name?.includes('<defunct>'))
|
|
43
|
+
|
|
44
|
+
return { isZombie, exists: processExists }
|
|
45
|
+
} catch (err) {
|
|
46
|
+
// 权限不足/命令执行失败 → 兜底返回
|
|
47
|
+
console.warn(`[${process.platform}] 检测进程 ${pid} 失败:`, err.message)
|
|
48
|
+
return { isZombie: false, exists: false, failed: true }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 通过 cpu 值判断是否为僵尸进程:最近连续 N 次 cpu 负载值全是 0%
|
|
54
|
+
* @param { object } opts
|
|
55
|
+
* @param { number[] } opts.cpus - 连续的 cpu 负载值
|
|
56
|
+
* @param { number } opts.maxHits - 最大命中次数
|
|
57
|
+
*/
|
|
58
|
+
const isZombieCpuProcess = (opts) => {
|
|
59
|
+
const cpus = opts.cpus
|
|
60
|
+
|
|
61
|
+
return cpus.length >= opts.maxHits && cpus.every((v) => v === 0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 导出函数
|
|
65
|
+
module.exports = { isZombieStateProcess, isZombieCpuProcess }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pm2-perfmonitor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "A pm2 module for performance monitoring. Automatically detect zombie processes and restart it",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "elenh",
|
|
@@ -49,8 +49,17 @@
|
|
|
49
49
|
"pmx": "latest"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/compat": "^2.0.3",
|
|
53
|
+
"@eslint/js": "^9.39.4",
|
|
52
54
|
"changelogen": "^0.6.2",
|
|
53
55
|
"cz-conventional-changelog": "^3.3.0",
|
|
54
|
-
"
|
|
56
|
+
"eslint": "^9.39.4",
|
|
57
|
+
"eslint-config-prettier": "^10.1.8",
|
|
58
|
+
"eslint-plugin-import": "^2.32.0",
|
|
59
|
+
"eslint-plugin-n": "^17.24.0",
|
|
60
|
+
"eslint-plugin-prettier": "^5.5.5",
|
|
61
|
+
"globals": "^17.4.0",
|
|
62
|
+
"minimist": "^1.2.8",
|
|
63
|
+
"prettier": "^3.8.1"
|
|
55
64
|
}
|
|
56
65
|
}
|