mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
refactor
This commit is contained in:
parent
11222c70bd
commit
49fbdb1ffd
9 changed files with 355 additions and 345 deletions
|
|
@ -24,6 +24,7 @@ export const setupWebsocket = (io: Server): void => {
|
||||||
? sentRoomName.toUpperCase()
|
? sentRoomName.toUpperCase()
|
||||||
: generateRoomName();
|
: generateRoomName();
|
||||||
|
|
||||||
|
console.log(`Created room with name: ${roomName}`);
|
||||||
if (!io.sockets.adapter.rooms.get(roomName)) {
|
if (!io.sockets.adapter.rooms.get(roomName)) {
|
||||||
socket.join(roomName);
|
socket.join(roomName);
|
||||||
socket.emit("create-success", roomName);
|
socket.emit("create-success", roomName);
|
||||||
|
|
@ -101,9 +102,14 @@ export const setupWebsocket = (io: Server): void => {
|
||||||
|
|
||||||
// Stress Testing
|
// Stress Testing
|
||||||
|
|
||||||
socket.on("message-test", ({ roomName, message }: { roomName: string; message: string }) => {
|
socket.on("message-from-teacher", ({ roomName, message }: { roomName: string; message: string }) => {
|
||||||
console.log(`Message reçu dans la salle ${roomName} : ${message}`);
|
console.log(`Message reçu dans la salle ${roomName} : ${message}`);
|
||||||
socket.to(roomName).emit("message", { id: socket.id, message });
|
socket.to(roomName).emit("message-sent-teacher", { message });
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message-from-student", ({ roomName, message }: { roomName: string; message: string }) => {
|
||||||
|
console.log(`Message reçu dans la salle ${roomName} : ${message}`);
|
||||||
|
socket.to(roomName).emit("message-sent-student", { message });
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("get-usage", () => {
|
socket.on("get-usage", () => {
|
||||||
|
|
|
||||||
72
test/stressTest/class/roomParticipant.js
Normal file
72
test/stressTest/class/roomParticipant.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
|
||||||
|
export class RoomParticipant {
|
||||||
|
constructor(username, roomName) {
|
||||||
|
this.username = username;
|
||||||
|
this.roomName = roomName;
|
||||||
|
this.socket = null;
|
||||||
|
this.maxRetries = 3;
|
||||||
|
this.retryDelay = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectToRoom(baseUrl, onConnectCallback) {
|
||||||
|
let retries = 0;
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const socket = io(baseUrl, {
|
||||||
|
path: `/api/room/${this.roomName}/socket`,
|
||||||
|
transports: ['websocket'],
|
||||||
|
timeout: 5000,
|
||||||
|
reconnection: true,
|
||||||
|
reconnectionAttempts: 3,
|
||||||
|
reconnectionDelay: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const connectionTimeout = setTimeout(() => {
|
||||||
|
socket.close();
|
||||||
|
reject(new Error('Connection timeout'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
clearTimeout(connectionTimeout);
|
||||||
|
this.socket = socket;
|
||||||
|
if (onConnectCallback) {
|
||||||
|
onConnectCallback();
|
||||||
|
}
|
||||||
|
resolve(socket);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('connect_error', (error) => {
|
||||||
|
clearTimeout(connectionTimeout);
|
||||||
|
reject(new Error(`Connection error: ${error.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
while (retries < this.maxRetries) {
|
||||||
|
try {
|
||||||
|
return await connect();
|
||||||
|
} catch (error) {
|
||||||
|
retries++;
|
||||||
|
if (retries === this.maxRetries) {
|
||||||
|
throw new Error(`Failed to connect ${this.username} after ${this.maxRetries} attempts: ${error.message}`);
|
||||||
|
}
|
||||||
|
console.warn(`Retry ${retries}/${this.maxRetries} for ${this.username}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.disconnect();
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,58 +1,41 @@
|
||||||
import { io } from "socket.io-client";
|
// student.js
|
||||||
|
import { RoomParticipant } from './roomParticipant.js';
|
||||||
|
|
||||||
export class Student {
|
export class Student extends RoomParticipant {
|
||||||
constructor(username, roomName) {
|
constructor(username, roomName) {
|
||||||
this.username = username;
|
super(username, roomName);
|
||||||
this.roomName = roomName;
|
|
||||||
this.socket = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToRoom(baseUrl) {
|
connectToRoom(baseUrl) {
|
||||||
return new Promise((resolve, reject) => {
|
return super.connectToRoom(baseUrl, () => {
|
||||||
try {
|
this.joinRoom();
|
||||||
this.socket = io(baseUrl, {
|
this.listenForTeacherMessage();
|
||||||
path: `/api/room/${this.roomName}/socket`,
|
|
||||||
transports: ['websocket'],
|
|
||||||
autoConnect: true,
|
|
||||||
reconnection: true,
|
|
||||||
reconnectionAttempts: 10,
|
|
||||||
reconnectionDelay: 10000,
|
|
||||||
timeout: 20000,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
|
||||||
this.joinRoom(this.roomName, this.username);
|
|
||||||
this.listenForMessages(); // Start listening for messages
|
|
||||||
resolve(this.socket);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('error', (error) => {
|
|
||||||
reject(new Error(`Connection error: ${error.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error connecting ${this.name} to room ${this.roomName}:`, error.message);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
joinRoom(roomName, username) {
|
joinRoom() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.emit('join-room', { roomName, username });
|
this.socket.emit('join-room', {
|
||||||
|
enteredRoomName: this.roomName,
|
||||||
|
username: this.username
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message) {
|
listenForTeacherMessage() {
|
||||||
if (this.socket && this.socket.connected) {
|
|
||||||
this.socket.emit('message-test', { room: this.roomName, message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listenForMessages() {
|
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on('message-test', (data) => {
|
this.socket.on('message-sent-teacher', ({ message }) => {
|
||||||
console.log(`Message received in room ${this.roomName} by ${this.username}:`, data.message);
|
this.respondToTeacher(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respondToTeacher(message) {
|
||||||
|
const reply = `${this.username} replying to: "${message}"`;
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.emit('message-from-student', {
|
||||||
|
roomName: this.roomName,
|
||||||
|
message: reply
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,45 @@
|
||||||
import { io } from "socket.io-client";
|
import { RoomParticipant } from './roomParticipant.js';
|
||||||
|
|
||||||
export class Teacher {
|
export class Teacher extends RoomParticipant {
|
||||||
constructor(username, roomName) {
|
constructor(username, roomName) {
|
||||||
this.username = username;
|
super(username, roomName);
|
||||||
this.roomName = roomName;
|
this.ready = false;
|
||||||
this.socket = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectToRoom(baseUrl) {
|
connectToRoom(baseUrl) {
|
||||||
return new Promise((resolve, reject) => {
|
return super.connectToRoom(baseUrl, () => {
|
||||||
try {
|
this.createRoom();
|
||||||
this.socket = io(baseUrl, {
|
this.listenForStudentMessage();
|
||||||
path: `/api/room/${this.roomName}/socket`,
|
|
||||||
transports: ['websocket'],
|
|
||||||
autoConnect: true,
|
|
||||||
reconnection: true,
|
|
||||||
reconnectionAttempts: 10,
|
|
||||||
reconnectionDelay: 10000,
|
|
||||||
timeout: 20000,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
// Add room creation confirmation listener
|
||||||
this.createRoom(this.roomName);
|
this.socket.on('create-success', () => {
|
||||||
this.listenForMessages(); // Start listening for messages
|
console.log(`Room ${this.roomName} created by teacher ${this.username}`);
|
||||||
resolve(this.socket);
|
this.ready = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('error', (error) => {
|
|
||||||
reject(new Error(`Connection error: ${error.message}`));
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error connecting ${this.name} to room ${this.roomName}:`, error.message);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoom() {
|
createRoom() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.emit('create-room', this.roomName || undefined);
|
this.socket.emit('create-room', this.roomName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(message) {
|
broadcastMessage(message) {
|
||||||
if (this.socket && this.socket.connected) {
|
if (this.socket && this.ready) {
|
||||||
this.socket.emit('message-test', { room: this.roomName, message });
|
this.socket.emit('message-from-teacher', {
|
||||||
|
roomName: this.roomName,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn(`Teacher ${this.username} not ready to broadcast yet`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listenForMessages() {
|
listenForStudentMessage() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on('message-test', (data) => {
|
this.socket.on('message-sent-student', ({ message }) => {
|
||||||
console.log(`Message received in room ${this.roomName} by ${this.username}:`, data.message);
|
//console.log(`Teacher ${this.username} received: "${message}"`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
test/stressTest/class/watcher.js
Normal file
54
test/stressTest/class/watcher.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { RoomParticipant } from './roomParticipant.js';
|
||||||
|
|
||||||
|
export class Watcher extends RoomParticipant {
|
||||||
|
|
||||||
|
roomRessourcesData = [];
|
||||||
|
checkRessourceInterval = null;
|
||||||
|
|
||||||
|
constructor(username, roomName) {
|
||||||
|
super(username, roomName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectToRoom(baseUrl) {
|
||||||
|
await super.connectToRoom(baseUrl);
|
||||||
|
this.startCheckingResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRessource() {
|
||||||
|
if (this.socket?.connected) {
|
||||||
|
try {
|
||||||
|
this.socket.emit("get-usage");
|
||||||
|
this.socket.once("usage-data", (data) => {
|
||||||
|
this.roomRessourcesData.push({ timestamp: Date.now(), ...data });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Error capturing metrics for room ${this.roomName}:`, error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`Socket not connected for room ${this.roomName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startCheckingResources(intervalMs = 500) {
|
||||||
|
if (this.checkRessourceInterval) {
|
||||||
|
console.warn(`Resource checking is already running for room ${this.roomName}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkRessourceInterval = setInterval(() => this.checkRessource(), intervalMs);
|
||||||
|
console.log(`Started resource checking for room ${this.roomName}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCheckingResources() {
|
||||||
|
if (this.checkRessourceInterval) {
|
||||||
|
clearInterval(this.checkRessourceInterval);
|
||||||
|
this.checkRessourceInterval = null;
|
||||||
|
console.log(`Stopped resource checking for room ${this.roomName}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.stopCheckingResources();
|
||||||
|
super.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,227 +1,187 @@
|
||||||
import { attemptLoginOrRegister, createRoomContainer, captureResourceUsageForContainers } from './utility/apiServices.js';
|
import { attemptLoginOrRegister, createRoomContainer } from './utility/apiServices.js';
|
||||||
import { Student } from './class/student.js';
|
import { Student } from './class/student.js';
|
||||||
import { Teacher } from './class/teacher.js';
|
import { Teacher } from './class/teacher.js';
|
||||||
import { writeMetricsToFile, generateGraphs } from './utility/writeMetrics.js';
|
import { Watcher } from './class/watcher.js';
|
||||||
|
|
||||||
const BASE_URL = 'http://localhost';
|
const BASE_URL = 'http://localhost';
|
||||||
const user = { username: 'admin@example.com', password: 'adminPassword' };
|
const user = { username: 'admin@example.com', password: 'adminPassword' };
|
||||||
const numberRooms = 20;
|
const numberRooms = 20;
|
||||||
const studentPerRoom = 58; // Max: 60, 1 reserved for teacher
|
const usersPerRoom = 60;
|
||||||
const roomAssociations = {};
|
const roomAssociations = {};
|
||||||
const allSockets = []; // Tracks all active WebSocket connections
|
const maxMessages = 20; // Number of conversation rounds
|
||||||
|
const conversationInterval = 1000; // Interval between teacher messages (ms)
|
||||||
|
const batchSize = 10; // Number of simultaneous connections
|
||||||
|
const batchDelay = 500;
|
||||||
|
|
||||||
const metrics = {
|
/**
|
||||||
roomsCreated: 0,
|
* Creates rooms with controlled concurrency.
|
||||||
roomsFailed: 0,
|
*/
|
||||||
teachersConnected: 0,
|
async function createRoomContainers() {
|
||||||
teachersFailed: 0,
|
console.log('Attempting login or register to get token');
|
||||||
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('Room creation time');
|
|
||||||
const roomCreationPromises = Array.from({ length: numberRooms }, async () => {
|
|
||||||
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('Failed to create a room.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled(roomCreationPromises);
|
|
||||||
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('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(`Teacher ${teacher.username} connected to room ${roomId}. Latency: ${latency}ms`);
|
|
||||||
} else {
|
|
||||||
metrics.teachersFailed++;
|
|
||||||
console.warn(`Failed to connect teacher ${index} to room ${roomId}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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('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++;
|
|
||||||
} else {
|
|
||||||
metrics.studentsFailed++;
|
|
||||||
console.warn(`Failed to connect student ${roomIndex}_${i} to room ${roomId}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
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('Closing all Socket.IO connections...');
|
|
||||||
allSockets.forEach((socket) => {
|
|
||||||
if (socket && socket.connected) {
|
|
||||||
try {
|
|
||||||
socket.disconnect();
|
|
||||||
console.log('Socket.IO connection disconnected.');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error disconnecting Socket.IO connection:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('All Socket.IO connections have been disconnected.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main function to orchestrate the workflow
|
|
||||||
async function main() {
|
|
||||||
try {
|
|
||||||
metrics.startTime = new Date();
|
|
||||||
|
|
||||||
// Login or register
|
|
||||||
const token = await attemptLoginOrRegister(BASE_URL, user.username, user.password);
|
const token = await attemptLoginOrRegister(BASE_URL, user.username, user.password);
|
||||||
if (!token) throw new Error('Failed to login or register.');
|
if (!token) throw new Error('Failed to login or register.');
|
||||||
|
|
||||||
// Room creation
|
console.log('Room creation');
|
||||||
await createRoomContainers(token);
|
const roomPromises = Array.from({ length: numberRooms }, async (_, index) => {
|
||||||
|
try {
|
||||||
|
const room = await createRoomContainer(BASE_URL, token);
|
||||||
|
if (room?.id) {
|
||||||
|
roomAssociations[room.id] = { watcher: null, teacher: null, students: [] };
|
||||||
|
console.log(`Room ${index + 1} created with ID: ${room.id}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Failed to create room ${index + 1}:`, err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Resource monitoring and test activities
|
// Use Promise.allSettled to ensure all promises complete
|
||||||
const roomIds = Object.keys(roomAssociations);
|
await Promise.allSettled(roomPromises);
|
||||||
const usageCaptureInterval = 100;
|
|
||||||
let testCompleted = false;
|
|
||||||
|
|
||||||
const resourceCapturePromise = captureResourceUsageForContainers(
|
console.log(`Total rooms created: ${Object.keys(roomAssociations).length}`);
|
||||||
BASE_URL,
|
console.log('Finished room creation');
|
||||||
roomIds,
|
}
|
||||||
usageCaptureInterval,
|
|
||||||
() => testCompleted,
|
/**
|
||||||
metrics
|
* Adds participants (teacher, watcher, students) to rooms.
|
||||||
|
*/
|
||||||
|
function addUsersToRoom() {
|
||||||
|
console.log('Adding room participants');
|
||||||
|
Object.keys(roomAssociations).forEach((roomId, roomIndex) => {
|
||||||
|
const participants = roomAssociations[roomId];
|
||||||
|
|
||||||
|
// Assign a teacher and watcher
|
||||||
|
console.log('adding users to room ' + roomId);
|
||||||
|
participants.teacher = new Teacher(`teacher_${roomIndex}`, roomId);
|
||||||
|
participants.watcher = new Watcher(`watcher_${roomIndex}`, roomId);
|
||||||
|
|
||||||
|
// Add students
|
||||||
|
for (let i = 0; i < usersPerRoom - 2; i++) {
|
||||||
|
participants.students.push(new Student(`student_${roomIndex}_${i}`, roomId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('Finished adding room participants');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects participants to their respective rooms.
|
||||||
|
*/
|
||||||
|
async function connectParticipants(baseUrl) {
|
||||||
|
console.log('Connecting participants in batches');
|
||||||
|
const batchSize = 10; // Connect 10 participants at a time
|
||||||
|
const batchDelay = 500; // Wait 500ms between batches
|
||||||
|
|
||||||
|
for (const [roomId, participants] of Object.entries(roomAssociations)) {
|
||||||
|
console.log(`Processing room ${roomId}`);
|
||||||
|
|
||||||
|
// Collect all participants for this room
|
||||||
|
const allParticipants = [
|
||||||
|
participants.teacher,
|
||||||
|
participants.watcher,
|
||||||
|
...participants.students
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
// Process participants in batches
|
||||||
|
for (let i = 0; i < allParticipants.length; i += batchSize) {
|
||||||
|
const batch = allParticipants.slice(i, i + batchSize);
|
||||||
|
const batchPromises = batch.map(participant =>
|
||||||
|
participant.connectToRoom(baseUrl)
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(
|
||||||
|
`Failed to connect ${participant.username} in room ${roomId}:`,
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const testPromise = (async () => {
|
await Promise.all(batchPromises);
|
||||||
await addAndConnectTeachers();
|
await new Promise(resolve => setTimeout(resolve, batchDelay));
|
||||||
await addAndConnectStudents();
|
}
|
||||||
await simulateConversation();
|
}
|
||||||
testCompleted = true;
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
||||||
})();
|
|
||||||
|
|
||||||
await Promise.all([resourceCapturePromise, testPromise]);
|
console.log('Finished connecting participants');
|
||||||
|
}
|
||||||
|
|
||||||
metrics.endTime = new Date();
|
/**
|
||||||
writeMetricsToFile(metrics);
|
* Simulates a conversation between the teacher and students in each room.
|
||||||
await generateGraphs(metrics.resourceUsage, metrics);
|
*/
|
||||||
|
async function simulateParticipants() {
|
||||||
|
const conversationPromises = Object.entries(roomAssociations).map(async ([roomId, participants]) => {
|
||||||
|
const { teacher, students } = participants;
|
||||||
|
|
||||||
console.log("All tasks completed successfully!");
|
if (!teacher || students.length === 0) {
|
||||||
|
console.warn(`Room ${roomId} has no teacher or students to simulate.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Starting simulation for room ${roomId}`);
|
||||||
|
|
||||||
|
// Wait for room creation and joins to complete
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
for (let i = 0; i < maxMessages; i++) {
|
||||||
|
const teacherMessage = `Message ${i + 1} from ${teacher.username}`;
|
||||||
|
teacher.broadcastMessage(teacherMessage);
|
||||||
|
// Add delay between messages
|
||||||
|
await new Promise(resolve => setTimeout(resolve, conversationInterval));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Finished simulation for room ${roomId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(conversationPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects all participants from rooms.
|
||||||
|
*/
|
||||||
|
function disconnectParticipants() {
|
||||||
|
console.time('Disconnecting participants');
|
||||||
|
Object.values(roomAssociations).forEach(participants => {
|
||||||
|
participants.teacher?.disconnect();
|
||||||
|
participants.watcher?.disconnect();
|
||||||
|
participants.students.forEach(student => student.disconnect());
|
||||||
|
});
|
||||||
|
console.timeEnd('Disconnecting participants');
|
||||||
|
console.log('All participants disconnected successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main orchestration function.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await createRoomContainers();
|
||||||
|
addUsersToRoom();
|
||||||
|
await connectParticipants(BASE_URL);
|
||||||
|
await simulateParticipants();
|
||||||
|
|
||||||
|
console.log('All tasks completed successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error.message);
|
console.error('Error:', error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle process interruptions
|
// Graceful shutdown handlers
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log('Process interrupted (Ctrl+C).');
|
console.log('Process interrupted (Ctrl+C).');
|
||||||
closeAllSockets();
|
disconnectParticipants();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('exit', () => closeAllSockets());
|
process.on('exit', disconnectParticipants);
|
||||||
process.on('uncaughtException', (err) => {
|
|
||||||
|
process.on('uncaughtException', err => {
|
||||||
console.error('Uncaught Exception:', err);
|
console.error('Uncaught Exception:', err);
|
||||||
closeAllSockets();
|
disconnectParticipants();
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
|
||||||
console.error('Unhandled Rejection:', promise, 'reason:', reason);
|
|
||||||
closeAllSockets();
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||||
|
disconnectParticipants();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Execute the main function
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
26
test/stressTest/package-lock.json
generated
26
test/stressTest/package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"chartjs-node-canvas": "^4.1.6",
|
"chartjs-node-canvas": "^4.1.6",
|
||||||
"dockerode": "^4.0.2",
|
"dockerode": "^4.0.2",
|
||||||
|
"p-limit": "^6.1.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.8.1"
|
"socket.io-client": "^4.8.1"
|
||||||
}
|
}
|
||||||
|
|
@ -814,6 +815,20 @@
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-limit": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==",
|
||||||
|
"dependencies": {
|
||||||
|
"yocto-queue": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-is-absolute": {
|
"node_modules/path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
|
@ -1186,6 +1201,17 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
|
},
|
||||||
|
"node_modules/yocto-queue": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"chartjs-node-canvas": "^4.1.6",
|
"chartjs-node-canvas": "^4.1.6",
|
||||||
"dockerode": "^4.0.2",
|
"dockerode": "^4.0.2",
|
||||||
|
"p-limit": "^6.1.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.8.1"
|
"socket.io-client": "^4.8.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { io } from "socket.io-client";
|
|
||||||
|
|
||||||
/**
|
// Logs in a user.
|
||||||
* Logs in a user.
|
|
||||||
* @param {string} baseUrl - The base URL of the API.
|
|
||||||
* @param {string} email - The user's email.
|
|
||||||
* @param {string} password - The user's password.
|
|
||||||
* @returns {Promise<string>} - The authentication token if successful.
|
|
||||||
*/
|
|
||||||
async function login(baseUrl, email, password) {
|
async function login(baseUrl, email, password) {
|
||||||
if (!email || !password) throw new Error("Email and password are required.");
|
if (!email || !password) throw new Error("Email and password are required.");
|
||||||
|
|
||||||
|
|
@ -27,13 +20,7 @@ async function login(baseUrl, email, password) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Registers a new user.
|
||||||
* Registers a new user.
|
|
||||||
* @param {string} baseUrl - The base URL of the API.
|
|
||||||
* @param {string} email - The user's email.
|
|
||||||
* @param {string} password - The user's password.
|
|
||||||
* @returns {Promise<string>} - A success message if registration is successful.
|
|
||||||
*/
|
|
||||||
async function register(baseUrl, email, password) {
|
async function register(baseUrl, email, password) {
|
||||||
if (!email || !password) throw new Error("Email and password are required.");
|
if (!email || !password) throw new Error("Email and password are required.");
|
||||||
|
|
||||||
|
|
@ -53,13 +40,7 @@ async function register(baseUrl, email, password) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Attempts to log in a user, or registers and logs in if the login fails.
|
||||||
* Attempts to log in a user, or registers and logs in if the login fails.
|
|
||||||
* @param {string} baseUrl - The base URL of the API.
|
|
||||||
* @param {string} username - The user's email/username.
|
|
||||||
* @param {string} password - The user's password.
|
|
||||||
* @returns {Promise<string|null>} - The authentication token if successful, otherwise null.
|
|
||||||
*/
|
|
||||||
export async function attemptLoginOrRegister(baseUrl, username, password) {
|
export async function attemptLoginOrRegister(baseUrl, username, password) {
|
||||||
try {
|
try {
|
||||||
return await login(baseUrl, username, password);
|
return await login(baseUrl, username, password);
|
||||||
|
|
@ -75,12 +56,7 @@ export async function attemptLoginOrRegister(baseUrl, username, password) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Creates a new room
|
||||||
* Creates a new room.
|
|
||||||
* @param {string} baseUrl - The base URL of the API.
|
|
||||||
* @param {string} token - The authorization token.
|
|
||||||
* @returns {Promise<object>} - The created room object if successful.
|
|
||||||
*/
|
|
||||||
export async function createRoomContainer(baseUrl, token) {
|
export async function createRoomContainer(baseUrl, token) {
|
||||||
if (!token) throw new Error("Authorization token is required.");
|
if (!token) throw new Error("Authorization token is required.");
|
||||||
|
|
||||||
|
|
@ -99,59 +75,3 @@ export async function createRoomContainer(baseUrl, token) {
|
||||||
throw error;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue