@hadesz/monitor
Version:
A complete server monitoring system with agents, server and dashboard
307 lines (262 loc) • 8.33 kB
JavaScript
const { exec } = require("child_process");
const { promisify } = require("util");
const fs = require("fs");
const execAsync = promisify(exec);
class NetworkMonitor {
constructor() {
this.prevStats = {};
this.platform = process.platform;
this.initialized = false;
}
/**
* 获取网络统计信息
*/
async getNetworkStats() {
switch (this.platform) {
case "linux":
return await this._getLinuxNetworkStats();
case "win32":
return await this._getWindowsNetworkStats();
case "darwin":
return await this._getMacNetworkStats();
default:
console.warn(`Unsupported platform: ${this.platform}`);
return {};
}
}
/**
* Linux: 通过/proc/net/dev获取网络统计
*/
async _getLinuxNetworkStats() {
return new Promise((resolve) => {
fs.readFile("/proc/net/dev", "utf8", (err, data) => {
if (err) {
console.error("读取/proc/net/dev失败:", err.message);
resolve({});
return;
}
const stats = {};
const lines = data.split("\n");
for (let i = 2; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const parts = line.split(/\s+/);
const interfaceName = parts[0].replace(":", "");
stats[interfaceName] = {
receiveBytes: parseInt(parts[1]) || 0,
transmitBytes: parseInt(parts[9]) || 0
};
}
resolve(stats);
});
});
}
/**
* Windows: 通过netstat -e获取网络统计
*/
async _getWindowsNetworkStats() {
try {
const { stdout } = await execAsync("netstat -e", { timeout: 5000 });
const stats = {};
const lines = stdout.split("\n");
let foundStart = false;
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.includes("Interface Statistics")) {
foundStart = true;
continue;
}
if (foundStart) {
const receiveMatch = trimmedLine.match(
/Bytes Received[^\d]*([\d,]+)/i
);
const transmitMatch = trimmedLine.match(/Bytes Sent[^\d]*([\d,]+)/i);
if (receiveMatch) {
stats["total"] = stats["total"] || {};
stats["total"].receiveBytes =
parseInt(receiveMatch[1].replace(/,/g, "")) || 0;
}
if (transmitMatch) {
stats["total"] = stats["total"] || {};
stats["total"].transmitBytes =
parseInt(transmitMatch[1].replace(/,/g, "")) || 0;
}
}
}
return stats;
} catch (error) {
console.error("Windows网络统计获取失败:", error.message);
return {};
}
}
/**
* macOS: 使用netstat获取网络统计(更可靠的方法)
*/
async _getMacNetworkStats() {
try {
// 使用 netstat -I <interface> -b 获取特定接口的字节统计
// 首先获取所有网络接口
const { stdout: ifconfigOutput } = await execAsync("ifconfig -l", {
timeout: 3000
});
const interfaces = ifconfigOutput.trim().split(" ");
const stats = {};
// 对每个接口获取统计信息
for (const iface of interfaces) {
if (
iface === "lo0" ||
iface.startsWith("bridge") ||
iface.startsWith("p2p")
) {
continue; // 跳过回环和虚拟接口
}
try {
const { stdout: netstatOutput } = await execAsync(
`netstat -I ${iface} -b 2>/dev/null | grep ${iface} | head -1`,
{ timeout: 3000, shell: true }
);
if (netstatOutput.trim()) {
const columns = netstatOutput.trim().split(/\s+/);
if (columns.length >= 10) {
// netstat -I <interface> -b 输出格式:
// Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll
stats[iface] = {
receiveBytes: parseInt(columns[6]) || 0, // Ibytes
transmitBytes: parseInt(columns[9]) || 0 // Obytes
};
}
}
} catch (interfaceError) {
// 忽略单个接口的错误
console.log(
`无法获取接口 ${iface} 的统计: ${interfaceError.message}`
);
}
}
// 如果上述方法失败,尝试使用 nstat 命令(如果可用)
if (Object.keys(stats).length === 0) {
try {
const { stdout: nstatOutput } = await execAsync("nstat -a", {
timeout: 3000
});
return this._parseMacNstat(nstatOutput);
} catch (nstatError) {
console.log("nstat 命令不可用:", nstatError.message);
}
}
return stats;
} catch (error) {
console.error("macOS网络统计获取失败:", error.message);
return {};
}
}
/**
* 解析macOS的nstat输出(备选方案)
*/
_parseMacNstat(output) {
const stats = {};
const lines = output.split("\n");
let totalReceive = 0;
let totalTransmit = 0;
for (const line of lines) {
const trimmedLine = line.trim();
// 查找网络接口的统计
if (trimmedLine.startsWith("IpExtInOctets")) {
const match = trimmedLine.match(/IpExtInOctets\s+(\d+)/);
if (match) totalReceive = parseInt(match[1]);
}
if (trimmedLine.startsWith("IpExtOutOctets")) {
const match = trimmedLine.match(/IpExtOutOctets\s+(\d+)/);
if (match) totalTransmit = parseInt(match[1]);
}
}
if (totalReceive > 0 || totalTransmit > 0) {
stats["total"] = {
receiveBytes: totalReceive,
transmitBytes: totalTransmit
};
}
return stats;
}
/**
* 获取网络IO速率 (KB/s)
*/
async getNetworkIO() {
try {
const currentStats = await this.getNetworkStats();
// 如果是第一次调用,只记录数据不计算速率
if (!this.initialized) {
this.prevStats = currentStats;
this.initialized = true;
this.lastTimestamp = Date.now();
return { in: 0, out: 0 };
}
const currentTimestamp = Date.now();
const timeDiff = (currentTimestamp - this.lastTimestamp) / 1000; // 转换为秒
// 确保时间差合理(避免除零或过大)
if (timeDiff < 0.1 || timeDiff > 60) {
this.prevStats = currentStats;
this.lastTimestamp = currentTimestamp;
return { in: 0, out: 0 };
}
let totalIn = 0;
let totalOut = 0;
// 计算所有接口的流量差值
for (const [iface, current] of Object.entries(currentStats)) {
// 跳过回环接口和虚拟接口
if (
iface === "lo" ||
iface === "lo0" ||
iface.startsWith("loop") ||
iface.startsWith("bridge")
) {
continue;
}
const previous = this.prevStats[iface];
if (
previous &&
current.receiveBytes !== undefined &&
previous.receiveBytes !== undefined
) {
const bytesIn = Math.max(
0,
current.receiveBytes - previous.receiveBytes
);
totalIn += bytesIn;
}
if (
previous &&
current.transmitBytes !== undefined &&
previous.transmitBytes !== undefined
) {
const bytesOut = Math.max(
0,
current.transmitBytes - previous.transmitBytes
);
totalOut += bytesOut;
}
}
// 更新上一次的统计数据
this.prevStats = currentStats;
this.lastTimestamp = currentTimestamp;
// 转换为KB/s
const result = {
in: Math.round(totalIn / timeDiff / 1024),
out: Math.round(totalOut / timeDiff / 1024),
platform: this.platform
};
return result;
} catch (error) {
console.error("获取网络IO失败:", error);
return { in: 0, out: 0, platform: this.platform };
}
}
/**
* 重置统计(用于重新开始计算)
*/
reset() {
this.prevStats = {};
this.initialized = false;
}
}
module.exports = NetworkMonitor;