@daemonitor/plugins 1.1.38 → 1.1.39
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/dist/lib/processes.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// Aggregate named service processes (nginx, php-fpm, db, …)
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Aggregate named service processes (nginx, php-fpm, db, …). A Linux host's
|
|
2
|
+
// come from /proc (sampleProcesses), with CPU measured over the time since
|
|
3
|
+
// the last sample, so a graph of it moves. Everywhere else, and a container's
|
|
4
|
+
// (LxcPlugin via `lxc exec … ps`), from `ps` output, whose %cpu is the average
|
|
5
|
+
// over each process's lifetime: for a daemon up for months it never moves.
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
5
8
|
// The command whose output parseProcesses() expects (headerless: comm rss pcpu).
|
|
6
9
|
export const PS_CMD = "ps -eo comm=,rss=,pcpu=";
|
|
7
10
|
const WATCH = [
|
|
@@ -12,6 +15,106 @@ const WATCH = [
|
|
|
12
15
|
{ key: "redis", test: (c) => c.startsWith("redis-server") || c.startsWith("redis") },
|
|
13
16
|
{ key: "node", test: (c) => c.startsWith("node") },
|
|
14
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* Parse /proc/<pid>/stat. Its comm is in parentheses and may hold spaces or
|
|
20
|
+
* a ')' of its own, so the fields are counted from after the last ')': the
|
|
21
|
+
* first one there is field 3 (state), so utime (14) is index 11.
|
|
22
|
+
*/
|
|
23
|
+
export function parseStat(pid, text) {
|
|
24
|
+
const open = text.indexOf("("), close = text.lastIndexOf(")");
|
|
25
|
+
if (open < 0 || close < open)
|
|
26
|
+
return null;
|
|
27
|
+
const f = text.slice(close + 2).trim().split(/\s+/);
|
|
28
|
+
const utime = Number(f[11]), stime = Number(f[12]), start = Number(f[19]), rss = Number(f[21]);
|
|
29
|
+
if (![utime, stime, start, rss].every(Number.isFinite))
|
|
30
|
+
return null;
|
|
31
|
+
return { pid, comm: text.slice(open + 1, close), ticks: utime + stime, rss, start };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Group processes by WATCH, each group's cpu as a percent of one core. With
|
|
35
|
+
* `prev` (each pid's ticks at the last sample, `dt` seconds ago), over that
|
|
36
|
+
* interval; a pid that wasn't there then started since, so all its ticks are
|
|
37
|
+
* the interval's. Without (the first sample), over each one's life so far,
|
|
38
|
+
* as ps reports it.
|
|
39
|
+
*/
|
|
40
|
+
export function groupStats(now, prev, o) {
|
|
41
|
+
const agg = new Map();
|
|
42
|
+
for (const p of now) {
|
|
43
|
+
const w = WATCH.find((x) => x.test(p.comm));
|
|
44
|
+
if (!w)
|
|
45
|
+
continue;
|
|
46
|
+
const cur = agg.get(w.key) || { name: w.key, count: 0, cpu: 0, mem: 0 };
|
|
47
|
+
cur.count++;
|
|
48
|
+
cur.mem += p.rss * o.page;
|
|
49
|
+
if (prev && o.dt > 0) {
|
|
50
|
+
const before = prev.get(p.pid);
|
|
51
|
+
// Fewer ticks than before is a reused pid: a new process.
|
|
52
|
+
const used = before != null && p.ticks >= before ? p.ticks - before : p.ticks;
|
|
53
|
+
cur.cpu += (used / o.hz / o.dt) * 100;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const life = o.uptime - p.start / o.hz;
|
|
57
|
+
if (life > 0)
|
|
58
|
+
cur.cpu += (p.ticks / o.hz / life) * 100;
|
|
59
|
+
}
|
|
60
|
+
agg.set(w.key, cur);
|
|
61
|
+
}
|
|
62
|
+
return Array.from(agg.values())
|
|
63
|
+
.map((p) => ({ ...p, cpu: Math.round(p.cpu * 10) / 10 }))
|
|
64
|
+
.sort((a, b) => b.mem - a.mem);
|
|
65
|
+
}
|
|
66
|
+
/** Every process's stat, or null where there's no /proc (macOS). */
|
|
67
|
+
export function readProcStats() {
|
|
68
|
+
let pids;
|
|
69
|
+
try {
|
|
70
|
+
pids = readdirSync("/proc").filter((d) => /^\d+$/.test(d));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const d of pids) {
|
|
77
|
+
try {
|
|
78
|
+
const s = parseStat(Number(d), readFileSync(`/proc/${d}/stat`, "utf8"));
|
|
79
|
+
if (s)
|
|
80
|
+
out.push(s);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// It exited between the listing and the read.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
// The last sample's ticks per pid, and when it was taken.
|
|
89
|
+
let last = null;
|
|
90
|
+
let hz = 0, page = 0;
|
|
91
|
+
const conf = (name, fallback) => {
|
|
92
|
+
try {
|
|
93
|
+
return Number(execSync(`getconf ${name}`, { encoding: "utf8", timeout: 2000 }).trim()) || fallback;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return fallback;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/** A Linux host's named processes from /proc, cpu since the last call; null where there's no /proc. */
|
|
100
|
+
export function sampleProcesses() {
|
|
101
|
+
const stats = readProcStats();
|
|
102
|
+
if (!stats)
|
|
103
|
+
return null;
|
|
104
|
+
if (!hz) {
|
|
105
|
+
hz = conf("CLK_TCK", 100);
|
|
106
|
+
page = conf("PAGESIZE", 4096);
|
|
107
|
+
}
|
|
108
|
+
let uptime = 0;
|
|
109
|
+
try {
|
|
110
|
+
uptime = Number(readFileSync("/proc/uptime", "utf8").split(" ")[0]) || 0;
|
|
111
|
+
}
|
|
112
|
+
catch { /* lifetime cpu reads 0 */ }
|
|
113
|
+
const at = Date.now();
|
|
114
|
+
const groups = groupStats(stats, last?.ticks ?? null, { dt: last ? (at - last.at) / 1000 : 0, hz, page, uptime });
|
|
115
|
+
last = { at, ticks: new Map(stats.map((s) => [s.pid, s.ticks])) };
|
|
116
|
+
return groups;
|
|
117
|
+
}
|
|
15
118
|
export function parseProcesses(out) {
|
|
16
119
|
const agg = new Map();
|
|
17
120
|
for (const line of String(out || "").split("\n")) {
|
|
@@ -2,11 +2,12 @@ import { createMonitoringPlugin } from "../../lib/MonitoringPlugin.js";
|
|
|
2
2
|
import { arch, cpus, freemem, hostname, loadavg, networkInterfaces, platform, release, totalmem, type, uptime, } from "os";
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
|
-
import { parseProcesses, PS_CMD } from "../../lib/processes.js";
|
|
5
|
+
import { parseProcesses, PS_CMD, sampleProcesses } from "../../lib/processes.js";
|
|
6
6
|
// Named service processes (nginx / php-fpm / db / …) running on the host.
|
|
7
|
+
// From /proc on Linux, cpu over the time since the last report; ps elsewhere.
|
|
7
8
|
function collectProcesses() {
|
|
8
9
|
try {
|
|
9
|
-
return parseProcesses(execSync(PS_CMD, { encoding: "utf8", timeout: 5000 }));
|
|
10
|
+
return sampleProcesses() ?? parseProcesses(execSync(PS_CMD, { encoding: "utf8", timeout: 5000 }));
|
|
10
11
|
}
|
|
11
12
|
catch {
|
|
12
13
|
return [];
|