@glassmkr/crucible 0.5.1 → 0.6.1
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/ntp.d.ts +7 -0
- package/dist/collect/ntp.js +91 -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 +20 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/types.d.ts +26 -0
- package/package.json +9 -2
- package/src/collect/conntrack.ts +27 -0
- package/src/collect/fd.ts +31 -0
- package/src/collect/ntp.ts +97 -0
- package/src/collect/systemd.ts +33 -0
- package/src/index.ts +8 -0
- package/src/lib/types.ts +30 -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,91 @@
|
|
|
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
|
+
// Only trust timedatectl if it returns a clear "yes" or "no"
|
|
6
|
+
if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
|
|
7
|
+
const synced = tdctl.trim() === "yes";
|
|
8
|
+
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
9
|
+
const ntpEnabled = statusOut?.trim() === "yes";
|
|
10
|
+
let offset = 0;
|
|
11
|
+
try {
|
|
12
|
+
const tsStatus = await run("timedatectl", ["timesync-status"]);
|
|
13
|
+
if (tsStatus) {
|
|
14
|
+
const match = tsStatus.match(/Offset:\s*([+-]?\d+\.?\d*)(us|ms|s)/);
|
|
15
|
+
if (match) {
|
|
16
|
+
const val = parseFloat(match[1]);
|
|
17
|
+
const unit = match[2];
|
|
18
|
+
if (unit === "us")
|
|
19
|
+
offset = val / 1_000_000;
|
|
20
|
+
else if (unit === "ms")
|
|
21
|
+
offset = val / 1000;
|
|
22
|
+
else
|
|
23
|
+
offset = val;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch { /* offset stays 0 */ }
|
|
28
|
+
return {
|
|
29
|
+
synced,
|
|
30
|
+
offset_seconds: offset,
|
|
31
|
+
source: "systemd-timesyncd",
|
|
32
|
+
daemon_running: ntpEnabled || synced,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Try chrony - validate output contains expected "Leap status" field
|
|
36
|
+
const chronyOut = await run("chronyc", ["tracking"]);
|
|
37
|
+
if (chronyOut) {
|
|
38
|
+
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
39
|
+
if (leapMatch) {
|
|
40
|
+
// Output looks like real chrony tracking data
|
|
41
|
+
const synced = leapMatch[1].trim() === "Normal";
|
|
42
|
+
let offset = 0;
|
|
43
|
+
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
44
|
+
if (offsetMatch)
|
|
45
|
+
offset = parseFloat(offsetMatch[1]);
|
|
46
|
+
return {
|
|
47
|
+
synced,
|
|
48
|
+
offset_seconds: Math.abs(offset),
|
|
49
|
+
source: "chrony",
|
|
50
|
+
daemon_running: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// chronyc returned output but not tracking data (error message, daemon not running)
|
|
54
|
+
// Fall through to ntpq
|
|
55
|
+
}
|
|
56
|
+
// Try ntpq - validate output contains the header line with "remote"
|
|
57
|
+
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
58
|
+
if (ntpqOut) {
|
|
59
|
+
const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
|
|
60
|
+
if (hasHeader) {
|
|
61
|
+
// Output looks like real ntpq peer table
|
|
62
|
+
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
63
|
+
let offset = 0;
|
|
64
|
+
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
65
|
+
if (selectedLine) {
|
|
66
|
+
const fields = selectedLine.trim().split(/\s+/);
|
|
67
|
+
if (fields.length >= 9) {
|
|
68
|
+
const rawOffset = parseFloat(fields[8]);
|
|
69
|
+
if (!isNaN(rawOffset))
|
|
70
|
+
offset = Math.abs(rawOffset) / 1000;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
synced,
|
|
75
|
+
offset_seconds: offset,
|
|
76
|
+
source: "ntpd",
|
|
77
|
+
daemon_running: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ntpq returned output but not peer table (error message, daemon not running)
|
|
81
|
+
// Fall through to "none"
|
|
82
|
+
}
|
|
83
|
+
// No time sync daemon found (or all probes returned invalid output)
|
|
84
|
+
return {
|
|
85
|
+
synced: false,
|
|
86
|
+
offset_seconds: 0,
|
|
87
|
+
source: "none",
|
|
88
|
+
daemon_running: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=ntp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ntp.js","sourceRoot":"","sources":["../../src/collect/ntp.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
@@ -34,6 +34,10 @@ import { collectSecurity } from "./collect/security.js";
|
|
|
34
34
|
import { collectZfs } from "./collect/zfs.js";
|
|
35
35
|
import { collectIoErrors } from "./collect/io-errors.js";
|
|
36
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";
|
|
37
41
|
const configPath = process.argv[2] || "/etc/glassmkr/collector.yaml";
|
|
38
42
|
const config = loadConfig(configPath);
|
|
39
43
|
console.log(`[collector] Starting. Server: ${config.server_name}. Interval: ${config.collection.interval_seconds}s`);
|
|
@@ -97,6 +101,22 @@ async function collect() {
|
|
|
97
101
|
snapshot.io_latency = collectIoLatency();
|
|
98
102
|
}
|
|
99
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 */ }
|
|
100
120
|
// Update Prometheus metrics
|
|
101
121
|
updateMetrics(snapshot);
|
|
102
122
|
// Evaluate alerts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC,CAAC,EAAE,CAAC;AACL,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAqB,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -23,6 +23,32 @@ export interface Snapshot {
|
|
|
23
23
|
read_iops: number;
|
|
24
24
|
write_iops: number;
|
|
25
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;
|
|
26
52
|
}
|
|
27
53
|
export interface ZfsPool {
|
|
28
54
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassmkr/crucible",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Lightweight bare metal server monitoring. IPMI, SMART, OS, network. Opinionated alerts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
13
|
"clean": "rm -rf dist"
|
|
14
14
|
},
|
|
15
|
-
"keywords": [
|
|
15
|
+
"keywords": [
|
|
16
|
+
"monitoring",
|
|
17
|
+
"bare-metal",
|
|
18
|
+
"ipmi",
|
|
19
|
+
"smart",
|
|
20
|
+
"server",
|
|
21
|
+
"alerts"
|
|
22
|
+
],
|
|
16
23
|
"license": "MIT",
|
|
17
24
|
"repository": {
|
|
18
25
|
"type": "git",
|
|
@@ -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,97 @@
|
|
|
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
|
+
// Only trust timedatectl if it returns a clear "yes" or "no"
|
|
14
|
+
if (tdctl !== null && (tdctl.trim() === "yes" || tdctl.trim() === "no")) {
|
|
15
|
+
const synced = tdctl.trim() === "yes";
|
|
16
|
+
const statusOut = await run("timedatectl", ["show", "--property=NTP", "--value"]);
|
|
17
|
+
const ntpEnabled = statusOut?.trim() === "yes";
|
|
18
|
+
|
|
19
|
+
let offset = 0;
|
|
20
|
+
try {
|
|
21
|
+
const tsStatus = await run("timedatectl", ["timesync-status"]);
|
|
22
|
+
if (tsStatus) {
|
|
23
|
+
const match = tsStatus.match(/Offset:\s*([+-]?\d+\.?\d*)(us|ms|s)/);
|
|
24
|
+
if (match) {
|
|
25
|
+
const val = parseFloat(match[1]);
|
|
26
|
+
const unit = match[2];
|
|
27
|
+
if (unit === "us") offset = val / 1_000_000;
|
|
28
|
+
else if (unit === "ms") offset = val / 1000;
|
|
29
|
+
else offset = val;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch { /* offset stays 0 */ }
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
synced,
|
|
36
|
+
offset_seconds: offset,
|
|
37
|
+
source: "systemd-timesyncd",
|
|
38
|
+
daemon_running: ntpEnabled || synced,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Try chrony - validate output contains expected "Leap status" field
|
|
43
|
+
const chronyOut = await run("chronyc", ["tracking"]);
|
|
44
|
+
if (chronyOut) {
|
|
45
|
+
const leapMatch = chronyOut.match(/Leap status\s*:\s*(.+)/);
|
|
46
|
+
if (leapMatch) {
|
|
47
|
+
// Output looks like real chrony tracking data
|
|
48
|
+
const synced = leapMatch[1].trim() === "Normal";
|
|
49
|
+
let offset = 0;
|
|
50
|
+
const offsetMatch = chronyOut.match(/Last offset\s*:\s*([+-]?\d+\.?\d*)\s*seconds/);
|
|
51
|
+
if (offsetMatch) offset = parseFloat(offsetMatch[1]);
|
|
52
|
+
return {
|
|
53
|
+
synced,
|
|
54
|
+
offset_seconds: Math.abs(offset),
|
|
55
|
+
source: "chrony",
|
|
56
|
+
daemon_running: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// chronyc returned output but not tracking data (error message, daemon not running)
|
|
60
|
+
// Fall through to ntpq
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Try ntpq - validate output contains the header line with "remote"
|
|
64
|
+
const ntpqOut = await run("ntpq", ["-pn"]);
|
|
65
|
+
if (ntpqOut) {
|
|
66
|
+
const hasHeader = ntpqOut.split("\n").some((line) => line.includes("remote"));
|
|
67
|
+
if (hasHeader) {
|
|
68
|
+
// Output looks like real ntpq peer table
|
|
69
|
+
const synced = ntpqOut.split("\n").some((line) => line.startsWith("*"));
|
|
70
|
+
let offset = 0;
|
|
71
|
+
const selectedLine = ntpqOut.split("\n").find((line) => line.startsWith("*"));
|
|
72
|
+
if (selectedLine) {
|
|
73
|
+
const fields = selectedLine.trim().split(/\s+/);
|
|
74
|
+
if (fields.length >= 9) {
|
|
75
|
+
const rawOffset = parseFloat(fields[8]);
|
|
76
|
+
if (!isNaN(rawOffset)) offset = Math.abs(rawOffset) / 1000;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
synced,
|
|
81
|
+
offset_seconds: offset,
|
|
82
|
+
source: "ntpd",
|
|
83
|
+
daemon_running: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ntpq returned output but not peer table (error message, daemon not running)
|
|
87
|
+
// Fall through to "none"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// No time sync daemon found (or all probes returned invalid output)
|
|
91
|
+
return {
|
|
92
|
+
synced: false,
|
|
93
|
+
offset_seconds: 0,
|
|
94
|
+
source: "none",
|
|
95
|
+
daemon_running: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -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
|
@@ -35,6 +35,10 @@ 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
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";
|
|
38
42
|
import type { Snapshot, IpmiInfo } from "./lib/types.js";
|
|
39
43
|
|
|
40
44
|
const configPath = process.argv[2] || "/etc/glassmkr/collector.yaml";
|
|
@@ -96,6 +100,10 @@ async function collect() {
|
|
|
96
100
|
try { snapshot.zfs = await collectZfs() ?? undefined; } catch { /* skip if ZFS not available */ }
|
|
97
101
|
try { snapshot.io_errors = await collectIoErrors() ?? undefined; } catch { /* skip on error */ }
|
|
98
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 */ }
|
|
99
107
|
|
|
100
108
|
// Update Prometheus metrics
|
|
101
109
|
updateMetrics(snapshot);
|
package/src/lib/types.ts
CHANGED
|
@@ -14,6 +14,36 @@ export interface Snapshot {
|
|
|
14
14
|
zfs?: ZfsData;
|
|
15
15
|
io_errors?: { count: number; devices: string[] };
|
|
16
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;
|
|
17
47
|
}
|
|
18
48
|
|
|
19
49
|
export interface ZfsPool {
|