UNPKG

node-os-utils

Version:

Advanced cross-platform operating system monitoring utilities with TypeScript support

1,308 lines 47.2 kB
"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