mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
not finished
This commit is contained in:
parent
5c24ae56a9
commit
11222c70bd
12 changed files with 1080 additions and 200 deletions
|
|
@ -2,7 +2,8 @@ import http from "http";
|
|||
import { Server, ServerOptions } from "socket.io";
|
||||
import { setupWebsocket } from "./socket/setupWebSocket";
|
||||
import dotenv from "dotenv";
|
||||
import express from 'express';
|
||||
import express from "express";
|
||||
import os from "os"; // Import the os module
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
|
@ -36,6 +37,7 @@ app.get('/health', (_, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
const ioOptions: Partial<ServerOptions> = {
|
||||
path: `/api/room/${roomId}/socket`,
|
||||
cors: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Server, Socket } from "socket.io";
|
||||
import os from "os";
|
||||
|
||||
const MAX_USERS_PER_ROOM = 60;
|
||||
const MAX_TOTAL_CONNECTIONS = 2000;
|
||||
|
|
@ -96,10 +97,62 @@ export const setupWebsocket = (io: Server): void => {
|
|||
socket.on("error", (error) => {
|
||||
console.error("WebSocket server error:", error);
|
||||
});
|
||||
|
||||
|
||||
// Stress Testing
|
||||
|
||||
socket.on("message-test", ({ roomName, message }: { roomName: string; message: string }) => {
|
||||
console.log(`Message reçu dans la salle ${roomName} : ${message}`);
|
||||
socket.to(roomName).emit("message", { id: socket.id, message });
|
||||
});
|
||||
|
||||
socket.on("get-usage", () => {
|
||||
try {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
const cpuUsage = process.cpuUsage();
|
||||
const totalMemory = os.totalmem();
|
||||
const freeMemory = os.freemem();
|
||||
const loadAverage = os.loadavg(); // Load average over 1, 5, and 15 minutes
|
||||
|
||||
// Calculate CPU usage percentage
|
||||
const userCpuPercentage = ((cpuUsage.user / 1e6) / os.cpus().length).toFixed(2); // in %
|
||||
const systemCpuPercentage = ((cpuUsage.system / 1e6) / os.cpus().length).toFixed(2); // in %
|
||||
|
||||
const usageData = {
|
||||
memory: {
|
||||
total: totalMemory,
|
||||
free: freeMemory,
|
||||
rss: memoryUsage.rss,
|
||||
heapTotal: memoryUsage.heapTotal,
|
||||
heapUsed: memoryUsage.heapUsed,
|
||||
external: memoryUsage.external,
|
||||
usagePercentage: (((totalMemory - freeMemory) / totalMemory) * 100).toFixed(2), // % used
|
||||
},
|
||||
cpu: {
|
||||
user: cpuUsage.user,
|
||||
system: cpuUsage.system,
|
||||
userPercentage: userCpuPercentage,
|
||||
systemPercentage: systemCpuPercentage,
|
||||
},
|
||||
system: {
|
||||
uptime: os.uptime(), // System uptime in seconds
|
||||
loadAverage: {
|
||||
"1min": loadAverage[0],
|
||||
"5min": loadAverage[1],
|
||||
"15min": loadAverage[2],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
socket.emit("usage-data", usageData); // Send usage data back to the client
|
||||
} catch (error) {
|
||||
console.error("Error getting usage data:", error);
|
||||
socket.emit("error", { message: "Failed to retrieve usage data" });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
const generateRoomName = (length = 6): string => {
|
||||
const characters = "0123456789";
|
||||
let result = "";
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export class Student {
|
|||
|
||||
this.socket.on('connect', () => {
|
||||
this.joinRoom(this.roomName, this.username);
|
||||
this.listenForMessages(); // Start listening for messages
|
||||
resolve(this.socket);
|
||||
});
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ export class Student {
|
|||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error connecting ${this.name} to room ${this.roomId}:`, error.message);
|
||||
console.error(`Error connecting ${this.name} to room ${this.roomName}:`, error.message);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
|
@ -41,4 +42,18 @@ export class Student {
|
|||
this.socket.emit('join-room', { roomName, username });
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
if (this.socket && this.socket.connected) {
|
||||
this.socket.emit('message-test', { room: this.roomName, message });
|
||||
}
|
||||
}
|
||||
|
||||
listenForMessages() {
|
||||
if (this.socket) {
|
||||
this.socket.on('message-test', (data) => {
|
||||
console.log(`Message received in room ${this.roomName} by ${this.username}:`, data.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,19 +22,15 @@ export class Teacher {
|
|||
|
||||
this.socket.on('connect', () => {
|
||||
this.createRoom(this.roomName);
|
||||
this.listenForMessages(); // Start listening for messages
|
||||
resolve(this.socket);
|
||||
});
|
||||
|
||||
this.socket.on('error', (error) => {
|
||||
reject(new Error(`Connection error: ${error.message}`));
|
||||
});
|
||||
|
||||
this.socket.on('create-success', () => {
|
||||
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error connecting ${this.name} to room ${this.roomId}:`, error.message);
|
||||
console.error(`Error connecting ${this.name} to room ${this.roomName}:`, error.message);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
|
@ -45,4 +41,18 @@ export class Teacher {
|
|||
this.socket.emit('create-room', this.roomName || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(message) {
|
||||
if (this.socket && this.socket.connected) {
|
||||
this.socket.emit('message-test', { room: this.roomName, message });
|
||||
}
|
||||
}
|
||||
|
||||
listenForMessages() {
|
||||
if (this.socket) {
|
||||
this.socket.on('message-test', (data) => {
|
||||
console.log(`Message received in room ${this.roomName} by ${this.username}:`, data.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { attemptLoginOrRegister, createRoomContainer } from './utility/apiServices.js';
|
||||
import { attemptLoginOrRegister, createRoomContainer, captureResourceUsageForContainers } from './utility/apiServices.js';
|
||||
import { Student } from './class/student.js';
|
||||
import { Teacher } from './class/teacher.js';
|
||||
import { writeMetricsToFile } from './utility/writeMetrics.js';
|
||||
import { writeMetricsToFile, generateGraphs } from './utility/writeMetrics.js';
|
||||
|
||||
const BASE_URL = 'http://localhost';
|
||||
const user = { username: 'admin@example.com', password: 'adminPassword' };
|
||||
const numberRooms = 5;
|
||||
const studentPerRoom = 59; // Max : 60, 1 place réservée pour le professeur
|
||||
const numberRooms = 20;
|
||||
const studentPerRoom = 58; // Max: 60, 1 reserved for teacher
|
||||
const roomAssociations = {};
|
||||
const allSockets = []; // Suivi de toutes les connexions WebSocket actives
|
||||
const allSockets = []; // Tracks all active WebSocket connections
|
||||
|
||||
// Métriques
|
||||
const metrics = {
|
||||
roomsCreated: 0,
|
||||
roomsFailed: 0,
|
||||
|
|
@ -18,133 +17,211 @@ const metrics = {
|
|||
teachersFailed: 0,
|
||||
studentsConnected: 0,
|
||||
studentsFailed: 0,
|
||||
messagesSent: 0,
|
||||
messagesReceived: 0,
|
||||
totalLatency: 0,
|
||||
maxLatency: 0,
|
||||
minLatency: Number.MAX_SAFE_INTEGER,
|
||||
throughput: 0,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
};
|
||||
|
||||
// Creates rooms and tracks their creation
|
||||
async function createRoomContainers(token) {
|
||||
console.time('Temps de création des salles');
|
||||
console.time('Room creation time');
|
||||
const roomCreationPromises = Array.from({ length: numberRooms }, async () => {
|
||||
const room = await createRoomContainer(BASE_URL, token);
|
||||
if (room?.id) {
|
||||
roomAssociations[room.id] = { teacher: null, students: [] };
|
||||
metrics.roomsCreated++;
|
||||
console.log(`Salle créée avec l'ID : ${room.id}`);
|
||||
} else {
|
||||
try {
|
||||
const room = await createRoomContainer(BASE_URL, token);
|
||||
if (room?.id) {
|
||||
roomAssociations[room.id] = { teacher: null, students: [] };
|
||||
metrics.roomsCreated++;
|
||||
console.log(`Room created with ID: ${room.id}`);
|
||||
}
|
||||
} catch {
|
||||
metrics.roomsFailed++;
|
||||
console.warn('Échec de la création d’une salle.');
|
||||
console.warn('Failed to create a room.');
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(roomCreationPromises);
|
||||
console.timeEnd('Temps de création des salles');
|
||||
console.log(`Nombre total de salles créées : ${Object.keys(roomAssociations).length}`);
|
||||
console.timeEnd('Room creation time');
|
||||
console.log(`Total rooms created: ${Object.keys(roomAssociations).length}`);
|
||||
}
|
||||
|
||||
// Connects teachers to their respective rooms
|
||||
async function addAndConnectTeachers() {
|
||||
console.time('Temps de connexion des enseignants');
|
||||
const teacherCreationPromises = Object.keys(roomAssociations).map(async (roomId, index) => {
|
||||
console.time('Teacher connection time');
|
||||
const teacherPromises = Object.keys(roomAssociations).map(async (roomId, index) => {
|
||||
const teacher = new Teacher(`teacher_${index}`, roomId);
|
||||
const start = Date.now();
|
||||
const socket = await teacher.connectToRoom(BASE_URL);
|
||||
const latency = Date.now() - start;
|
||||
|
||||
metrics.totalLatency += latency;
|
||||
metrics.maxLatency = Math.max(metrics.maxLatency, latency);
|
||||
metrics.minLatency = Math.min(metrics.minLatency, latency);
|
||||
|
||||
if (socket.connected) {
|
||||
allSockets.push(socket);
|
||||
roomAssociations[roomId].teacher = teacher;
|
||||
metrics.teachersConnected++;
|
||||
console.log(`Enseignant ${teacher.username} connecté à la salle ${roomId}. Latence : ${latency}ms`);
|
||||
console.log(`Teacher ${teacher.username} connected to room ${roomId}. Latency: ${latency}ms`);
|
||||
} else {
|
||||
metrics.teachersFailed++;
|
||||
console.warn(`Échec de la connexion de l'enseignant ${index} à la salle ${roomId}`);
|
||||
console.warn(`Failed to connect teacher ${index} to room ${roomId}`);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(teacherCreationPromises);
|
||||
console.timeEnd('Temps de connexion des enseignants');
|
||||
console.log('Tous les enseignants ont été ajoutés et connectés à leurs salles respectives.');
|
||||
await Promise.allSettled(teacherPromises);
|
||||
console.timeEnd('Teacher connection time');
|
||||
console.log('All teachers connected to their respective rooms.');
|
||||
}
|
||||
|
||||
// Connects students to their respective rooms
|
||||
async function addAndConnectStudents() {
|
||||
console.time('Temps de connexion des étudiants');
|
||||
const studentCreationPromises = Object.entries(roomAssociations).flatMap(([roomId, association], roomIndex) =>
|
||||
console.time('Student connection time');
|
||||
const studentPromises = Object.entries(roomAssociations).flatMap(([roomId, association], roomIndex) =>
|
||||
Array.from({ length: studentPerRoom }, async (_, i) => {
|
||||
const student = new Student(`student_${roomIndex}_${i}`, roomId);
|
||||
const start = Date.now();
|
||||
const socket = await student.connectToRoom(BASE_URL);
|
||||
const latency = Date.now() - start;
|
||||
|
||||
metrics.totalLatency += latency;
|
||||
metrics.maxLatency = Math.max(metrics.maxLatency, latency);
|
||||
metrics.minLatency = Math.min(metrics.minLatency, latency);
|
||||
|
||||
if (socket.connected) {
|
||||
allSockets.push(socket);
|
||||
association.students.push(student);
|
||||
metrics.studentsConnected++;
|
||||
console.log(`Étudiant ${student.username} connecté à la salle ${roomId}. Latence : ${latency}ms`);
|
||||
} else {
|
||||
metrics.studentsFailed++;
|
||||
console.warn(`Échec de la connexion de l'étudiant ${roomIndex}_${i} à la salle ${roomId}`);
|
||||
console.warn(`Failed to connect student ${roomIndex}_${i} to room ${roomId}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.allSettled(studentCreationPromises);
|
||||
console.timeEnd('Temps de connexion des étudiants');
|
||||
console.log('Tous les étudiants ont été ajoutés et connectés à leurs salles respectives.');
|
||||
await Promise.allSettled(studentPromises);
|
||||
console.timeEnd('Student connection time');
|
||||
console.log('All students connected to their respective rooms.');
|
||||
}
|
||||
|
||||
// Simulates conversations in all rooms
|
||||
async function simulateConversation() {
|
||||
console.log("Conversation simulation started...");
|
||||
const messages = [
|
||||
"Bonjour, tout le monde !",
|
||||
"Pouvez-vous répondre à la question 1 ?",
|
||||
"J'ai une question sur l'exercice.",
|
||||
"Voici la réponse à la question 1.",
|
||||
"Merci pour vos réponses, continuons avec la question 2.",
|
||||
"Je ne comprends pas bien, pouvez-vous expliquer à nouveau ?",
|
||||
];
|
||||
|
||||
const interval = 1000;
|
||||
const duration = 10000;
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < duration) {
|
||||
for (const [roomId, association] of Object.entries(roomAssociations)) {
|
||||
if (association.teacher) {
|
||||
const teacherMessage = `Teacher says: ${messages[Math.floor(Math.random() * messages.length)]}`;
|
||||
association.teacher.sendMessage(teacherMessage);
|
||||
metrics.messagesSent++;
|
||||
}
|
||||
|
||||
for (const student of association.students) {
|
||||
const studentMessage = `${student.username} says: ${messages[Math.floor(Math.random() * messages.length)]}`;
|
||||
student.sendMessage(studentMessage);
|
||||
metrics.messagesSent++;
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
|
||||
console.log("Conversation simulation ended.");
|
||||
}
|
||||
|
||||
// Closes all active WebSocket connections
|
||||
function closeAllSockets() {
|
||||
console.log('Fermeture de toutes les connexions Socket.IO...');
|
||||
console.log('Closing all Socket.IO connections...');
|
||||
allSockets.forEach((socket) => {
|
||||
if (socket && socket.connected) {
|
||||
try {
|
||||
socket.disconnect();
|
||||
console.log('Connexion Socket.IO déconnectée.');
|
||||
console.log('Socket.IO connection disconnected.');
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la déconnexion du socket Socket.IO :', error.message);
|
||||
console.error('Error disconnecting Socket.IO connection:', error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('Toutes les connexions Socket.IO ont été déconnectées.');
|
||||
}
|
||||
|
||||
function generateReport() {
|
||||
console.log('Toutes les tâches ont été terminées.');
|
||||
console.log('--- Résultats du test de charge ---');
|
||||
console.log(`Salles créées : ${metrics.roomsCreated}`);
|
||||
console.log(`Échecs de création de salles : ${metrics.roomsFailed}`);
|
||||
console.log(`Enseignants connectés : ${metrics.teachersConnected}`);
|
||||
console.log(`Échecs de connexion des enseignants : ${metrics.teachersFailed}`);
|
||||
console.log(`Étudiants connectés : ${metrics.studentsConnected}`);
|
||||
console.log(`Échecs de connexion des étudiants : ${metrics.studentsFailed}`);
|
||||
console.log(`Durée totale d'exécution : ${(metrics.endTime - metrics.startTime) / 1000}s`);
|
||||
console.log('Utilisation de la mémoire :', process.memoryUsage());
|
||||
writeMetricsToFile( metrics);
|
||||
console.log('All Socket.IO connections have been disconnected.');
|
||||
}
|
||||
|
||||
// Main function to orchestrate the workflow
|
||||
async function main() {
|
||||
try {
|
||||
metrics.startTime = new Date();
|
||||
const token = await attemptLoginOrRegister(BASE_URL, user.username, user.password);
|
||||
if (!token) throw new Error('Échec de la connexion.');
|
||||
|
||||
// Login or register
|
||||
const token = await attemptLoginOrRegister(BASE_URL, user.username, user.password);
|
||||
if (!token) throw new Error('Failed to login or register.');
|
||||
|
||||
// Room creation
|
||||
await createRoomContainers(token);
|
||||
await addAndConnectTeachers();
|
||||
await addAndConnectStudents();
|
||||
|
||||
// Resource monitoring and test activities
|
||||
const roomIds = Object.keys(roomAssociations);
|
||||
const usageCaptureInterval = 100;
|
||||
let testCompleted = false;
|
||||
|
||||
const resourceCapturePromise = captureResourceUsageForContainers(
|
||||
BASE_URL,
|
||||
roomIds,
|
||||
usageCaptureInterval,
|
||||
() => testCompleted,
|
||||
metrics
|
||||
);
|
||||
|
||||
const testPromise = (async () => {
|
||||
await addAndConnectTeachers();
|
||||
await addAndConnectStudents();
|
||||
await simulateConversation();
|
||||
testCompleted = true;
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
})();
|
||||
|
||||
await Promise.all([resourceCapturePromise, testPromise]);
|
||||
|
||||
metrics.endTime = new Date();
|
||||
writeMetricsToFile(metrics);
|
||||
await generateGraphs(metrics.resourceUsage, metrics);
|
||||
|
||||
generateReport();
|
||||
console.log("All tasks completed successfully!");
|
||||
} catch (error) {
|
||||
console.error('Une erreur est survenue :', error.message);
|
||||
console.error('Error:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion de l'interruption et de la fermeture
|
||||
// Handle process interruptions
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Script interrompu (Ctrl+C).');
|
||||
console.log('Process interrupted (Ctrl+C).');
|
||||
closeAllSockets();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('exit', closeAllSockets);
|
||||
process.on('exit', () => closeAllSockets());
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('Uncaught Exception:', err);
|
||||
closeAllSockets();
|
||||
process.exit(1);
|
||||
});
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection:', promise, 'reason:', reason);
|
||||
closeAllSockets();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main();
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"roomsCreated": 5,
|
||||
"roomsFailed": 0,
|
||||
"teachersConnected": 5,
|
||||
"teachersFailed": 0,
|
||||
"studentsConnected": 295,
|
||||
"studentsFailed": 0,
|
||||
"startTime": "2024-11-16T01:06:28.606Z",
|
||||
"endTime": "2024-11-16T01:06:31.135Z",
|
||||
"executionTime": 2.529,
|
||||
"memoryUsage": {
|
||||
"rss": 72040448,
|
||||
"heapTotal": 51122176,
|
||||
"heapUsed": 23656264,
|
||||
"external": 4503630,
|
||||
"arrayBuffers": 170538
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"roomsCreated": 5,
|
||||
"roomsFailed": 0,
|
||||
"teachersConnected": 5,
|
||||
"teachersFailed": 0,
|
||||
"studentsConnected": 295,
|
||||
"studentsFailed": 0,
|
||||
"startTime": "2024-11-16T01:08:33.133Z",
|
||||
"endTime": "2024-11-16T01:08:35.626Z",
|
||||
"executionTime": 2.493,
|
||||
"memoryUsage": {
|
||||
"rss": 71737344,
|
||||
"heapTotal": 51122176,
|
||||
"heapUsed": 23472768,
|
||||
"external": 4499368,
|
||||
"arrayBuffers": 166276
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"roomsCreated": 5,
|
||||
"roomsFailed": 0,
|
||||
"teachersConnected": 5,
|
||||
"teachersFailed": 0,
|
||||
"studentsConnected": 295,
|
||||
"studentsFailed": 0,
|
||||
"startTime": "2024-11-16T01:09:27.129Z",
|
||||
"endTime": "2024-11-16T01:09:30.032Z",
|
||||
"executionTime": 2.903,
|
||||
"memoryUsage": {
|
||||
"rss": 72146944,
|
||||
"heapTotal": 50860032,
|
||||
"heapUsed": 22783944,
|
||||
"external": 4496062,
|
||||
"arrayBuffers": 162970
|
||||
}
|
||||
}
|
||||
549
test/stressTest/package-lock.json
generated
549
test/stressTest/package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"chartjs-node-canvas": "^4.1.6",
|
||||
"dockerode": "^4.0.2",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1"
|
||||
|
|
@ -20,6 +21,25 @@
|
|||
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
|
||||
"integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q=="
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
|
|
@ -46,6 +66,11 @@
|
|||
"undici-types": "~6.19.8"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
|
@ -58,6 +83,43 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
|
|
@ -81,6 +143,11 @@
|
|||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
|
|
@ -126,6 +193,15 @@
|
|||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
|
|
@ -158,11 +234,51 @@
|
|||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/canvas": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
|
||||
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||
"nan": "^2.17.0",
|
||||
"simple-get": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
|
||||
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chartjs-node-canvas": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-node-canvas/-/chartjs-node-canvas-4.1.6.tgz",
|
||||
"integrity": "sha512-UQJbPWrvqB/FoLclGA9BaLQmZbzSYlujF4w8NZd6Xzb+sqgACBb2owDX6m7ifCXLjUW5Nz0Qx0qqrTtQkkSoYw==",
|
||||
"dependencies": {
|
||||
"canvas": "^2.8.0",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chart.js": "^3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
|
@ -174,6 +290,16 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
|
|
@ -224,6 +350,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||
"dependencies": {
|
||||
"mimic-response": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
|
|
@ -232,6 +369,19 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/docker-modem": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz",
|
||||
|
|
@ -259,6 +409,11 @@
|
|||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
|
@ -344,6 +499,90 @@
|
|||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
|
|
@ -363,11 +602,51 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
|
|
@ -387,6 +666,70 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
|
|
@ -400,8 +743,7 @@
|
|||
"node_modules/nan": {
|
||||
"version": "2.22.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
|
||||
"integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
|
||||
"optional": true
|
||||
"integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
|
|
@ -411,6 +753,51 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
|
@ -427,6 +814,14 @@
|
|||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
|
|
@ -454,6 +849,21 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
|
@ -478,6 +888,56 @@
|
|||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
||||
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
||||
"dependencies": {
|
||||
"decompress-response": "^4.2.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||
|
|
@ -560,6 +1020,46 @@
|
|||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
|
||||
|
|
@ -586,6 +1086,24 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
|
|
@ -609,6 +1127,28 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
|
@ -641,6 +1181,11 @@
|
|||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"chartjs-node-canvas": "^4.1.6",
|
||||
"dockerode": "^4.0.2",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import axios from "axios";
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
/**
|
||||
* Logs in a user.
|
||||
|
|
@ -8,24 +9,18 @@ import axios from "axios";
|
|||
* @returns {Promise<string>} - The authentication token if successful.
|
||||
*/
|
||||
async function login(baseUrl, email, password) {
|
||||
if (!email || !password) {
|
||||
throw new Error("Email and password are required.");
|
||||
}
|
||||
|
||||
const url = `${baseUrl}/api/user/login`;
|
||||
const payload = { email, password };
|
||||
if (!email || !password) throw new Error("Email and password are required.");
|
||||
|
||||
try {
|
||||
const res = await axios.post(url, payload, {
|
||||
const res = await axios.post(`${baseUrl}/api/user/login`, { email, password }, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
if (res.status !== 200 || !res.data.token) {
|
||||
throw new Error(`Login failed. Status: ${res.status}`);
|
||||
if (res.status === 200 && res.data.token) {
|
||||
console.log(`Login successful for ${email}`);
|
||||
return res.data.token;
|
||||
}
|
||||
|
||||
console.log(`Login successful for ${email}`);
|
||||
return res.data.token;
|
||||
throw new Error(`Login failed. Status: ${res.status}`);
|
||||
} catch (error) {
|
||||
console.error(`Login error for ${email}:`, error.message);
|
||||
throw error;
|
||||
|
|
@ -40,24 +35,18 @@ async function login(baseUrl, email, password) {
|
|||
* @returns {Promise<string>} - A success message if registration is successful.
|
||||
*/
|
||||
async function register(baseUrl, email, password) {
|
||||
if (!email || !password) {
|
||||
throw new Error("Email and password are required.");
|
||||
}
|
||||
|
||||
const url = `${baseUrl}/api/user/register`;
|
||||
const payload = { email, password };
|
||||
if (!email || !password) throw new Error("Email and password are required.");
|
||||
|
||||
try {
|
||||
const res = await axios.post(url, payload, {
|
||||
const res = await axios.post(`${baseUrl}/api/user/register`, { email, password }, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(`Registration failed. Status: ${res.status}`);
|
||||
if (res.status === 200) {
|
||||
console.log(`Registration successful for ${email}`);
|
||||
return res.data.message || "Registration completed successfully.";
|
||||
}
|
||||
|
||||
console.log(`Registration successful for ${email}`);
|
||||
return res.data.message || "Registration completed successfully.";
|
||||
throw new Error(`Registration failed. Status: ${res.status}`);
|
||||
} catch (error) {
|
||||
console.error(`Registration error for ${email}:`, error.message);
|
||||
throw error;
|
||||
|
|
@ -73,21 +62,14 @@ async function register(baseUrl, email, password) {
|
|||
*/
|
||||
export async function attemptLoginOrRegister(baseUrl, username, password) {
|
||||
try {
|
||||
const token = await login(baseUrl, username, password);
|
||||
console.log(`User successfully logged in: ${username}`);
|
||||
return token;
|
||||
return await login(baseUrl, username, password);
|
||||
} catch (loginError) {
|
||||
console.log(`Login failed for ${username}. Attempting registration...`);
|
||||
|
||||
try {
|
||||
const registerResponse = await register(baseUrl, username, password);
|
||||
console.log(`User successfully registered: ${username}`);
|
||||
|
||||
const token = await login(baseUrl, username, password);
|
||||
console.log(`User successfully logged in after registration: ${username}`);
|
||||
return token;
|
||||
await register(baseUrl, username, password);
|
||||
return await login(baseUrl, username, password);
|
||||
} catch (registerError) {
|
||||
console.error(`Registration failed for ${username}:`, registerError.message);
|
||||
console.error(`Registration and login failed for ${username}:`, registerError.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,28 +82,76 @@ export async function attemptLoginOrRegister(baseUrl, username, password) {
|
|||
* @returns {Promise<object>} - The created room object if successful.
|
||||
*/
|
||||
export async function createRoomContainer(baseUrl, token) {
|
||||
if (!token) {
|
||||
throw new Error("Authorization token is required.");
|
||||
}
|
||||
|
||||
const url = `${baseUrl}/api/room`;
|
||||
if (!token) throw new Error("Authorization token is required.");
|
||||
|
||||
try {
|
||||
const res = await axios.post(url, {}, {
|
||||
const res = await axios.post(`${baseUrl}/api/room`, {}, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error(`Room creation failed. Status: ${res.status}`);
|
||||
}
|
||||
|
||||
//console.log("Room successfully created:", res.data);
|
||||
return res.data;
|
||||
if (res.status === 200) return res.data;
|
||||
throw new Error(`Room creation failed. Status: ${res.status}`);
|
||||
} catch (error) {
|
||||
console.error("Room creation error:", error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures resource usage from multiple containers via WebSocket.
|
||||
* @param {string} baseUrl - The base URL of the API.
|
||||
* @param {string[]} roomIds - List of room IDs.
|
||||
* @param {number} interval - Time interval between captures (ms).
|
||||
* @param {function} shouldStop - Callback to determine if capturing should stop.
|
||||
* @param {object} metrics - Metrics object to store resource usage.
|
||||
*/
|
||||
export async function captureResourceUsageForContainers(baseUrl, roomIds, interval, shouldStop, metrics) {
|
||||
console.log("Starting resource usage capture...");
|
||||
|
||||
const sockets = {};
|
||||
const resourceData = {};
|
||||
|
||||
// Initialize WebSocket connections for each room
|
||||
roomIds.forEach((id) => {
|
||||
resourceData[id] = [];
|
||||
const socket = io(baseUrl, {
|
||||
path: `/api/room/${id}/socket`,
|
||||
transports: ["websocket"],
|
||||
autoConnect: true,
|
||||
reconnection: true,
|
||||
});
|
||||
|
||||
socket.on("connect", () => console.log(`Connected to room ${id}`));
|
||||
socket.on("connect_error", (err) => console.error(`Connection error for room ${id}:`, err.message));
|
||||
sockets[id] = socket;
|
||||
});
|
||||
|
||||
// Capture resource usage periodically
|
||||
while (!shouldStop()) {
|
||||
for (const id of roomIds) {
|
||||
const socket = sockets[id];
|
||||
if (socket?.connected) {
|
||||
try {
|
||||
socket.emit("get-usage");
|
||||
socket.once("usage-data", (data) => {
|
||||
resourceData[id].push({ timestamp: Date.now(), ...data });
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`Error capturing metrics for room ${id}:`, error.message);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Socket not connected for room ${id}`);
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
|
||||
// Close all WebSocket connections
|
||||
Object.values(sockets).forEach((socket) => socket.close());
|
||||
console.log("Resource usage capture completed.");
|
||||
|
||||
metrics.resourceUsage = resourceData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,227 @@
|
|||
import fs from 'fs';
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { ChartJSNodeCanvas } from "chartjs-node-canvas";
|
||||
|
||||
/**
|
||||
* Écrit les métriques dans un fichier JSON.
|
||||
* @param {string} filename - Nom du fichier où écrire les métriques.
|
||||
* @param {Object} metrics - Objet contenant les métriques à enregistrer.
|
||||
*/
|
||||
// Ensure a directory exists, creating it if necessary
|
||||
function ensureDirectoryExists(directory) {
|
||||
try {
|
||||
fs.mkdirSync(directory, { recursive: true });
|
||||
} catch (err) {
|
||||
console.error(`Error creating directory ${directory}:`, err.message);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// Write metrics to a JSON file
|
||||
export function writeMetricsToFile(metrics) {
|
||||
if (!metrics.endTime) {
|
||||
console.error("Error: metrics.endTime is not defined. Ensure it is set before calling this function.");
|
||||
return;
|
||||
}
|
||||
|
||||
const directory = `./metrics/${metrics.startTime.toISOString().replace(/[:.]/g, "-")}`;
|
||||
const filename = path.join(directory, "metrics_report.json");
|
||||
|
||||
// Ensure the directory exists
|
||||
ensureDirectoryExists(directory);
|
||||
|
||||
const metricsData = {
|
||||
...metrics,
|
||||
startTime: metrics.startTime?.toISOString(),
|
||||
endTime: metrics.endTime?.toISOString(),
|
||||
executionTime: metrics.endTime && metrics.startTime
|
||||
? (metrics.endTime - metrics.startTime) / 1000
|
||||
: null,
|
||||
memoryUsage: process.memoryUsage(),
|
||||
summary: {
|
||||
roomsCreated: metrics.roomsCreated,
|
||||
roomsFailed: metrics.roomsFailed,
|
||||
teachersConnected: metrics.teachersConnected,
|
||||
teachersFailed: metrics.teachersFailed,
|
||||
studentsConnected: metrics.studentsConnected,
|
||||
studentsFailed: metrics.studentsFailed,
|
||||
},
|
||||
messages: {
|
||||
messagesSent: metrics.messagesSent,
|
||||
messagesReceived: metrics.messagesReceived,
|
||||
throughput: metrics.throughput.toFixed(2), // Messages per second
|
||||
},
|
||||
latencies: {
|
||||
totalLatency: metrics.totalLatency,
|
||||
averageLatency: metrics.teachersConnected + metrics.studentsConnected
|
||||
? (metrics.totalLatency / (metrics.teachersConnected + metrics.studentsConnected)).toFixed(2)
|
||||
: null,
|
||||
maxLatency: metrics.maxLatency,
|
||||
minLatency: metrics.minLatency,
|
||||
},
|
||||
timing: {
|
||||
startTime: metrics.startTime?.toISOString(),
|
||||
endTime: metrics.endTime?.toISOString(),
|
||||
executionTimeInSeconds: metrics.endTime && metrics.startTime
|
||||
? (metrics.endTime - metrics.startTime) / 1000
|
||||
: null,
|
||||
},
|
||||
};
|
||||
|
||||
fs.writeFile(`metrics_report_${Date.now()}`, JSON.stringify(metricsData, null, 4), (err) => {
|
||||
// Write metrics to a file
|
||||
fs.writeFile(filename, JSON.stringify(metricsData, null, 4), (err) => {
|
||||
if (err) {
|
||||
console.error('Erreur lors de l\'écriture des métriques dans le fichier :', err.message);
|
||||
console.error(`Error writing metrics to file:`, err.message);
|
||||
} else {
|
||||
console.log(`Métriques enregistrées dans le fichier metrics_report_${Date.now()}.`);
|
||||
console.log(`Metrics saved to file: ${filename}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generate charts for resource data
|
||||
export async function generateGraphs(resourceData, metrics) {
|
||||
if (!metrics.endTime) {
|
||||
console.error("Error: metrics.endTime is not defined. Ensure it is set before calling this function.");
|
||||
return;
|
||||
}
|
||||
|
||||
const directory = `./metrics/${metrics.startTime.toISOString().replace(/[:.]/g, "-")}`;
|
||||
ensureDirectoryExists(directory);
|
||||
|
||||
const chartJSNodeCanvas = new ChartJSNodeCanvas({ width: 800, height: 600 });
|
||||
|
||||
// Aggregated data for all containers
|
||||
const aggregatedTimestamps = [];
|
||||
const aggregatedMemoryUsage = [];
|
||||
const aggregatedCpuUserPercentage = [];
|
||||
const aggregatedCpuSystemPercentage = [];
|
||||
|
||||
// Generate charts for individual containers
|
||||
for (const [roomId, data] of Object.entries(resourceData)) {
|
||||
if (!data || !data.length) {
|
||||
console.warn(`No data available for room ${roomId}. Skipping individual charts.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract data
|
||||
const timestamps = data.map((point) => new Date(point.timestamp).toLocaleTimeString());
|
||||
const memoryUsage = data.map((point) => (point.memory.rss || 0) / (1024 * 1024)); // MB
|
||||
const cpuUserPercentage = data.map((point) => point.cpu.userPercentage || 0);
|
||||
const cpuSystemPercentage = data.map((point) => point.cpu.systemPercentage || 0);
|
||||
|
||||
// Update aggregated data
|
||||
data.forEach((point, index) => {
|
||||
if (!aggregatedTimestamps[index]) {
|
||||
aggregatedTimestamps[index] = timestamps[index];
|
||||
aggregatedMemoryUsage[index] = 0;
|
||||
aggregatedCpuUserPercentage[index] = 0;
|
||||
aggregatedCpuSystemPercentage[index] = 0;
|
||||
}
|
||||
aggregatedMemoryUsage[index] += memoryUsage[index];
|
||||
aggregatedCpuUserPercentage[index] += cpuUserPercentage[index];
|
||||
aggregatedCpuSystemPercentage[index] += cpuSystemPercentage[index];
|
||||
});
|
||||
|
||||
// Memory usage chart
|
||||
await saveChart(
|
||||
chartJSNodeCanvas,
|
||||
{
|
||||
labels: timestamps,
|
||||
datasets: [
|
||||
{
|
||||
label: "Memory Usage (MB)",
|
||||
data: memoryUsage,
|
||||
borderColor: "blue",
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"Time",
|
||||
"Memory Usage (MB)",
|
||||
path.join(directory, `memory-usage-room-${roomId}.png`)
|
||||
);
|
||||
|
||||
// CPU usage chart
|
||||
await saveChart(
|
||||
chartJSNodeCanvas,
|
||||
{
|
||||
labels: timestamps,
|
||||
datasets: [
|
||||
{
|
||||
label: "CPU User Usage (%)",
|
||||
data: cpuUserPercentage,
|
||||
borderColor: "red",
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: "CPU System Usage (%)",
|
||||
data: cpuSystemPercentage,
|
||||
borderColor: "orange",
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"Time",
|
||||
"CPU Usage (%)",
|
||||
path.join(directory, `cpu-usage-room-${roomId}.png`)
|
||||
);
|
||||
|
||||
console.log(`Charts generated for room ${roomId}`);
|
||||
}
|
||||
|
||||
// Ensure aggregated data is not empty
|
||||
if (!aggregatedTimestamps.length) {
|
||||
console.error("Error: Aggregated data is empty. Verify container data.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Aggregated memory usage chart
|
||||
await saveChart(
|
||||
chartJSNodeCanvas,
|
||||
{
|
||||
labels: aggregatedTimestamps,
|
||||
datasets: [
|
||||
{
|
||||
label: "Total Memory Usage (MB)",
|
||||
data: aggregatedMemoryUsage,
|
||||
borderColor: "blue",
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"Time",
|
||||
"Memory Usage (MB)",
|
||||
path.join(directory, "aggregated-memory-usage.png")
|
||||
);
|
||||
|
||||
// Aggregated CPU usage chart
|
||||
await saveChart(
|
||||
chartJSNodeCanvas,
|
||||
{
|
||||
labels: aggregatedTimestamps,
|
||||
datasets: [
|
||||
{
|
||||
label: "Total CPU User Usage (%)",
|
||||
data: aggregatedCpuUserPercentage,
|
||||
borderColor: "red",
|
||||
fill: false,
|
||||
},
|
||||
{
|
||||
label: "Total CPU System Usage (%)",
|
||||
data: aggregatedCpuSystemPercentage,
|
||||
borderColor: "orange",
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
"Time",
|
||||
"CPU Usage (%)",
|
||||
path.join(directory, "aggregated-cpu-usage.png")
|
||||
);
|
||||
|
||||
console.log("Aggregated charts generated.");
|
||||
}
|
||||
|
||||
// Helper function to save a chart
|
||||
async function saveChart(chartJSNodeCanvas, data, xLabel, yLabel, outputFile) {
|
||||
const chartConfig = {
|
||||
type: "line",
|
||||
data,
|
||||
options: {
|
||||
scales: {
|
||||
x: { title: { display: true, text: xLabel } },
|
||||
y: { title: { display: true, text: yLabel } },
|
||||
},
|
||||
},
|
||||
};
|
||||
const chartBuffer = await chartJSNodeCanvas.renderToBuffer(chartConfig);
|
||||
fs.writeFileSync(outputFile, chartBuffer);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue