@daemonitor/plugins 1.1.8 → 1.1.9
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.
|
@@ -39,8 +39,13 @@ const QNAP = {
|
|
|
39
39
|
hdNumber: "1.3.6.1.4.1.24681.1.2.10.0", // installed disk count
|
|
40
40
|
hdDescr: "1.3.6.1.4.1.24681.1.2.11.1.2.", // + index
|
|
41
41
|
hdTemp: "1.3.6.1.4.1.24681.1.2.11.1.3.",
|
|
42
|
-
hdStatus: "1.3.6.1.4.1.24681.1.2.11.1.4.", // 0 = ready/ok
|
|
42
|
+
hdStatus: "1.3.6.1.4.1.24681.1.2.11.1.4.", // 0 = ready/ok, negative = fault/empty
|
|
43
|
+
hdModel: "1.3.6.1.4.1.24681.1.2.11.1.5.", // "--" only when the bay is empty
|
|
44
|
+
hdCapacity: "1.3.6.1.4.1.24681.1.2.11.1.6.", // "7.28 TB" / "--"
|
|
43
45
|
hdSmart: "1.3.6.1.4.1.24681.1.2.11.1.7.", // "GOOD" / "Warning"
|
|
46
|
+
memTotal: "1.3.6.1.4.1.24681.1.2.2.0", // "16013.4 MB"
|
|
47
|
+
memFree: "1.3.6.1.4.1.24681.1.2.3.0", // "11984.9 MB"
|
|
48
|
+
fanSpeed: "1.3.6.1.4.1.24681.1.2.15.1.3.", // + index, "1835 RPM"
|
|
44
49
|
};
|
|
45
50
|
function slug(s) {
|
|
46
51
|
return s.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -199,13 +204,16 @@ async function snmpWalkIndexed(t, oid, timeout = 10000) {
|
|
|
199
204
|
async function collectQnap(t) {
|
|
200
205
|
const tempWarn = t.tempWarnC ?? 70;
|
|
201
206
|
const volWarn = t.volumeWarnPct ?? 90;
|
|
202
|
-
const [model, cpuTemp, sysTemp, hdNumRaw, cpuLoads, storDescrs] = await Promise.all([
|
|
207
|
+
const [model, cpuTemp, sysTemp, hdNumRaw, cpuLoads, storDescrs, memTotal, memFree, fanSpeeds] = await Promise.all([
|
|
203
208
|
snmpGet(t, QNAP.model),
|
|
204
209
|
snmpGet(t, QNAP.cpuTemp),
|
|
205
210
|
snmpGet(t, QNAP.sysTemp),
|
|
206
211
|
snmpGet(t, QNAP.hdNumber),
|
|
207
212
|
snmpWalk(t, "1.3.6.1.2.1.25.3.3.1.2"), // hrProcessorLoad (per core)
|
|
208
213
|
snmpWalkIndexed(t, "1.3.6.1.2.1.25.2.3.1.3"), // hrStorageDescr
|
|
214
|
+
snmpGet(t, QNAP.memTotal),
|
|
215
|
+
snmpGet(t, QNAP.memFree),
|
|
216
|
+
snmpWalk(t, "1.3.6.1.4.1.24681.1.2.15.1.3"), // SystemFanSpeed table
|
|
209
217
|
]);
|
|
210
218
|
if (model == null && cpuTemp == null && !cpuLoads.length) {
|
|
211
219
|
return { name: t.name, ip: t.ip, kind: "qnap", reachable: false };
|
|
@@ -213,20 +221,32 @@ async function collectQnap(t) {
|
|
|
213
221
|
// CPU: average per-core load.
|
|
214
222
|
const cores = cpuLoads.map(Number).filter((n) => !isNaN(n));
|
|
215
223
|
const cpuPct = cores.length ? Math.round(cores.reduce((a, b) => a + b, 0) / cores.length) : undefined;
|
|
216
|
-
//
|
|
224
|
+
// RAM used %. Values look like "16013.4 MB".
|
|
225
|
+
const memTotalMB = parseNum(memTotal), memFreeMB = parseNum(memFree);
|
|
226
|
+
const memPct = memTotalMB && memTotalMB > 0 && memFreeMB != null
|
|
227
|
+
? Math.round(((memTotalMB - memFreeMB) / memTotalMB) * 100) : undefined;
|
|
228
|
+
// Fans — each entry looks like "1835 RPM".
|
|
229
|
+
const fans = fanSpeeds.map((f) => parseNum(f)).filter((n) => n != null && n > 0);
|
|
230
|
+
// Disks — iterate 1..hdNumber (bounded). Presence is judged by MODEL/CAPACITY:
|
|
231
|
+
// an empty bay reports model "--", but a FAILING disk can report an unreadable
|
|
232
|
+
// temp ("-- C/-- F") + negative status yet still advertise a real model. The old
|
|
233
|
+
// temp+status heuristic silently skipped those bad disks (e.g. an HDD in SMART
|
|
234
|
+
// "Warning" with status -9) — so the NAS looked healthy while QTS flagged it.
|
|
217
235
|
const hdN = Math.min(Number(hdNumRaw) || 0, 24);
|
|
218
236
|
const disks = [];
|
|
219
237
|
for (let i = 1; i <= hdN; i++) {
|
|
220
|
-
const [st, tp, sm, ds] = await Promise.all([
|
|
221
|
-
snmpGet(t, QNAP.hdStatus + i), snmpGet(t, QNAP.hdTemp + i), snmpGet(t, QNAP.hdSmart + i),
|
|
238
|
+
const [st, tp, sm, ds, md, cap] = await Promise.all([
|
|
239
|
+
snmpGet(t, QNAP.hdStatus + i), snmpGet(t, QNAP.hdTemp + i), snmpGet(t, QNAP.hdSmart + i),
|
|
240
|
+
snmpGet(t, QNAP.hdDescr + i), snmpGet(t, QNAP.hdModel + i), snmpGet(t, QNAP.hdCapacity + i),
|
|
222
241
|
]);
|
|
242
|
+
const model = md && md !== "--" ? md : undefined;
|
|
243
|
+
const capacity = cap && cap !== "--" ? cap : undefined;
|
|
244
|
+
// Only a truly empty bay (no model AND no capacity) is skipped.
|
|
245
|
+
if (!model && !capacity)
|
|
246
|
+
continue;
|
|
223
247
|
const status = st != null ? Number(st) : undefined;
|
|
224
248
|
const tempC = parseC(tp);
|
|
225
|
-
|
|
226
|
-
// status. Skip it — an empty slot isn't a failed disk.
|
|
227
|
-
if (tempC == null && (status == null || status < 0))
|
|
228
|
-
continue;
|
|
229
|
-
disks.push({ slot: i, descr: ds || undefined, tempC, status, smart: sm && sm !== "--" ? sm : undefined });
|
|
249
|
+
disks.push({ slot: i, descr: ds || undefined, model, capacity, tempC, status, smart: sm && sm !== "--" ? sm : undefined });
|
|
230
250
|
}
|
|
231
251
|
// A present disk is bad only on a real error status, an explicitly-bad SMART
|
|
232
252
|
// verdict, or overheating — not a blank "--" or an empty bay.
|
|
@@ -248,7 +268,10 @@ async function collectQnap(t) {
|
|
|
248
268
|
name: t.name, ip: t.ip, kind: "qnap", reachable: true,
|
|
249
269
|
model: model || undefined,
|
|
250
270
|
cpuPct, cpuTempC: parseC(cpuTemp), sysTempC: parseC(sysTemp),
|
|
251
|
-
|
|
271
|
+
memPct, memTotalMB: memTotalMB || undefined,
|
|
272
|
+
fans, fanRpm: fans.length ? Math.min(...fans) : undefined,
|
|
273
|
+
disks, diskCount: disks.length,
|
|
274
|
+
badDisks: badDisks.map((d) => ({ slot: d.slot, descr: d.descr, model: d.model, status: d.status, smart: d.smart, tempC: d.tempC })),
|
|
252
275
|
volumes, fullVolumes: fullVols,
|
|
253
276
|
tempWarnC: tempWarn, volumeWarnPct: volWarn,
|
|
254
277
|
};
|