@daemonitor/plugins 1.1.16 → 1.1.17
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.
|
@@ -5,6 +5,14 @@ import { hostname } from "os";
|
|
|
5
5
|
const execAsync = promisify(exec);
|
|
6
6
|
const EXEC_OPTS = { maxBuffer: 32 * 1024 * 1024 }; // containers.length can be large
|
|
7
7
|
const HEALTH_RE = /\((healthy|unhealthy|health: starting|starting)\)/i;
|
|
8
|
+
// Previous cumulative NetIO/BlockIO totals per container id, to derive a
|
|
9
|
+
// per-second rate across polls. Module-level (one collect per host per cycle).
|
|
10
|
+
const ioPrev = {};
|
|
11
|
+
// docker stats renders "12MB / 7.02MB" (rx / tx, or read / write). Split + convert.
|
|
12
|
+
function pairToBytes(value) {
|
|
13
|
+
const [x, y] = (value || "").split("/").map((p) => p.trim());
|
|
14
|
+
return { a: memToBytes(x), b: memToBytes(y) };
|
|
15
|
+
}
|
|
8
16
|
/** Convert docker's "83.86MiB" / "1.952GiB" memory strings to bytes. */
|
|
9
17
|
function memToBytes(value) {
|
|
10
18
|
if (!value)
|
|
@@ -52,13 +60,17 @@ async function collectContainers(bin) {
|
|
|
52
60
|
}
|
|
53
61
|
if (!containers.length)
|
|
54
62
|
return containers;
|
|
55
|
-
// 2. live stats for running containers (one batched call, best-effort)
|
|
63
|
+
// 2. live stats for running containers (one batched call, best-effort).
|
|
64
|
+
// NetIO/BlockIO are CUMULATIVE since container start, so we derive a per-second
|
|
65
|
+
// rate from the delta vs the previous poll (first poll → no rate).
|
|
56
66
|
try {
|
|
57
|
-
const { stdout: statsOut } = await execAsync(`${bin} stats --no-stream --format '{{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}'`, EXEC_OPTS);
|
|
67
|
+
const { stdout: statsOut } = await execAsync(`${bin} stats --no-stream --format '{{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}|{{.NetIO}}|{{.BlockIO}}'`, EXEC_OPTS);
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const seen = new Set();
|
|
58
70
|
for (const line of statsOut.split("\n")) {
|
|
59
71
|
if (!line.trim())
|
|
60
72
|
continue;
|
|
61
|
-
const [id, cpuPerc, memUsage, memPerc] = line.split("|");
|
|
73
|
+
const [id, cpuPerc, memUsage, memPerc, netIO, blkIO] = line.split("|");
|
|
62
74
|
const c = byId.get(id);
|
|
63
75
|
if (!c)
|
|
64
76
|
continue;
|
|
@@ -67,7 +79,28 @@ async function collectContainers(bin) {
|
|
|
67
79
|
c.mem = memToBytes(used);
|
|
68
80
|
c.memLimit = memToBytes(limit);
|
|
69
81
|
c.memPercent = parseFloat((memPerc || "").replace("%", "")) || 0;
|
|
82
|
+
const net = pairToBytes(netIO); // { a: rx, b: tx }
|
|
83
|
+
const blk = pairToBytes(blkIO); // { a: read, b: write }
|
|
84
|
+
seen.add(id);
|
|
85
|
+
const prev = ioPrev[id];
|
|
86
|
+
if (prev && now > prev.ts) {
|
|
87
|
+
const dt = (now - prev.ts) / 1000;
|
|
88
|
+
// Guard counter resets (container restart): negative delta → skip that field.
|
|
89
|
+
if (net.a >= prev.rx)
|
|
90
|
+
c.netIn = Math.round((net.a - prev.rx) / dt);
|
|
91
|
+
if (net.b >= prev.tx)
|
|
92
|
+
c.netOut = Math.round((net.b - prev.tx) / dt);
|
|
93
|
+
if (blk.a >= prev.rd)
|
|
94
|
+
c.blkRead = Math.round((blk.a - prev.rd) / dt);
|
|
95
|
+
if (blk.b >= prev.wr)
|
|
96
|
+
c.blkWrite = Math.round((blk.b - prev.wr) / dt);
|
|
97
|
+
}
|
|
98
|
+
ioPrev[id] = { rx: net.a, tx: net.b, rd: blk.a, wr: blk.b, ts: now };
|
|
70
99
|
}
|
|
100
|
+
// Drop prev samples for containers no longer present.
|
|
101
|
+
for (const k of Object.keys(ioPrev))
|
|
102
|
+
if (!seen.has(k))
|
|
103
|
+
delete ioPrev[k];
|
|
71
104
|
}
|
|
72
105
|
catch (err) {
|
|
73
106
|
console.error("docker: stats collection failed:", err.message);
|