improvment: added prometheus metrics for the number of groups in a room

This commit is contained in:
kharhamel 2020-10-30 15:23:50 +01:00
parent 6658e2d666
commit b1d2543631
5 changed files with 80 additions and 32 deletions

View File

@ -1,5 +1,4 @@
import {App} from "../Server/sifrr.server"; import {App} from "../Server/sifrr.server";
import {IoSocketController} from "_Controller/IoSocketController";
import {HttpRequest, HttpResponse} from "uWebSockets.js"; import {HttpRequest, HttpResponse} from "uWebSockets.js";
const register = require('prom-client').register; const register = require('prom-client').register;
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics; const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;

View File

@ -152,7 +152,7 @@ export class GameRoom {
closestItem.join(user); closestItem.join(user);
} else { } else {
const closestUser : User = closestItem; const closestUser : User = closestItem;
const group: Group = new Group([ const group: Group = new Group(this.roomId,[
user, user,
closestUser closestUser
], this.connectCallback, this.disconnectCallback, this.positionNotifier); ], this.connectCallback, this.disconnectCallback, this.positionNotifier);
@ -200,7 +200,6 @@ export class GameRoom {
if (group === undefined) { if (group === undefined) {
throw new Error("The user is part of no group"); throw new Error("The user is part of no group");
} }
const oldPosition = group.getPosition();
group.leave(user); group.leave(user);
if (group.isEmpty()) { if (group.isEmpty()) {
this.positionNotifier.leave(group); this.positionNotifier.leave(group);
@ -209,6 +208,7 @@ export class GameRoom {
throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World."); throw new Error("Could not find group "+group.getId()+" referenced by user "+user.id+" in World.");
} }
this.groups.delete(group); this.groups.delete(group);
//todo: is the group garbage collected?
} else { } else {
group.updatePosition(); group.updatePosition();
//this.positionNotifier.updatePosition(group, group.getPosition(), oldPosition); //this.positionNotifier.updatePosition(group, group.getPosition(), oldPosition);

View File

@ -3,6 +3,7 @@ import { User } from "./User";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {Movable} from "_Model/Movable"; import {Movable} from "_Model/Movable";
import {PositionNotifier} from "_Model/PositionNotifier"; import {PositionNotifier} from "_Model/PositionNotifier";
import {gaugeManager} from "../Services/GaugeManager";
export class Group implements Movable { export class Group implements Movable {
static readonly MAX_PER_GROUP = 4; static readonly MAX_PER_GROUP = 4;
@ -13,12 +14,23 @@ export class Group implements Movable {
private users: Set<User>; private users: Set<User>;
private x!: number; private x!: number;
private y!: number; private y!: number;
private hasEditedGauge: boolean = false;
private wasDestroyed: boolean = false;
private roomId: string;
constructor(users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) { constructor(roomId: string, users: User[], private connectCallback: ConnectCallback, private disconnectCallback: DisconnectCallback, private positionNotifier: PositionNotifier) {
this.roomId = roomId;
this.users = new Set<User>(); this.users = new Set<User>();
this.id = Group.nextId; this.id = Group.nextId;
Group.nextId++; Group.nextId++;
//we only send a event for prometheus metrics if the group lives more than 5 seconds
setTimeout(() => {
if (!this.wasDestroyed) {
this.hasEditedGauge = true;
gaugeManager.incNbGroupsPerRoomGauge(roomId);
}
}, 5000);
users.forEach((user: User) => { users.forEach((user: User) => {
this.join(user); this.join(user);
@ -113,9 +125,11 @@ export class Group implements Movable {
*/ */
destroy(): void destroy(): void
{ {
if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
for (const user of this.users) { for (const user of this.users) {
this.leave(user); this.leave(user);
} }
this.wasDestroyed = true;
} }
get getSize(){ get getSize(){

View File

@ -0,0 +1,54 @@
import {Counter, Gauge} from "prom-client";
//this class should manage all the custom metrics used by prometheus
class GaugeManager {
private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>;
private nbGroupsPerRoomGauge: Gauge<string>;
private nbGroupsPerRoomCounter: Counter<string>;
constructor() {
this.nbClientsGauge = new Gauge({
name: 'workadventure_nb_sockets',
help: 'Number of connected sockets',
labelNames: [ ]
});
this.nbClientsPerRoomGauge = new Gauge({
name: 'workadventure_nb_clients_per_room',
help: 'Number of clients per room',
labelNames: [ 'room' ]
});
this.nbGroupsPerRoomCounter = new Counter({
name: 'workadventure_counter_groups_per_room',
help: 'Counter of groups per room',
labelNames: [ 'room' ]
});
this.nbGroupsPerRoomGauge = new Gauge({
name: 'workadventure_nb_groups_per_room',
help: 'Number of groups per room',
labelNames: [ 'room' ]
});
}
incNbClientPerRoomGauge(roomId: string): void {
this.nbClientsGauge.inc();
this.nbClientsPerRoomGauge.inc({ room: roomId });
}
decNbClientPerRoomGauge(roomId: string): void {
this.nbClientsGauge.dec();
this.nbClientsPerRoomGauge.dec({ room: roomId });
}
incNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomCounter.inc({ room: roomId })
this.nbGroupsPerRoomGauge.inc({ room: roomId })
}
decNbGroupsPerRoomGauge(roomId: string): void {
this.nbGroupsPerRoomGauge.dec({ room: roomId })
}
}
export const gaugeManager = new GaugeManager();

View File

@ -23,7 +23,6 @@ import {
WebRtcStartMessage, WebRtcStartMessage,
QueryJitsiJwtMessage, QueryJitsiJwtMessage,
SendJitsiJwtMessage, SendJitsiJwtMessage,
CharacterLayerMessage,
SendUserMessage SendUserMessage
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface"; import {PointInterface} from "../Model/Websocket/PointInterface";
@ -37,11 +36,11 @@ import {Movable} from "../Model/Movable";
import {PositionInterface} from "../Model/PositionInterface"; import {PositionInterface} from "../Model/PositionInterface";
import {adminApi, CharacterTexture} from "./AdminApi"; import {adminApi, CharacterTexture} from "./AdminApi";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {Gauge} from "prom-client";
import {emitError, emitInBatch} from "./IoSocketHelpers"; import {emitError, emitInBatch} from "./IoSocketHelpers";
import Jwt from "jsonwebtoken"; import Jwt from "jsonwebtoken";
import {JITSI_URL} from "../Enum/EnvironmentVariable"; import {JITSI_URL} from "../Enum/EnvironmentVariable";
import {clientEventsEmitter} from "./ClientEventsEmitter"; import {clientEventsEmitter} from "./ClientEventsEmitter";
import {gaugeManager} from "./GaugeManager";
interface AdminSocketRoomsList { interface AdminSocketRoomsList {
[index: string]: number; [index: string]: number;
@ -58,30 +57,13 @@ export interface AdminSocketData {
export class SocketManager { export class SocketManager {
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>(); private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>(); private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
private nbClientsGauge: Gauge<string>;
private nbClientsPerRoomGauge: Gauge<string>;
constructor() { constructor() {
this.nbClientsGauge = new Gauge({ clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
name: 'workadventure_nb_sockets', gaugeManager.incNbClientPerRoomGauge(roomId);
help: 'Number of connected sockets',
labelNames: [ ]
}); });
this.nbClientsPerRoomGauge = new Gauge({ clientEventsEmitter.registerToClientLeave((clientUUid: string, roomId: string) => {
name: 'workadventure_nb_clients_per_room', gaugeManager.decNbClientPerRoomGauge(roomId);
help: 'Number of clients per room',
labelNames: [ 'room' ]
});
clientEventsEmitter.registerToClientJoin((clientUUid, roomId) => {
this.nbClientsGauge.inc();
// Let's log server load when a user joins
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
});
clientEventsEmitter.registerToClientLeave((clientUUid, roomId) => {
this.nbClientsGauge.dec();
// Let's log server load when a user leaves
console.log('A user left (', this.sockets.size, ' connected users)');
}); });
} }
@ -107,7 +89,6 @@ export class SocketManager {
const viewport = client.viewport; const viewport = client.viewport;
try { try {
this.sockets.set(client.userId, client); //todo: should this be at the end of the function? this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
//join new previous room //join new previous room
const gameRoom = this.joinRoom(client, position); const gameRoom = this.joinRoom(client, position);
@ -377,8 +358,8 @@ export class SocketManager {
} finally { } finally {
//delete Client.roomId; //delete Client.roomId;
this.sockets.delete(Client.userId); this.sockets.delete(Client.userId);
this.nbClientsPerRoomGauge.dec({ room: Client.roomId });
clientEventsEmitter.emitClientLeave(Client.userUuid, Client.roomId); clientEventsEmitter.emitClientLeave(Client.userUuid, Client.roomId);
console.log('A user left (', this.sockets.size, ' connected users)');
} }
} }
} }
@ -410,8 +391,6 @@ export class SocketManager {
private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom { private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom {
const roomId = client.roomId; const roomId = client.roomId;
//join user in room
this.nbClientsPerRoomGauge.inc({ room: roomId });
client.position = position; client.position = position;
const world = this.Worlds.get(roomId) const world = this.Worlds.get(roomId)
@ -425,6 +404,8 @@ export class SocketManager {
}); });
//join world //join world
world.join(client, client.position); world.join(client, client.position);
clientEventsEmitter.emitClientJoin(client.userUuid, client.roomId);
console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
return world; return world;
} }