@glassmkr/crucible 0.5.0 → 0.6.0
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.
- package/README.md +4 -4
- package/dist/collect/conntrack.d.ts +7 -0
- package/dist/collect/conntrack.js +16 -0
- package/dist/collect/conntrack.js.map +1 -0
- package/dist/collect/fd.d.ts +7 -0
- package/dist/collect/fd.js +20 -0
- package/dist/collect/fd.js.map +1 -0
- package/dist/collect/io-latency.d.ts +8 -0
- package/dist/collect/io-latency.js +80 -0
- package/dist/collect/io-latency.js.map +1 -0
- package/dist/collect/ntp.d.ts +7 -0
- package/dist/collect/ntp.js +84 -0
- package/dist/collect/ntp.js.map +1 -0
- package/dist/collect/systemd.d.ts +5 -0
- package/dist/collect/systemd.js +23 -0
- package/dist/collect/systemd.js.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/types.d.ts +33 -0
- package/package.json +1 -1
- package/src/collect/conntrack.ts +27 -0
- package/src/collect/fd.ts +31 -0
- package/src/collect/io-latency.ts +103 -0
- package/src/collect/ntp.ts +90 -0
- package/src/collect/systemd.ts +33 -0
- package/src/index.ts +10 -0
- package/src/lib/types.ts +31 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/@glassmkr/crucible)
|
|
5
5
|
|
|
6
|
-
Lightweight bare metal server monitoring agent. Collects hardware and OS health data, pushes snapshots to [Forge](https://forge.glassmkr.com) every 5 minutes. Forge evaluates
|
|
6
|
+
Lightweight bare metal server monitoring agent. Collects hardware and OS health data, pushes snapshots to [Forge](https://forge.glassmkr.com) every 5 minutes. Forge evaluates 36 alert rules and sends notifications.
|
|
7
7
|
|
|
8
8
|
Open source. MIT licensed. Built by [Glassmkr](https://glassmkr.com).
|
|
9
9
|
|
|
@@ -34,9 +34,9 @@ npm install -g @glassmkr/crucible
|
|
|
34
34
|
|
|
35
35
|
## Alert Rules
|
|
36
36
|
|
|
37
|
-
Crucible collects the data. Forge evaluates
|
|
37
|
+
Crucible collects the data. Forge evaluates 36 alert rules server-side and sends notifications via Telegram and Slack.
|
|
38
38
|
|
|
39
|
-
**Categories:** OS (
|
|
39
|
+
**Categories:** OS (9), Storage (8), Network (4), Hardware/IPMI (5), ZFS (2), Security (6), Service Health (2).
|
|
40
40
|
|
|
41
41
|
**Priorities:** P1 Urgent, P2 High, P3 Medium, P4 Low.
|
|
42
42
|
|
|
@@ -79,7 +79,7 @@ Full configuration reference: [forge.glassmkr.com/docs/configuration](https://fo
|
|
|
79
79
|
|
|
80
80
|
- [Getting Started](https://forge.glassmkr.com/docs/getting-started)
|
|
81
81
|
- [Configuration Reference](https://forge.glassmkr.com/docs/configuration)
|
|
82
|
-
- [Alert Rules (
|
|
82
|
+
- [Alert Rules (36)](https://forge.glassmkr.com/docs/alerts)
|
|
83
83
|
- [Troubleshooting](https://forge.glassmkr.com/docs/troubleshooting)
|
|
84
84
|
- [API Reference](https://forge.glassmkr.com/docs/api)
|
|
85
85
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
export function collectConntrack() {
|
|
3
|
+
const countRaw = readProcFile("/proc/sys/net/netfilter/nf_conntrack_count");
|
|
4
|
+
const maxRaw = readProcFile("/proc/sys/net/netfilter/nf_conntrack_max");
|
|
5
|
+
if (!countRaw || !maxRaw) {
|
|
6
|
+
return { available: false, count: 0, max: 0, percent: 0 };
|
|
7
|
+
}
|
|
8
|
+
const count = parseInt(countRaw.trim(), 10);
|
|
9
|
+
const max = parseInt(maxRaw.trim(), 10);
|
|
10
|
+
if (isNaN(count) || isNaN(max) || max === 0) {
|
|
11
|
+
return { available: false, count: 0, max: 0, percent: 0 };
|
|
12
|
+
}
|
|
13
|
+
const percent = Math.round(((count / max) * 100) * 10) / 10;
|
|
14
|
+
return { available: true, count, max, percent };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=conntrack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conntrack.js","sourceRoot":"","sources":["../../src/collect/conntrack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAS/C,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,YAAY,CAAC,4CAA4C,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,YAAY,CAAC,0CAA0C,CAAC,CAAC;IAExE,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAExC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QAC5C,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAC5D,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
export function collectFileDescriptors() {
|
|
3
|
+
const raw = readProcFile("/proc/sys/fs/file-nr");
|
|
4
|
+
if (!raw) {
|
|
5
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
6
|
+
}
|
|
7
|
+
const parts = raw.trim().split(/\s+/);
|
|
8
|
+
if (parts.length < 3) {
|
|
9
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
10
|
+
}
|
|
11
|
+
const allocated = parseInt(parts[0], 10);
|
|
12
|
+
const free = parseInt(parts[1], 10);
|
|
13
|
+
const max = parseInt(parts[2], 10);
|
|
14
|
+
if (isNaN(allocated) || isNaN(max) || max === 0) {
|
|
15
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
16
|
+
}
|
|
17
|
+
const percent = Math.round(((allocated / max) * 100) * 10) / 10;
|
|
18
|
+
return { allocated, free: isNaN(free) ? 0 : free, max, percent };
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=fd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fd.js","sourceRoot":"","sources":["../../src/collect/fd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAS/C,MAAM,UAAU,sBAAsB;IACpC,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;IACjD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEnC,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAChE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
// Previous cumulative counters for delta computation
|
|
3
|
+
const previousCounters = new Map();
|
|
4
|
+
// Match physical block devices, not partitions or virtual devices
|
|
5
|
+
function isPhysicalDevice(name) {
|
|
6
|
+
// sd*, vd*, xvd* without trailing partition number
|
|
7
|
+
if (/^(sd|vd|xvd)[a-z]+$/.test(name))
|
|
8
|
+
return true;
|
|
9
|
+
// nvme*n* without partition suffix (nvme0n1 yes, nvme0n1p1 no)
|
|
10
|
+
if (/^nvme\d+n\d+$/.test(name))
|
|
11
|
+
return true;
|
|
12
|
+
// md* (RAID arrays)
|
|
13
|
+
if (/^md\d+$/.test(name))
|
|
14
|
+
return true;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
function parseDiskstats() {
|
|
18
|
+
const raw = readProcFile("/proc/diskstats") || "";
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const line of raw.split("\n")) {
|
|
21
|
+
const parts = line.trim().split(/\s+/);
|
|
22
|
+
if (parts.length < 11)
|
|
23
|
+
continue;
|
|
24
|
+
const name = parts[2];
|
|
25
|
+
if (!isPhysicalDevice(name))
|
|
26
|
+
continue;
|
|
27
|
+
result[name] = {
|
|
28
|
+
reads_completed: Number(parts[3]) || 0,
|
|
29
|
+
read_time_ms: Number(parts[6]) || 0,
|
|
30
|
+
writes_completed: Number(parts[7]) || 0,
|
|
31
|
+
write_time_ms: Number(parts[10]) || 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function delta(current, previous) {
|
|
37
|
+
if (current >= previous)
|
|
38
|
+
return current - previous;
|
|
39
|
+
return current; // counter wrapped or reset
|
|
40
|
+
}
|
|
41
|
+
export function collectIoLatency() {
|
|
42
|
+
const current = parseDiskstats();
|
|
43
|
+
const results = [];
|
|
44
|
+
const currentDevices = new Set();
|
|
45
|
+
for (const [name, counters] of Object.entries(current)) {
|
|
46
|
+
currentDevices.add(name);
|
|
47
|
+
const prev = previousCounters.get(name);
|
|
48
|
+
// Store current for next cycle
|
|
49
|
+
previousCounters.set(name, { ...counters });
|
|
50
|
+
if (!prev) {
|
|
51
|
+
// First cycle: no delta, report null latency
|
|
52
|
+
results.push({
|
|
53
|
+
device: name,
|
|
54
|
+
avg_read_latency_ms: null,
|
|
55
|
+
avg_write_latency_ms: null,
|
|
56
|
+
read_iops: 0,
|
|
57
|
+
write_iops: 0,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const deltaReads = delta(counters.reads_completed, prev.reads_completed);
|
|
62
|
+
const deltaReadTime = delta(counters.read_time_ms, prev.read_time_ms);
|
|
63
|
+
const deltaWrites = delta(counters.writes_completed, prev.writes_completed);
|
|
64
|
+
const deltaWriteTime = delta(counters.write_time_ms, prev.write_time_ms);
|
|
65
|
+
results.push({
|
|
66
|
+
device: name,
|
|
67
|
+
avg_read_latency_ms: deltaReads > 0 ? Math.round((deltaReadTime / deltaReads) * 100) / 100 : null,
|
|
68
|
+
avg_write_latency_ms: deltaWrites > 0 ? Math.round((deltaWriteTime / deltaWrites) * 100) / 100 : null,
|
|
69
|
+
read_iops: deltaReads,
|
|
70
|
+
write_iops: deltaWrites,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Remove stale devices
|
|
74
|
+
for (const name of previousCounters.keys()) {
|
|
75
|
+
if (!currentDevices.has(name))
|
|
76
|
+
previousCounters.delete(name);
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=io-latency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io-latency.js","sourceRoot":"","sources":["../../src/collect/io-latency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAiB/C,qDAAqD;AACrD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA6B,CAAC;AAE9D,kEAAkE;AAClE,SAAS,gBAAgB,CAAC,IAAY;IACpC,mDAAmD;IACnD,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,+DAA+D;IAC/D,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,oBAAoB;IACpB,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,GAAG,GAAG,YAAY,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,MAAM,GAAsC,EAAE,CAAC;IAErD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;YAAE,SAAS;QAEhC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAAE,SAAS;QAEtC,MAAM,CAAC,IAAI,CAAC,GAAG;YACb,eAAe,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACtC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACnC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACvC,aAAa,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;SACtC,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,KAAK,CAAC,OAAe,EAAE,QAAgB;IAC9C,IAAI,OAAO,IAAI,QAAQ;QAAE,OAAO,OAAO,GAAG,QAAQ,CAAC;IACnD,OAAO,OAAO,CAAC,CAAC,2BAA2B;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IAEzC,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAExC,+BAA+B;QAC/B,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC;QAE5C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,6CAA6C;YAC7C,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,IAAI;gBACZ,mBAAmB,EAAE,IAAI;gBACzB,oBAAoB,EAAE,IAAI;gBAC1B,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,CAAC;aACd,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACzE,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5E,MAAM,cAAc,GAAG,KAAK,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QAEzE,OAAO,CAAC,IAAI,CAAC;YACX,MAAM,EAAE,IAAI;YACZ,mBAAmB,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI;YACjG,oBAAoB,EAAE,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI;YACrG,SAAS,EAAE,UAAU;YACrB,UAAU,EAAE,WAAW;SACxB,CAAC,CAAC;IACL,CAAC;IAED,uBAAuB;IACvB,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { run } from "../lib/exec.js";
|
|
2
|
+
export async function collectNtp() {
|
|
3
|
+
// Try timedatectl first (systemd-timesyncd)
|
|
4
|
+
const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
|
|
5
|
+
if (tdctl !== null) {
|
|
6
|
+
const synced = tdctl.trim() === "yes";
|
|
7
|
+
// Get the source daemon name
|
|
8
|
+
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
9
|
+
const ntpEnabled = statusOut?.trim() === "yes";
|
|
10
|
+
// Try to get offset from timedatectl timesync-status
|
|
11
|
+
let offset = 0;
|
|
12
|
+
try {
|
|
13
|
+
const tsStatus = await run("timedatectl", ["timesync-status"]);
|
|
14
|
+
if (tsStatus) {
|
|
15
|
+
const match = tsStatus.match(/Offset:\s*([+-]?\d+\.?\d*)(us|ms|s)/);
|
|
16
|
+
if (match) {
|
|
17
|
+
const val = parseFloat(match[1]);
|
|
18
|
+
const unit = match[2];
|
|
19
|
+
if (unit === "us")
|
|
20
|
+
offset = val / 1_000_000;
|
|
21
|
+
else if (unit === "ms")
|
|
22
|
+
offset = val / 1000;
|
|
23
|
+
else
|
|
24
|
+
offset = val;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch { /* offset stays 0 */ }
|
|
29
|
+
return {
|
|
30
|
+
synced,
|
|
31
|
+
offset_seconds: offset,
|
|
32
|
+
source: "systemd-timesyncd",
|
|
33
|
+
daemon_running: ntpEnabled || synced,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Try chrony
|
|
37
|
+
const chronyOut = await run("chronyc", ["tracking"]);
|
|
38
|
+
if (chronyOut) {
|
|
39
|
+
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
40
|
+
const synced = leapMatch ? leapMatch[1].trim() === "Normal" : false;
|
|
41
|
+
let offset = 0;
|
|
42
|
+
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
43
|
+
if (offsetMatch)
|
|
44
|
+
offset = parseFloat(offsetMatch[1]);
|
|
45
|
+
return {
|
|
46
|
+
synced,
|
|
47
|
+
offset_seconds: Math.abs(offset),
|
|
48
|
+
source: "chrony",
|
|
49
|
+
daemon_running: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Try ntpq
|
|
53
|
+
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
54
|
+
if (ntpqOut) {
|
|
55
|
+
// A line starting with * means a selected peer (synced)
|
|
56
|
+
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
57
|
+
let offset = 0;
|
|
58
|
+
// Parse offset from the selected peer line
|
|
59
|
+
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
60
|
+
if (selectedLine) {
|
|
61
|
+
const fields = selectedLine.trim().split(/\s+/);
|
|
62
|
+
// offset is typically field 8 (in ms)
|
|
63
|
+
if (fields.length >= 9) {
|
|
64
|
+
const rawOffset = parseFloat(fields[8]);
|
|
65
|
+
if (!isNaN(rawOffset))
|
|
66
|
+
offset = Math.abs(rawOffset) / 1000;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
synced,
|
|
71
|
+
offset_seconds: offset,
|
|
72
|
+
source: "ntpd",
|
|
73
|
+
daemon_running: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// No time sync daemon found
|
|
77
|
+
return {
|
|
78
|
+
synced: false,
|
|
79
|
+
offset_seconds: 0,
|
|
80
|
+
source: "none",
|
|
81
|
+
daemon_running: false,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=ntp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AASrC,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,4CAA4C;IAC5C,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,4BAA4B,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1F,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC;QACtC,6BAA6B;QAC7B,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,CAAC,CAAC,CAAC;QAClF,MAAM,UAAU,GAAG,SAAS,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC;QAE/C,qDAAqD;QACrD,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAC/D,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;gBACpE,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;oBACjC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBACtB,IAAI,IAAI,KAAK,IAAI;wBAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;yBACvC,IAAI,IAAI,KAAK,IAAI;wBAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC;;wBACvC,MAAM,GAAG,GAAG,CAAC;gBACpB,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;QAEhC,OAAO;YACL,MAAM;YACN,cAAc,EAAE,MAAM;YACtB,MAAM,EAAE,mBAAmB;YAC3B,cAAc,EAAE,UAAU,IAAI,MAAM;SACrC,CAAC;IACJ,CAAC;IAED,aAAa;IACb,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IACrD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACpE,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACpF,IAAI,WAAW;YAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,OAAO;YACL,MAAM;YACN,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAChC,MAAM,EAAE,QAAQ;YAChB,cAAc,EAAE,IAAI;SACrB,CAAC;IACJ,CAAC;IAED,WAAW;IACX,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,IAAI,OAAO,EAAE,CAAC;QACZ,wDAAwD;QACxD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACxE,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,2CAA2C;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAChD,sCAAsC;YACtC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACvB,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBACxC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;oBAAE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;YAC7D,CAAC;QACH,CAAC;QACD,OAAO;YACL,MAAM;YACN,cAAc,EAAE,MAAM;YACtB,MAAM,EAAE,MAAM;YACd,cAAc,EAAE,IAAI;SACrB,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,OAAO;QACL,MAAM,EAAE,KAAK;QACb,cAAc,EAAE,CAAC;QACjB,MAAM,EAAE,MAAM;QACd,cAAc,EAAE,KAAK;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { run } from "../lib/exec.js";
|
|
2
|
+
// Units commonly in failed state by design or misconfiguration
|
|
3
|
+
const DEFAULT_EXCLUDES = [
|
|
4
|
+
"systemd-networkd-wait-online.service",
|
|
5
|
+
];
|
|
6
|
+
export async function collectSystemd(extraExcludes = []) {
|
|
7
|
+
const output = await run("systemctl", [
|
|
8
|
+
"list-units", "--type=service", "--state=failed", "--no-legend", "--plain",
|
|
9
|
+
]);
|
|
10
|
+
if (!output || output.trim() === "") {
|
|
11
|
+
return { failed_units: [], failed_count: 0 };
|
|
12
|
+
}
|
|
13
|
+
const excludes = new Set([...DEFAULT_EXCLUDES, ...extraExcludes]);
|
|
14
|
+
const units = [];
|
|
15
|
+
for (const line of output.trim().split("\n")) {
|
|
16
|
+
const unit = line.trim().split(/\s+/)[0];
|
|
17
|
+
if (unit && unit.endsWith(".service") && !excludes.has(unit)) {
|
|
18
|
+
units.push(unit);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { failed_units: units, failed_count: units.length };
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=systemd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/collect/systemd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAOrC,+DAA+D;AAC/D,MAAM,gBAAgB,GAAG;IACvB,sCAAsC;CACvC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,gBAA0B,EAAE;IAC/D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE;QACpC,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS;KAC3E,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACpC,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC;IAClE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;AAC7D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,11 @@ import { pushToForge, initForgeAgent } from "./push/forge.js";
|
|
|
33
33
|
import { collectSecurity } from "./collect/security.js";
|
|
34
34
|
import { collectZfs } from "./collect/zfs.js";
|
|
35
35
|
import { collectIoErrors } from "./collect/io-errors.js";
|
|
36
|
+
import { collectIoLatency } from "./collect/io-latency.js";
|
|
37
|
+
import { collectConntrack } from "./collect/conntrack.js";
|
|
38
|
+
import { collectSystemd } from "./collect/systemd.js";
|
|
39
|
+
import { collectNtp } from "./collect/ntp.js";
|
|
40
|
+
import { collectFileDescriptors } from "./collect/fd.js";
|
|
36
41
|
const configPath = process.argv[2] || "/etc/glassmkr/collector.yaml";
|
|
37
42
|
const config = loadConfig(configPath);
|
|
38
43
|
console.log(`[collector] Starting. Server: ${config.server_name}. Interval: ${config.collection.interval_seconds}s`);
|
|
@@ -92,6 +97,26 @@ async function collect() {
|
|
|
92
97
|
snapshot.io_errors = await collectIoErrors() ?? undefined;
|
|
93
98
|
}
|
|
94
99
|
catch { /* skip on error */ }
|
|
100
|
+
try {
|
|
101
|
+
snapshot.io_latency = collectIoLatency();
|
|
102
|
+
}
|
|
103
|
+
catch { /* skip on error */ }
|
|
104
|
+
try {
|
|
105
|
+
snapshot.conntrack = collectConntrack();
|
|
106
|
+
}
|
|
107
|
+
catch { /* skip on error */ }
|
|
108
|
+
try {
|
|
109
|
+
snapshot.systemd = await collectSystemd();
|
|
110
|
+
}
|
|
111
|
+
catch { /* skip on error */ }
|
|
112
|
+
try {
|
|
113
|
+
snapshot.ntp = await collectNtp();
|
|
114
|
+
}
|
|
115
|
+
catch { /* skip on error */ }
|
|
116
|
+
try {
|
|
117
|
+
snapshot.file_descriptors = collectFileDescriptors();
|
|
118
|
+
}
|
|
119
|
+
catch { /* skip on error */ }
|
|
95
120
|
// Update Prometheus metrics
|
|
96
121
|
updateMetrics(snapshot);
|
|
97
122
|
// Evaluate alerts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -16,6 +16,39 @@ export interface Snapshot {
|
|
|
16
16
|
count: number;
|
|
17
17
|
devices: string[];
|
|
18
18
|
};
|
|
19
|
+
io_latency?: Array<{
|
|
20
|
+
device: string;
|
|
21
|
+
avg_read_latency_ms: number | null;
|
|
22
|
+
avg_write_latency_ms: number | null;
|
|
23
|
+
read_iops: number;
|
|
24
|
+
write_iops: number;
|
|
25
|
+
}>;
|
|
26
|
+
conntrack?: ConntrackData;
|
|
27
|
+
systemd?: SystemdData;
|
|
28
|
+
ntp?: NtpData;
|
|
29
|
+
file_descriptors?: FileDescriptorData;
|
|
30
|
+
}
|
|
31
|
+
export interface ConntrackData {
|
|
32
|
+
available: boolean;
|
|
33
|
+
count: number;
|
|
34
|
+
max: number;
|
|
35
|
+
percent: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SystemdData {
|
|
38
|
+
failed_units: string[];
|
|
39
|
+
failed_count: number;
|
|
40
|
+
}
|
|
41
|
+
export interface NtpData {
|
|
42
|
+
synced: boolean;
|
|
43
|
+
offset_seconds: number;
|
|
44
|
+
source: string;
|
|
45
|
+
daemon_running: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface FileDescriptorData {
|
|
48
|
+
allocated: number;
|
|
49
|
+
free: number;
|
|
50
|
+
max: number;
|
|
51
|
+
percent: number;
|
|
19
52
|
}
|
|
20
53
|
export interface ZfsPool {
|
|
21
54
|
name: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
|
|
3
|
+
export interface ConntrackData {
|
|
4
|
+
available: boolean;
|
|
5
|
+
count: number;
|
|
6
|
+
max: number;
|
|
7
|
+
percent: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function collectConntrack(): ConntrackData {
|
|
11
|
+
const countRaw = readProcFile("/proc/sys/net/netfilter/nf_conntrack_count");
|
|
12
|
+
const maxRaw = readProcFile("/proc/sys/net/netfilter/nf_conntrack_max");
|
|
13
|
+
|
|
14
|
+
if (!countRaw || !maxRaw) {
|
|
15
|
+
return { available: false, count: 0, max: 0, percent: 0 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const count = parseInt(countRaw.trim(), 10);
|
|
19
|
+
const max = parseInt(maxRaw.trim(), 10);
|
|
20
|
+
|
|
21
|
+
if (isNaN(count) || isNaN(max) || max === 0) {
|
|
22
|
+
return { available: false, count: 0, max: 0, percent: 0 };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const percent = Math.round(((count / max) * 100) * 10) / 10;
|
|
26
|
+
return { available: true, count, max, percent };
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
|
|
3
|
+
export interface FileDescriptorData {
|
|
4
|
+
allocated: number;
|
|
5
|
+
free: number;
|
|
6
|
+
max: number;
|
|
7
|
+
percent: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function collectFileDescriptors(): FileDescriptorData {
|
|
11
|
+
const raw = readProcFile("/proc/sys/fs/file-nr");
|
|
12
|
+
if (!raw) {
|
|
13
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parts = raw.trim().split(/\s+/);
|
|
17
|
+
if (parts.length < 3) {
|
|
18
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const allocated = parseInt(parts[0], 10);
|
|
22
|
+
const free = parseInt(parts[1], 10);
|
|
23
|
+
const max = parseInt(parts[2], 10);
|
|
24
|
+
|
|
25
|
+
if (isNaN(allocated) || isNaN(max) || max === 0) {
|
|
26
|
+
return { allocated: 0, free: 0, max: 0, percent: 0 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const percent = Math.round(((allocated / max) * 100) * 10) / 10;
|
|
30
|
+
return { allocated, free: isNaN(free) ? 0 : free, max, percent };
|
|
31
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readProcFile } from "../lib/parse.js";
|
|
2
|
+
|
|
3
|
+
export interface IoLatencyInfo {
|
|
4
|
+
device: string;
|
|
5
|
+
avg_read_latency_ms: number | null;
|
|
6
|
+
avg_write_latency_ms: number | null;
|
|
7
|
+
read_iops: number;
|
|
8
|
+
write_iops: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface DiskstatsCounters {
|
|
12
|
+
reads_completed: number;
|
|
13
|
+
read_time_ms: number;
|
|
14
|
+
writes_completed: number;
|
|
15
|
+
write_time_ms: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Previous cumulative counters for delta computation
|
|
19
|
+
const previousCounters = new Map<string, DiskstatsCounters>();
|
|
20
|
+
|
|
21
|
+
// Match physical block devices, not partitions or virtual devices
|
|
22
|
+
function isPhysicalDevice(name: string): boolean {
|
|
23
|
+
// sd*, vd*, xvd* without trailing partition number
|
|
24
|
+
if (/^(sd|vd|xvd)[a-z]+$/.test(name)) return true;
|
|
25
|
+
// nvme*n* without partition suffix (nvme0n1 yes, nvme0n1p1 no)
|
|
26
|
+
if (/^nvme\d+n\d+$/.test(name)) return true;
|
|
27
|
+
// md* (RAID arrays)
|
|
28
|
+
if (/^md\d+$/.test(name)) return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseDiskstats(): Record<string, DiskstatsCounters> {
|
|
33
|
+
const raw = readProcFile("/proc/diskstats") || "";
|
|
34
|
+
const result: Record<string, DiskstatsCounters> = {};
|
|
35
|
+
|
|
36
|
+
for (const line of raw.split("\n")) {
|
|
37
|
+
const parts = line.trim().split(/\s+/);
|
|
38
|
+
if (parts.length < 11) continue;
|
|
39
|
+
|
|
40
|
+
const name = parts[2];
|
|
41
|
+
if (!isPhysicalDevice(name)) continue;
|
|
42
|
+
|
|
43
|
+
result[name] = {
|
|
44
|
+
reads_completed: Number(parts[3]) || 0,
|
|
45
|
+
read_time_ms: Number(parts[6]) || 0,
|
|
46
|
+
writes_completed: Number(parts[7]) || 0,
|
|
47
|
+
write_time_ms: Number(parts[10]) || 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function delta(current: number, previous: number): number {
|
|
55
|
+
if (current >= previous) return current - previous;
|
|
56
|
+
return current; // counter wrapped or reset
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function collectIoLatency(): IoLatencyInfo[] {
|
|
60
|
+
const current = parseDiskstats();
|
|
61
|
+
const results: IoLatencyInfo[] = [];
|
|
62
|
+
const currentDevices = new Set<string>();
|
|
63
|
+
|
|
64
|
+
for (const [name, counters] of Object.entries(current)) {
|
|
65
|
+
currentDevices.add(name);
|
|
66
|
+
const prev = previousCounters.get(name);
|
|
67
|
+
|
|
68
|
+
// Store current for next cycle
|
|
69
|
+
previousCounters.set(name, { ...counters });
|
|
70
|
+
|
|
71
|
+
if (!prev) {
|
|
72
|
+
// First cycle: no delta, report null latency
|
|
73
|
+
results.push({
|
|
74
|
+
device: name,
|
|
75
|
+
avg_read_latency_ms: null,
|
|
76
|
+
avg_write_latency_ms: null,
|
|
77
|
+
read_iops: 0,
|
|
78
|
+
write_iops: 0,
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const deltaReads = delta(counters.reads_completed, prev.reads_completed);
|
|
84
|
+
const deltaReadTime = delta(counters.read_time_ms, prev.read_time_ms);
|
|
85
|
+
const deltaWrites = delta(counters.writes_completed, prev.writes_completed);
|
|
86
|
+
const deltaWriteTime = delta(counters.write_time_ms, prev.write_time_ms);
|
|
87
|
+
|
|
88
|
+
results.push({
|
|
89
|
+
device: name,
|
|
90
|
+
avg_read_latency_ms: deltaReads > 0 ? Math.round((deltaReadTime / deltaReads) * 100) / 100 : null,
|
|
91
|
+
avg_write_latency_ms: deltaWrites > 0 ? Math.round((deltaWriteTime / deltaWrites) * 100) / 100 : null,
|
|
92
|
+
read_iops: deltaReads,
|
|
93
|
+
write_iops: deltaWrites,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Remove stale devices
|
|
98
|
+
for (const name of previousCounters.keys()) {
|
|
99
|
+
if (!currentDevices.has(name)) previousCounters.delete(name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return results;
|
|
103
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { run } from "../lib/exec.js";
|
|
2
|
+
|
|
3
|
+
export interface NtpData {
|
|
4
|
+
synced: boolean;
|
|
5
|
+
offset_seconds: number;
|
|
6
|
+
source: string;
|
|
7
|
+
daemon_running: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function collectNtp(): Promise<NtpData> {
|
|
11
|
+
// Try timedatectl first (systemd-timesyncd)
|
|
12
|
+
const tdctl = await run("timedatectl", ["show", "--property=NTPSynchronized", "--value"]);
|
|
13
|
+
if (tdctl !== null) {
|
|
14
|
+
const synced = tdctl.trim() === "yes";
|
|
15
|
+
// Get the source daemon name
|
|
16
|
+
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
17
|
+
const ntpEnabled = statusOut?.trim() === "yes";
|
|
18
|
+
|
|
19
|
+
// Try to get offset from timedatectl timesync-status
|
|
20
|
+
let offset = 0;
|
|
21
|
+
try {
|
|
22
|
+
const tsStatus = await run("timedatectl", ["timesync-status"]);
|
|
23
|
+
if (tsStatus) {
|
|
24
|
+
const match = tsStatus.match(/Offset:\s*([+-]?\d+\.?\d*)(us|ms|s)/);
|
|
25
|
+
if (match) {
|
|
26
|
+
const val = parseFloat(match[1]);
|
|
27
|
+
const unit = match[2];
|
|
28
|
+
if (unit === "us") offset = val / 1_000_000;
|
|
29
|
+
else if (unit === "ms") offset = val / 1000;
|
|
30
|
+
else offset = val;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch { /* offset stays 0 */ }
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
synced,
|
|
37
|
+
offset_seconds: offset,
|
|
38
|
+
source: "systemd-timesyncd",
|
|
39
|
+
daemon_running: ntpEnabled || synced,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Try chrony
|
|
44
|
+
const chronyOut = await run("chronyc", ["tracking"]);
|
|
45
|
+
if (chronyOut) {
|
|
46
|
+
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
47
|
+
const synced = leapMatch ? leapMatch[1].trim() === "Normal" : false;
|
|
48
|
+
let offset = 0;
|
|
49
|
+
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
50
|
+
if (offsetMatch) offset = parseFloat(offsetMatch[1]);
|
|
51
|
+
return {
|
|
52
|
+
synced,
|
|
53
|
+
offset_seconds: Math.abs(offset),
|
|
54
|
+
source: "chrony",
|
|
55
|
+
daemon_running: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Try ntpq
|
|
60
|
+
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
61
|
+
if (ntpqOut) {
|
|
62
|
+
// A line starting with * means a selected peer (synced)
|
|
63
|
+
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
64
|
+
let offset = 0;
|
|
65
|
+
// Parse offset from the selected peer line
|
|
66
|
+
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
67
|
+
if (selectedLine) {
|
|
68
|
+
const fields = selectedLine.trim().split(/\s+/);
|
|
69
|
+
// offset is typically field 8 (in ms)
|
|
70
|
+
if (fields.length >= 9) {
|
|
71
|
+
const rawOffset = parseFloat(fields[8]);
|
|
72
|
+
if (!isNaN(rawOffset)) offset = Math.abs(rawOffset) / 1000;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
synced,
|
|
77
|
+
offset_seconds: offset,
|
|
78
|
+
source: "ntpd",
|
|
79
|
+
daemon_running: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// No time sync daemon found
|
|
84
|
+
return {
|
|
85
|
+
synced: false,
|
|
86
|
+
offset_seconds: 0,
|
|
87
|
+
source: "none",
|
|
88
|
+
daemon_running: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { run } from "../lib/exec.js";
|
|
2
|
+
|
|
3
|
+
export interface SystemdData {
|
|
4
|
+
failed_units: string[];
|
|
5
|
+
failed_count: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Units commonly in failed state by design or misconfiguration
|
|
9
|
+
const DEFAULT_EXCLUDES = [
|
|
10
|
+
"systemd-networkd-wait-online.service",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export async function collectSystemd(extraExcludes: string[] = []): Promise<SystemdData> {
|
|
14
|
+
const output = await run("systemctl", [
|
|
15
|
+
"list-units", "--type=service", "--state=failed", "--no-legend", "--plain",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
if (!output || output.trim() === "") {
|
|
19
|
+
return { failed_units: [], failed_count: 0 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const excludes = new Set([...DEFAULT_EXCLUDES, ...extraExcludes]);
|
|
23
|
+
const units: string[] = [];
|
|
24
|
+
|
|
25
|
+
for (const line of output.trim().split("\n")) {
|
|
26
|
+
const unit = line.trim().split(/\s+/)[0];
|
|
27
|
+
if (unit && unit.endsWith(".service") && !excludes.has(unit)) {
|
|
28
|
+
units.push(unit);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { failed_units: units, failed_count: units.length };
|
|
33
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,11 @@ import { pushToForge, initForgeAgent } from "./push/forge.js";
|
|
|
34
34
|
import { collectSecurity, type SecurityData } from "./collect/security.js";
|
|
35
35
|
import { collectZfs } from "./collect/zfs.js";
|
|
36
36
|
import { collectIoErrors } from "./collect/io-errors.js";
|
|
37
|
+
import { collectIoLatency } from "./collect/io-latency.js";
|
|
38
|
+
import { collectConntrack } from "./collect/conntrack.js";
|
|
39
|
+
import { collectSystemd } from "./collect/systemd.js";
|
|
40
|
+
import { collectNtp } from "./collect/ntp.js";
|
|
41
|
+
import { collectFileDescriptors } from "./collect/fd.js";
|
|
37
42
|
import type { Snapshot, IpmiInfo } from "./lib/types.js";
|
|
38
43
|
|
|
39
44
|
const configPath = process.argv[2] || "/etc/glassmkr/collector.yaml";
|
|
@@ -94,6 +99,11 @@ async function collect() {
|
|
|
94
99
|
// ZFS and I/O errors: collect every cycle (lightweight checks)
|
|
95
100
|
try { snapshot.zfs = await collectZfs() ?? undefined; } catch { /* skip if ZFS not available */ }
|
|
96
101
|
try { snapshot.io_errors = await collectIoErrors() ?? undefined; } catch { /* skip on error */ }
|
|
102
|
+
try { snapshot.io_latency = collectIoLatency(); } catch { /* skip on error */ }
|
|
103
|
+
try { snapshot.conntrack = collectConntrack(); } catch { /* skip on error */ }
|
|
104
|
+
try { snapshot.systemd = await collectSystemd(); } catch { /* skip on error */ }
|
|
105
|
+
try { snapshot.ntp = await collectNtp(); } catch { /* skip on error */ }
|
|
106
|
+
try { snapshot.file_descriptors = collectFileDescriptors(); } catch { /* skip on error */ }
|
|
97
107
|
|
|
98
108
|
// Update Prometheus metrics
|
|
99
109
|
updateMetrics(snapshot);
|
package/src/lib/types.ts
CHANGED
|
@@ -13,6 +13,37 @@ export interface Snapshot {
|
|
|
13
13
|
security?: SecurityData;
|
|
14
14
|
zfs?: ZfsData;
|
|
15
15
|
io_errors?: { count: number; devices: string[] };
|
|
16
|
+
io_latency?: Array<{ device: string; avg_read_latency_ms: number | null; avg_write_latency_ms: number | null; read_iops: number; write_iops: number }>;
|
|
17
|
+
conntrack?: ConntrackData;
|
|
18
|
+
systemd?: SystemdData;
|
|
19
|
+
ntp?: NtpData;
|
|
20
|
+
file_descriptors?: FileDescriptorData;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ConntrackData {
|
|
24
|
+
available: boolean;
|
|
25
|
+
count: number;
|
|
26
|
+
max: number;
|
|
27
|
+
percent: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SystemdData {
|
|
31
|
+
failed_units: string[];
|
|
32
|
+
failed_count: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface NtpData {
|
|
36
|
+
synced: boolean;
|
|
37
|
+
offset_seconds: number;
|
|
38
|
+
source: string;
|
|
39
|
+
daemon_running: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FileDescriptorData {
|
|
43
|
+
allocated: number;
|
|
44
|
+
free: number;
|
|
45
|
+
max: number;
|
|
46
|
+
percent: number;
|
|
16
47
|
}
|
|
17
48
|
|
|
18
49
|
export interface ZfsPool {
|