aboutsummaryrefslogtreecommitdiff
path: root/src/server/DashStats.ts
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
committerSophie Zhang <sophie_zhang@brown.edu>2024-01-25 11:35:26 -0500
commitf3dab2a56db5e4a6a3dca58185d94e1ff7d1dc32 (patch)
treea7bc895266b53bb620dbd2dd71bad2e83b555446 /src/server/DashStats.ts
parentb5c5410b4af5d2c68d2107d3f064f6e3ec4ac3f2 (diff)
parent136f3d9f349d54e8bdd73b6380ea47c19e5edebf (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/server/DashStats.ts')
-rw-r--r--src/server/DashStats.ts154
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`;