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:
richardtekula
2025-12-05 09:00:31 +01:00
parent eb5582feb6
commit 03b7a215bb
3 changed files with 211 additions and 0 deletions

View 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(),
};
};