From bb4ef54db9964e25f30049514c8234d336df2b50 Mon Sep 17 00:00:00 2001 From: MathieuSevignyLavallee <89943988+MathieuSevignyLavallee@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:41:21 -0500 Subject: [PATCH] new approach using cgroup --- quizRoom/socket/setupWebSocket.ts | 160 +++++++++++++++++++++++++++--- test/stressTest/class/watcher.js | 1 + test/stressTest/main.js | 6 +- 3 files changed, 148 insertions(+), 19 deletions(-) diff --git a/quizRoom/socket/setupWebSocket.ts b/quizRoom/socket/setupWebSocket.ts index 82d08fd..a04a694 100644 --- a/quizRoom/socket/setupWebSocket.ts +++ b/quizRoom/socket/setupWebSocket.ts @@ -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(); - - getMetrics() { + private cgroupv2 = this.isCgroupV2(); + private lastCPUUsage = 0; + private lastCPUTime = Date.now(); + + 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; - - const memoryUsage = process.memoryUsage(); - const mbFactor = 1024 * 1024; - + } catch (error) { + console.error('Error getting fallback CPU usage:', error); + return 0; + } + } + + 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 { - memoryUsedMB: (memoryUsage.rss / mbFactor).toFixed(2), - memoryUsedPercentage: ( - (memoryUsage.rss / this.totalSystemMemory) * 100 - ).toFixed(2), - cpuUsedPercentage: cpuUsage.toFixed(2) + 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: 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" + }; } } } diff --git a/test/stressTest/class/watcher.js b/test/stressTest/class/watcher.js index 845b728..3bc1fce 100644 --- a/test/stressTest/class/watcher.js +++ b/test/stressTest/class/watcher.js @@ -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) { diff --git a/test/stressTest/main.js b/test/stressTest/main.js index 6027f4f..c066d3f 100644 --- a/test/stressTest/main.js +++ b/test/stressTest/main.js @@ -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(); } }