Add server status monitoring endpoint
- Add status.service.js with CPU, RAM, Disk, Backend stats - RAM calculation matches htop (reads /proc/meminfo) - Includes uploads folder size, DB table count - Returns both system and backend (process) uptime 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
186
src/services/status.service.js
Normal file
186
src/services/status.service.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
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 = execSync('cat /proc/meminfo', { encoding: '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 = execSync('df -BG / | tail -1', { encoding: 'utf8' });
|
||||
const parts = output.trim().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 = execSync(`du -sm "${uploadsPath}" 2>/dev/null || echo "0"`, { 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(),
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user