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