add prom metrics

This commit is contained in:
Holly Stubbs 2024-07-03 23:47:45 +01:00
parent 8df3041449
commit 5c7d3bedd8
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
4 changed files with 57 additions and 9 deletions

View file

@ -1,7 +1,8 @@
{ {
"ports": { "ports": {
"http": 39194, "http": 39194,
"ws": 39195 "ws": 39195,
"metrics": 9100
}, },
"session": { "session": {
"validity": 86400, "validity": 86400,

View file

@ -20,6 +20,9 @@ import CreateEditPartyData from "./interfaces/CreateEditPartyData";
import JoinPartyData from "./interfaces/JoinPartyData"; import JoinPartyData from "./interfaces/JoinPartyData";
import IdData from "./interfaces/IdData"; import IdData from "./interfaces/IdData";
import Party from "./objects/Party"; 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()}`); Console.customHeader(`MultiProbe server started at ${new Date()}`);
@ -27,6 +30,26 @@ const users = new FunkyArray<string, RemoteUser>();
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name); 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 // Web stuff
const sessions = new FunkyArray<string, SessionUser>(); const sessions = new FunkyArray<string, SessionUser>();
@ -38,6 +61,7 @@ const sessionExpiryInterval = setInterval(() => {
sessions.remove(key); sessions.remove(key);
} }
} }
webSessions.Value = sessions.length;
}, 3600000); }, 3600000);
const fastify = Fastify({ const fastify = Fastify({
@ -178,6 +202,7 @@ fastify.post("/account/register", async (req, res) => {
const key = randomBytes(Config.session.length).toString("hex"); const key = randomBytes(Config.session.length).toString("hex");
sessions.set(key, new SessionUser(user.Id, validPeriod)); sessions.set(key, new SessionUser(user.Id, validPeriod));
webSessions.Value = sessions.length;
res.setCookie("MP_SESSION", key, { res.setCookie("MP_SESSION", key, {
path: "/", path: "/",
@ -204,6 +229,7 @@ fastify.post("/account/login", async (req, res) => {
const key = randomBytes(Config.session.length).toString("hex"); const key = randomBytes(Config.session.length).toString("hex");
sessions.set(key, new SessionUser(user.Id, validPeriod)); sessions.set(key, new SessionUser(user.Id, validPeriod));
webSessions.Value = sessions.length;
res.setCookie("MP_SESSION", key, { res.setCookie("MP_SESSION", key, {
path: "/", path: "/",
@ -369,6 +395,19 @@ const afkInterval = setInterval(() => {
}); });
}, 5000); }, 5000);
async function updateConnectionMetrics() {
onlineUsers.Value = users.length;
let userCount = 0;
const checkedUsers = new Array<string>();
await users.forEach(user => {
if (!checkedUsers.includes(user.username)) {
userCount++;
checkedUsers.push(user.username);
}
});
onlineUsersUnique.Value = userCount;
}
websocketServer.on("connection", (socket) => { websocketServer.on("connection", (socket) => {
const myUUID = crypto.randomUUID(); const myUUID = crypto.randomUUID();
let user:RemoteUser; let user:RemoteUser;
@ -381,6 +420,7 @@ websocketServer.on("connection", (socket) => {
users.forEach(otherUser => otherUser.send(userLeftPacket)); users.forEach(otherUser => otherUser.send(userLeftPacket));
sendGroupUpdate(user); sendGroupUpdate(user);
} }
updateConnectionMetrics();
} }
async function sendGroupUpdate(sendUser:RemoteUser, groupSend = false) { async function sendGroupUpdate(sendUser:RemoteUser, groupSend = false) {
@ -421,11 +461,14 @@ websocketServer.on("connection", (socket) => {
socket.on("message", async (data) => { socket.on("message", async (data) => {
const reader = createReader(Endian.LE, data as Buffer); const reader = createReader(Endian.LE, data as Buffer);
dataIn.add(reader.length);
if (reader.length > 0 && reader.length < 1024) { if (reader.length > 0 && reader.length < 1024) {
switch (reader.readUByte()) { switch (reader.readUByte()) {
case MessageType.KeepAlive: case MessageType.KeepAlive:
{ {
if (user !== undefined) {
user.lastKeepAliveTime = Date.now(); user.lastKeepAliveTime = Date.now();
}
break; break;
} }
case MessageType.ClientDetails: 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); usersToSend.writeUInt(otherUser.id).writeShortString(otherUser.username).writeFloat(otherUser.cursorX).writeInt(otherUser.cursorY).writeBool(otherUser.isAfk);
} }
if (dbParty) { 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 { } 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()); sendToAllButSelf(user, createWriter(Endian.LE, 6 + dbUser.Username.length).writeByte(MessageType.ClientJoined).writeUInt(user.id).writeShortString(dbUser.Username).toBuffer());
user.send(usersToSend.toBuffer()); user.send(usersToSend.toBuffer());
sendGroupUpdate(user); sendGroupUpdate(user);
updateConnectionMetrics();
break; break;
} }
case MessageType.CursorPos: case MessageType.CursorPos:

View file

@ -2,9 +2,7 @@ import { readFileSync } from "fs";
const config = JSON.parse(readFileSync("./config.json").toString()); const config = JSON.parse(readFileSync("./config.json").toString());
export default class Config { export default abstract class Config {
public constructor() { throw new Error("Static Class"); }
public static ports:ConfigPorts = config.ports; public static ports:ConfigPorts = config.ports;
public static session:ConfigSession = config.session; public static session:ConfigSession = config.session;
public static githost:string = config.githost; public static githost:string = config.githost;
@ -13,7 +11,8 @@ export default class Config {
interface ConfigPorts { interface ConfigPorts {
http: number, http: number,
ws: number ws: number,
metrics: number
} }
interface ConfigSession { interface ConfigSession {

View file

@ -1,3 +1,4 @@
import IMetric from "simple-prom/lib/interfaces/IMetric";
import { WebSocket } from "ws"; import { WebSocket } from "ws";
export default class RemoteUser { export default class RemoteUser {
@ -9,6 +10,7 @@ export default class RemoteUser {
public readonly username:string; public readonly username:string;
public readonly currentURL:string; public readonly currentURL:string;
public readonly rawURL:string = ""; public readonly rawURL:string = "";
private readonly dataOut:IMetric;
public cursorX:number = 0; public cursorX:number = 0;
public cursorY:number = 0; public cursorY:number = 0;
public allowedPings:number; public allowedPings:number;
@ -20,13 +22,14 @@ export default class RemoteUser {
public isAfk:boolean; public isAfk:boolean;
public timeLastMovedCursor: number; 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.socket = socket;
this.connectionUUID = connectionUUID; this.connectionUUID = connectionUUID;
this.id = RemoteUser.USER_IDS++; this.id = RemoteUser.USER_IDS++;
this.username = username; this.username = username;
this.currentURL = currentURL; this.currentURL = currentURL;
this.rawURL = rawURL; this.rawURL = rawURL;
this.dataOut = dataOut;
this.allowedPings = 10; this.allowedPings = 10;
this.lastPingReset = Date.now(); this.lastPingReset = Date.now();
this.userId = userId; this.userId = userId;
@ -38,6 +41,7 @@ export default class RemoteUser {
} }
send(data:Buffer) { send(data:Buffer) {
this.dataOut.add(data.length);
this.socket.send(data); this.socket.send(data);
} }
} }