2024-10-29 16:47:10 -04:00
|
|
|
import { Server, Socket } from "socket.io";
|
2024-12-07 19:33:38 -05:00
|
|
|
import Docker from 'dockerode';
|
2024-12-07 15:41:21 -05:00
|
|
|
import fs from 'fs';
|
2024-10-29 16:47:10 -04:00
|
|
|
|
|
|
|
|
const MAX_USERS_PER_ROOM = 60;
|
|
|
|
|
const MAX_TOTAL_CONNECTIONS = 2000;
|
|
|
|
|
|
|
|
|
|
export const setupWebsocket = (io: Server): void => {
|
|
|
|
|
let totalConnections = 0;
|
|
|
|
|
|
|
|
|
|
io.on("connection", (socket: Socket) => {
|
|
|
|
|
if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
|
|
|
|
|
console.log("Connection limit reached. Disconnecting client.");
|
|
|
|
|
socket.emit("join-failure", "Le nombre maximum de connexions a été atteint");
|
|
|
|
|
socket.disconnect(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
totalConnections++;
|
|
|
|
|
console.log("A user connected:", socket.id, "| Total connections:", totalConnections);
|
|
|
|
|
|
2024-11-11 15:46:02 -05:00
|
|
|
socket.on("create-room", (sentRoomName) => {
|
|
|
|
|
// Ensure sentRoomName is a string before applying toUpperCase()
|
2024-11-26 17:04:22 -05:00
|
|
|
const roomName = (typeof sentRoomName === "string" && sentRoomName.trim() !== "")
|
|
|
|
|
? sentRoomName.toUpperCase()
|
2024-11-11 15:46:02 -05:00
|
|
|
: generateRoomName();
|
2024-11-26 17:04:22 -05:00
|
|
|
|
2024-11-27 18:36:59 -05:00
|
|
|
console.log(`Created room with name: ${roomName}`);
|
2024-10-29 16:47:10 -04:00
|
|
|
if (!io.sockets.adapter.rooms.get(roomName)) {
|
|
|
|
|
socket.join(roomName);
|
|
|
|
|
socket.emit("create-success", roomName);
|
|
|
|
|
} else {
|
|
|
|
|
socket.emit("create-failure");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("join-room", ({ enteredRoomName, username }: { enteredRoomName: string; username: string }) => {
|
|
|
|
|
if (io.sockets.adapter.rooms.has(enteredRoomName)) {
|
|
|
|
|
const clientsInRoom = io.sockets.adapter.rooms.get(enteredRoomName)?.size || 0;
|
|
|
|
|
|
|
|
|
|
if (clientsInRoom <= MAX_USERS_PER_ROOM) {
|
|
|
|
|
socket.join(enteredRoomName);
|
|
|
|
|
socket.to(enteredRoomName).emit("user-joined", { id: socket.id, name: username, answers: [] });
|
|
|
|
|
socket.emit("join-success");
|
|
|
|
|
} else {
|
|
|
|
|
socket.emit("join-failure", "La salle est remplie");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
socket.emit("join-failure", "Le nom de la salle n'existe pas");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("next-question", ({ roomName, question }: { roomName: string; question: string }) => {
|
|
|
|
|
socket.to(roomName).emit("next-question", question);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("launch-student-mode", ({ roomName, questions }: { roomName: string; questions: string[] }) => {
|
|
|
|
|
socket.to(roomName).emit("launch-student-mode", questions);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("end-quiz", ({ roomName }: { roomName: string }) => {
|
|
|
|
|
socket.to(roomName).emit("end-quiz");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("message", (data: string) => {
|
|
|
|
|
console.log("Received message from", socket.id, ":", data);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("disconnect", () => {
|
|
|
|
|
totalConnections--;
|
|
|
|
|
console.log("A user disconnected:", socket.id, "| Total connections:", totalConnections);
|
|
|
|
|
|
|
|
|
|
for (const [room] of io.sockets.adapter.rooms) {
|
|
|
|
|
if (room !== socket.id) {
|
|
|
|
|
io.to(room).emit("user-disconnected", socket.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.on("submit-answer", ({
|
|
|
|
|
roomName,
|
|
|
|
|
username,
|
|
|
|
|
answer,
|
|
|
|
|
idQuestion,
|
|
|
|
|
}: {
|
|
|
|
|
roomName: string;
|
|
|
|
|
username: string;
|
|
|
|
|
answer: string;
|
|
|
|
|
idQuestion: string;
|
|
|
|
|
}) => {
|
|
|
|
|
socket.to(roomName).emit("submit-answer-room", {
|
|
|
|
|
idUser: socket.id,
|
|
|
|
|
username,
|
|
|
|
|
answer,
|
|
|
|
|
idQuestion,
|
|
|
|
|
});
|
|
|
|
|
});
|
2024-11-11 15:16:59 -05:00
|
|
|
|
|
|
|
|
socket.on("error", (error) => {
|
|
|
|
|
console.error("WebSocket server error:", error);
|
|
|
|
|
});
|
2024-10-29 16:47:10 -04:00
|
|
|
|
2024-11-11 15:16:59 -05:00
|
|
|
|
2024-11-26 17:04:22 -05:00
|
|
|
// Stress Testing
|
|
|
|
|
|
2024-11-27 18:36:59 -05:00
|
|
|
socket.on("message-from-teacher", ({ roomName, message }: { roomName: string; message: string }) => {
|
2024-11-26 17:04:22 -05:00
|
|
|
console.log(`Message reçu dans la salle ${roomName} : ${message}`);
|
2024-11-27 18:36:59 -05:00
|
|
|
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 });
|
2024-11-26 17:04:22 -05:00
|
|
|
});
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
interface ContainerStats {
|
|
|
|
|
containerId: string;
|
|
|
|
|
containerName: string;
|
|
|
|
|
memoryUsedMB: number | null;
|
|
|
|
|
memoryUsedPercentage: number | null;
|
|
|
|
|
cpuUsedPercentage: number | null;
|
|
|
|
|
error?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 20:24:56 -05:00
|
|
|
class ContainerMetrics {
|
2024-12-07 19:33:38 -05:00
|
|
|
private docker: Docker;
|
|
|
|
|
private containerName: string;
|
2024-12-07 15:41:21 -05:00
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
private bytesToMB(bytes: number): number {
|
|
|
|
|
return Math.round(bytes / (1024 * 1024));
|
2024-12-07 15:41:21 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
constructor() {
|
|
|
|
|
this.docker = new Docker({
|
|
|
|
|
socketPath: process.platform === 'win32' ? '//./pipe/docker_engine' : '/var/run/docker.sock'
|
|
|
|
|
});
|
|
|
|
|
this.containerName = `room_${process.env.ROOM_ID}`;
|
2024-12-07 15:41:21 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
private async getContainerNetworks(containerId: string): Promise<string[]> {
|
|
|
|
|
const container = this.docker.getContainer(containerId);
|
|
|
|
|
const info = await container.inspect();
|
|
|
|
|
return Object.keys(info.NetworkSettings.Networks);
|
2024-12-07 15:41:21 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
public async getAllContainerStats(): Promise<ContainerStats[]> {
|
2024-12-07 15:41:21 -05:00
|
|
|
try {
|
2024-12-07 19:33:38 -05:00
|
|
|
// First get our container to find its networks
|
|
|
|
|
const ourContainer = await this.docker.listContainers({
|
|
|
|
|
all: true,
|
|
|
|
|
filters: { name: [this.containerName] }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!ourContainer.length) {
|
|
|
|
|
throw new Error(`Container ${this.containerName} not found`);
|
2024-12-07 15:41:21 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
const ourNetworks = await this.getContainerNetworks(ourContainer[0].Id);
|
|
|
|
|
|
|
|
|
|
// Get all containers
|
|
|
|
|
const allContainers = await this.docker.listContainers();
|
|
|
|
|
|
|
|
|
|
// Get stats for containers on the same networks
|
|
|
|
|
const containerStats = await Promise.all(
|
|
|
|
|
allContainers.map(async (container): Promise<ContainerStats | null> => {
|
|
|
|
|
try {
|
|
|
|
|
const containerNetworks = await this.getContainerNetworks(container.Id);
|
|
|
|
|
// Check if container shares any network with our container
|
|
|
|
|
if (!containerNetworks.some(network => ourNetworks.includes(network))) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const stats = await this.docker.getContainer(container.Id).stats({ stream: false });
|
|
|
|
|
|
|
|
|
|
const memoryStats = {
|
|
|
|
|
usage: stats.memory_stats.usage,
|
|
|
|
|
limit: stats.memory_stats.limit || 0,
|
|
|
|
|
percent: stats.memory_stats.limit ? (stats.memory_stats.usage / stats.memory_stats.limit) * 100 : 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cpuDelta = stats.cpu_stats?.cpu_usage?.total_usage - (stats.precpu_stats?.cpu_usage?.total_usage || 0);
|
|
|
|
|
const systemDelta = stats.cpu_stats?.system_cpu_usage - (stats.precpu_stats?.system_cpu_usage || 0);
|
|
|
|
|
const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * (stats.cpu_stats?.online_cpus || 1) * 100 : 0;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
containerId: container.Id,
|
|
|
|
|
containerName: container.Names[0].replace(/^\//, ''),
|
|
|
|
|
memoryUsedMB: this.bytesToMB(memoryStats.usage),
|
|
|
|
|
memoryUsedPercentage: memoryStats.percent,
|
|
|
|
|
cpuUsedPercentage: cpuPercent
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
containerId: container.Id,
|
|
|
|
|
containerName: container.Names[0].replace(/^\//, ''),
|
|
|
|
|
memoryUsedMB: null,
|
|
|
|
|
memoryUsedPercentage: null,
|
|
|
|
|
cpuUsedPercentage: null,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Change the filter to use proper type predicate
|
|
|
|
|
return containerStats.filter((stats): stats is ContainerStats => stats !== null);
|
2024-12-05 20:24:56 -05:00
|
|
|
} catch (error) {
|
2024-12-07 19:33:38 -05:00
|
|
|
console.error('Stats error:', error);
|
|
|
|
|
return [{
|
|
|
|
|
containerId: 'unknown',
|
|
|
|
|
containerName: 'unknown',
|
|
|
|
|
memoryUsedMB: null,
|
|
|
|
|
memoryUsedPercentage: null,
|
|
|
|
|
cpuUsedPercentage: null,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error)
|
|
|
|
|
}];
|
2024-12-05 20:24:56 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const containerMetrics = new ContainerMetrics();
|
|
|
|
|
|
2024-12-07 19:33:38 -05:00
|
|
|
socket.on("get-usage", async () => {
|
2024-12-05 20:24:56 -05:00
|
|
|
try {
|
2024-12-07 19:33:38 -05:00
|
|
|
const usageData = await containerMetrics.getAllContainerStats();
|
2024-12-05 20:24:56 -05:00
|
|
|
socket.emit("usage-data", usageData);
|
2024-11-26 17:04:22 -05:00
|
|
|
} catch (error) {
|
|
|
|
|
socket.emit("error", { message: "Failed to retrieve usage data" });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
});
|
2024-11-11 15:16:59 -05:00
|
|
|
|
2024-10-29 16:47:10 -04:00
|
|
|
const generateRoomName = (length = 6): string => {
|
|
|
|
|
const characters = "0123456789";
|
|
|
|
|
let result = "";
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
};
|