test/server.js

139 lines
3.9 KiB
JavaScript

const http = require("http");
const fs = require("fs");
const path = require("path");
const os = require("os");
const { exec } = require("child_process");
const PORT = process.env.PORT || 3000;
const MIME = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".svg": "image/svg+xml",
};
function run(cmd) {
return new Promise((resolve) => {
exec(cmd, { timeout: 4000 }, (err, stdout) => {
if (err) return resolve(null);
resolve(stdout.trim());
});
});
}
let lastCpu = os.cpus();
function cpuUsage() {
const now = os.cpus();
let total = 0, idle = 0;
for (let i = 0; i < now.length; i++) {
const cur = now[i].times;
const prev = lastCpu[i].times;
const t = (cur.user + cur.nice + cur.sys + cur.idle + cur.irq) -
(prev.user + prev.nice + prev.sys + prev.idle + prev.irq);
const i2 = cur.idle - prev.idle;
if (t > 0) { total += t; idle += i2; }
}
lastCpu = now;
return total > 0 ? Math.round(((total - idle) / total) * 1000) / 10 : 0;
}
async function diskInfo() {
const out = await run("df -k -P /");
if (!out) return null;
const line = out.split("\n").pop();
const parts = line.split(/\s+/);
if (parts.length < 5) return null;
const total = +parts[1] * 1024;
const used = +parts[2] * 1024;
const free = +parts[3] * 1024;
return {
total,
used,
free,
percent: total > 0 ? Math.round((used / total) * 1000) / 10 : 0,
};
}
async function topProcesses() {
const out = await run("ps -eo pid=,comm=,%cpu=,%mem= --sort=-%cpu | head -8");
if (!out) return [];
const rows = out.split("\n").filter(Boolean).map((l) => {
const m = l.trim().split(/\s+/);
if (m.length < 4) return null;
const pid = +m[0];
const name = m.slice(1, m.length - 2).join(" ") || "?";
return { pid, name, cpu: +parseFloat(m[m.length - 2]).toFixed(1), mem: +parseFloat(m[m.length - 1]).toFixed(1) };
});
return rows.filter(Boolean);
}
function cpuInfo() {
const cpus = os.cpus();
const speed = cpus[0] ? Math.round(cpus[0].speed / 100) / 10 : 0;
const model = cpus[0] ? cpus[0].model.trim() : "";
return {
usage: cpuUsage(),
model,
cores: cpus.length,
speed,
};
}
function memInfo() {
const total = os.totalmem();
const free = os.freemem();
const used = total - free;
return { total, used, free, percent: Math.round((used / total) * 1000) / 10 };
}
function networkInfo() {
const ifs = os.networkInterfaces();
for (const name of Object.keys(ifs)) {
for (const a of ifs[name] || []) {
if (a.family === "IPv4" && !a.internal) return name + ": " + a.address;
}
}
return "—";
}
async function metrics() {
const disk = await diskInfo();
return {
status: "ok",
hostname: os.hostname(),
os: os.platform(),
release: os.release(),
arch: os.arch(),
uptime: Math.floor(os.uptime()),
loadavg: os.loadavg(),
cpu: cpuInfo(),
mem: memInfo(),
disk: disk || { total: 0, used: 0, free: 0, percent: 0 },
network: networkInfo(),
processes: await topProcesses(),
timestamp: Date.now(),
};
}
http.createServer(async (req, res) => {
const url = (req.url || "/").split("?")[0];
if (req.method === "GET" && url === "/api/metrics") {
res.writeHead(200, { "Content-Type": MIME[".json"] });
return res.end(JSON.stringify(await metrics()));
}
let file = url === "/" ? "/index.html" : url;
const full = path.join(__dirname, file);
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
const type = MIME[path.extname(full)] || "application/octet-stream";
res.writeHead(200, { "Content-Type": type });
return res.end(fs.readFileSync(full));
}
res.writeHead(404);
res.end("Not found");
}).listen(PORT, () => console.log("Мониторинг запущен на порту " + PORT));