import os from 'os'; import path from 'path'; import { execFileSync } from 'child_process'; import fs from 'fs'; import { pool } from '../config/database.js'; /** * Get CPU usage percentage */ const getCpuUsage = () => { const cpus = os.cpus(); let totalIdle = 0; let totalTick = 0; cpus.forEach((cpu) => { for (const type in cpu.times) { totalTick += cpu.times[type]; } totalIdle += cpu.times.idle; }); const idle = totalIdle / cpus.length; const total = totalTick / cpus.length; const percent = Math.round(((total - idle) / total) * 100); return { percent }; }; /** * Get memory usage (Linux - reads /proc/meminfo for accurate values) */ const getMemoryUsage = () => { try { // Read /proc/meminfo for accurate memory stats (like htop) const meminfo = fs.readFileSync('/proc/meminfo', 'utf8'); const lines = meminfo.split('\n'); const getValue = (key) => { const line = lines.find(l => l.startsWith(key)); if (!line) return 0; const match = line.match(/(\d+)/); return match ? parseInt(match[1]) : 0; }; const totalKB = getValue('MemTotal'); const freeKB = getValue('MemFree'); const buffersKB = getValue('Buffers'); const cachedKB = getValue('Cached'); const sreclaimableKB = getValue('SReclaimable'); // Available = Free + Buffers + Cached + SReclaimable (like htop calculates) const availableKB = freeKB + buffersKB + cachedKB + sreclaimableKB; const usedKB = totalKB - availableKB; // Convert to GB const totalGB = Math.round((totalKB / (1024 ** 2)) * 10) / 10; const usedGB = Math.round((usedKB / (1024 ** 2)) * 10) / 10; return { used: usedGB, total: totalGB, unit: 'GB', }; } catch { // Fallback to os module const totalBytes = os.totalmem(); const freeBytes = os.freemem(); const usedBytes = totalBytes - freeBytes; return { used: Math.round((usedBytes / (1024 ** 3)) * 10) / 10, total: Math.round((totalBytes / (1024 ** 3)) * 10) / 10, unit: 'GB', }; } }; /** * Get disk usage (Linux/macOS) */ const getDiskUsage = () => { try { const output = execFileSync('df', ['-BG', '/'], { encoding: 'utf8' }); const lines = output.trim().split('\n'); const parts = lines[lines.length - 1].split(/\s+/); const totalGB = parseInt(parts[1]) || 0; const usedGB = parseInt(parts[2]) || 0; return { used: usedGB, total: totalGB, unit: 'GB', }; } catch { return { used: 0, total: 0, unit: 'GB' }; } }; /** * Get Node.js backend process stats (CPU + RAM) */ const getBackendStats = () => { const memUsage = process.memoryUsage(); // Get CPU usage for this process const cpuUsage = process.cpuUsage(); const cpuPercent = Math.round(((cpuUsage.user + cpuUsage.system) / 1000000) * 100) / 100; return { memoryMB: Math.round((memUsage.rss / (1024 ** 2)) * 10) / 10, heapMB: Math.round((memUsage.heapUsed / (1024 ** 2)) * 10) / 10, cpuTime: cpuPercent, // CPU time in seconds }; }; /** * Get uploads directory size */ const getUploadsSize = () => { try { const uploadsPath = path.join(process.cwd(), 'uploads'); const output = execFileSync('du', ['-sm', uploadsPath], { encoding: 'utf8' }); const sizeMB = parseInt(output.split('\t')[0]) || 0; return { sizeMB, unit: 'MB', }; } catch { return { sizeMB: 0, unit: 'MB' }; } }; /** * Get database table count */ const getTableCount = async () => { try { const result = await pool.query(` SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' `); return parseInt(result.rows[0]?.count) || 0; } catch { return 0; } }; /** * Get system uptime in seconds */ const getSystemUptime = () => { return Math.floor(os.uptime()); }; /** * Get Node.js process uptime in seconds */ const getProcessUptime = () => { return Math.floor(process.uptime()); }; /** * Get complete server status */ export const getServerStatus = async () => { const tableCount = await getTableCount(); return { cpu: getCpuUsage(), memory: getMemoryUsage(), disk: getDiskUsage(), backend: getBackendStats(), uploads: getUploadsSize(), database: { tableCount, }, systemUptimeSec: getSystemUptime(), backendUptimeSec: getProcessUptime(), hostname: os.hostname(), platform: os.platform(), nodeVersion: process.version, timestamp: new Date().toISOString(), }; };