sawa-control-panel/backend/services/systemInfo.js

139 lines
4.1 KiB
JavaScript

const fs = require('fs');
const os = require('os');
const { execFileSync } = require('child_process');
/**
* Reads and parses /proc/stat to calculate CPU usage.
* To get a real-time percentage, it samples twice with a small delay.
* @returns {Promise<number>} CPU usage percent (0-100)
*/
const getCpuUsage = async () => {
const getStats = () => {
const data = fs.readFileSync('/proc/stat', 'utf8');
const lines = data.split('\n');
const cpuLine = lines[0].split(/\s+/).slice(1);
const idle = parseInt(cpuLine[3], 10);
const total = cpuLine.reduce((acc, val) => acc + parseInt(val, 10), 0);
return { idle, total };
};
const stats1 = getStats();
await new Promise(resolve => setTimeout(resolve, 200));
const stats2 = getStats();
const idleDiff = stats2.idle - stats1.idle;
const totalDiff = stats2.total - stats1.total;
if (totalDiff === 0) return 0;
const usage = 100 * (1 - idleDiff / totalDiff);
return parseFloat(usage.toFixed(1));
};
/**
* Reads /proc/meminfo to calculate memory stats in MB.
* @returns {object} { total, used, free }
*/
const getMemoryInfo = () => {
const data = fs.readFileSync('/proc/meminfo', 'utf8');
const lines = data.split('\n');
const stats = {};
lines.forEach(line => {
const parts = line.split(':');
if (parts.length === 2) {
stats[parts[0].trim()] = parseInt(parts[1].trim().split(' ')[0], 10);
}
});
const total = Math.round(stats.MemTotal / 1024);
const free = Math.round(stats.MemFree / 1024);
const buffers = Math.round((stats.Buffers || 0) / 1024);
const cached = Math.round((stats.Cached || 0) / 1024);
const available = Math.round((stats.MemAvailable || (stats.MemFree + stats.Buffers + stats.Cached)) / 1024);
const used = total - available;
return {
total,
used: Math.max(0, used),
free: available
};
};
const getDiskUsage = () => {
try {
// -T shows filesystem type
const output = execFileSync('df', ['-T'], { encoding: 'utf-8' });
const lines = output.trim().split('\n').slice(1); // Skip header
const allowedFS = ['ext4', 'ext3', 'xfs', 'btrfs', 'vfat'];
return lines.map(line => {
const parts = line.split(/\s+/);
const totalBlocks = parseInt(parts[2]);
const usedBlocks = parseInt(parts[3]);
const availBlocks = parseInt(parts[4]);
return {
mount: parts[6],
type: parts[1],
total: (totalBlocks * 1024 / 1e9).toFixed(1), // GB
used: (usedBlocks * 1024 / 1e9).toFixed(1), // GB
free: (availBlocks * 1024 / 1e9).toFixed(1),// GB
percent: parts[5], // Keep as string with % for now as per current frontend usage
unit: 'GB'
};
}).filter(d =>
allowedFS.includes(d.type) &&
parseFloat(d.total) > 0 &&
d.mount.startsWith('/')
);
} catch (err) {
console.error('Error getting disk usage:', err.message);
return [];
}
};
/**
* Calculates system uptime.
* @returns {object} { seconds, human }
*/
const getUptime = () => {
const seconds = Math.floor(os.uptime());
const days = Math.floor(seconds / (24 * 3600));
const hours = Math.floor((seconds % (24 * 3600)) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
let human = '';
if (days > 0) human += `${days} day${days > 1 ? 's' : ''}, `;
if (hours > 0) human += `${hours} hour${hours > 1 ? 's' : ''}, `;
human += `${minutes} minute${minutes > 1 ? 's' : ''}`;
return {
seconds,
human
};
};
/**
* Reads /proc/loadavg to get system load.
* @returns {object} { one, five, fifteen }
*/
const getLoadAverage = () => {
const data = fs.readFileSync('/proc/loadavg', 'utf8');
const parts = data.trim().split(/\s+/);
return {
one: parseFloat(parts[0]),
five: parseFloat(parts[1]),
fifteen: parseFloat(parts[2])
};
};
module.exports = {
getCpuUsage,
getMemoryInfo,
getDiskUsage,
getUptime,
getLoadAverage
};