import { Injectable, Logger } from '@nestjs/common'; import * as os from 'os'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); interface ServerMetrics { hostname: string; cpu: { model: string; cores: number; usagePercent: number }; memory: { total: string; used: string; free: string; percent: number }; disk: { total: string; used: string; free: string; percent: number }; uptime: string; processes: { pid: number; cpu: string; mem: string; command: string }[]; network: { ip: string }; } const SSH_KEY_PATH = process.env.SSH_KEY_PATH || '/home/ubuntu/.ssh/wangdl.pem'; const REMOTE_HOST = '10.2.0.7'; @Injectable() export class AdminServersService { private readonly logger = new Logger(AdminServersService.name); async getLocalMetrics(): Promise { const cpus = os.cpus(); const totalMem = os.totalmem(); const freeMem = os.freemem(); const usedMem = totalMem - freeMem; const loadAvg = os.loadavg(); const cpuUsage = Math.min(100, Math.round((loadAvg[0] / cpus.length) * 100)); let disk = { total: '-', used: '-', free: '-', percent: 0 }; try { const { stdout } = await execAsync("df -h / | tail -1 | awk '{print $2,$3,$4,$5}'"); const parts = stdout.trim().split(/\s+/); disk = { total: parts[0] || '-', used: parts[1] || '-', free: parts[2] || '-', percent: parseInt(parts[3]) || 0 }; } catch {} let processes: ServerMetrics['processes'] = []; try { const { stdout } = await execAsync("ps aux --sort=-%mem --no-headers | head -8 | awk '{print $2,$3,$4,$11}'"); processes = stdout.trim().split('\n').filter(Boolean).map(line => { const [pid, cpu, mem, ...cmd] = line.trim().split(/\s+/); return { pid: parseInt(pid), cpu: cpu + '%', mem: mem + '%', command: (cmd || []).join(' ').slice(0, 50) }; }); } catch {} const nets = os.networkInterfaces(); const ip = Object.values(nets).flat().find(n => n?.family === 'IPv4' && !n.internal)?.address || 'unknown'; const d = Math.floor(os.uptime() / 86400); const h = Math.floor((os.uptime() % 86400) / 3600); const m = Math.floor((os.uptime() % 3600) / 60); return { hostname: os.hostname(), cpu: { model: cpus[0]?.model || '', cores: cpus.length, usagePercent: cpuUsage }, memory: { total: (totalMem / 1e9).toFixed(1) + 'G', used: (usedMem / 1e9).toFixed(1) + 'G', free: (freeMem / 1e9).toFixed(1) + 'G', percent: Math.round((usedMem / totalMem) * 100) }, disk, uptime: `${d}d ${h}h ${m}m`, processes, network: { ip }, }; } async getRemoteMetrics(): Promise { try { const base = `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -i ${SSH_KEY_PATH} ubuntu@${REMOTE_HOST}`; const cmds = [ `${base} hostname`, `${base} "hostname -I | awk '{print \\$1}'"`, `${base} "cat /proc/loadavg | awk '{print \\$1}'"`, `${base} "cat /proc/cpuinfo | grep processor | wc -l"`, `${base} "free -m | grep Mem | awk '{print \\$2,\\$3,\\$4}'"`, `${base} "df -h / | tail -1 | awk '{print \\$2,\\$3,\\$4,\\$5}'"`, `${base} "uptime -p | sed 's/up //'"`, `${base} "ps aux --sort=-%mem --no-headers | head -6 | awk '{print \\$2,\\$3,\\$4,\\$11}'"`, ]; const results = await Promise.all(cmds.map(c => execAsync(c, { timeout: 5000 }).then(r => r.stdout.trim()).catch(() => ''))); const hostname = results[0] || 'remote'; const ip = results[1] || '10.2.0.7'; const load1 = parseFloat(results[2]) || 0; const cores = parseInt(results[3]) || 4; const cpuUsage = Math.min(100, Math.round((load1 / cores) * 100)); const memParts = results[4].split(/\s+/); const memTotal = memParts[0] ? (parseInt(memParts[0]) / 1024).toFixed(1) + 'G' : '-'; const memUsed = memParts[1] ? (parseInt(memParts[1]) / 1024).toFixed(1) + 'G' : '-'; const memFree = memParts[2] ? (parseInt(memParts[2]) / 1024).toFixed(1) + 'G' : '-'; const memPercent = memParts[0] && memParts[1] ? Math.round((parseInt(memParts[1]) / parseInt(memParts[0])) * 100) : 0; const diskParts = results[5].split(/\s+/); const diskPercent = parseInt(diskParts[3]) || 0; const processes = results[7].split('\n').filter(Boolean).map(line => { const [pid, cpu, mem, ...cmd] = line.trim().split(/\s+/); return { pid: parseInt(pid), cpu: cpu + '%', mem: mem + '%', command: (cmd || []).join(' ').slice(0, 50) }; }); return { hostname, cpu: { model: 'Intel Xeon (Lighthouse)', cores, usagePercent: cpuUsage }, memory: { total: memTotal, used: memUsed, free: memFree, percent: memPercent }, disk: { total: diskParts[0] || '-', used: diskParts[1] || '-', free: diskParts[2] || '-', percent: diskPercent }, uptime: results[6] || '-', processes, network: { ip }, }; } catch (err: any) { this.logger.warn('Remote metrics failed: ' + err.message); return null; } } async getAllMetrics() { const [local, remote] = await Promise.all([this.getLocalMetrics(), this.getRemoteMetrics()]); const servers = [ { name: '蜂驰云 8核32G', role: '生产核心', ...local }, ]; if (remote) servers.push({ name: '轻量云 4核4G', role: '工具/辅助', ...remote }); return { servers }; } }