new approach using cgroup

This commit is contained in:
MathieuSevignyLavallee 2024-12-07 15:41:21 -05:00
parent b3d65e0a1e
commit bb4ef54db9
3 changed files with 148 additions and 19 deletions

View file

@ -1,5 +1,6 @@
import { Server, Socket } from "socket.io";
import os from "os";
import fs from 'fs';
const MAX_USERS_PER_ROOM = 60;
const MAX_TOTAL_CONNECTIONS = 2000;
@ -114,30 +115,159 @@ export const setupWebsocket = (io: Server): void => {
class ContainerMetrics {
private totalSystemMemory = os.totalmem();
private cgroupv2 = this.isCgroupV2();
private lastCPUUsage = 0;
private lastCPUTime = Date.now();
getMetrics() {
private isCgroupV2(): boolean {
return fs.existsSync('/sys/fs/cgroup/cgroup.controllers');
}
private readCgroupFile(filepath: string): string {
try {
return fs.readFileSync(filepath, 'utf-8').trim();
} catch (error) {
console.debug(`Could not read ${filepath}`);
return '';
}
}
private getCgroupCPUUsage(): number {
try {
if (this.cgroupv2) {
const usage = this.readCgroupFile('/sys/fs/cgroup/cpu.stat');
const usageMatch = usage.match(/usage_usec\s+(\d+)/);
if (usageMatch) {
const currentUsage = Number(usageMatch[1]) / 1000000;
const currentTime = Date.now();
const cpuDelta = currentUsage - this.lastCPUUsage;
const timeDelta = (currentTime - this.lastCPUTime) / 1000;
this.lastCPUUsage = currentUsage;
this.lastCPUTime = currentTime;
return (cpuDelta / timeDelta) * 100;
}
}
const cgroupV1Paths = [
'/sys/fs/cgroup/cpu/cpuacct.usage',
'/sys/fs/cgroup/cpuacct/cpuacct.usage',
'/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage'
];
for (const path of cgroupV1Paths) {
const usage = this.readCgroupFile(path);
if (usage) {
const currentUsage = Number(usage) / 1000000000;
const currentTime = Date.now();
const cpuDelta = currentUsage - this.lastCPUUsage;
const timeDelta = (currentTime - this.lastCPUTime) / 1000;
this.lastCPUUsage = currentUsage;
this.lastCPUTime = currentTime;
return (cpuDelta / timeDelta) * 100;
}
}
return this.getFallbackCPUUsage();
} catch (error) {
return this.getFallbackCPUUsage();
}
}
private getFallbackCPUUsage(): number {
try {
// Get CPU usage percentage directly from system
const cpus = os.cpus();
const cpuUsage = cpus.reduce((acc, cpu) => {
return cpus.reduce((acc, cpu) => {
const total = Object.values(cpu.times).reduce((a, b) => a + b);
const idle = cpu.times.idle;
return acc + ((total - idle) / total) * 100;
}, 0) / cpus.length;
} catch (error) {
console.error('Error getting fallback CPU usage:', error);
return 0;
}
}
const memoryUsage = process.memoryUsage();
private getCgroupMemoryUsage(): { used: number; limit: number } | null {
try {
// First get process memory as baseline
const processMemory = process.memoryUsage();
const baselineMemory = processMemory.rss;
if (this.cgroupv2) {
const memUsage = Number(this.readCgroupFile('/sys/fs/cgroup/memory.current'));
if (!isNaN(memUsage) && memUsage > 0) {
return {
used: Math.max(baselineMemory, memUsage),
limit: this.totalSystemMemory
};
}
}
// Try cgroup v1
const v1Paths = {
usage: '/sys/fs/cgroup/memory/memory.usage_in_bytes',
limit: '/sys/fs/cgroup/memory/memory.limit_in_bytes'
};
const memoryUsage = Number(this.readCgroupFile(v1Paths.usage));
if (!isNaN(memoryUsage) && memoryUsage > 0) {
return {
used: Math.max(baselineMemory, memoryUsage),
limit: this.totalSystemMemory
};
}
// Fallback to process memory
return {
used: baselineMemory,
limit: this.totalSystemMemory
};
} catch (error) {
console.debug('Error reading cgroup memory:', error);
return null;
}
}
public getMetrics() {
try {
const mbFactor = 1024 * 1024;
let memoryData = this.getCgroupMemoryUsage();
if (!memoryData) {
const processMemory = process.memoryUsage();
memoryData = {
used: processMemory.rss,
limit: this.totalSystemMemory
};
}
const memoryUsedMB = memoryData.used / mbFactor;
const memoryTotalMB = memoryData.limit / mbFactor;
const memoryPercentage = (memoryData.used / memoryData.limit) * 100;
console.debug(`
Memory Usage: ${memoryUsedMB.toFixed(2)} MB
Memory Total: ${memoryTotalMB.toFixed(2)} MB
Memory %: ${memoryPercentage.toFixed(2)}%
`);
return {
memoryUsedMB: (memoryUsage.rss / mbFactor).toFixed(2),
memoryUsedPercentage: (
(memoryUsage.rss / this.totalSystemMemory) * 100
).toFixed(2),
cpuUsedPercentage: cpuUsage.toFixed(2)
memoryUsedMB: memoryUsedMB.toFixed(2),
memoryUsedPercentage: memoryPercentage.toFixed(2),
cpuUsedPercentage: this.getCgroupCPUUsage().toFixed(2)
};
} catch (error) {
console.error("Error getting container metrics:", error);
throw error;
console.error("Error getting metrics:", error);
return {
memoryUsedMB: "0",
memoryUsedPercentage: "0",
cpuUsedPercentage: "0"
};
}
}
}

View file

@ -22,6 +22,7 @@ export class Watcher extends RoomParticipant {
try {
this.socket.emit("get-usage");
this.socket.once("usage-data", (data) => {
//console.log(`Watcher ${this.username} received data:`, data);
this.roomRessourcesData.push({ timestamp: Date.now(), ...data });
});
} catch (error) {

View file

@ -9,13 +9,13 @@ import generateMetricsReport from './utility/metrics_generator.js';
dotenv.config();
const config = {
baseUrl: process.env.BASE_URL || 'http://msevignyl.duckdns.org',
baseUrl: process.env.BASE_URL || 'http://localhost',
auth: {
username: process.env.USER_EMAIL || 'admin@admin.com',
password: process.env.USER_PASSWORD || 'admin'
},
rooms: {
count: parseInt(process.env.NUMBER_ROOMS || '5'),
count: parseInt(process.env.NUMBER_ROOMS || '2'),
usersPerRoom: parseInt(process.env.USERS_PER_ROOM || '60'),
batchSize: 5,
batchDelay: 250
@ -178,8 +178,6 @@ async function main() {
} catch (error) {
metrics.logError('main', error);
console.error('Error:', error.message);
} finally {
cleanup();
}
}