diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2024-01-25 11:35:26 -0500 |
| commit | f3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch) | |
| tree | a7bc895266b53bb620dbd2dd71bad2e83b555446 /src/server/DashStats.ts | |
| parent | b5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff) | |
| parent | 136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff) | |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/server/DashStats.ts')
| -rw-r--r-- | src/server/DashStats.ts | 154 |
1 files changed, 76 insertions, 78 deletions
diff --git a/src/server/DashStats.ts b/src/server/DashStats.ts index 8d341db63..a9e6af67c 100644 --- a/src/server/DashStats.ts +++ b/src/server/DashStats.ts @@ -3,23 +3,24 @@ import { Response } from 'express'; import SocketIO from 'socket.io'; import { timeMap } from './ApiManagers/UserManager'; import { WebSocket } from './websocket'; -const fs = require('fs'); +import * as fs from 'fs'; /** * DashStats focuses on tracking user data for each session. - * + * * This includes time connected, number of operations, and * the rate of their operations */ export namespace DashStats { - export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend. + export const SAMPLING_INTERVAL = 1000; // in milliseconds (ms) - Time interval to update the frontend. export const RATE_INTERVAL = 10; // in seconds (s) - Used to calculate rate - const statsCSVFilename = './src/server/stats/userLoginStats.csv'; + const statsCSVDirectory = './src/server/stats/'; + const statsCSVFilename = statsCSVDirectory + 'userLoginStats.csv'; const columns = ['USERNAME', 'ACTION', 'TIME']; /** - * UserStats holds the stats associated with a particular user. + * UserStats holds the stats associated with a particular user. */ interface UserStats { socketId: string; @@ -30,18 +31,18 @@ export namespace DashStats { } /** - * UserLastOperations is the queue object for each user - * storing their past operations. + * UserLastOperations is the queue object for each user + * storing their past operations. */ interface UserLastOperations { sampleOperations: number; // stores how many operations total are in this rate section (10 sec, for example) lastSampleOperations: number; // stores how many total operations were recorded at the last sample - previousOperationsQueue: number[]; // stores the operations to calculate rate. + previousOperationsQueue: number[]; // stores the operations to calculate rate. } /** * StatsDataBundle represents an object that will be sent to the frontend view - * on each websocket update. + * on each websocket update. */ interface StatsDataBundle { connectedUsers: UserStats[]; @@ -57,48 +58,44 @@ export namespace DashStats { } /** - * ServerTraffic describes the current traffic going to the backend. + * ServerTraffic describes the current traffic going to the backend. */ enum ServerTraffic { NOT_BUSY, BUSY, - VERY_BUSY + VERY_BUSY, } // These values can be changed after further testing how many - // users correspond to each traffic level in Dash. + // users correspond to each traffic level in Dash. const BUSY_SERVER_BOUND = 2; const VERY_BUSY_SERVER_BOUND = 3; - const serverTrafficMessages = [ - "Not Busy", - "Busy", - "Very Busy" - ] + const serverTrafficMessages = ['Not Busy', 'Busy', 'Very Busy']; // lastUserOperations maps each username to a UserLastOperations - // structure + // structure export const lastUserOperations = new Map<string, UserLastOperations>(); /** * handleStats is called when the /stats route is called, providing a JSON * object with relevant stats. In this case, we return the number of - * current connections and + * current connections and * @param res Response object from Express */ export function handleStats(res: Response) { let current = getCurrentStats(); const results: CSVStore[] = []; res.json({ - currentConnections: current.length, - socketMap: current, - }); + currentConnections: current.length, + socketMap: current, + }); } /** - * getUpdatedStatesBundle() sends an updated copy of the current stats to the - * frontend /statsview route via websockets. - * + * getUpdatedStatesBundle() sends an updated copy of the current stats to the + * frontend /statsview route via websockets. + * * @returns a StatsDataBundle that is sent to the frontend view on each websocket update */ export function getUpdatedStatsBundle(): StatsDataBundle { @@ -106,43 +103,43 @@ export namespace DashStats { return { connectedUsers: current, - } + }; } /** - * handleStatsView() is called when the /statsview route is called. This + * handleStatsView() is called when the /statsview route is called. This * will use pug to render a frontend view of the current stats - * - * @param res + * + * @param res */ export function handleStatsView(res: Response) { let current = getCurrentStats(); - let connectedUsers = current.map((socketPair) => { - return socketPair.time + " - " + socketPair.username + " Operations: " + socketPair.operations; - }) + let connectedUsers = current.map(socketPair => { + return socketPair.time + ' - ' + socketPair.username + ' Operations: ' + socketPair.operations; + }); let serverTraffic = ServerTraffic.NOT_BUSY; - if(current.length < BUSY_SERVER_BOUND) { + if (current.length < BUSY_SERVER_BOUND) { serverTraffic = ServerTraffic.NOT_BUSY; - } else if(current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) { + } else if (current.length >= BUSY_SERVER_BOUND && current.length < VERY_BUSY_SERVER_BOUND) { serverTraffic = ServerTraffic.BUSY; } else { serverTraffic = ServerTraffic.VERY_BUSY; } - - res.render("stats.pug", { - title: "Dash Stats", + + res.render('stats.pug', { + title: 'Dash Stats', numConnections: connectedUsers.length, serverTraffic: serverTraffic, - serverTrafficMessage : serverTrafficMessages[serverTraffic], - connectedUsers: connectedUsers + serverTrafficMessage: serverTrafficMessages[serverTraffic], + connectedUsers: connectedUsers, }); } /** - * logUserLogin() writes a login event to the CSV file. - * + * logUserLogin() writes a login event to the CSV file. + * * @param username the username in the format of "username@domain.com logged in" * @param socket the websocket associated with the current connection */ @@ -152,12 +149,13 @@ export namespace DashStats { console.log(magenta(`User ${username.split(' ')[0]} logged in at: ${currentDate.toISOString()}`)); let toWrite: CSVStore = { - USERNAME : username, - ACTION : "loggedIn", - TIME : currentDate.toISOString() - } + USERNAME: username, + ACTION: 'loggedIn', + TIME: currentDate.toISOString(), + }; - let statsFile = fs.createWriteStream(statsCSVFilename, { flags: "a"}); + if (!fs.existsSync(statsCSVDirectory)) fs.mkdirSync(statsCSVDirectory); + let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' }); statsFile.write(convertToCSV(toWrite)); statsFile.end(); console.log(cyan(convertToCSV(toWrite))); @@ -165,21 +163,21 @@ export namespace DashStats { } /** - * logUserLogout() writes a logout event to the CSV file. - * + * logUserLogout() writes a logout event to the CSV file. + * * @param username the username in the format of "username@domain.com logged in" - * @param socket the websocket associated with the current connection. + * @param socket the websocket associated with the current connection. */ export function logUserLogout(username: string | undefined, socket: SocketIO.Socket) { if (!(username === undefined)) { let currentDate = new Date(); - let statsFile = fs.createWriteStream(statsCSVFilename, { flags: "a"}); + let statsFile = fs.createWriteStream(statsCSVFilename, { flags: 'a' }); let toWrite: CSVStore = { - USERNAME : username, - ACTION : "loggedOut", - TIME : currentDate.toISOString() - } + USERNAME: username, + ACTION: 'loggedOut', + TIME: currentDate.toISOString(), + }; statsFile.write(convertToCSV(toWrite)); statsFile.end(); } @@ -188,22 +186,22 @@ export namespace DashStats { /** * getLastOperationsOrDefault() is a helper method that will attempt * to query the lastUserOperations map for a specified username. If the - * username is not in the map, an empty UserLastOperations object is returned. - * @param username + * username is not in the map, an empty UserLastOperations object is returned. + * @param username * @returns the user's UserLastOperations structure or an empty * UserLastOperations object (All values set to 0) if the username is not found. */ function getLastOperationsOrDefault(username: string): UserLastOperations { - if(lastUserOperations.get(username) === undefined) { + if (lastUserOperations.get(username) === undefined) { let initializeOperationsQueue = []; - for(let i = 0; i < RATE_INTERVAL; i++) { + for (let i = 0; i < RATE_INTERVAL; i++) { initializeOperationsQueue.push(0); } return { sampleOperations: 0, lastSampleOperations: 0, - previousOperationsQueue: initializeOperationsQueue - } + previousOperationsQueue: initializeOperationsQueue, + }; } return lastUserOperations.get(username)!; } @@ -211,19 +209,19 @@ export namespace DashStats { /** * updateLastOperations updates a specific user's UserLastOperations information * for the current sampling cycle. The method removes old/outdated counts for - * operations from the queue and adds new data for the current sampling - * cycle to the queue, updating the total count as it goes. + * operations from the queue and adds new data for the current sampling + * cycle to the queue, updating the total count as it goes. * @param lastOperationData the old UserLastOperations data that must be updated * @param currentOperations the total number of operations measured for this sampling cycle. - * @returns the udpated UserLastOperations structure. + * @returns the udpated UserLastOperations structure. */ function updateLastOperations(lastOperationData: UserLastOperations, currentOperations: number): UserLastOperations { // create a copy of the UserLastOperations to modify let newLastOperationData: UserLastOperations = { sampleOperations: lastOperationData.sampleOperations, lastSampleOperations: lastOperationData.lastSampleOperations, - previousOperationsQueue: lastOperationData.previousOperationsQueue.slice() - } + previousOperationsQueue: lastOperationData.previousOperationsQueue.slice(), + }; let newSampleOperations = newLastOperationData.sampleOperations; newSampleOperations -= newLastOperationData.previousOperationsQueue.shift()!; // removes and returns the first element of the queue @@ -241,20 +239,20 @@ export namespace DashStats { /** * getUserOperationsOrDefault() is a helper method to get the user's total - * operations for the CURRENT sampling interval. The method will return 0 + * operations for the CURRENT sampling interval. The method will return 0 * if the username is not in the userOperations map. * @param username the username to search the map for - * @returns the total number of operations recorded up to this sampling cycle. + * @returns the total number of operations recorded up to this sampling cycle. */ function getUserOperationsOrDefault(username: string): number { - return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)! + return WebSocket.userOperations.get(username) === undefined ? 0 : WebSocket.userOperations.get(username)!; } /** * getCurrentStats() calculates the total stats for this cycle. In this case, - * getCurrentStats() returns an Array of UserStats[] objects describing + * getCurrentStats() returns an Array of UserStats[] objects describing * the stats for each user - * @returns an array of UserStats storing data for each user at the current moment. + * @returns an array of UserStats storing data for each user at the current moment. */ function getCurrentStats(): UserStats[] { let socketPairs: UserStats[] = []; @@ -262,20 +260,20 @@ export namespace DashStats { let username = value.split(' ')[0]; let connectionTime = new Date(timeMap[username]); - let connectionTimeString = connectionTime.toLocaleDateString() + " " + connectionTime.toLocaleTimeString(); + let connectionTimeString = connectionTime.toLocaleDateString() + ' ' + connectionTime.toLocaleTimeString(); if (!key.disconnected) { let lastRecordedOperations = getLastOperationsOrDefault(username); let currentUserOperationCount = getUserOperationsOrDefault(username); - socketPairs.push({ + socketPairs.push({ socketId: key.id, username: username, - time: connectionTimeString.includes("Invalid Date") ? "" : connectionTimeString, - operations : WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0, - rate: lastRecordedOperations.sampleOperations + time: connectionTimeString.includes('Invalid Date') ? '' : connectionTimeString, + operations: WebSocket.userOperations.get(username) ? WebSocket.userOperations.get(username)! : 0, + rate: lastRecordedOperations.sampleOperations, }); - lastUserOperations.set(username, updateLastOperations(lastRecordedOperations,currentUserOperationCount)); + lastUserOperations.set(username, updateLastOperations(lastRecordedOperations, currentUserOperationCount)); } } return socketPairs; @@ -283,9 +281,9 @@ export namespace DashStats { /** * convertToCSV() is a helper method that stringifies a CSVStore object - * that can be written to the CSV file later. + * that can be written to the CSV file later. * @param dataObject the object to stringify - * @returns the object as a string. + * @returns the object as a string. */ function convertToCSV(dataObject: CSVStore): string { return `${dataObject.USERNAME},${dataObject.ACTION},${dataObject.TIME}\n`; |
