@daemonitor/plugins 1.1.15 → 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);
|
|
@@ -98,6 +98,27 @@ function availableMem() {
|
|
|
98
98
|
// fall through to freemem()
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
if (platform() === "darwin") {
|
|
102
|
+
// macOS keeps `freemem()` near zero by design (RAM held as cache/compressed).
|
|
103
|
+
// Reclaimable memory ≈ free + inactive + speculative + purgeable pages —
|
|
104
|
+
// this tracks the kernel's own "available" (memory_pressure free %), so a
|
|
105
|
+
// healthy Mac doesn't read as 100% used.
|
|
106
|
+
try {
|
|
107
|
+
const out = execSync("vm_stat", { encoding: "utf8", timeout: 4000 });
|
|
108
|
+
const page = parseInt(out.match(/page size of (\d+) bytes/)?.[1] || "16384", 10);
|
|
109
|
+
const pages = (re) => parseInt(out.match(re)?.[1] || "0", 10);
|
|
110
|
+
const free = pages(/Pages free:\s+(\d+)/);
|
|
111
|
+
const inactive = pages(/Pages inactive:\s+(\d+)/);
|
|
112
|
+
const speculative = pages(/Pages speculative:\s+(\d+)/);
|
|
113
|
+
const purgeable = pages(/Pages purgeable:\s+(\d+)/);
|
|
114
|
+
const avail = (free + inactive + speculative + purgeable) * page;
|
|
115
|
+
if (avail > 0)
|
|
116
|
+
return avail;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// fall through to freemem()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
101
122
|
return freemem();
|
|
102
123
|
}
|
|
103
124
|
// Cumulative rx/tx byte counters summed across real interfaces (Linux
|
|
@@ -105,27 +126,55 @@ function availableMem() {
|
|
|
105
126
|
// so the figure reflects actual host network traffic, not container-to-host
|
|
106
127
|
// chatter. Returns null off Linux (rate is then omitted, not faked).
|
|
107
128
|
function readNetTotals() {
|
|
108
|
-
if (platform()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
if (platform() === "linux") {
|
|
130
|
+
try {
|
|
131
|
+
const data = readFileSync("/proc/net/dev", "utf8");
|
|
132
|
+
let rx = 0, tx = 0;
|
|
133
|
+
for (const line of data.split("\n")) {
|
|
134
|
+
const m = line.match(/^\s*([^:]+):\s*(.*)$/);
|
|
135
|
+
if (!m)
|
|
136
|
+
continue;
|
|
137
|
+
const iface = m[1].trim();
|
|
138
|
+
if (iface === "lo" || /^(veth|docker|br-|lxcbr|virbr|tap|tun)/.test(iface))
|
|
139
|
+
continue;
|
|
140
|
+
const cols = m[2].trim().split(/\s+/).map(Number);
|
|
141
|
+
rx += cols[0] || 0; // column 1 = rx bytes
|
|
142
|
+
tx += cols[8] || 0; // column 9 = tx bytes
|
|
143
|
+
}
|
|
144
|
+
return { rx, tx };
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
123
148
|
}
|
|
124
|
-
return { rx, tx };
|
|
125
149
|
}
|
|
126
|
-
|
|
127
|
-
|
|
150
|
+
if (platform() === "darwin") {
|
|
151
|
+
// `netstat -ibn`: Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll.
|
|
152
|
+
// Per-interface byte counters repeat across a NIC's address rows, so take one
|
|
153
|
+
// row per interface; skip loopback and virtual (bridge/utun/awdl/llw/gif/stf/ap).
|
|
154
|
+
try {
|
|
155
|
+
const out = execSync("netstat -ibn", { encoding: "utf8", timeout: 4000 });
|
|
156
|
+
let rx = 0, tx = 0;
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
for (const line of out.split("\n").slice(1)) {
|
|
159
|
+
const c = line.trim().split(/\s+/);
|
|
160
|
+
if (c.length < 10)
|
|
161
|
+
continue;
|
|
162
|
+
const name = c[0];
|
|
163
|
+
if (!name || name === "lo0" || /^(bridge|utun|llw|awdl|gif|stf|ap\d|anpi|en\d*_)/.test(name))
|
|
164
|
+
continue;
|
|
165
|
+
if (seen.has(name))
|
|
166
|
+
continue;
|
|
167
|
+
seen.add(name);
|
|
168
|
+
rx += Number(c[6]) || 0; // Ibytes
|
|
169
|
+
tx += Number(c[9]) || 0; // Obytes
|
|
170
|
+
}
|
|
171
|
+
return { rx, tx };
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
128
176
|
}
|
|
177
|
+
return null;
|
|
129
178
|
}
|
|
130
179
|
// Host OS metrics: cpu (from cpus().times), memory, disk, load, uptime,
|
|
131
180
|
// addresses, and network throughput. Matches the frontend `os` fleetAdapter case.
|