From 5c7d3bedd810cced99c91297b00f6e1128450f5a Mon Sep 17 00:00:00 2001 From: Holly Date: Wed, 3 Jul 2024 23:47:45 +0100 Subject: [PATCH] add prom metrics --- server/config.example.json | 3 ++- server/index.ts | 50 +++++++++++++++++++++++++++++++++--- server/objects/Config.ts | 7 +++-- server/objects/RemoteUser.ts | 6 ++++- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/server/config.example.json b/server/config.example.json index 76642c6..bba38e7 100644 --- a/server/config.example.json +++ b/server/config.example.json @@ -1,7 +1,8 @@ { "ports": { "http": 39194, - "ws": 39195 + "ws": 39195, + "metrics": 9100 }, "session": { "validity": 86400, diff --git a/server/index.ts b/server/index.ts index 745f95a..673b642 100644 --- a/server/index.ts +++ b/server/index.ts @@ -20,6 +20,9 @@ import CreateEditPartyData from "./interfaces/CreateEditPartyData"; import JoinPartyData from "./interfaces/JoinPartyData"; import IdData from "./interfaces/IdData"; import Party from "./objects/Party"; +import SimpleProm from "simple-prom"; +import Gauge from "simple-prom/lib/objects/Gauge"; +import Counter from "simple-prom/lib/objects/Counter"; Console.customHeader(`MultiProbe server started at ${new Date()}`); @@ -27,6 +30,26 @@ const users = new FunkyArray(); new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name); +const metrics = SimpleProm.init({ + selfHost: true, + selfHostPort: Config.ports.metrics ?? 9100 +}); + +const webSessions = metrics.addMetric(new Gauge("multiprobe_web_sessions")); +webSessions.setHelpText("Number of valid web sessions"); + +const onlineUsers = metrics.addMetric(new Gauge("multiprobe_open_connections")); +onlineUsers.setHelpText("Number of connections to the websocket"); + +const onlineUsersUnique = metrics.addMetric(new Gauge("multiprobe_unique_connections")); +onlineUsersUnique.setHelpText("Number of unique user connections to the websocket"); + +const dataIn = metrics.addMetric(new Counter("multiprobe_data_in")); +dataIn.setHelpText("Data received by the server in bytes"); + +const dataOut = metrics.addMetric(new Counter("multiprobe_data_out")); +dataOut.setHelpText("Data sent by the server in bytes"); + // Web stuff const sessions = new FunkyArray(); @@ -38,6 +61,7 @@ const sessionExpiryInterval = setInterval(() => { sessions.remove(key); } } + webSessions.Value = sessions.length; }, 3600000); const fastify = Fastify({ @@ -178,6 +202,7 @@ fastify.post("/account/register", async (req, res) => { const key = randomBytes(Config.session.length).toString("hex"); sessions.set(key, new SessionUser(user.Id, validPeriod)); + webSessions.Value = sessions.length; res.setCookie("MP_SESSION", key, { path: "/", @@ -204,6 +229,7 @@ fastify.post("/account/login", async (req, res) => { const key = randomBytes(Config.session.length).toString("hex"); sessions.set(key, new SessionUser(user.Id, validPeriod)); + webSessions.Value = sessions.length; res.setCookie("MP_SESSION", key, { path: "/", @@ -369,6 +395,19 @@ const afkInterval = setInterval(() => { }); }, 5000); +async function updateConnectionMetrics() { + onlineUsers.Value = users.length; + let userCount = 0; + const checkedUsers = new Array(); + await users.forEach(user => { + if (!checkedUsers.includes(user.username)) { + userCount++; + checkedUsers.push(user.username); + } + }); + onlineUsersUnique.Value = userCount; +} + websocketServer.on("connection", (socket) => { const myUUID = crypto.randomUUID(); let user:RemoteUser; @@ -381,6 +420,7 @@ websocketServer.on("connection", (socket) => { users.forEach(otherUser => otherUser.send(userLeftPacket)); sendGroupUpdate(user); } + updateConnectionMetrics(); } async function sendGroupUpdate(sendUser:RemoteUser, groupSend = false) { @@ -421,11 +461,14 @@ websocketServer.on("connection", (socket) => { socket.on("message", async (data) => { const reader = createReader(Endian.LE, data as Buffer); + dataIn.add(reader.length); if (reader.length > 0 && reader.length < 1024) { switch (reader.readUByte()) { case MessageType.KeepAlive: { - user.lastKeepAliveTime = Date.now(); + if (user !== undefined) { + user.lastKeepAliveTime = Date.now(); + } break; } case MessageType.ClientDetails: @@ -462,13 +505,14 @@ websocketServer.on("connection", (socket) => { usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY).writeBool(otherUser.isAfk); } if (dbParty) { - user = users.set(myUUID, new RemoteUser(socket, myUUID, dbUser.Username, page, rawURL, dbUser.Id, dbParty.Id, dbParty.Name)); + user = users.set(myUUID, new RemoteUser(socket, dataOut, myUUID, dbUser.Username, page, rawURL, dbUser.Id, dbParty.Id, dbParty.Name)); } else { - user = users.set(myUUID, new RemoteUser(socket, myUUID, dbUser.Username, page, rawURL, dbUser.Id, Number.MIN_VALUE, "")); + user = users.set(myUUID, new RemoteUser(socket, dataOut, myUUID, dbUser.Username, page, rawURL, dbUser.Id, Number.MIN_VALUE, "")); } sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer()); user.send(usersToSend.toBuffer()); sendGroupUpdate(user); + updateConnectionMetrics(); break; } case MessageType.CursorPos: diff --git a/server/objects/Config.ts b/server/objects/Config.ts index 24d115f..aef4ad3 100644 --- a/server/objects/Config.ts +++ b/server/objects/Config.ts @@ -2,9 +2,7 @@ import { readFileSync } from "fs"; const config = JSON.parse(readFileSync("./config.json").toString()); -export default class Config { - public constructor() { throw new Error("Static Class"); } - +export default abstract class Config { public static ports:ConfigPorts = config.ports; public static session:ConfigSession = config.session; public static githost:string = config.githost; @@ -13,7 +11,8 @@ export default class Config { interface ConfigPorts { http: number, - ws: number + ws: number, + metrics: number } interface ConfigSession { diff --git a/server/objects/RemoteUser.ts b/server/objects/RemoteUser.ts index 1c1c580..b8d650d 100644 --- a/server/objects/RemoteUser.ts +++ b/server/objects/RemoteUser.ts @@ -1,3 +1,4 @@ +import IMetric from "simple-prom/lib/interfaces/IMetric"; import { WebSocket } from "ws"; export default class RemoteUser { @@ -9,6 +10,7 @@ export default class RemoteUser { public readonly username:string; public readonly currentURL:string; public readonly rawURL:string = ""; + private readonly dataOut:IMetric; public cursorX:number = 0; public cursorY:number = 0; public allowedPings:number; @@ -20,13 +22,14 @@ export default class RemoteUser { public isAfk:boolean; public timeLastMovedCursor: number; - constructor(socket:WebSocket, connectionUUID:string, username:string, currentURL:string, rawURL:string, userId:number, groupId:number, groupName:string) { + constructor(socket:WebSocket, dataOut:IMetric, connectionUUID:string, username:string, currentURL:string, rawURL:string, userId:number, groupId:number, groupName:string) { this.socket = socket; this.connectionUUID = connectionUUID; this.id = RemoteUser.USER_IDS++; this.username = username; this.currentURL = currentURL; this.rawURL = rawURL; + this.dataOut = dataOut; this.allowedPings = 10; this.lastPingReset = Date.now(); this.userId = userId; @@ -38,6 +41,7 @@ export default class RemoteUser { } send(data:Buffer) { + this.dataOut.add(data.length); this.socket.send(data); } } \ No newline at end of file