Files
SenMeshNet/electron/bridges/inspection/adapters/linuxServerAdapter.cjs

133 lines
4.8 KiB
JavaScript
Raw Normal View History

"use strict";
/**
* Linux 服务器采集器
*
* 采集指标(告警相关):
* - cpu_usage CPU 使用率(%)
* - memory_usage 内存使用率(%)
* - disk_usage 磁盘使用率(%,只显示超过 80% 的挂载点)
*
* 实现说明:
* 直接使用 POSIX shell 命令,命令与 NetMesh System Manager 的概览一致。
* 传入 exec 函数来执行远程命令(由调度器注入,复用 SSH 连接池)。
*/
const BaseAdapter = require("./baseAdapter.cjs");
class LinuxServerAdapter extends BaseAdapter {
constructor(connectionInfo, options = {}) {
super(connectionInfo, options);
this.adapterName = "linux_server";
// 支持外部注入 exec 函数(用于复用 SSH 连接)
if (options.execFn) {
this.execute = options.execFn;
}
}
getSupportedMetrics() {
return [
"cpu_usage",
"memory_usage",
"disk_usage",
];
}
async collectMetrics() {
const metrics = {};
const raw = {};
const DEBUG = process.env.INSPECTION_DEBUG === "1";
// 1. CPU 使用率(采样 top)
try {
raw.cpu = await this.execute("top -bn1 2>/dev/null | head -n 5");
if (DEBUG) console.log("[LinuxAdapter] CPU stdout:", JSON.stringify(raw.cpu?.slice(0, 200)));
const cpuMatch = (raw.cpu || "").match(/Cpu\(s\):\s*([\d.]+)\s*us,\s*([\d.]+)\s*sy/i);
if (cpuMatch) {
metrics.cpu_usage = Math.round(parseFloat(cpuMatch[1]) + parseFloat(cpuMatch[2]));
} else {
// 尝试 mpstat 兜底(部分系统 top 不支持 batch 模式)
try {
raw.cpuMpstat = await this.execute("mpstat 1 1 2>/dev/null | tail -n 1");
if (DEBUG) console.log("[LinuxAdapter] mpstat stdout:", JSON.stringify(raw.cpuMpstat?.slice(0, 200)));
const idleMatch = (raw.cpuMpstat || "").match(/(?:%?idle|idle)\s*[:=]?\s*([\d.]+)/i);
if (idleMatch) metrics.cpu_usage = Math.round(100 - parseFloat(idleMatch[1]));
} catch (_) { /* ignore */ }
}
} catch (e) {
if (DEBUG) console.error("[LinuxAdapter] CPU 采集失败:", e.message);
throw e; // SSH 失败或其他错误必须向上传播
}
// 2. 内存使用率
try {
raw.mem = await this.execute("free -k 2>/dev/null | head -n 2 | tail -n 1");
if (DEBUG) console.log("[LinuxAdapter] MEM stdout:", JSON.stringify(raw.mem?.slice(0, 200)));
// 支持中英文输出: "Mem:" 或 "内存:"
const memMatch = (raw.mem || "").match(/(?:Mem:|内存[::])\s+(\d+)\s+(\d+)/);
if (memMatch) {
const total = parseInt(memMatch[1], 10);
const used = parseInt(memMatch[2], 10);
if (total > 0) {
metrics.memory_usage = Math.min(100, Math.round((used / total) * 100));
}
} else {
// 兜底:/proc/meminfo
try {
raw.memInfo = await this.execute("cat /proc/meminfo 2>/dev/null | head -n 3");
if (DEBUG) console.log("[LinuxAdapter] meminfo stdout:", JSON.stringify(raw.memInfo?.slice(0, 300)));
const memInfo = raw.memInfo || "";
const totalMatch = memInfo.match(/MemTotal:\s+(\d+)/);
const availMatch = memInfo.match(/MemAvailable:\s+(\d+)/);
if (totalMatch && availMatch) {
const total = parseInt(totalMatch[1], 10);
const avail = parseInt(availMatch[1], 10);
metrics.memory_usage = Math.min(100, Math.round(((total - avail) / total) * 100));
}
} catch (_) { /* ignore */ }
}
} catch (e) {
if (DEBUG) console.error("[LinuxAdapter] MEM 采集失败:", e.message);
throw e;
}
// 3. 磁盘使用率(显示所有挂载点)
try {
raw.disk = await this.execute("df -h 2>/dev/null | tail -n +2 | grep -v '/dev/loop'");
if (DEBUG) console.log("[LinuxAdapter] DISK stdout:", JSON.stringify(raw.disk?.slice(0, 300)));
const disks = this.parseDiskUsage(raw.disk);
if (disks.length > 0) metrics.disk_usage = disks;
} catch (e) {
if (DEBUG) console.error("[LinuxAdapter] DISK 采集失败:", e.message);
throw e;
}
if (DEBUG) console.log("[LinuxAdapter] 最终 metrics:", JSON.stringify(metrics));
return metrics;
}
/**
* 解析磁盘使用率(df -h 输出格式),返回所有挂载点
*/
parseDiskUsage(text) {
const disks = [];
const lines = (text || "").split("\n");
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (parts.length < 6) continue;
const usageStr = parts[4] || "";
const usageMatch = usageStr.match(/^(\d+)%/);
if (!usageMatch) continue;
const usagePercent = parseInt(usageMatch[1], 10);
disks.push({
filesystem: parts[0],
usage_percent: usagePercent,
mount: parts[parts.length - 1],
});
}
return disks;
}
}
module.exports = LinuxServerAdapter;