node-os-utils
Version:
Advanced cross-platform operating system monitoring utilities with TypeScript support
1,308 lines • 47.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinuxAdapter = void 0;
const os_1 = __importDefault(require("os"));
const fs_1 = require("fs");
const fsSync = __importStar(require("fs"));
const platform_adapter_1 = require("../core/platform-adapter");
const command_executor_1 = require("../utils/command-executor");
const errors_1 = require("../types/errors");
/**
* Linux 平台适配器
*
* 实现 Linux 系统的监控功能,主要通过 /proc、/sys 文件系统和系统命令
*/
class LinuxAdapter extends platform_adapter_1.BasePlatformAdapter {
constructor() {
super('linux');
this.processListCommand = 'ps -eo pid=,ppid=,comm=,%cpu=,%mem=,rss=,stat=,user=,args=';
this.cpuUsageSamplingInterval = 200;
// Linux 系统路径常量
this.paths = {
cpuinfo: '/proc/cpuinfo',
meminfo: '/proc/meminfo',
stat: '/proc/stat',
loadavg: '/proc/loadavg',
uptime: '/proc/uptime',
diskstats: '/proc/diskstats',
netDev: '/proc/net/dev',
version: '/proc/version',
mounts: '/proc/mounts',
thermal: '/sys/class/thermal',
cpufreq: '/sys/devices/system/cpu'
};
this.executor = new command_executor_1.CommandExecutor('linux');
this.containerMode = this.detectContainerEnvironment();
if (this.containerMode) {
this.supportedFeatures.system.services = false;
}
}
/**
* 执行系统命令
*/
async executeCommand(command, options) {
return this.executor.execute(command, options);
}
/**
* 读取文件内容
*/
async readFile(path) {
try {
return await fs_1.promises.readFile(path, 'utf8');
}
catch (error) {
if (error.code === 'ENOENT') {
throw new errors_1.MonitorError(`File not found: ${path}`, errors_1.ErrorCode.FILE_NOT_FOUND, this.platformName, { path });
}
if (error.code === 'EACCES') {
throw new errors_1.MonitorError(`Permission denied: ${path}`, errors_1.ErrorCode.PERMISSION_DENIED, this.platformName, { path });
}
throw new errors_1.MonitorError(`Failed to read file: ${path}`, errors_1.ErrorCode.COMMAND_FAILED, this.platformName, { path, error: error.message });
}
}
/**
* 检查文件是否存在
*/
async fileExists(path) {
try {
await fs_1.promises.access(path);
return true;
}
catch {
return false;
}
}
/**
* 读取 /proc/cpuinfo 并解析成结构化 CPU 信息
*/
async getCPUInfo() {
try {
const cpuinfoContent = await this.readFile(this.paths.cpuinfo);
return this.parseCPUInfo(cpuinfoContent);
}
catch (error) {
throw this.createCommandError('getCPUInfo', error);
}
}
/**
* 读取 /proc/stat,计算总体 CPU 使用情况
*/
async getCPUUsage() {
try {
const firstSnapshot = await this.captureCpuStat();
await this.delay(this.cpuUsageSamplingInterval);
const secondSnapshot = await this.captureCpuStat();
return this.calculateCpuUsage(firstSnapshot, secondSnapshot);
}
catch (error) {
throw this.createCommandError('getCPUUsage', error);
}
}
/**
* 遍历 /sys/class/thermal,聚合 CPU 温度传感器
*/
async getCPUTemperature() {
try {
const thermalExists = await this.fileExists(this.paths.thermal);
if (!thermalExists) {
throw this.createUnsupportedError('cpu.temperature');
}
// 尝试读取温度传感器
const thermalZones = await this.findThermalZones();
const temperatures = [];
for (const zone of thermalZones) {
try {
const tempPath = `${this.paths.thermal}/${zone}/temp`;
const tempContent = await this.readFile(tempPath);
let typeContent = 'unknown';
try {
typeContent = await this.readFile(`${this.paths.thermal}/${zone}/type`);
}
catch {
// 如果无法读取类型,使用默认值
}
const temp = this.safeParseInt(tempContent.trim()) / 1000; // 转换为摄氏度
temperatures.push({
zone,
type: typeContent.trim(),
temperature: temp
});
}
catch {
// 忽略无法读取的传感器
}
}
return temperatures;
}
catch (error) {
throw this.createCommandError('getCPUTemperature', error);
}
}
/**
* 读取 /proc/meminfo 解析内存占用
*/
async getMemoryInfo() {
try {
const meminfoContent = await this.readFile(this.paths.meminfo);
return this.parseMemoryInfo(meminfoContent);
}
catch (error) {
throw this.createCommandError('getMemoryInfo', error);
}
}
/**
* 获取内存使用情况
*/
async getMemoryUsage() {
// Linux 上内存信息和使用情况来自同一个文件
return this.getMemoryInfo();
}
/**
* 获取磁盘信息
*/
async getDiskInfo() {
try {
const result = await this.executeCommand('df -h');
this.validateCommandResult(result, 'df -h');
return this.parseDiskInfo(result.stdout);
}
catch (error) {
throw this.createCommandError('getDiskInfo', error);
}
}
/**
* 获取磁盘 I/O 统计
*/
async getDiskIO() {
try {
const diskstatsContent = await this.readFile(this.paths.diskstats);
return this.parseDiskStats(diskstatsContent);
}
catch (error) {
throw this.createCommandError('getDiskIO', error);
}
}
/**
* 获取网络接口列表
*/
async getNetworkInterfaces() {
try {
const result = await this.executeCommand('ip addr show');
this.validateCommandResult(result, 'ip addr show');
return this.parseNetworkInterfaces(result.stdout);
}
catch (primaryError) {
// 回退到 ifconfig
try {
const result = await this.executeCommand('ifconfig');
this.validateCommandResult(result, 'ifconfig');
return this.parseIfconfigOutput(result.stdout);
}
catch (fallbackError) {
throw this.createCommandError('getNetworkInterfaces', {
primary: this.summarizeErrorDetails(primaryError),
fallback: this.summarizeErrorDetails(fallbackError)
});
}
}
}
/**
* 获取网络统计信息
*/
async getNetworkStats() {
try {
const netdevContent = await this.readFile(this.paths.netDev);
return this.parseNetworkStats(netdevContent);
}
catch (error) {
throw this.createCommandError('getNetworkStats', error);
}
}
/**
* 获取进程列表
*/
async getProcesses() {
try {
const result = await this.executeCommand(this.processListCommand);
this.validateCommandResult(result, 'ps command');
return this.parseProcessList(result.stdout);
}
catch (error) {
throw this.createCommandError('getProcesses', error);
}
}
/**
* 获取特定进程信息
*/
async getProcessInfo(pid) {
try {
const procPath = `/proc/${pid}`;
const exists = await this.fileExists(procPath);
if (!exists) {
throw new errors_1.MonitorError(`Process ${pid} not found`, errors_1.ErrorCode.NOT_AVAILABLE, this.platformName, { pid });
}
const [stat, status, cmdline] = await Promise.allSettled([
this.readFile(`${procPath}/stat`),
this.readFile(`${procPath}/status`),
this.readFile(`${procPath}/cmdline`)
]).then(results => [
results[0].status === 'fulfilled' ? results[0].value : '',
results[1].status === 'fulfilled' ? results[1].value : '',
results[2].status === 'fulfilled' ? results[2].value : ''
]);
const statContent = stat || await this.readFile(`${procPath}/stat`).catch(() => '');
return this.parseProcessInfo(pid, statContent, status, cmdline);
}
catch (error) {
throw this.createCommandError('getProcessInfo', error);
}
}
/**
* 获取系统信息
*/
async getSystemInfo() {
try {
const [uname, version, uptime, loadavg, machine] = await Promise.allSettled([
this.executeCommand('uname -a'),
this.readFile(this.paths.version),
this.readFile(this.paths.uptime),
this.readFile(this.paths.loadavg),
this.executeCommand('uname -m')
]).then(results => [
results[0].status === 'fulfilled' ? results[0].value : null,
results[1].status === 'fulfilled' ? results[1].value : '',
results[2].status === 'fulfilled' ? results[2].value : '',
results[3].status === 'fulfilled' ? results[3].value : '',
results[4].status === 'fulfilled' ? results[4].value : null
]);
const unameOutput = typeof uname === 'string' ? uname : uname?.stdout || '';
const versionOutput = typeof version === 'string' ? version : version?.stdout || '';
const uptimeOutput = typeof uptime === 'string' ? uptime : uptime?.stdout || '';
const loadavgOutput = typeof loadavg === 'string' ? loadavg : loadavg?.stdout || '';
const machineOutput = typeof machine === 'string' ? machine : machine?.stdout || '';
return this.parseSystemInfo(unameOutput, versionOutput, uptimeOutput, loadavgOutput, machineOutput);
}
catch (error) {
throw this.createCommandError('getSystemInfo', error);
}
}
/**
* 获取系统负载
*/
async getSystemLoad() {
try {
const loadavgContent = await this.readFile(this.paths.loadavg);
return this.parseLoadAverage(loadavgContent);
}
catch (error) {
throw this.createCommandError('getSystemLoad', error);
}
}
/**
* 初始化支持的功能
*/
initializeSupportedFeatures() {
return {
cpu: {
info: true,
usage: true,
temperature: true,
frequency: true,
cache: true,
perCore: true,
cores: true
},
memory: {
info: true,
usage: true,
swap: true,
pressure: false, // Linux 内存压力需要额外工具
detailed: true,
virtual: true
},
disk: {
info: true,
io: true,
health: false, // 需要 smartctl
smart: false,
filesystem: true,
usage: true,
stats: true,
mounts: true,
filesystems: true
},
network: {
interfaces: true,
stats: true,
connections: true,
bandwidth: true,
gateway: true
},
process: {
list: true,
details: true,
tree: true,
monitor: true,
info: true,
kill: true,
openFiles: true,
environment: true
},
system: {
info: true,
load: true,
uptime: true,
users: true,
services: false // 需要 systemctl
}
};
}
// 私有解析方法
parseCPUInfo(content) {
const lines = content.split('\n');
const cpus = [];
let currentCPU = {};
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
if (Object.keys(currentCPU).length > 0) {
cpus.push(currentCPU);
currentCPU = {};
}
continue;
}
const separatorIndex = trimmed.indexOf(':');
if (separatorIndex === -1)
continue;
const key = trimmed.substring(0, separatorIndex).trim();
const value = trimmed.substring(separatorIndex + 1).trim();
currentCPU[key] = value;
}
if (Object.keys(currentCPU).length > 0) {
cpus.push(currentCPU);
}
return {
cpus,
count: cpus.length,
model: cpus[0]?.['model name'] || 'Unknown',
vendor: cpus[0]?.['vendor_id'] || 'Unknown',
architecture: cpus[0]?.['cpu family'] || 'Unknown'
};
}
async captureCpuStat() {
const content = await this.readFile(this.paths.stat);
return this.parseCpuStat(content);
}
parseCpuStat(content) {
const lines = content
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0);
const summaryLine = lines.find(line => line.startsWith('cpu '));
if (!summaryLine) {
throw this.createParseError(content, 'CPU usage line not found');
}
const summary = this.parseCpuTimesFromLine(summaryLine);
const cores = [];
for (const line of lines) {
if (line.startsWith('cpu') && !line.startsWith('cpu ')) {
cores.push(this.parseCpuTimesFromLine(line));
}
}
return {
summary,
cores
};
}
parseCpuTimesFromLine(line) {
const parts = line.trim().split(/\s+/);
parts.shift(); // 移除 cpu 标识
const values = parts.map(value => this.safeParseInt(value));
return {
user: values[0] ?? 0,
nice: values[1] ?? 0,
system: values[2] ?? 0,
idle: values[3] ?? 0,
iowait: values[4] ?? 0,
irq: values[5] ?? 0,
softirq: values[6] ?? 0,
steal: values[7] ?? 0,
guest: values[8] ?? 0,
guestNice: values[9] ?? 0
};
}
calculateCpuUsage(previous, current) {
const summaryDelta = this.diffCpuTimes(previous.summary, current.summary);
const percentages = this.computeUsagePercentages(summaryDelta);
const cores = this.calculateCoreUsage(previous.cores, current.cores);
return {
overall: percentages.overall,
user: percentages.user,
system: percentages.system,
idle: percentages.idle,
iowait: percentages.iowait,
irq: percentages.irq,
softirq: percentages.softirq,
cores
};
}
calculateCoreUsage(previousCores, currentCores) {
const coreCount = Math.min(previousCores.length, currentCores.length);
const usages = [];
for (let i = 0; i < coreCount; i++) {
const delta = this.diffCpuTimes(previousCores[i], currentCores[i]);
const total = this.getCpuTotal(delta);
if (total <= 0) {
usages.push(0);
continue;
}
const busy = total - delta.idle;
usages.push((busy / total) * 100);
}
return usages;
}
diffCpuTimes(previous, current) {
return {
user: this.safeDiff(current.user, previous.user),
nice: this.safeDiff(current.nice, previous.nice),
system: this.safeDiff(current.system, previous.system),
idle: this.safeDiff(current.idle, previous.idle),
iowait: this.safeDiff(current.iowait, previous.iowait),
irq: this.safeDiff(current.irq, previous.irq),
softirq: this.safeDiff(current.softirq, previous.softirq),
steal: this.safeDiff(current.steal, previous.steal),
guest: this.safeDiff(current.guest, previous.guest),
guestNice: this.safeDiff(current.guestNice, previous.guestNice)
};
}
computeUsagePercentages(delta) {
const total = this.getCpuTotal(delta);
if (total <= 0) {
return {
overall: 0,
user: 0,
system: 0,
idle: 0,
iowait: 0,
irq: 0,
softirq: 0
};
}
return {
overall: ((total - delta.idle) / total) * 100,
user: ((delta.user + delta.nice) / total) * 100,
system: (delta.system / total) * 100,
idle: (delta.idle / total) * 100,
iowait: (delta.iowait / total) * 100,
irq: (delta.irq / total) * 100,
softirq: (delta.softirq / total) * 100
};
}
getCpuTotal(times) {
return times.user + times.nice + times.system + times.idle + times.iowait + times.irq + times.softirq + times.steal + times.guest + times.guestNice;
}
safeDiff(current, previous) {
const diff = current - previous;
return diff > 0 ? diff : 0;
}
parseMemoryInfo(content) {
const memInfo = this.parseKeyValueOutput(content);
const total = this.convertToBytes(memInfo['MemTotal'] || '0', 'kB');
const available = this.convertToBytes(memInfo['MemAvailable'] || memInfo['MemFree'] || '0', 'kB');
const free = this.convertToBytes(memInfo['MemFree'] || '0', 'kB');
const cached = this.convertToBytes(memInfo['Cached'] || '0', 'kB');
const buffers = this.convertToBytes(memInfo['Buffers'] || '0', 'kB');
const used = total - available;
return {
total,
available,
used,
free,
cached,
buffers,
usagePercentage: total > 0 ? (used / total) * 100 : 0,
swap: {
total: this.convertToBytes(memInfo['SwapTotal'] || '0', 'kB'),
free: this.convertToBytes(memInfo['SwapFree'] || '0', 'kB'),
used: this.convertToBytes(memInfo['SwapTotal'] || '0', 'kB') - this.convertToBytes(memInfo['SwapFree'] || '0', 'kB')
}
};
}
parseDiskInfo(output) {
const lines = output.split('\n').filter(line => line.trim());
if (lines.length < 2)
return [];
const disks = [];
for (let i = 1; i < lines.length; i++) {
const fields = lines[i].split(/\s+/);
if (fields.length >= 6) {
const [filesystem, size, used, available, usagePercent, mountpoint] = fields;
disks.push({
filesystem,
mountpoint,
size: this.convertToBytes(size),
used: this.convertToBytes(used),
available: this.convertToBytes(available),
usagePercentage: this.safeParseNumber(usagePercent.replace('%', ''))
});
}
}
return disks;
}
/**
* 解析 ip/ifconfig 输出的网卡信息,稳定提取接口名称
*/
parseNetworkInterfaces(output) {
const interfaces = [];
const lines = output.split('\n');
let current = null;
const pushCurrent = () => {
if (current) {
interfaces.push({
name: current.name,
addresses: current.addresses,
state: current.state,
mtu: current.mtu,
internal: current.internal || current.loopbackDetected
});
}
current = null;
};
for (const rawLine of lines) {
const headerMatch = rawLine.match(/^\s*(\d+):\s*([^:@]+)(?:@[^:]+)?:\s*(.*)$/);
if (headerMatch) {
pushCurrent();
const name = headerMatch[2];
const rest = headerMatch[3] || '';
const stateMatch = rest.match(/\bstate\s+([A-Z]+)/i);
const mtuMatch = rest.match(/\bmtu\s+(\d+)/i);
const isLoopback = /\bLOOPBACK\b/i.test(rest);
current = {
name,
addresses: [],
state: stateMatch ? stateMatch[1].toLowerCase() : 'down',
mtu: mtuMatch ? this.safeParseInt(mtuMatch[1]) : 0,
internal: name === 'lo' || name === 'lo0',
loopbackDetected: isLoopback
};
continue;
}
if (!current) {
continue;
}
const line = rawLine.trim();
if (!line) {
continue;
}
if (/loopback/i.test(line)) {
current.loopbackDetected = true;
}
const inetMatch = line.match(/^inet\s+([^\s/]+)(?:\/\d+)?/);
if (inetMatch) {
current.addresses.push({
address: inetMatch[1],
family: 'IPv4'
});
continue;
}
const inet6Match = line.match(/^inet6\s+([^\s/]+)(?:\/\d+)?/);
if (inet6Match) {
current.addresses.push({
address: inet6Match[1],
family: 'IPv6'
});
}
}
pushCurrent();
return interfaces;
}
parseIfconfigOutput(output) {
// 解析 ifconfig 输出的简化版本
const interfaces = [];
const blocks = output.split(/\n\n/);
for (const block of blocks) {
const lines = block.split('\n');
const interfaceLine = lines[0];
if (!interfaceLine)
continue;
const nameMatch = interfaceLine.match(/^(\w+):/);
if (!nameMatch)
continue;
const name = nameMatch[1];
const addresses = [];
for (const line of lines) {
const inetMatch = line.match(/inet\s+([^\s]+)/);
if (inetMatch) {
addresses.push({
address: inetMatch[1],
family: 'IPv4'
});
}
}
interfaces.push({
name,
addresses,
state: interfaceLine.includes('UP') ? 'up' : 'down'
});
}
return interfaces;
}
/**
* 解析 /proc/net/dev 网络统计数据
*
* 将原始列按接口拆分,并提取收发字节/错误/丢包等指标
*/
parseNetworkStats(content) {
const lines = content.split('\n');
const stats = [];
for (let i = 2; i < lines.length; i++) { // 跳过头部
const line = lines[i].trim();
if (!line)
continue;
const fields = line.split(/\s+/);
if (fields.length >= 17) {
const [iface, ...values] = fields;
const name = iface.replace(':', '');
stats.push({
interface: name,
rxBytes: this.safeParseInt(values[0]),
rxPackets: this.safeParseInt(values[1]),
rxErrors: this.safeParseInt(values[2]),
rxDropped: this.safeParseInt(values[3]),
txBytes: this.safeParseInt(values[8]),
txPackets: this.safeParseInt(values[9]),
txErrors: this.safeParseInt(values[10]),
txDropped: this.safeParseInt(values[11])
});
}
}
return stats;
}
parseProcessList(output) {
const lines = output.split('\n').filter(line => line.trim());
const processes = [];
for (const line of lines) {
const match = line.match(/^(\d+)\s+(\d+)\s+(\S+)\s+([\d.]+)\s+([\d.]+)\s+(\d+)\s+(\S+)\s+(\S+)\s*(.*)$/);
if (!match) {
continue;
}
const [, pid, ppid, comm, pcpu, pmem, rss, state, user, args] = match;
const command = args.trim() || comm;
processes.push({
pid: this.safeParseInt(pid),
ppid: this.safeParseInt(ppid),
name: comm,
command,
cpuUsage: this.safeParseNumber(pcpu),
memoryUsage: this.safeParseInt(rss) * 1024, // rss 以 KB 计,转换为字节
memoryPercentage: this.safeParseNumber(pmem),
state,
user
});
}
return processes;
}
/**
* 综合 /proc/[pid] 下多份文件,拼装进程详细信息
*/
parseProcessInfo(pid, stat, status, cmdline) {
const statusInfo = this.parseKeyValueOutput(status, '\t');
const statFields = stat.split(' ');
const startTimeTicks = this.safeParseInt(statFields[21]);
const sysconf = os_1.default.constants?.sysconf;
const hertz = typeof sysconf?.SC_CLK_TCK === 'number' && sysconf.SC_CLK_TCK > 0
? sysconf.SC_CLK_TCK
: 100;
let uptimeMs = 0;
try {
uptimeMs = os_1.default.uptime() * 1000;
}
catch {
uptimeMs = 0;
}
const bootTimeMs = uptimeMs > 0 ? Date.now() - uptimeMs : Date.now();
// 通过时钟节拍 + 开机时间估算进程启动时间,兼容无法访问 uptime 的环境
const computedStartTime = startTimeTicks > 0
? bootTimeMs + (startTimeTicks / hertz) * 1000
: bootTimeMs;
const startTime = Math.min(computedStartTime, Date.now());
const rssPages = this.safeParseInt(statFields[23]);
const pageSize = typeof sysconf?.SC_PAGESIZE === 'number' && sysconf.SC_PAGESIZE > 0
? sysconf.SC_PAGESIZE
: 4096;
const rssBytesFromStat = rssPages > 0 ? rssPages * pageSize : 0;
const vmRssRaw = statusInfo['VmRSS'] ?? statusInfo['VmRSS:'] ?? '0';
const vmSizeRaw = statusInfo['VmSize'] ?? statusInfo['VmSize:'] ?? '0';
const threadsRaw = statusInfo['Threads'] ?? statusInfo['Threads:'];
// 优先使用 /proc/[pid]/status 中的 KiB 数值,缺失时回退到 stat 的页数
const rssFromStatus = this.convertToBytes(vmRssRaw, 'kB');
const memoryUsage = rssFromStatus > 0 ? rssFromStatus : rssBytesFromStat;
return {
pid,
name: statusInfo['Name'] || 'Unknown',
command: cmdline.replace(/\0/g, ' ').trim(),
state: statFields[2] || 'Unknown',
ppid: this.safeParseInt(statFields[3]),
threads: this.safeParseInt(threadsRaw),
vmSize: this.convertToBytes(vmSizeRaw, 'kB'),
vmRSS: memoryUsage,
memoryUsage,
memory: memoryUsage,
rss: memoryUsage,
startTime
};
}
parseSystemInfo(uname, version, uptime, loadavg, machine) {
const unameFields = uname.trim().split(' ');
const uptimeFields = uptime.trim().split(' ');
const loadFields = loadavg.trim().split(' ');
const uptimeSeconds = this.safeParseNumber(uptimeFields[0]);
const uptimeMs = uptimeSeconds * 1000;
const bootTime = Date.now() - uptimeMs;
const architecture = this.resolveArchitecture(unameFields, machine);
return {
hostname: unameFields[1] || 'Unknown',
platform: 'linux', // 统一返回标准平台名称,与 os.platform() 保持一致
release: unameFields[2] || 'Unknown',
version: version.trim(),
arch: architecture,
uptime: uptimeMs,
uptimeSeconds,
bootTime,
loadAverage: {
load1: this.safeParseNumber(loadFields[0]),
load5: this.safeParseNumber(loadFields[1]),
load15: this.safeParseNumber(loadFields[2])
}
};
}
parseLoadAverage(content) {
const fields = content.trim().split(' ');
return {
load1: this.safeParseNumber(fields[0]),
load5: this.safeParseNumber(fields[1]),
load15: this.safeParseNumber(fields[2]),
processes: {
active: this.safeParseInt(fields[3]?.split('/')[0]),
total: this.safeParseInt(fields[3]?.split('/')[1])
}
};
}
async findThermalZones() {
try {
const items = await fs_1.promises.readdir(this.paths.thermal);
return items.filter(item => item.startsWith('thermal_zone'));
}
catch {
return [];
}
}
// 实现抽象方法
/**
* 获取磁盘使用情况
*/
async getDiskUsage() {
try {
const result = await this.executeCommand('df -B1');
return this.parseDiskUsage(result.stdout);
}
catch (error) {
throw this.createCommandError('getDiskUsage', error);
}
}
/**
* 获取磁盘统计
*/
async getDiskStats() {
try {
const diskstatsContent = await this.readFile(this.paths.diskstats);
return this.parseDiskStats(diskstatsContent);
}
catch (error) {
throw this.createCommandError('getDiskStats', error);
}
}
/**
* 获取挂载点
*/
async getMounts() {
try {
const mountsContent = await this.readFile(this.paths.mounts);
return this.parseMounts(mountsContent);
}
catch (error) {
throw this.createCommandError('getMounts', error);
}
}
/**
* 获取文件系统
*/
async getFileSystems() {
try {
const result = await this.executeCommand('cat /proc/filesystems');
return this.parseFileSystems(result.stdout);
}
catch (error) {
throw this.createCommandError('getFileSystems', error);
}
}
/**
* 获取网络连接
*/
async getNetworkConnections() {
try {
const result = await this.executeCommand('ss -tuln');
return this.parseNetworkConnections(result.stdout);
}
catch (primaryError) {
try {
const result = await this.executeCommand('netstat -tuln');
this.validateCommandResult(result, 'netstat -tuln');
return this.parseNetstatConnections(result.stdout);
}
catch (fallbackError) {
throw this.createCommandError('getNetworkConnections', {
primary: this.summarizeErrorDetails(primaryError),
fallback: this.summarizeErrorDetails(fallbackError)
});
}
}
}
/**
* 获取默认网关
*/
async getDefaultGateway() {
try {
const result = await this.executeCommand('ip route show default');
return this.parseDefaultGateway(result.stdout);
}
catch (error) {
throw this.createCommandError('getDefaultGateway', error);
}
}
/**
* 获取进程列表
*/
async getProcessList() {
try {
const result = await this.executeCommand(this.processListCommand);
this.validateCommandResult(result, 'ps command');
return this.parseProcessList(result.stdout);
}
catch (error) {
throw this.createCommandError('getProcessList', error);
}
}
/**
* 杀死进程
*/
async killProcess(pid, signal = 'TERM') {
try {
const result = await this.executeCommand(`kill -${signal} ${pid}`);
return result.exitCode === 0;
}
catch (error) {
return false;
}
}
/**
* 获取进程打开文件
*/
async getProcessOpenFiles(pid) {
try {
const result = await this.executeCommand(`lsof -p ${pid} -Fn`);
return this.parseOpenFiles(result.stdout);
}
catch (error) {
return [];
}
}
/**
* 获取进程环境变量
*/
async getProcessEnvironment(pid) {
try {
const environContent = await this.readFile(`/proc/${pid}/environ`);
return this.parseEnvironment(environContent);
}
catch (error) {
return {};
}
}
/**
* 获取系统运行时间
*/
async getSystemUptime() {
try {
const uptimeContent = await this.readFile(this.paths.uptime);
const uptimeFields = uptimeContent.trim().split(' ');
return {
uptime: this.safeParseNumber(uptimeFields[0]) * 1000, // 转换为毫秒
idleTime: this.safeParseNumber(uptimeFields[1]) * 1000 // 转换为毫秒
};
}
catch (error) {
throw this.createCommandError('getSystemUptime', error);
}
}
/**
* 获取系统用户
*/
async getSystemUsers() {
try {
const result = await this.executeCommand('who');
return this.parseSystemUsers(result.stdout);
}
catch (error) {
throw this.createCommandError('getSystemUsers', error);
}
}
/**
* 获取系统服务
*/
async getSystemServices() {
if (this.containerMode) {
throw this.createUnsupportedError('system.services (container)');
}
try {
const result = await this.executeCommand('systemctl list-units --type=service --no-pager');
return this.parseSystemServices(result.stdout);
}
catch (error) {
throw this.createCommandError('getSystemServices', error);
}
}
// 私有解析方法
parseDiskUsage(output) {
const lines = output.split('\n').slice(1); // 跳过标题行
const disks = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 6) {
disks.push({
device: fields[0],
total: this.safeParseInt(fields[1]),
used: this.safeParseInt(fields[2]),
available: this.safeParseInt(fields[3]),
usagePercentage: this.safeParseNumber(fields[4].replace('%', '')),
mountPoint: fields[5]
});
}
}
return disks;
}
parseDiskStats(content) {
const lines = content.split('\n').filter(line => line.trim());
const stats = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 14) {
stats.push({
device: fields[2],
reads: this.safeParseInt(fields[3]),
readsMerged: this.safeParseInt(fields[4]),
readSectors: this.safeParseInt(fields[5]),
readTime: this.safeParseInt(fields[6]),
writes: this.safeParseInt(fields[7]),
writesMerged: this.safeParseInt(fields[8]),
writeSectors: this.safeParseInt(fields[9]),
writeTime: this.safeParseInt(fields[10]),
ioInProgress: this.safeParseInt(fields[11]),
ioTime: this.safeParseInt(fields[12]),
weightedIOTime: this.safeParseInt(fields[13])
});
}
}
return stats;
}
parseMounts(content) {
const lines = content.split('\n').filter(line => line.trim());
const mounts = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 6) {
mounts.push({
device: fields[0],
mountPoint: fields[1],
filesystem: fields[2],
options: fields[3].split(','),
dump: this.safeParseInt(fields[4]),
pass: this.safeParseInt(fields[5])
});
}
}
return mounts;
}
parseFileSystems(output) {
const lines = output.split('\n').filter(line => line.trim());
const filesystems = [];
for (const line of lines) {
const trimmed = line.trim();
if (trimmed) {
const isNodev = trimmed.startsWith('nodev\t');
const name = isNodev ? trimmed.substring(6) : trimmed;
filesystems.push({
name,
supported: !isNodev
});
}
}
return filesystems;
}
parseNetworkConnections(output) {
const lines = output.split('\n').slice(1); // 跳过标题行
const connections = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 5) {
connections.push({
protocol: fields[0],
state: fields[1],
localAddress: fields[4],
foreignAddress: fields[5] || '*:*'
});
}
}
return connections;
}
parseNetstatConnections(output) {
const lines = output.split('\n').filter(line => {
const trimmed = line.trim();
return trimmed && !trimmed.startsWith('Proto') && !trimmed.startsWith('Active');
});
const connections = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length < 5) {
continue;
}
const protocol = fields[0].toLowerCase();
const hasState = fields.length >= 6;
const localFieldIndex = 3;
const foreignFieldIndex = 4;
connections.push({
protocol,
state: hasState ? fields[5].toLowerCase() : 'unknown',
localAddress: fields[localFieldIndex],
foreignAddress: fields[foreignFieldIndex] || '*:*'
});
}
return connections;
}
parseDefaultGateway(output) {
const lines = output.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith('default')) {
continue;
}
const fields = trimmed.split(/\s+/);
if (fields.length === 0) {
continue;
}
let gateway = null;
let interfaceName = null;
for (let i = 0; i < fields.length; i++) {
const token = fields[i];
if (token === 'via' && fields[i + 1]) {
gateway = fields[i + 1];
}
else if (token === 'dev' && fields[i + 1]) {
interfaceName = fields[i + 1];
}
}
if (!gateway && fields.length > 1) {
const candidate = fields[1];
if (candidate && candidate !== 'dev' && candidate !== 'proto' && candidate !== 'metric' && candidate !== 'scope') {
gateway = candidate;
}
}
if (gateway !== null || interfaceName !== null) {
return {
gateway: gateway ?? null,
interface: interfaceName ?? 'unknown'
};
}
}
return null;
}
parseOpenFiles(output) {
const lines = output.split('\n');
const files = [];
for (const line of lines) {
if (line.startsWith('n')) {
files.push(line.substring(1));
}
}
return files;
}
parseEnvironment(content) {
const env = {};
const variables = content.split('\0').filter(v => v);
for (const variable of variables) {
const equalIndex = variable.indexOf('=');
if (equalIndex > 0) {
const key = variable.substring(0, equalIndex);
const value = variable.substring(equalIndex + 1);
env[key] = value;
}
}
return env;
}
parseSystemUsers(output) {
const lines = output.split('\n').filter(line => line.trim());
const users = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 3) {
users.push({
user: fields[0],
terminal: fields[1],
loginTime: fields.slice(2).join(' ')
});
}
}
return users;
}
parseSystemServices(output) {
const lines = output.split('\n').slice(1); // 跳过标题行
const services = [];
for (const line of lines) {
const fields = line.trim().split(/\s+/);
if (fields.length >= 4) {
services.push({
unit: fields[0],
load: fields[1],
active: fields[2],
sub: fields[3],
description: fields.slice(4).join(' ')
});
}
}
return services;
}
async delay(ms) {
if (ms <= 0) {
return;
}
await new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 推断主机架构
*
* 同时考虑 `uname -m`、`uname -a` 末尾字段与 `os.arch()`,过滤掉 `SMP` 等噪声标记,
* 尽可能返回准确的 CPU 架构标识。
*/
resolveArchitecture(unameFields, machineOutput) {
const archPattern = /^(x86_64|amd64|arm64|aarch64|armv\d+l?|i[3-6]86|ppc64le|ppc64|s390x|mips64el|mips64|mipsel|loongarch\d*|riscv\d+|sparc64)$/i;
const normalize = (value) => value?.trim() || '';
const candidates = [];
const normalizedMachine = normalize(machineOutput);
if (normalizedMachine) {
candidates.push(normalizedMachine);
}
for (let i = unameFields.length - 1; i >= 0; i--) {
const token = normalize(unameFields[i]);
if (token) {
candidates.push(token);
}
}
try {
const runtimeArch = normalize(os_1.default.arch());
if (runtimeArch) {
candidates.push(runtimeArch);
}
}
catch {
// ignore runtime arch fetch errors
}
for (const candidate of candidates) {
if (candidate && archPattern.test(candidate)) {
return candidate;
}
}
return 'Unknown';
}
/**
* 归纳命令执行阶段的错误信息
*
* 便于在调用栈中保留主、次命令的失败原因,帮助上层监控器输出更具可读性的诊断数据。
*/
summarizeErrorDetails(error) {
if (!error) {
return { message: 'Unknown error' };
}
if (error instanceof errors_1.MonitorError) {
return {
code: error.code,
message: error.message,
details: error.details || null
};
}
if (error instanceof Error) {
return {
name: error.name,
message: error.message
};
}
return { message: String(error) };
}
/**
* 检测当前进程是否运行在容器环境中
*
* 通过常见标记文件和 cgroup 信息判断,若命中则代表部分系统能力(如 systemd)不可用。
*/
detectContainerEnvironment() {
try {
const indicatorPaths = ['/.dockerenv', '/run/.containerenv'];
for (const path of indicatorPaths) {
if (fsSync.existsSync(path)) {
return true;
}
}
const cgroupPath = '/proc/1/cgroup';
if (fsSync.existsSync(cgroupPath)) {
const content = fsSync.readFileSync(cgroupPath, 'utf8');
if (/(docker|containerd|kubepods|lxc|podman)/i.test(content)) {
return true;
}
}
const envIndicators = ['CONTAINER', 'KUBERNETES_SERVICE_HOST'];
if (envIndicators.some(key => process.env[key])) {
return true;
}
}
catch {
// 检测失败时默认视为非容器环境
}
return false;
}
}
exports.LinuxAdapter = LinuxAdapter;
//# sourceMappingURL=linux-adapter.js.map