BREAKING CHANGE(smartmetrics): add system-wide metrics collection, Prometheus gauges, and normalized CPU reporting
This commit is contained in:
94
ts/smartmetrics.sysusage.ts
Normal file
94
ts/smartmetrics.sysusage.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
export interface ISystemUsageResult {
|
||||
cpuPercent: number; // 0-100% system-wide CPU utilization
|
||||
memTotalBytes: number; // total physical RAM
|
||||
memAvailableBytes: number; // available memory
|
||||
memUsedBytes: number; // memTotal - memAvailable
|
||||
memUsedPercent: number; // 0-100%
|
||||
loadAvg1: number; // 1-min load average
|
||||
loadAvg5: number; // 5-min load average
|
||||
loadAvg15: number; // 15-min load average
|
||||
}
|
||||
|
||||
// History for system CPU delta tracking
|
||||
interface ICpuSnapshot {
|
||||
idle: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
let prevCpuSnapshot: ICpuSnapshot | null = null;
|
||||
|
||||
function readProcStat(): ICpuSnapshot | null {
|
||||
try {
|
||||
const content = fs.readFileSync('/proc/stat', 'utf8');
|
||||
const firstLine = content.split('\n')[0]; // "cpu user nice system idle iowait irq softirq steal ..."
|
||||
const parts = firstLine.split(/\s+/).slice(1).map(Number);
|
||||
// parts: [user, nice, system, idle, iowait, irq, softirq, steal, ...]
|
||||
const idle = parts[3] + (parts[4] || 0); // idle + iowait
|
||||
const total = parts.reduce((sum, v) => sum + v, 0);
|
||||
return { idle, total };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getMemoryInfo(): { totalBytes: number; availableBytes: number } {
|
||||
try {
|
||||
const content = fs.readFileSync('/proc/meminfo', 'utf8');
|
||||
let memTotal = 0;
|
||||
let memAvailable = 0;
|
||||
for (const line of content.split('\n')) {
|
||||
if (line.startsWith('MemTotal:')) {
|
||||
memTotal = parseInt(line.split(/\s+/)[1], 10) * 1024; // kB to bytes
|
||||
} else if (line.startsWith('MemAvailable:')) {
|
||||
memAvailable = parseInt(line.split(/\s+/)[1], 10) * 1024;
|
||||
}
|
||||
}
|
||||
if (memTotal > 0 && memAvailable > 0) {
|
||||
return { totalBytes: memTotal, availableBytes: memAvailable };
|
||||
}
|
||||
} catch {
|
||||
// fall through to os fallback
|
||||
}
|
||||
// Fallback using os module
|
||||
const totalBytes = os.totalmem();
|
||||
const availableBytes = os.freemem();
|
||||
return { totalBytes, availableBytes };
|
||||
}
|
||||
|
||||
export async function getSystemUsage(): Promise<ISystemUsageResult> {
|
||||
// CPU
|
||||
let cpuPercent = 0;
|
||||
const currentSnapshot = readProcStat();
|
||||
if (currentSnapshot && prevCpuSnapshot) {
|
||||
const totalDelta = currentSnapshot.total - prevCpuSnapshot.total;
|
||||
const idleDelta = currentSnapshot.idle - prevCpuSnapshot.idle;
|
||||
if (totalDelta > 0) {
|
||||
cpuPercent = ((totalDelta - idleDelta) / totalDelta) * 100;
|
||||
}
|
||||
}
|
||||
if (currentSnapshot) {
|
||||
prevCpuSnapshot = currentSnapshot;
|
||||
}
|
||||
|
||||
// Memory
|
||||
const mem = getMemoryInfo();
|
||||
const memUsedBytes = mem.totalBytes - mem.availableBytes;
|
||||
const memUsedPercent = mem.totalBytes > 0 ? (memUsedBytes / mem.totalBytes) * 100 : 0;
|
||||
|
||||
// Load averages
|
||||
const [loadAvg1, loadAvg5, loadAvg15] = os.loadavg();
|
||||
|
||||
return {
|
||||
cpuPercent,
|
||||
memTotalBytes: mem.totalBytes,
|
||||
memAvailableBytes: mem.availableBytes,
|
||||
memUsedBytes,
|
||||
memUsedPercent,
|
||||
loadAvg1,
|
||||
loadAvg5,
|
||||
loadAvg15,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user