@daemonitor/plugins 1.1.8 → 1.1.10
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,15 @@ 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,
|
|
207
|
+
const [model, cpuTemp, sysTemp, 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
|
-
snmpGet(t, QNAP.hdNumber),
|
|
207
211
|
snmpWalk(t, "1.3.6.1.2.1.25.3.3.1.2"), // hrProcessorLoad (per core)
|
|
208
212
|
snmpWalkIndexed(t, "1.3.6.1.2.1.25.2.3.1.3"), // hrStorageDescr
|
|
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
|
|
209
216
|
]);
|
|
210
217
|
if (model == null && cpuTemp == null && !cpuLoads.length) {
|
|
211
218
|
return { name: t.name, ip: t.ip, kind: "qnap", reachable: false };
|
|
@@ -213,20 +220,42 @@ async function collectQnap(t) {
|
|
|
213
220
|
// CPU: average per-core load.
|
|
214
221
|
const cores = cpuLoads.map(Number).filter((n) => !isNaN(n));
|
|
215
222
|
const cpuPct = cores.length ? Math.round(cores.reduce((a, b) => a + b, 0) / cores.length) : undefined;
|
|
216
|
-
//
|
|
217
|
-
const
|
|
223
|
+
// RAM used %. Values look like "16013.4 MB".
|
|
224
|
+
const memTotalMB = parseNum(memTotal), memFreeMB = parseNum(memFree);
|
|
225
|
+
const memPct = memTotalMB && memTotalMB > 0 && memFreeMB != null
|
|
226
|
+
? Math.round(((memTotalMB - memFreeMB) / memTotalMB) * 100) : undefined;
|
|
227
|
+
// Fans — each entry looks like "1835 RPM".
|
|
228
|
+
const fans = fanSpeeds.map((f) => parseNum(f)).filter((n) => n != null && n > 0);
|
|
229
|
+
// Disks — walk each column of the disk table ONCE (6 small walks) rather than
|
|
230
|
+
// 6 gets × N bays. QNAP's SNMP agent is flaky under concurrent-get bursts and
|
|
231
|
+
// silently drops rows (partial reads that hid disks); a per-column walk is a
|
|
232
|
+
// single session each — far gentler and returns every populated row atomically.
|
|
233
|
+
// Presence keys on MODEL/CAPACITY ("--" = empty bay), so a FAILING disk
|
|
234
|
+
// (unreadable temp, negative status) is still reported, not mistaken for empty.
|
|
235
|
+
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
|
+
const [wStatus, wTemp, wSmart, wDescr, wModel, wCap] = await Promise.all([
|
|
237
|
+
snmpWalkIndexed(t, DT + "4"), snmpWalkIndexed(t, DT + "3"), snmpWalkIndexed(t, DT + "7"),
|
|
238
|
+
snmpWalkIndexed(t, DT + "2"), snmpWalkIndexed(t, DT + "5"), snmpWalkIndexed(t, DT + "6"),
|
|
239
|
+
]);
|
|
240
|
+
const col = (rows) => {
|
|
241
|
+
const m = {};
|
|
242
|
+
for (const r of rows)
|
|
243
|
+
m[r.idx] = r.val;
|
|
244
|
+
return m;
|
|
245
|
+
};
|
|
246
|
+
const cSt = col(wStatus), cTp = col(wTemp), cSm = col(wSmart), cDs = col(wDescr), cMd = col(wModel), cCap = col(wCap);
|
|
247
|
+
const slots = (Object.keys(cMd).length ? Object.keys(cMd) : Object.keys(cDs))
|
|
248
|
+
.sort((a, b) => Number(a) - Number(b));
|
|
218
249
|
const disks = [];
|
|
219
|
-
for (
|
|
220
|
-
const [
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const tempC = parseC(tp);
|
|
225
|
-
// Empty bay: no readable temp and a non-ready (negative/absent, e.g. -5)
|
|
226
|
-
// status. Skip it — an empty slot isn't a failed disk.
|
|
227
|
-
if (tempC == null && (status == null || status < 0))
|
|
250
|
+
for (const i of slots) {
|
|
251
|
+
const model = cMd[i] && cMd[i] !== "--" ? cMd[i] : undefined;
|
|
252
|
+
const capacity = cCap[i] && cCap[i] !== "--" ? cCap[i] : undefined;
|
|
253
|
+
// Only a truly empty bay (no model AND no capacity) is skipped.
|
|
254
|
+
if (!model && !capacity)
|
|
228
255
|
continue;
|
|
229
|
-
|
|
256
|
+
const status = cSt[i] != null ? Number(cSt[i]) : undefined;
|
|
257
|
+
const tempC = parseC(cTp[i] || null);
|
|
258
|
+
disks.push({ slot: Number(i), descr: cDs[i] || undefined, model, capacity, tempC, status, smart: cSm[i] && cSm[i] !== "--" ? cSm[i] : undefined });
|
|
230
259
|
}
|
|
231
260
|
// A present disk is bad only on a real error status, an explicitly-bad SMART
|
|
232
261
|
// verdict, or overheating — not a blank "--" or an empty bay.
|
|
@@ -248,7 +277,10 @@ async function collectQnap(t) {
|
|
|
248
277
|
name: t.name, ip: t.ip, kind: "qnap", reachable: true,
|
|
249
278
|
model: model || undefined,
|
|
250
279
|
cpuPct, cpuTempC: parseC(cpuTemp), sysTempC: parseC(sysTemp),
|
|
251
|
-
|
|
280
|
+
memPct, memTotalMB: memTotalMB || undefined,
|
|
281
|
+
fans, fanRpm: fans.length ? Math.min(...fans) : undefined,
|
|
282
|
+
disks, diskCount: disks.length,
|
|
283
|
+
badDisks: badDisks.map((d) => ({ slot: d.slot, descr: d.descr, model: d.model, status: d.status, smart: d.smart, tempC: d.tempC })),
|
|
252
284
|
volumes, fullVolumes: fullVols,
|
|
253
285
|
tempWarnC: tempWarn, volumeWarnPct: volWarn,
|
|
254
286
|
};
|