@daemonitor/plugins 1.1.12 → 1.1.14

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.
@@ -28,7 +28,26 @@ const SYS = {
28
28
  name: "1.3.6.1.2.1.1.5.0", // sysName
29
29
  ifOper: "1.3.6.1.2.1.2.2.1.8", // ifOperStatus (walk): 1=up 2=down
30
30
  ifAdmin: "1.3.6.1.2.1.2.2.1.7", // ifAdminStatus (walk): 1=up (enabled)
31
+ ifHCIn: "1.3.6.1.2.1.31.1.1.1.6", // ifHCInOctets (walk, Counter64)
32
+ ifHCOut: "1.3.6.1.2.1.31.1.1.1.10", // ifHCOutOctets (walk, Counter64)
31
33
  };
34
+ // UCD-SNMP-MIB + HOST-RESOURCES — CPU/RAM/load on net-snmp devices (EdgeOS
35
+ // routers, Linux hosts). All best-effort: absent on gear that doesn't ship the
36
+ // UCD MIB (many switches), so every read is null-tolerant.
37
+ const UCD = {
38
+ cpuIdle: "1.3.6.1.4.1.2021.11.11.0", // ssCpuIdle (%) → cpu% = 100 - idle
39
+ load1: "1.3.6.1.4.1.2021.10.1.3.1", // laLoad 1-min
40
+ load5: "1.3.6.1.4.1.2021.10.1.3.2",
41
+ load15: "1.3.6.1.4.1.2021.10.1.3.3",
42
+ memTotal: "1.3.6.1.4.1.2021.4.5.0", // memTotalReal (KB)
43
+ memAvail: "1.3.6.1.4.1.2021.4.6.0", // memAvailReal (KB, free excl. cache)
44
+ memBuffer: "1.3.6.1.4.1.2021.4.14.0", // memBuffer (KB)
45
+ memCached: "1.3.6.1.4.1.2021.4.15.0", // memCached (KB)
46
+ hrProcLoad: "1.3.6.1.2.1.25.3.3.1.2", // hrProcessorLoad (walk, per-core %) — CPU fallback
47
+ };
48
+ // Per-target previous interface-octet sample, for throughput (bps) rate calc
49
+ // across polls. Module-level: one collectDevice call per target per cycle.
50
+ const netPrev = {};
32
51
  // QNAP enterprise MIB (NAS-MIB, 1.3.6.1.4.1.24681.1.2). A full walk of this
33
52
  // subtree times out on QTS, so everything here is fetched by exact OID.
34
53
  const QNAP = {
@@ -167,6 +186,54 @@ async function collectDevice(t) {
167
186
  }
168
187
  // sysUpTime timeticks → seconds
169
188
  const upSecs = uptimeRaw != null ? Math.round((Number(String(uptimeRaw).replace(/[^0-9]/g, "")) || 0) / 100) : undefined;
189
+ // CPU / RAM / load / network — best-effort UCD-SNMP + IF-MIB counters. Absent
190
+ // on gear without the UCD MIB (returns "No Such Object" fast → null), so every
191
+ // field is optional and simply omitted when unsupported.
192
+ const [cpuIdle, load1, load5, load15, memTotal, memAvail, memBuffer, memCached, procLoads, hcIn, hcOut] = await Promise.all([
193
+ snmpGet(t, UCD.cpuIdle), snmpGet(t, UCD.load1), snmpGet(t, UCD.load5), snmpGet(t, UCD.load15),
194
+ snmpGet(t, UCD.memTotal), snmpGet(t, UCD.memAvail), snmpGet(t, UCD.memBuffer), snmpGet(t, UCD.memCached),
195
+ snmpWalk(t, UCD.hrProcLoad), snmpWalk(t, SYS.ifHCIn), snmpWalk(t, SYS.ifHCOut),
196
+ ]);
197
+ // CPU %: prefer ssCpuIdle (100 - idle); fall back to avg per-core hrProcessorLoad.
198
+ let cpuPct;
199
+ if (cpuIdle != null && !isNaN(Number(cpuIdle))) {
200
+ cpuPct = Math.max(0, Math.min(100, 100 - Math.round(Number(cpuIdle))));
201
+ }
202
+ else {
203
+ const cores = procLoads.map(Number).filter((n) => !isNaN(n));
204
+ if (cores.length)
205
+ cpuPct = Math.round(cores.reduce((a, b) => a + b, 0) / cores.length);
206
+ }
207
+ const load1n = parseNum(load1);
208
+ const loadavg = [parseNum(load1), parseNum(load5), parseNum(load15)].filter((n) => n != null);
209
+ // RAM used %: (total - free - buffers - cache) / total. memAvailReal excludes
210
+ // cache already on some agents; subtracting buffer+cache is safe (they default 0).
211
+ let memPct, memTotalMB;
212
+ const mTot = parseNum(memTotal), mAvail = parseNum(memAvail);
213
+ const mBuf = parseNum(memBuffer) || 0, mCache = parseNum(memCached) || 0;
214
+ if (mTot && mTot > 0 && mAvail != null) {
215
+ const usedKb = Math.max(0, mTot - mAvail - mBuf - mCache);
216
+ memPct = Math.round((usedKb / mTot) * 100);
217
+ memTotalMB = Math.round(mTot / 1024);
218
+ }
219
+ // Network throughput (aggregate bytes/sec, to match the OS plugin's NET
220
+ // metric): delta of summed HC octets vs the prior poll. First poll (or a
221
+ // counter reset) yields no rate.
222
+ let netIn, netOut;
223
+ const sumIn = hcIn.map(Number).filter((n) => !isNaN(n)).reduce((a, b) => a + b, 0);
224
+ const sumOut = hcOut.map(Number).filter((n) => !isNaN(n)).reduce((a, b) => a + b, 0);
225
+ if (hcIn.length || hcOut.length) {
226
+ const now = Date.now();
227
+ const prev = netPrev[t.ip];
228
+ if (prev && sumIn >= prev.in && sumOut >= prev.out && now > prev.ts) {
229
+ const dt = (now - prev.ts) / 1000;
230
+ if (dt > 0) {
231
+ netIn = Math.round((sumIn - prev.in) / dt);
232
+ netOut = Math.round((sumOut - prev.out) / dt);
233
+ }
234
+ }
235
+ netPrev[t.ip] = { in: sumIn, out: sumOut, ts: now };
236
+ }
170
237
  return {
171
238
  name: t.name,
172
239
  ip: t.ip,
@@ -177,6 +244,13 @@ async function collectDevice(t) {
177
244
  uptimeSecs: upSecs,
178
245
  portsUp,
179
246
  portsTotal,
247
+ cpuPct,
248
+ load1: load1n,
249
+ loadavg: loadavg.length ? loadavg : undefined,
250
+ memPct,
251
+ memTotalMB,
252
+ netIn,
253
+ netOut,
180
254
  };
181
255
  }
182
256
  // Walk that keeps each row's index (numeric OID suffix) — needed to resolve the
@@ -77,6 +77,33 @@ async function tunnelStatus(port) {
77
77
  return { up: false };
78
78
  }
79
79
  }
