@aidalinfo/aegis-agent 0.1.1 → 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 +241 -71
- 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,45 +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
|
-
import { type as osType, release as osRelease, uptime as osUptime } from "os";
|
|
130
309
|
async function collectMetrics(config) {
|
|
131
|
-
const [cpu, memory, disks] = await Promise.all([
|
|
310
|
+
const [cpu, memory, disks, uptimeSeconds, os] = await Promise.all([
|
|
132
311
|
collectCpu(),
|
|
133
312
|
collectMemory(),
|
|
134
|
-
collectDisk()
|
|
313
|
+
collectDisk(),
|
|
314
|
+
collectUptime(),
|
|
315
|
+
collectOs()
|
|
135
316
|
]);
|
|
136
|
-
const metrics = {
|
|
137
|
-
cpu,
|
|
138
|
-
memory,
|
|
139
|
-
disks,
|
|
140
|
-
os: `${osType()} ${osRelease()}`,
|
|
141
|
-
uptimeSeconds: Math.round(osUptime())
|
|
142
|
-
};
|
|
317
|
+
const metrics = { cpu, memory, disks, os, uptimeSeconds };
|
|
143
318
|
if (config.mode === "full") {
|
|
144
|
-
const [network, swap, topProcesses, temperature] = await Promise.all([
|
|
319
|
+
const [network, swap, topProcesses, temperature, loadAverage] = await Promise.all([
|
|
145
320
|
collectNetwork(),
|
|
146
321
|
collectSwap(),
|
|
147
322
|
collectProcesses(),
|
|
148
|
-
collectTemperature()
|
|
323
|
+
collectTemperature(),
|
|
324
|
+
collectLoad()
|
|
149
325
|
]);
|
|
150
|
-
Object.assign(metrics, {
|
|
151
|
-
network,
|
|
152
|
-
loadAverage: collectLoad(),
|
|
153
|
-
swap,
|
|
154
|
-
topProcesses,
|
|
155
|
-
temperature
|
|
156
|
-
});
|
|
326
|
+
Object.assign(metrics, { network, loadAverage, swap, topProcesses, temperature });
|
|
157
327
|
}
|
|
158
328
|
return {
|
|
159
329
|
hostname: config.hostname,
|
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
|
+
};
|