@aidalinfo/aegis-agent 0.1.1 → 0.2.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/dist/bin.js +256 -73
- package/dist/index.d.ts +5 -3
- package/dist/index.js +346 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -7,14 +7,25 @@ import { execSync as execSync2 } from "child_process";
|
|
|
7
7
|
// src/config.ts
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { readFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
10
11
|
import { hostname } from "os";
|
|
11
12
|
var ConfigSchema = z.object({
|
|
12
13
|
interval: z.coerce.number().int().min(5).default(30),
|
|
13
14
|
endpoint: z.string().url(),
|
|
14
15
|
apiKey: z.string().min(1),
|
|
15
16
|
mode: z.enum(["core", "full"]).default("core"),
|
|
16
|
-
hostname: z.string().
|
|
17
|
+
hostname: z.string().min(1)
|
|
17
18
|
});
|
|
19
|
+
function resolveHostname() {
|
|
20
|
+
const hostRoot = process.env.AEGIS_HOST_ROOT;
|
|
21
|
+
if (hostRoot) {
|
|
22
|
+
try {
|
|
23
|
+
return readFileSync(join(hostRoot, "/etc/hostname"), "utf8").trim();
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return hostname();
|
|
28
|
+
}
|
|
18
29
|
function loadConfig(configPath = "/etc/aegis-agent/config.json") {
|
|
19
30
|
let fileConfig = {};
|
|
20
31
|
try {
|
|
@@ -27,85 +38,251 @@ function loadConfig(configPath = "/etc/aegis-agent/config.json") {
|
|
|
27
38
|
...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
|
|
28
39
|
...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
|
|
29
40
|
...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
|
|
30
|
-
|
|
41
|
+
hostname: process.env.AEGIS_HOSTNAME ?? fileConfig.hostname ?? resolveHostname()
|
|
31
42
|
});
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
// src/metrics/hostpaths.ts
|
|
46
|
+
import { readFile, statfs } from "fs/promises";
|
|
47
|
+
import { join as join2 } from "path";
|
|
48
|
+
var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
|
|
49
|
+
var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
|
|
50
|
+
var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
|
|
51
|
+
function readProc(rel) {
|
|
52
|
+
return readFile(join2(HOST_PROC, rel), "utf8");
|
|
53
|
+
}
|
|
54
|
+
function readHostFile(absPath) {
|
|
55
|
+
return readFile(HOST_ROOT ? join2(HOST_ROOT, absPath) : absPath, "utf8");
|
|
56
|
+
}
|
|
57
|
+
function statHostFs(mount) {
|
|
58
|
+
return statfs(HOST_ROOT ? join2(HOST_ROOT, mount) : mount);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/metrics/proc-parse.ts
|
|
62
|
+
function parseProcStat(content) {
|
|
63
|
+
const line = content.split("\n").find((l) => l.startsWith("cpu "));
|
|
64
|
+
if (!line) return { idle: 0, total: 0 };
|
|
65
|
+
const nums = line.trim().split(/\s+/).slice(1).map(Number);
|
|
66
|
+
const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
|
|
67
|
+
const total = nums.reduce((a, b) => a + (b || 0), 0);
|
|
68
|
+
return { idle, total };
|
|
69
|
+
}
|
|
70
|
+
function cpuPercent(prev3, cur) {
|
|
71
|
+
const dTotal = cur.total - prev3.total;
|
|
72
|
+
const dIdle = cur.idle - prev3.idle;
|
|
73
|
+
if (dTotal <= 0) return 0;
|
|
74
|
+
return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
|
|
75
|
+
}
|
|
76
|
+
function parseMemInfo(content) {
|
|
77
|
+
const get = (key) => {
|
|
78
|
+
const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
|
|
79
|
+
return m ? Number(m[1]) : 0;
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
memTotalKb: get("MemTotal"),
|
|
83
|
+
memAvailableKb: get("MemAvailable"),
|
|
84
|
+
swapTotalKb: get("SwapTotal"),
|
|
85
|
+
swapFreeKb: get("SwapFree")
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function memUsage(m) {
|
|
89
|
+
const totalMb = Math.round(m.memTotalKb / 1024);
|
|
90
|
+
const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
|
|
91
|
+
return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
|
|
92
|
+
}
|
|
93
|
+
function swapUsage(m) {
|
|
94
|
+
const totalMb = Math.round(m.swapTotalKb / 1024);
|
|
95
|
+
const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
|
|
96
|
+
return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
|
|
97
|
+
}
|
|
98
|
+
function parseLoadAvg(content) {
|
|
99
|
+
const [a, b, c] = content.trim().split(/\s+/).map(Number);
|
|
100
|
+
return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
|
|
101
|
+
}
|
|
102
|
+
function parseUptime(content) {
|
|
103
|
+
return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
|
|
104
|
+
}
|
|
105
|
+
function parseOsRelease(content) {
|
|
106
|
+
const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
|
|
107
|
+
return m ? m[1] : null;
|
|
108
|
+
}
|
|
109
|
+
function parseNetDev(content) {
|
|
110
|
+
const out = [];
|
|
111
|
+
for (const line of content.split("\n")) {
|
|
112
|
+
const m = line.match(/^\s*([^:]+):\s*(.+)$/);
|
|
113
|
+
if (!m) continue;
|
|
114
|
+
const iface = m[1].trim();
|
|
115
|
+
if (iface === "lo") continue;
|
|
116
|
+
const f = m[2].trim().split(/\s+/).map(Number);
|
|
117
|
+
out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function netRates(prev3, cur, dtSec) {
|
|
122
|
+
if (dtSec <= 0) return [];
|
|
123
|
+
const prevMap = new Map(prev3.map((p) => [p.iface, p]));
|
|
124
|
+
return cur.map((c) => {
|
|
125
|
+
const p = prevMap.get(c.iface);
|
|
126
|
+
const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
|
|
127
|
+
const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
|
|
128
|
+
return {
|
|
129
|
+
interface: c.iface,
|
|
130
|
+
rxBytesPerSec: Math.round(rx / dtSec),
|
|
131
|
+
txBytesPerSec: Math.round(tx / dtSec)
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
var PSEUDO_FS = /* @__PURE__ */ new Set([
|
|
136
|
+
"sysfs",
|
|
137
|
+
"proc",
|
|
138
|
+
"tmpfs",
|
|
139
|
+
"devtmpfs",
|
|
140
|
+
"devpts",
|
|
141
|
+
"cgroup",
|
|
142
|
+
"cgroup2",
|
|
143
|
+
"pstore",
|
|
144
|
+
"bpf",
|
|
145
|
+
"mqueue",
|
|
146
|
+
"debugfs",
|
|
147
|
+
"tracefs",
|
|
148
|
+
"securityfs",
|
|
149
|
+
"fusectl",
|
|
150
|
+
"configfs",
|
|
151
|
+
"overlay",
|
|
152
|
+
"autofs",
|
|
153
|
+
"hugetlbfs",
|
|
154
|
+
"squashfs",
|
|
155
|
+
"ramfs",
|
|
156
|
+
"nsfs",
|
|
157
|
+
"binfmt_misc",
|
|
158
|
+
"efivarfs"
|
|
159
|
+
]);
|
|
160
|
+
var DOCKER_FILE_MOUNTS = /* @__PURE__ */ new Set(["/etc/hostname", "/etc/resolv.conf", "/etc/hosts"]);
|
|
161
|
+
function parseMounts(content) {
|
|
162
|
+
const seen = /* @__PURE__ */ new Set();
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const line of content.split("\n")) {
|
|
165
|
+
const parts = line.split(/\s+/);
|
|
166
|
+
if (parts.length < 3) continue;
|
|
167
|
+
const [, mount, fstype] = parts;
|
|
168
|
+
if (PSEUDO_FS.has(fstype)) continue;
|
|
169
|
+
if (DOCKER_FILE_MOUNTS.has(mount)) continue;
|
|
170
|
+
if (seen.has(mount)) continue;
|
|
171
|
+
seen.add(mount);
|
|
172
|
+
out.push(mount);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function parseDiskStats(content) {
|
|
177
|
+
let readSectors = 0;
|
|
178
|
+
let writeSectors = 0;
|
|
179
|
+
for (const line of content.split("\n")) {
|
|
180
|
+
const f = line.trim().split(/\s+/);
|
|
181
|
+
if (f.length < 11) continue;
|
|
182
|
+
const name = f[2];
|
|
183
|
+
if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
|
|
184
|
+
readSectors += Number(f[5]) || 0;
|
|
185
|
+
writeSectors += Number(f[9]) || 0;
|
|
186
|
+
}
|
|
187
|
+
return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
|
|
188
|
+
}
|
|
189
|
+
function diskIoRates(prev3, cur, dtSec) {
|
|
190
|
+
if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
|
|
191
|
+
return {
|
|
192
|
+
readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
|
|
193
|
+
writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
34
197
|
// src/metrics/cpu.ts
|
|
35
|
-
|
|
198
|
+
var prev = null;
|
|
36
199
|
async function collectCpu() {
|
|
37
|
-
const
|
|
38
|
-
|
|
200
|
+
const cur = parseProcStat(await readProc("stat"));
|
|
201
|
+
const usagePercent = prev ? cpuPercent(prev, cur) : 0;
|
|
202
|
+
prev = cur;
|
|
203
|
+
return { usagePercent };
|
|
39
204
|
}
|
|
40
205
|
|
|
41
206
|
// src/metrics/memory.ts
|
|
42
|
-
import si2 from "systeminformation";
|
|
43
207
|
async function collectMemory() {
|
|
44
|
-
|
|
45
|
-
const totalMb = Math.round(mem.total / 1048576);
|
|
46
|
-
const usedMb = Math.round(mem.used / 1048576);
|
|
47
|
-
return {
|
|
48
|
-
totalMb,
|
|
49
|
-
usedMb,
|
|
50
|
-
usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
|
|
51
|
-
};
|
|
208
|
+
return memUsage(parseMemInfo(await readProc("meminfo")));
|
|
52
209
|
}
|
|
53
210
|
|
|
54
211
|
// src/metrics/disk.ts
|
|
55
|
-
|
|
212
|
+
var prevIo = null;
|
|
213
|
+
function normaliseMountForLabel(rawMount) {
|
|
214
|
+
if (!HOST_ROOT) return rawMount;
|
|
215
|
+
if (rawMount === HOST_ROOT) return "/";
|
|
216
|
+
const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
|
|
217
|
+
if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
|
|
218
|
+
return rawMount;
|
|
219
|
+
}
|
|
56
220
|
async function collectDisk() {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
221
|
+
const rawMounts = parseMounts(await readProc("mounts"));
|
|
222
|
+
const stats = parseDiskStats(await readProc("diskstats"));
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
|
|
225
|
+
if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
|
|
226
|
+
prevIo = { stats, t: now };
|
|
227
|
+
const out = [];
|
|
228
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
229
|
+
for (const rawMount of rawMounts) {
|
|
230
|
+
try {
|
|
231
|
+
const label = normaliseMountForLabel(rawMount);
|
|
232
|
+
if (seenLabels.has(label)) continue;
|
|
233
|
+
seenLabels.add(label);
|
|
234
|
+
const fs = await statHostFs(label);
|
|
235
|
+
const bsize = Number(fs.bsize);
|
|
236
|
+
const blocks = Number(fs.blocks);
|
|
237
|
+
const bfree = Number(fs.bfree);
|
|
238
|
+
const bavail = Number(fs.bavail);
|
|
239
|
+
if (!blocks) continue;
|
|
240
|
+
const totalBytes = blocks * bsize;
|
|
241
|
+
const usedBytes = (blocks - bfree) * bsize;
|
|
242
|
+
const denom = blocks - bfree + bavail;
|
|
243
|
+
out.push({
|
|
244
|
+
mount: label,
|
|
245
|
+
totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
|
|
246
|
+
usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
|
|
247
|
+
usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
|
|
248
|
+
readBytesPerSec: io.readBytesPerSec,
|
|
249
|
+
writeBytesPerSec: io.writeBytesPerSec
|
|
250
|
+
});
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
68
255
|
}
|
|
69
256
|
|
|
70
257
|
// src/metrics/network.ts
|
|
71
|
-
|
|
258
|
+
var prev2 = null;
|
|
72
259
|
async function collectNetwork() {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
260
|
+
const counters = parseNetDev(await readProc("net/dev"));
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
if (!prev2) {
|
|
263
|
+
prev2 = { counters, t: now };
|
|
264
|
+
return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
|
|
265
|
+
}
|
|
266
|
+
const dtSec = (now - prev2.t) / 1e3;
|
|
267
|
+
const rates = netRates(prev2.counters, counters, dtSec);
|
|
268
|
+
prev2 = { counters, t: now };
|
|
269
|
+
return rates;
|
|
79
270
|
}
|
|
80
271
|
|
|
81
272
|
// src/metrics/load.ts
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const [l1, l5, l15] = loadavg();
|
|
85
|
-
return {
|
|
86
|
-
"1m": Math.round(l1 * 100) / 100,
|
|
87
|
-
"5m": Math.round(l5 * 100) / 100,
|
|
88
|
-
"15m": Math.round(l15 * 100) / 100
|
|
89
|
-
};
|
|
273
|
+
async function collectLoad() {
|
|
274
|
+
return parseLoadAvg(await readProc("loadavg"));
|
|
90
275
|
}
|
|
91
276
|
|
|
92
277
|
// src/metrics/swap.ts
|
|
93
|
-
import si5 from "systeminformation";
|
|
94
278
|
async function collectSwap() {
|
|
95
|
-
|
|
96
|
-
const totalMb = Math.round(mem.swaptotal / 1048576);
|
|
97
|
-
const usedMb = Math.round(mem.swapused / 1048576);
|
|
98
|
-
return {
|
|
99
|
-
totalMb,
|
|
100
|
-
usedMb,
|
|
101
|
-
usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10
|
|
102
|
-
};
|
|
279
|
+
return swapUsage(parseMemInfo(await readProc("meminfo")));
|
|
103
280
|
}
|
|
104
281
|
|
|
105
282
|
// src/metrics/processes.ts
|
|
106
|
-
import
|
|
283
|
+
import si from "systeminformation";
|
|
107
284
|
async function collectProcesses() {
|
|
108
|
-
const { list } = await
|
|
285
|
+
const { list } = await si.processes();
|
|
109
286
|
return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
|
|
110
287
|
pid: p.pid,
|
|
111
288
|
name: p.name,
|
|
@@ -115,45 +292,51 @@ async function collectProcesses() {
|
|
|
115
292
|
}
|
|
116
293
|
|
|
117
294
|
// src/metrics/temperature.ts
|
|
118
|
-
import
|
|
295
|
+
import si2 from "systeminformation";
|
|
119
296
|
async function collectTemperature() {
|
|
120
297
|
try {
|
|
121
|
-
const temp = await
|
|
298
|
+
const temp = await si2.cpuTemperature();
|
|
122
299
|
return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
|
|
123
300
|
} catch {
|
|
124
301
|
return { cpuCelsius: null };
|
|
125
302
|
}
|
|
126
303
|
}
|
|
127
304
|
|
|
305
|
+
// src/metrics/system.ts
|
|
306
|
+
async function collectUptime() {
|
|
307
|
+
try {
|
|
308
|
+
return parseUptime(await readProc("uptime"));
|
|
309
|
+
} catch {
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function collectOs() {
|
|
314
|
+
try {
|
|
315
|
+
return parseOsRelease(await readHostFile("/etc/os-release"));
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
128
321
|
// src/collector.ts
|
|
129
|
-
import { type as osType, release as osRelease, uptime as osUptime } from "os";
|
|
130
322
|
async function collectMetrics(config) {
|
|
131
|
-
const [cpu, memory, disks] = await Promise.all([
|
|
323
|
+
const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
|
|
132
324
|
collectCpu(),
|
|
133
325
|
collectMemory(),
|
|
134
|
-
collectDisk()
|
|
326
|
+
collectDisk(),
|
|
327
|
+
collectUptime(),
|
|
328
|
+
collectOs()
|
|
135
329
|
]);
|
|
136
|
-
const metrics = {
|
|
137
|
-
cpu,
|
|
138
|
-
memory,
|
|
139
|
-
disks,
|
|
140
|
-
os: `${osType()} ${osRelease()}`,
|
|
141
|
-
uptimeSeconds: Math.round(osUptime())
|
|
142
|
-
};
|
|
330
|
+
const metrics = { cpu, memory, disks, os, uptimeSeconds };
|
|
143
331
|
if (config.mode === "full") {
|
|
144
|
-
const [network, swap, topProcesses, temperature] = await Promise.all([
|
|
332
|
+
const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
|
|
145
333
|
collectNetwork(),
|
|
146
334
|
collectSwap(),
|
|
147
335
|
collectProcesses(),
|
|
148
|
-
collectTemperature()
|
|
336
|
+
collectTemperature(),
|
|
337
|
+
collectLoad()
|
|
149
338
|
]);
|
|
150
|
-
Object.assign(metrics, {
|
|
151
|
-
network,
|
|
152
|
-
loadAverage: collectLoad(),
|
|
153
|
-
swap,
|
|
154
|
-
topProcesses,
|
|
155
|
-
temperature
|
|
156
|
-
});
|
|
339
|
+
Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
|
|
157
340
|
}
|
|
158
341
|
return {
|
|
159
342
|
hostname: config.hostname,
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
5
5
|
endpoint: z.ZodString;
|
|
6
6
|
apiKey: z.ZodString;
|
|
7
7
|
mode: z.ZodDefault<z.ZodEnum<["core", "full"]>>;
|
|
8
|
-
hostname: z.
|
|
8
|
+
hostname: z.ZodString;
|
|
9
9
|
}, "strip", z.ZodTypeAny, {
|
|
10
10
|
interval: number;
|
|
11
11
|
endpoint: string;
|
|
@@ -15,11 +15,12 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
15
15
|
}, {
|
|
16
16
|
endpoint: string;
|
|
17
17
|
apiKey: string;
|
|
18
|
+
hostname: string;
|
|
18
19
|
interval?: number | undefined;
|
|
19
20
|
mode?: "core" | "full" | undefined;
|
|
20
|
-
hostname?: string | undefined;
|
|
21
21
|
}>;
|
|
22
22
|
type Config = z.infer<typeof ConfigSchema>;
|
|
23
|
+
declare function loadConfig(configPath?: string): Config;
|
|
23
24
|
|
|
24
25
|
interface MetricsPayload {
|
|
25
26
|
hostname: string;
|
|
@@ -27,5 +28,6 @@ interface MetricsPayload {
|
|
|
27
28
|
mode: 'core' | 'full';
|
|
28
29
|
metrics: Record<string, unknown>;
|
|
29
30
|
}
|
|
31
|
+
declare function collectMetrics(config: Config): Promise<MetricsPayload>;
|
|
30
32
|
|
|
31
|
-
export type
|
|
33
|
+
export { type Config, ConfigSchema, type MetricsPayload, collectMetrics, loadConfig };
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// src/metrics/hostpaths.ts
|
|
2
|
+
import { readFile, statfs } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
var HOST_PROC = process.env.AEGIS_HOST_PROC || "/proc";
|
|
5
|
+
var HOST_SYS = process.env.AEGIS_HOST_SYS || "/sys";
|
|
6
|
+
var HOST_ROOT = process.env.AEGIS_HOST_ROOT || "";
|
|
7
|
+
function readProc(rel) {
|
|
8
|
+
return readFile(join(HOST_PROC, rel), "utf8");
|
|
9
|
+
}
|
|
10
|
+
function readHostFile(absPath) {
|
|
11
|
+
return readFile(HOST_ROOT ? join(HOST_ROOT, absPath) : absPath, "utf8");
|
|
12
|
+
}
|
|
13
|
+
function statHostFs(mount) {
|
|
14
|
+
return statfs(HOST_ROOT ? join(HOST_ROOT, mount) : mount);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/metrics/proc-parse.ts
|
|
18
|
+
function parseProcStat(content) {
|
|
19
|
+
const line = content.split("\n").find((l) => l.startsWith("cpu "));
|
|
20
|
+
if (!line) return { idle: 0, total: 0 };
|
|
21
|
+
const nums = line.trim().split(/\s+/).slice(1).map(Number);
|
|
22
|
+
const idle = (nums[3] ?? 0) + (nums[4] ?? 0);
|
|
23
|
+
const total = nums.reduce((a, b) => a + (b || 0), 0);
|
|
24
|
+
return { idle, total };
|
|
25
|
+
}
|
|
26
|
+
function cpuPercent(prev3, cur) {
|
|
27
|
+
const dTotal = cur.total - prev3.total;
|
|
28
|
+
const dIdle = cur.idle - prev3.idle;
|
|
29
|
+
if (dTotal <= 0) return 0;
|
|
30
|
+
return Math.round((dTotal - dIdle) / dTotal * 1e3) / 10;
|
|
31
|
+
}
|
|
32
|
+
function parseMemInfo(content) {
|
|
33
|
+
const get = (key) => {
|
|
34
|
+
const m = content.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
|
|
35
|
+
return m ? Number(m[1]) : 0;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
memTotalKb: get("MemTotal"),
|
|
39
|
+
memAvailableKb: get("MemAvailable"),
|
|
40
|
+
swapTotalKb: get("SwapTotal"),
|
|
41
|
+
swapFreeKb: get("SwapFree")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function memUsage(m) {
|
|
45
|
+
const totalMb = Math.round(m.memTotalKb / 1024);
|
|
46
|
+
const usedMb = Math.round((m.memTotalKb - m.memAvailableKb) / 1024);
|
|
47
|
+
return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
|
|
48
|
+
}
|
|
49
|
+
function swapUsage(m) {
|
|
50
|
+
const totalMb = Math.round(m.swapTotalKb / 1024);
|
|
51
|
+
const usedMb = Math.round((m.swapTotalKb - m.swapFreeKb) / 1024);
|
|
52
|
+
return { totalMb, usedMb, usagePercent: totalMb === 0 ? 0 : Math.round(usedMb / totalMb * 1e3) / 10 };
|
|
53
|
+
}
|
|
54
|
+
function parseLoadAvg(content) {
|
|
55
|
+
const [a, b, c] = content.trim().split(/\s+/).map(Number);
|
|
56
|
+
return { "1m": a ?? 0, "5m": b ?? 0, "15m": c ?? 0 };
|
|
57
|
+
}
|
|
58
|
+
function parseUptime(content) {
|
|
59
|
+
return Math.round(Number(content.trim().split(/\s+/)[0] ?? 0));
|
|
60
|
+
}
|
|
61
|
+
function parseOsRelease(content) {
|
|
62
|
+
const m = content.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
|
|
63
|
+
return m ? m[1] : null;
|
|
64
|
+
}
|
|
65
|
+
function parseNetDev(content) {
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const line of content.split("\n")) {
|
|
68
|
+
const m = line.match(/^\s*([^:]+):\s*(.+)$/);
|
|
69
|
+
if (!m) continue;
|
|
70
|
+
const iface = m[1].trim();
|
|
71
|
+
if (iface === "lo") continue;
|
|
72
|
+
const f = m[2].trim().split(/\s+/).map(Number);
|
|
73
|
+
out.push({ iface, rxBytes: f[0] ?? 0, txBytes: f[8] ?? 0 });
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function netRates(prev3, cur, dtSec) {
|
|
78
|
+
if (dtSec <= 0) return [];
|
|
79
|
+
const prevMap = new Map(prev3.map((p) => [p.iface, p]));
|
|
80
|
+
return cur.map((c) => {
|
|
81
|
+
const p = prevMap.get(c.iface);
|
|
82
|
+
const rx = p ? Math.max(0, c.rxBytes - p.rxBytes) : 0;
|
|
83
|
+
const tx = p ? Math.max(0, c.txBytes - p.txBytes) : 0;
|
|
84
|
+
return {
|
|
85
|
+
interface: c.iface,
|
|
86
|
+
rxBytesPerSec: Math.round(rx / dtSec),
|
|
87
|
+
txBytesPerSec: Math.round(tx / dtSec)
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
var PSEUDO_FS = /* @__PURE__ */ new Set([
|
|
92
|
+
"sysfs",
|
|
93
|
+
"proc",
|
|
94
|
+
"tmpfs",
|
|
95
|
+
"devtmpfs",
|
|
96
|
+
"devpts",
|
|
97
|
+
"cgroup",
|
|
98
|
+
"cgroup2",
|
|
99
|
+
"pstore",
|
|
100
|
+
"bpf",
|
|
101
|
+
"mqueue",
|
|
102
|
+
"debugfs",
|
|
103
|
+
"tracefs",
|
|
104
|
+
"securityfs",
|
|
105
|
+
"fusectl",
|
|
106
|
+
"configfs",
|
|
107
|
+
"overlay",
|
|
108
|
+
"autofs",
|
|
109
|
+
"hugetlbfs",
|
|
110
|
+
"squashfs",
|
|
111
|
+
"ramfs",
|
|
112
|
+
"nsfs",
|
|
113
|
+
"binfmt_misc",
|
|
114
|
+
"efivarfs"
|
|
115
|
+
]);
|
|
116
|
+
var DOCKER_FILE_MOUNTS = /* @__PURE__ */ new Set(["/etc/hostname", "/etc/resolv.conf", "/etc/hosts"]);
|
|
117
|
+
function parseMounts(content) {
|
|
118
|
+
const seen = /* @__PURE__ */ new Set();
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const line of content.split("\n")) {
|
|
121
|
+
const parts = line.split(/\s+/);
|
|
122
|
+
if (parts.length < 3) continue;
|
|
123
|
+
const [, mount, fstype] = parts;
|
|
124
|
+
if (PSEUDO_FS.has(fstype)) continue;
|
|
125
|
+
if (DOCKER_FILE_MOUNTS.has(mount)) continue;
|
|
126
|
+
if (seen.has(mount)) continue;
|
|
127
|
+
seen.add(mount);
|
|
128
|
+
out.push(mount);
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function parseDiskStats(content) {
|
|
133
|
+
let readSectors = 0;
|
|
134
|
+
let writeSectors = 0;
|
|
135
|
+
for (const line of content.split("\n")) {
|
|
136
|
+
const f = line.trim().split(/\s+/);
|
|
137
|
+
if (f.length < 11) continue;
|
|
138
|
+
const name = f[2];
|
|
139
|
+
if (/\d$/.test(name) || name.startsWith("loop") || name.startsWith("ram") || name.startsWith("dm-")) continue;
|
|
140
|
+
readSectors += Number(f[5]) || 0;
|
|
141
|
+
writeSectors += Number(f[9]) || 0;
|
|
142
|
+
}
|
|
143
|
+
return { readBytes: readSectors * 512, writeBytes: writeSectors * 512 };
|
|
144
|
+
}
|
|
145
|
+
function diskIoRates(prev3, cur, dtSec) {
|
|
146
|
+
if (dtSec <= 0) return { readBytesPerSec: 0, writeBytesPerSec: 0 };
|
|
147
|
+
return {
|
|
148
|
+
readBytesPerSec: Math.round(Math.max(0, cur.readBytes - prev3.readBytes) / dtSec),
|
|
149
|
+
writeBytesPerSec: Math.round(Math.max(0, cur.writeBytes - prev3.writeBytes) / dtSec)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/metrics/cpu.ts
|
|
154
|
+
var prev = null;
|
|
155
|
+
async function collectCpu() {
|
|
156
|
+
const cur = parseProcStat(await readProc("stat"));
|
|
157
|
+
const usagePercent = prev ? cpuPercent(prev, cur) : 0;
|
|
158
|
+
prev = cur;
|
|
159
|
+
return { usagePercent };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/metrics/memory.ts
|
|
163
|
+
async function collectMemory() {
|
|
164
|
+
return memUsage(parseMemInfo(await readProc("meminfo")));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/metrics/disk.ts
|
|
168
|
+
var prevIo = null;
|
|
169
|
+
function normaliseMountForLabel(rawMount) {
|
|
170
|
+
if (!HOST_ROOT) return rawMount;
|
|
171
|
+
if (rawMount === HOST_ROOT) return "/";
|
|
172
|
+
const prefix = HOST_ROOT.endsWith("/") ? HOST_ROOT : HOST_ROOT + "/";
|
|
173
|
+
if (rawMount.startsWith(prefix)) return "/" + rawMount.slice(prefix.length);
|
|
174
|
+
return rawMount;
|
|
175
|
+
}
|
|
176
|
+
async function collectDisk() {
|
|
177
|
+
const rawMounts = parseMounts(await readProc("mounts"));
|
|
178
|
+
const stats = parseDiskStats(await readProc("diskstats"));
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
let io = { readBytesPerSec: 0, writeBytesPerSec: 0 };
|
|
181
|
+
if (prevIo) io = diskIoRates(prevIo.stats, stats, (now - prevIo.t) / 1e3);
|
|
182
|
+
prevIo = { stats, t: now };
|
|
183
|
+
const out = [];
|
|
184
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
185
|
+
for (const rawMount of rawMounts) {
|
|
186
|
+
try {
|
|
187
|
+
const label = normaliseMountForLabel(rawMount);
|
|
188
|
+
if (seenLabels.has(label)) continue;
|
|
189
|
+
seenLabels.add(label);
|
|
190
|
+
const fs = await statHostFs(label);
|
|
191
|
+
const bsize = Number(fs.bsize);
|
|
192
|
+
const blocks = Number(fs.blocks);
|
|
193
|
+
const bfree = Number(fs.bfree);
|
|
194
|
+
const bavail = Number(fs.bavail);
|
|
195
|
+
if (!blocks) continue;
|
|
196
|
+
const totalBytes = blocks * bsize;
|
|
197
|
+
const usedBytes = (blocks - bfree) * bsize;
|
|
198
|
+
const denom = blocks - bfree + bavail;
|
|
199
|
+
out.push({
|
|
200
|
+
mount: label,
|
|
201
|
+
totalGb: Math.round(totalBytes / 1073741824 * 10) / 10,
|
|
202
|
+
usedGb: Math.round(usedBytes / 1073741824 * 10) / 10,
|
|
203
|
+
usagePercent: denom > 0 ? Math.round((blocks - bfree) / denom * 1e3) / 10 : 0,
|
|
204
|
+
readBytesPerSec: io.readBytesPerSec,
|
|
205
|
+
writeBytesPerSec: io.writeBytesPerSec
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/metrics/network.ts
|
|
214
|
+
var prev2 = null;
|
|
215
|
+
async function collectNetwork() {
|
|
216
|
+
const counters = parseNetDev(await readProc("net/dev"));
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
if (!prev2) {
|
|
219
|
+
prev2 = { counters, t: now };
|
|
220
|
+
return counters.map((c) => ({ interface: c.iface, rxBytesPerSec: 0, txBytesPerSec: 0 }));
|
|
221
|
+
}
|
|
222
|
+
const dtSec = (now - prev2.t) / 1e3;
|
|
223
|
+
const rates = netRates(prev2.counters, counters, dtSec);
|
|
224
|
+
prev2 = { counters, t: now };
|
|
225
|
+
return rates;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/metrics/load.ts
|
|
229
|
+
async function collectLoad() {
|
|
230
|
+
return parseLoadAvg(await readProc("loadavg"));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/metrics/swap.ts
|
|
234
|
+
async function collectSwap() {
|
|
235
|
+
return swapUsage(parseMemInfo(await readProc("meminfo")));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/metrics/processes.ts
|
|
239
|
+
import si from "systeminformation";
|
|
240
|
+
async function collectProcesses() {
|
|
241
|
+
const { list } = await si.processes();
|
|
242
|
+
return list.sort((a, b) => b.cpu - a.cpu).slice(0, 5).map((p) => ({
|
|
243
|
+
pid: p.pid,
|
|
244
|
+
name: p.name,
|
|
245
|
+
cpuPercent: Math.round(p.cpu * 10) / 10,
|
|
246
|
+
memPercent: Math.round(p.mem * 10) / 10
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/metrics/temperature.ts
|
|
251
|
+
import si2 from "systeminformation";
|
|
252
|
+
async function collectTemperature() {
|
|
253
|
+
try {
|
|
254
|
+
const temp = await si2.cpuTemperature();
|
|
255
|
+
return { cpuCelsius: temp.main !== null ? Math.round(temp.main * 10) / 10 : null };
|
|
256
|
+
} catch {
|
|
257
|
+
return { cpuCelsius: null };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/metrics/system.ts
|
|
262
|
+
async function collectUptime() {
|
|
263
|
+
try {
|
|
264
|
+
return parseUptime(await readProc("uptime"));
|
|
265
|
+
} catch {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function collectOs() {
|
|
270
|
+
try {
|
|
271
|
+
return parseOsRelease(await readHostFile("/etc/os-release"));
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/collector.ts
|
|
278
|
+
async function collectMetrics(config) {
|
|
279
|
+
const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
|
|
280
|
+
collectCpu(),
|
|
281
|
+
collectMemory(),
|
|
282
|
+
collectDisk(),
|
|
283
|
+
collectUptime(),
|
|
284
|
+
collectOs()
|
|
285
|
+
]);
|
|
286
|
+
const metrics = { cpu, memory, disks, os, uptimeSeconds };
|
|
287
|
+
if (config.mode === "full") {
|
|
288
|
+
const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
|
|
289
|
+
collectNetwork(),
|
|
290
|
+
collectSwap(),
|
|
291
|
+
collectProcesses(),
|
|
292
|
+
collectTemperature(),
|
|
293
|
+
collectLoad()
|
|
294
|
+
]);
|
|
295
|
+
Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
hostname: config.hostname,
|
|
299
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300
|
+
mode: config.mode,
|
|
301
|
+
metrics
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/config.ts
|
|
306
|
+
import { z } from "zod";
|
|
307
|
+
import { readFileSync } from "fs";
|
|
308
|
+
import { join as join2 } from "path";
|
|
309
|
+
import { hostname } from "os";
|
|
310
|
+
var ConfigSchema = z.object({
|
|
311
|
+
interval: z.coerce.number().int().min(5).default(30),
|
|
312
|
+
endpoint: z.string().url(),
|
|
313
|
+
apiKey: z.string().min(1),
|
|
314
|
+
mode: z.enum(["core", "full"]).default("core"),
|
|
315
|
+
hostname: z.string().min(1)
|
|
316
|
+
});
|
|
317
|
+
function resolveHostname() {
|
|
318
|
+
const hostRoot = process.env.AEGIS_HOST_ROOT;
|
|
319
|
+
if (hostRoot) {
|
|
320
|
+
try {
|
|
321
|
+
return readFileSync(join2(hostRoot, "/etc/hostname"), "utf8").trim();
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return hostname();
|
|
326
|
+
}
|
|
327
|
+
function loadConfig(configPath = "/etc/aegis-agent/config.json") {
|
|
328
|
+
let fileConfig = {};
|
|
329
|
+
try {
|
|
330
|
+
fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
return ConfigSchema.parse({
|
|
334
|
+
...fileConfig,
|
|
335
|
+
...process.env.AEGIS_INTERVAL !== void 0 && { interval: process.env.AEGIS_INTERVAL },
|
|
336
|
+
...process.env.AEGIS_ENDPOINT !== void 0 && { endpoint: process.env.AEGIS_ENDPOINT },
|
|
337
|
+
...process.env.AEGIS_API_KEY !== void 0 && { apiKey: process.env.AEGIS_API_KEY },
|
|
338
|
+
...process.env.AEGIS_MODE !== void 0 && { mode: process.env.AEGIS_MODE },
|
|
339
|
+
hostname: process.env.AEGIS_HOSTNAME ?? fileConfig.hostname ?? resolveHostname()
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
export {
|
|
343
|
+
ConfigSchema,
|
|
344
|
+
collectMetrics,
|
|
345
|
+
loadConfig
|
|
346
|
+
};
|