80
+ // Inbound tunnels: on the PBX, each connected SBC (and any tunnelled client) holds
81
+ // an established session whose LOCAL port is the tunnel port (5090). Returns the
82
+ // set of distinct peer IPs — matching a configured SBC's edge IP tells us that SBC
83
+ // is still reaching the PBX, observed entirely from the PBX side.
84
+ async function inboundTunnelPeers(port) {
85
+ try {
86
+ const { stdout } = await pexec("ss", ["-tn", "state", "established"], { timeout: 6000 });
87
+ const suffix = `:${port}`;
88
+ const peers = new Set();
89
+ for (const line of stdout.split("\n")) {
90
+ const cols = line.trim().split(/\s+/);
91
+ if (cols.length < 2)
92
+ continue;
93
+ const local = cols[cols.length - 2];
94
+ const peer = cols[cols.length - 1];
95
+ if (!local || !local.endsWith(suffix))
96
+ continue; // only sessions we LISTEN for on :port
97
+ const ip = peer.replace(/:\d+$/, "").replace(/^\[|\]$/g, "").replace(/^::ffff:/, "");
98
+ if (ip)
99
+ peers.add(ip);
100
+ }
101
+ return [...peers];
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
80
107
  async function collect3cx(inst) {
81
108
  const role = inst.role === "sbc" ? "sbc" : "pbx";
82
109
  const name = inst.name || hostname();
@@ -133,7 +160,19 @@ async function collect3cx(inst) {
133
160
  const sip = await Promise.all(sipPorts.map(async (port) => ({ port, listening: await portListening(host, port) })));
134
161
  const sipOk = sip.every((p) => p.listening);
135
162
  const consoleProbe = { port: consolePort, listening: await portListening(host, consolePort) };
136
- return { ...svcCommon, sip, sipOk, console: consoleProbe };
163
+ // Watch branch SBCs from the PBX side, if any are configured.
164
+ let sbcs = undefined;
165
+ let sbcTunnelCount = undefined;
166
+ if (Array.isArray(inst.sbcs) && inst.sbcs.length) {
167
+ const peers = await inboundTunnelPeers(pbxTunnelPort);
168
+ sbcTunnelCount = peers.length;
169
+ sbcs = inst.sbcs.map((s) => ({
170
+ name: s.name,
171
+ peer: s.peer,
172
+ connected: s.peer ? peers.includes(s.peer) : peers.length > 0,
173
+ }));
174
+ }
175
+ return { ...svcCommon, sip, sipOk, console: consoleProbe, ...(sbcs ? { sbcs, sbcTunnelCount } : {}) };
137
176
  }
138
177
  export function createThreeCXPlugin() {
139
178
  let refreshTimer = null;
@@ -151,6 +190,7 @@ export function createThreeCXPlugin() {
151
190
  sipPorts: cfg.sipPorts,
152
191
  consolePort: cfg.consolePort,
153
192
  pbxTunnelPort: cfg.pbxTunnelPort,
193
+ sbcs: cfg.sbcs,
154
194
  systemctlBin: cfg.systemctlBin,
155
195
  uniqueId: cfg.uniqueId,
156
196
  }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daemonitor/plugins",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "daemonitor plugins",
5
5
  "types": "types/index.d.ts",
6
6
  "main": "dist/index.js",