@daemonitor/plugins 1.1.10 → 1.1.11
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.
|
@@ -204,19 +204,20 @@ async function snmpWalkIndexed(t, oid, timeout = 10000) {
|
|
|
204
204
|
async function collectQnap(t) {
|
|
205
205
|
const tempWarn = t.tempWarnC ?? 70;
|
|
206
206
|
const volWarn = t.volumeWarnPct ?? 90;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
snmpGet(t, QNAP.memTotal),
|
|
214
|
-
snmpGet(t, QNAP.memFree),
|
|
215
|
-
snmpWalk(t, "1.3.6.1.4.1.24681.1.2.15.1.3"), // SystemFanSpeed table
|
|
207
|
+
// The light scalar gets can run together, but the WALKS must be SERIALIZED:
|
|
208
|
+
// QNAP's SNMP agent silently drops rows (and whole tables) under concurrent
|
|
209
|
+
// sessions, producing partial reads that flip a failing NAS back to a false
|
|
210
|
+
// "healthy". 5-min cadence — sequential latency is fine.
|
|
211
|
+
const [model, cpuTemp, sysTemp, memTotal, memFree] = await Promise.all([
|
|
212
|
+
snmpGet(t, QNAP.model), snmpGet(t, QNAP.cpuTemp), snmpGet(t, QNAP.sysTemp),
|
|
213
|
+
snmpGet(t, QNAP.memTotal), snmpGet(t, QNAP.memFree),
|
|
216
214
|
]);
|
|
217
|
-
if (model == null && cpuTemp == null
|
|
215
|
+
if (model == null && cpuTemp == null) {
|
|
218
216
|
return { name: t.name, ip: t.ip, kind: "qnap", reachable: false };
|
|
219
217
|
}
|
|
218
|
+
const cpuLoads = await snmpWalk(t, "1.3.6.1.2.1.25.3.3.1.2"); // hrProcessorLoad (per core)
|
|
219
|
+
const storDescrs = await snmpWalkIndexed(t, "1.3.6.1.2.1.25.2.3.1.3"); // hrStorageDescr
|
|
220
|
+
const fanSpeeds = await snmpWalk(t, "1.3.6.1.4.1.24681.1.2.15.1.3"); // SystemFanSpeed table
|
|
220
221
|
// CPU: average per-core load.
|
|
221
222
|
const cores = cpuLoads.map(Number).filter((n) => !isNaN(n));
|
|
222
223
|
const cpuPct = cores.length ? Math.round(cores.reduce((a, b) => a + b, 0) / cores.length) : undefined;
|
|
@@ -233,10 +234,14 @@ async function collectQnap(t) {
|
|
|
233
234
|
// Presence keys on MODEL/CAPACITY ("--" = empty bay), so a FAILING disk
|
|
234
235
|
// (unreadable temp, negative status) is still reported, not mistaken for empty.
|
|
235
236
|
const DT = "1.3.6.1.4.1.24681.1.2.11.1."; // disk table columns: 2=descr 3=temp 4=status 5=model 6=capacity 7=smart
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
// Serialized (not Promise.all) — concurrent column walks are what make the
|
|
238
|
+
// agent drop rows.
|
|
239
|
+
const wStatus = await snmpWalkIndexed(t, DT + "4");
|
|
240
|
+
const wTemp = await snmpWalkIndexed(t, DT + "3");
|
|
241
|
+
const wSmart = await snmpWalkIndexed(t, DT + "7");
|
|
242
|
+
const wDescr = await snmpWalkIndexed(t, DT + "2");
|
|
243
|
+
const wModel = await snmpWalkIndexed(t, DT + "5");
|
|
244
|
+
const wCap = await snmpWalkIndexed(t, DT + "6");
|
|
240
245
|
const col = (rows) => {
|
|
241
246
|
const m = {};
|
|
242
247
|
for (const r of rows)
|
|
@@ -257,6 +262,12 @@ async function collectQnap(t) {
|
|
|
257
262
|
const tempC = parseC(cTp[i] || null);
|
|
258
263
|
disks.push({ slot: Number(i), descr: cDs[i] || undefined, model, capacity, tempC, status, smart: cSm[i] && cSm[i] !== "--" ? cSm[i] : undefined });
|
|
259
264
|
}
|
|
265
|
+
// A reachable QNAP always has ≥1 disk. Zero means the disk-table walks came
|
|
266
|
+
// back empty (agent timed out) — treat as an INCOMPLETE read, not a clean
|
|
267
|
+
// bill of health, so a real disk error isn't falsely cleared to green.
|
|
268
|
+
if (!disks.length) {
|
|
269
|
+
return { name: t.name, ip: t.ip, kind: "qnap", reachable: true, error: "SNMP read incomplete — disk table empty" };
|
|
270
|
+
}
|
|
260
271
|
// A present disk is bad only on a real error status, an explicitly-bad SMART
|
|
261
272
|
// verdict, or overheating — not a blank "--" or an empty bay.
|
|
262
273
|
const badDisks = disks.filter((d) => (d.status != null && d.status !== 0) ||
|