From 2a0636873beb209b07585698bdb712dd743b0e87 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Mon, 3 Jun 2024 17:42:00 +0200 Subject: [PATCH 01/23] initial commit --- .../gateway/board-collaboration.gateway.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index edcf4fd6c8e..f973e36ff26 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -1,10 +1,12 @@ import { WsValidationPipe, Socket } from '@infra/socketio'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; import { UseGuards, UsePipes } from '@nestjs/common'; -import { SubscribeMessage, WebSocketGateway, WsException } from '@nestjs/websockets'; +import { SubscribeMessage, WebSocketGateway, WebSocketServer, WsException } from '@nestjs/websockets'; import { WsJwtAuthGuard } from '@src/modules/authentication/guard/ws-jwt-auth.guard'; +import { Server } from 'socket.io'; import { BoardResponseMapper, CardResponseMapper, ContentElementResponseFactory } from '../controller/mapper'; import { ColumnResponseMapper } from '../controller/mapper/column-response.mapper'; +import { MetricsService } from '../metrics/metrics.service'; import { BoardDoAuthorizableService } from '../service'; import { BoardUc, CardUc, ColumnUc, ElementUc } from '../uc'; import { @@ -33,12 +35,17 @@ import { UpdateContentElementMessageParams } from './dto/update-content-element. @WebSocketGateway(BoardCollaborationConfiguration.websocket) @UseGuards(WsJwtAuthGuard) export class BoardCollaborationGateway { + @WebSocketServer() + server?: Server; + + // TODO: use loggables instead of legacy logger constructor( private readonly orm: MikroORM, private readonly boardUc: BoardUc, private readonly columnUc: ColumnUc, private readonly cardUc: CardUc, private readonly elementUc: ElementUc, + private readonly metricsService: MetricsService, private readonly authorizableService: BoardDoAuthorizableService // to be removed ) {} @@ -48,6 +55,28 @@ export class BoardCollaborationGateway { return user; } + private getActiveBoardRooms() { + if (!this.server) { + throw new Error('Server is not initialized'); + } + + const activeRooms = Array.from(this.server.of('/').adapter.rooms.keys()).filter((key) => key.startsWith('board_')); + return activeRooms; + } + + private getActiveUsers() { + if (!this.server) { + throw new Error('Server is not initialized'); + } + + const activeRooms = Array.from(this.server.of('/').adapter.sids.keys()); + return activeRooms; + } + + public afterInit(): void { + // this.logger.log('Socket gateway initialized'); + } + @SubscribeMessage('delete-board-request') @UseRequestContext() async deleteBoard(socket: Socket, data: DeleteBoardMessageParams) { @@ -171,6 +200,12 @@ export class BoardCollaborationGateway { const responsePayload = BoardResponseMapper.mapToResponse(board); await emitter.emitToClient('fetch-board-success', responsePayload); + + const roomCount = Object.keys(this.getActiveBoardRooms()).length; + console.log('rooms', this.getActiveBoardRooms()); + console.log('roomCount', this.getActiveBoardRooms()); + console.log('active users', this.getActiveUsers()); + this.metricsService.setNumberOfBoardRooms(roomCount); } catch (err) { socket.emit('fetch-board-failure', data); } From d8c96d6af5313f38e920552ee5ee52e7dd1ddd32 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Mon, 3 Jun 2024 18:15:12 +0200 Subject: [PATCH 02/23] adding metrics service --- .../src/modules/board/board-ws-api.module.ts | 3 +- .../modules/board/metrics/metrics.service.ts | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/modules/board/metrics/metrics.service.ts diff --git a/apps/server/src/modules/board/board-ws-api.module.ts b/apps/server/src/modules/board/board-ws-api.module.ts index 6955e56e43f..5ade81c90a8 100644 --- a/apps/server/src/modules/board/board-ws-api.module.ts +++ b/apps/server/src/modules/board/board-ws-api.module.ts @@ -4,11 +4,12 @@ import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '../authorization'; import { BoardModule } from './board.module'; import { BoardCollaborationGateway } from './gateway/board-collaboration.gateway'; +import { MetricsService } from './metrics/metrics.service'; import { BoardUc, CardUc, ColumnUc, ElementUc } from './uc'; @Module({ imports: [BoardModule, forwardRef(() => AuthorizationModule), LoggerModule], - providers: [BoardCollaborationGateway, CardUc, ColumnUc, ElementUc, BoardUc, CourseRepo], + providers: [BoardCollaborationGateway, CardUc, ColumnUc, ElementUc, BoardUc, CourseRepo, MetricsService], exports: [], }) export class BoardWsApiModule {} diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts new file mode 100644 index 00000000000..21ccab42499 --- /dev/null +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { Gauge, register } from 'prom-client'; + +@Injectable() +export class MetricsService { + private numberOfUsersOnServerCounter: Gauge; + + private numberOfBoardroomsOnServerCounter: Gauge; + + constructor() { + this.numberOfUsersOnServerCounter = new Gauge({ + name: 'sc_boards_users', + help: 'Number of active users per pod', + }); + + this.numberOfBoardroomsOnServerCounter = new Gauge({ + name: 'sc_boards_rooms', + help: 'Number of active boards per pod', + }); + + register.registerMetric(this.numberOfUsersOnServerCounter); + register.registerMetric(this.numberOfBoardroomsOnServerCounter); + } + + public incrementNumberOfUsersOnServerCounter(): void { + this.numberOfUsersOnServerCounter.inc(); + } + + public decrementNumberOfUsersOnServerCounter(): void { + this.numberOfUsersOnServerCounter.dec(); + } + + public setNumberOfBoardRooms(value: number): void { + this.numberOfBoardroomsOnServerCounter.set(value); + } +} From 925955650745a7b69496d47ac20588f30346642e Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 4 Jun 2024 15:30:13 +0200 Subject: [PATCH 03/23] add metrics service to deployment --- .../roles/schulcloud-server-core/tasks/main.yml | 7 +++++++ .../board-collaboration-svc-monitor.yml.j2 | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 ansible/roles/schulcloud-server-core/templates/board-collaboration-svc-monitor.yml.j2 diff --git a/ansible/roles/schulcloud-server-core/tasks/main.yml b/ansible/roles/schulcloud-server-core/tasks/main.yml index 6d17727669d..14e0e435aa3 100644 --- a/ansible/roles/schulcloud-server-core/tasks/main.yml +++ b/ansible/roles/schulcloud-server-core/tasks/main.yml @@ -322,3 +322,10 @@ template: board-collaboration-ingress.yml.j2 apply: yes state: "{{ 'present' if WITH_BOARD_COLLABORATION else 'absent'}}" + + - name: BoardCollaborationServiceMonitor + kubernetes.core.k8s: + kubeconfig: ~/.kube/config + namespace: '{{ NAMESPACE }}' + template: board-collaboration-svc-monitor.yml.j2 + state: "{{ 'present' if WITH_BOARD_COLLABORATION else 'absent'}}" diff --git a/ansible/roles/schulcloud-server-core/templates/board-collaboration-svc-monitor.yml.j2 b/ansible/roles/schulcloud-server-core/templates/board-collaboration-svc-monitor.yml.j2 new file mode 100644 index 00000000000..bf749d56ab3 --- /dev/null +++ b/ansible/roles/schulcloud-server-core/templates/board-collaboration-svc-monitor.yml.j2 @@ -0,0 +1,17 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: board-collaboration-svc-monitor + namespace: {{ NAMESPACE }} + labels: + app: board-collaboration +spec: + selector: + matchExpressions: + - key: app.kubernetes.io/name + operator: in + values: + - board-collaboration-svc + endpoints: + - path: /metrics + port: api-metrics From b46123d7dd5ce9de72aa0cc6aae33a4f28e7d9df Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 4 Jun 2024 15:30:40 +0200 Subject: [PATCH 04/23] add code to track amout of users on the server --- .../modules/board/gateway/board-collaboration.gateway.ts | 6 +++--- apps/server/src/modules/board/metrics/metrics.service.ts | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index f973e36ff26..c8a3bff3d9b 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -202,9 +202,9 @@ export class BoardCollaborationGateway { await emitter.emitToClient('fetch-board-success', responsePayload); const roomCount = Object.keys(this.getActiveBoardRooms()).length; - console.log('rooms', this.getActiveBoardRooms()); - console.log('roomCount', this.getActiveBoardRooms()); - console.log('active users', this.getActiveUsers()); + const userCount = Object.keys(this.getActiveUsers()).length; + console.log('users:', userCount, 'rooms:', roomCount); + this.metricsService.setNumberOfUsers(userCount); this.metricsService.setNumberOfBoardRooms(roomCount); } catch (err) { socket.emit('fetch-board-failure', data); diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 21ccab42499..123f2fad911 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -22,12 +22,8 @@ export class MetricsService { register.registerMetric(this.numberOfBoardroomsOnServerCounter); } - public incrementNumberOfUsersOnServerCounter(): void { - this.numberOfUsersOnServerCounter.inc(); - } - - public decrementNumberOfUsersOnServerCounter(): void { - this.numberOfUsersOnServerCounter.dec(); + public setNumberOfUsers(value: number): void { + this.numberOfUsersOnServerCounter.set(value); } public setNumberOfBoardRooms(value: number): void { From 703b83cd77efefc595a13ceeedc82bc08d8fa880 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 4 Jun 2024 15:58:31 +0200 Subject: [PATCH 05/23] add port for accessing metrics server --- .../templates/board-collaboration-service.yml.j2 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible/roles/schulcloud-server-core/templates/board-collaboration-service.yml.j2 b/ansible/roles/schulcloud-server-core/templates/board-collaboration-service.yml.j2 index d01d0f2a652..3ae23122697 100644 --- a/ansible/roles/schulcloud-server-core/templates/board-collaboration-service.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/board-collaboration-service.yml.j2 @@ -13,9 +13,9 @@ spec: targetPort: 4450 protocol: TCP name: websocket - #- port: {{ PORT_METRICS_SERVER }} - # targetPort: 9090 # TODO - # protocol: TCP - # name: api-metrics + - port: {{ PORT_METRICS_SERVER }} + targetPort: 9090 # TODO + protocol: TCP + name: api-metrics selector: app: board-collaboration From b1a90abc4978f621e5e1c21c41977b2a0c8ddf84 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 4 Jun 2024 16:16:32 +0200 Subject: [PATCH 06/23] add container-port for api-metrics to deployment --- .../templates/board-collaboration-deployment.yml.j2 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible/roles/schulcloud-server-core/templates/board-collaboration-deployment.yml.j2 b/ansible/roles/schulcloud-server-core/templates/board-collaboration-deployment.yml.j2 index 4f94b759975..220bd7e6e02 100644 --- a/ansible/roles/schulcloud-server-core/templates/board-collaboration-deployment.yml.j2 +++ b/ansible/roles/schulcloud-server-core/templates/board-collaboration-deployment.yml.j2 @@ -49,9 +49,9 @@ spec: - containerPort: 4450 name: websocket protocol: TCP - # - containerPort: 9090 - # name: api-metrics - # protocol: TCP + - containerPort: 9090 + name: api-metrics + protocol: TCP envFrom: - configMapRef: name: api-configmap From fb5d3bdc20e46017755ca184b45fbb152e5ff97c Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 4 Jun 2024 17:02:29 +0200 Subject: [PATCH 07/23] add prometheus metrics to board collaboration app --- .../src/apps/board-collaboration.app.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/apps/server/src/apps/board-collaboration.app.ts b/apps/server/src/apps/board-collaboration.app.ts index 770889cb362..4b97d192384 100644 --- a/apps/server/src/apps/board-collaboration.app.ts +++ b/apps/server/src/apps/board-collaboration.app.ts @@ -8,20 +8,25 @@ import { install as sourceMapInstall } from 'source-map-support'; // application imports import { SwaggerDocumentOptions } from '@nestjs/swagger'; import { DB_URL } from '@src/config'; -import { LegacyLogger } from '@src/core/logger'; +import { LegacyLogger, Logger } from '@src/core/logger'; import { MongoIoAdapter } from '@src/infra/socketio'; import { BoardCollaborationModule } from '@src/modules/board/board-collaboration.module'; import { enableOpenApiDocs } from '@src/shared/controller/swagger'; +import express from 'express'; +import { + addPrometheusMetricsMiddlewaresIfEnabled, + createAndStartPrometheusMetricsAppIfEnabled, +} from '@src/apps/helpers/prometheus-metrics'; +import { ExpressAdapter } from '@nestjs/platform-express'; +import { AppStartLoggable } from './helpers/app-start-loggable'; async function bootstrap() { sourceMapInstall(); - const nestApp = await NestFactory.create(BoardCollaborationModule); - - // WinstonLogger + const nestExpress = express(); + const nestExpressAdapter = new ExpressAdapter(nestExpress); + const nestApp = await NestFactory.create(BoardCollaborationModule, nestExpressAdapter); nestApp.useLogger(await nestApp.resolve(LegacyLogger)); - - // customize nest app settings nestApp.enableCors({ exposedHeaders: ['Content-Disposition'] }); const mongoIoAdapter = new MongoIoAdapter(nestApp); @@ -33,19 +38,35 @@ async function bootstrap() { operationIdFactory: (_controllerKey: string, methodKey: string) => methodKey, }; enableOpenApiDocs(nestApp, 'docs', options); + const logger = await nestApp.resolve(Logger); await nestApp.init(); + // mount instances + const rootExpress = express(); + + addPrometheusMetricsMiddlewaresIfEnabled(logger, rootExpress); const port = 4450; - const basePath = '/board-collaboration'; + const basePath = '/board-collaboration'; // '/api/v3'; // '/board-collaboration'; + + // exposed alias mounts + rootExpress.use(basePath, nestExpress); + + rootExpress.listen(port, () => { + logger.info( + new AppStartLoggable({ + appName: 'BoardCollaboration server app', + port, + }) + ); - nestApp.setGlobalPrefix(basePath); - await nestApp.listen(port); + createAndStartPrometheusMetricsAppIfEnabled(logger); + }); console.log('##########################################'); - console.log(`### Start Board Collaboration Server ###`); - console.log(`### Port: ${port} ###`); - console.log(`### Base path: ${basePath} ###`); + console.log(`### Start Board Collaboration Server`); + console.log(`### Port: ${port}`); + console.log(`### Base path: ${basePath}`); console.log('##########################################'); } void bootstrap(); From 1e1a3c1b41f69732feb2a4309b46d6636baa83fd Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 5 Jun 2024 14:01:39 +0200 Subject: [PATCH 08/23] fix express app setup for board-collaboration --- .../src/apps/board-collaboration.app.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/server/src/apps/board-collaboration.app.ts b/apps/server/src/apps/board-collaboration.app.ts index 4b97d192384..1f5bd41c56e 100644 --- a/apps/server/src/apps/board-collaboration.app.ts +++ b/apps/server/src/apps/board-collaboration.app.ts @@ -18,7 +18,6 @@ import { createAndStartPrometheusMetricsAppIfEnabled, } from '@src/apps/helpers/prometheus-metrics'; import { ExpressAdapter } from '@nestjs/platform-express'; -import { AppStartLoggable } from './helpers/app-start-loggable'; async function bootstrap() { sourceMapInstall(); @@ -31,7 +30,6 @@ async function bootstrap() { const mongoIoAdapter = new MongoIoAdapter(nestApp); await mongoIoAdapter.connectToMongoDb(DB_URL); - nestApp.useWebSocketAdapter(mongoIoAdapter); const options: SwaggerDocumentOptions = { @@ -42,24 +40,12 @@ async function bootstrap() { await nestApp.init(); - // mount instances - const rootExpress = express(); - - addPrometheusMetricsMiddlewaresIfEnabled(logger, rootExpress); + addPrometheusMetricsMiddlewaresIfEnabled(logger, nestExpress); const port = 4450; const basePath = '/board-collaboration'; // '/api/v3'; // '/board-collaboration'; - // exposed alias mounts - rootExpress.use(basePath, nestExpress); - - rootExpress.listen(port, () => { - logger.info( - new AppStartLoggable({ - appName: 'BoardCollaboration server app', - port, - }) - ); - + nestApp.setGlobalPrefix(basePath); + await nestApp.listen(port, () => { createAndStartPrometheusMetricsAppIfEnabled(logger); }); From fa53e2cc057c8da59455c3a3890e65f473e92dfc Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 5 Jun 2024 14:35:02 +0200 Subject: [PATCH 09/23] add update of metrics on disconnect --- .../src/apps/board-collaboration.app.ts | 2 +- .../gateway/board-collaboration.gateway.ts | 43 ++++++++++++------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/apps/server/src/apps/board-collaboration.app.ts b/apps/server/src/apps/board-collaboration.app.ts index 1f5bd41c56e..695d40a4d40 100644 --- a/apps/server/src/apps/board-collaboration.app.ts +++ b/apps/server/src/apps/board-collaboration.app.ts @@ -42,7 +42,7 @@ async function bootstrap() { addPrometheusMetricsMiddlewaresIfEnabled(logger, nestExpress); const port = 4450; - const basePath = '/board-collaboration'; // '/api/v3'; // '/board-collaboration'; + const basePath = '/board-collaboration'; nestApp.setGlobalPrefix(basePath); await nestApp.listen(port, () => { diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index c8a3bff3d9b..63a364f12fe 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -35,6 +35,7 @@ import { UpdateContentElementMessageParams } from './dto/update-content-element. @WebSocketGateway(BoardCollaborationConfiguration.websocket) @UseGuards(WsJwtAuthGuard) export class BoardCollaborationGateway { + // implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server?: Server; @@ -55,26 +56,42 @@ export class BoardCollaborationGateway { return user; } - private getActiveBoardRooms() { + private updateRoomsAndUsersMetrics() { if (!this.server) { throw new Error('Server is not initialized'); } - const activeRooms = Array.from(this.server.of('/').adapter.rooms.keys()).filter((key) => key.startsWith('board_')); - return activeRooms; + const userCount = Array.from(this.server.of('/').adapter.sids.keys()).length; + const roomCount = Array.from(this.server.of('/').adapter.rooms.keys()).filter((key) => + key.startsWith('board_') + ).length; + this.metricsService.setNumberOfUsers(userCount); + this.metricsService.setNumberOfBoardRooms(roomCount); } - private getActiveUsers() { - if (!this.server) { + public handleConnection(socket: Socket): void { + if (!socket) { throw new Error('Server is not initialized'); } - - const activeRooms = Array.from(this.server.of('/').adapter.sids.keys()); - return activeRooms; + this.updateRoomsAndUsersMetrics(); + // this.logger.log(`Client connected: ${socket.id}`); + // if (userRole === "teacher") { + // this.metricsService.incrementNumberOfEditors(); + // } else { + // this.metricsService.incrementNumberOfViewers(); + // } } - public afterInit(): void { - // this.logger.log('Socket gateway initialized'); + public handleDisconnect(socket: Socket): void { + if (!socket) { + throw new Error('Server is not initialized'); + } + this.updateRoomsAndUsersMetrics(); + // if (userRole === "teacher") { + // this.metricsService.decrementNumberOfEditors(); + // } else { + // this.metricsService.decrementNumberOfViewers(); + // } } @SubscribeMessage('delete-board-request') @@ -200,12 +217,6 @@ export class BoardCollaborationGateway { const responsePayload = BoardResponseMapper.mapToResponse(board); await emitter.emitToClient('fetch-board-success', responsePayload); - - const roomCount = Object.keys(this.getActiveBoardRooms()).length; - const userCount = Object.keys(this.getActiveUsers()).length; - console.log('users:', userCount, 'rooms:', roomCount); - this.metricsService.setNumberOfUsers(userCount); - this.metricsService.setNumberOfBoardRooms(roomCount); } catch (err) { socket.emit('fetch-board-failure', data); } From 8e433ad34d60980ce867b51203c58ed23e45ded2 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 5 Jun 2024 14:57:40 +0200 Subject: [PATCH 10/23] ensure metrics updates are made on fetchBoard, too --- .../src/modules/board/gateway/board-collaboration.gateway.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 63a364f12fe..5197e52ced7 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -217,6 +217,7 @@ export class BoardCollaborationGateway { const responsePayload = BoardResponseMapper.mapToResponse(board); await emitter.emitToClient('fetch-board-success', responsePayload); + this.updateRoomsAndUsersMetrics(); } catch (err) { socket.emit('fetch-board-failure', data); } From 2979f3b1cefa78165043fdc1576550e2512aaf67 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 5 Jun 2024 16:31:20 +0200 Subject: [PATCH 11/23] add metrics for editorCount and viewerCount --- .../gateway/board-collaboration.gateway.ts | 37 ++++++++++++------- .../modules/board/metrics/metrics.service.ts | 30 +++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 5197e52ced7..93fbeec5948 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -2,7 +2,9 @@ import { WsValidationPipe, Socket } from '@infra/socketio'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; import { UseGuards, UsePipes } from '@nestjs/common'; import { SubscribeMessage, WebSocketGateway, WebSocketServer, WsException } from '@nestjs/websockets'; +import { RoleName } from '@shared/domain/interface'; import { WsJwtAuthGuard } from '@src/modules/authentication/guard/ws-jwt-auth.guard'; +import { UserUc } from '@src/modules/user/uc'; import { Server } from 'socket.io'; import { BoardResponseMapper, CardResponseMapper, ContentElementResponseFactory } from '../controller/mapper'; import { ColumnResponseMapper } from '../controller/mapper/column-response.mapper'; @@ -47,6 +49,7 @@ export class BoardCollaborationGateway { private readonly cardUc: CardUc, private readonly elementUc: ElementUc, private readonly metricsService: MetricsService, + private readonly userUc: UserUc, private readonly authorizableService: BoardDoAuthorizableService // to be removed ) {} @@ -56,6 +59,13 @@ export class BoardCollaborationGateway { return user; } + private async hasUserEditRights(socket: Socket) { + const { userId } = this.getCurrentUser(socket); + const [user] = await this.userUc.me(userId); + const isTeacher = user.getRoles().find((role) => role.name === RoleName.TEACHER); + return isTeacher; + } + private updateRoomsAndUsersMetrics() { if (!this.server) { throw new Error('Server is not initialized'); @@ -69,29 +79,30 @@ export class BoardCollaborationGateway { this.metricsService.setNumberOfBoardRooms(roomCount); } - public handleConnection(socket: Socket): void { + public async handleConnection(socket: Socket): Promise { if (!socket) { throw new Error('Server is not initialized'); } this.updateRoomsAndUsersMetrics(); - // this.logger.log(`Client connected: ${socket.id}`); - // if (userRole === "teacher") { - // this.metricsService.incrementNumberOfEditors(); - // } else { - // this.metricsService.incrementNumberOfViewers(); - // } + const hasEditRights = await this.hasUserEditRights(socket); + if (hasEditRights) { + this.metricsService.incrementNumberOfEditors(); + } else { + this.metricsService.incrementNumberOfViewers(); + } } - public handleDisconnect(socket: Socket): void { + public async handleDisconnect(socket: Socket): Promise { if (!socket) { throw new Error('Server is not initialized'); } this.updateRoomsAndUsersMetrics(); - // if (userRole === "teacher") { - // this.metricsService.decrementNumberOfEditors(); - // } else { - // this.metricsService.decrementNumberOfViewers(); - // } + const hasEditRights = await this.hasUserEditRights(socket); + if (hasEditRights) { + this.metricsService.decrementNumberOfEditors(); + } else { + this.metricsService.decrementNumberOfViewers(); + } } @SubscribeMessage('delete-board-request') diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 123f2fad911..eae092e0315 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -7,6 +7,10 @@ export class MetricsService { private numberOfBoardroomsOnServerCounter: Gauge; + private numberOfEditorsOnServerCounter: Gauge; + + private numberOfViewersOnServerCounter: Gauge; + constructor() { this.numberOfUsersOnServerCounter = new Gauge({ name: 'sc_boards_users', @@ -18,6 +22,16 @@ export class MetricsService { help: 'Number of active boards per pod', }); + this.numberOfEditorsOnServerCounter = new Gauge({ + name: 'sc_boards_editors', + help: 'Number of active editors per pod', + }); + + this.numberOfViewersOnServerCounter = new Gauge({ + name: 'sc_boards_viewers', + help: 'Number of active viewers per pod', + }); + register.registerMetric(this.numberOfUsersOnServerCounter); register.registerMetric(this.numberOfBoardroomsOnServerCounter); } @@ -29,4 +43,20 @@ export class MetricsService { public setNumberOfBoardRooms(value: number): void { this.numberOfBoardroomsOnServerCounter.set(value); } + + public incrementNumberOfEditors(): void { + this.numberOfEditorsOnServerCounter.inc(); + } + + public decrementNumberOfEditors(): void { + this.numberOfEditorsOnServerCounter.dec(); + } + + public incrementNumberOfViewers(): void { + this.numberOfViewersOnServerCounter.inc(); + } + + public decrementNumberOfViewers(): void { + this.numberOfViewersOnServerCounter.dec(); + } } From 09ebc6702d856fd11d9d98225a81eddbcd98f167 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 5 Jun 2024 17:06:05 +0200 Subject: [PATCH 12/23] fix role check --- .../gateway/board-collaboration.gateway.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 93fbeec5948..13e8ef61e82 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -4,7 +4,6 @@ import { UseGuards, UsePipes } from '@nestjs/common'; import { SubscribeMessage, WebSocketGateway, WebSocketServer, WsException } from '@nestjs/websockets'; import { RoleName } from '@shared/domain/interface'; import { WsJwtAuthGuard } from '@src/modules/authentication/guard/ws-jwt-auth.guard'; -import { UserUc } from '@src/modules/user/uc'; import { Server } from 'socket.io'; import { BoardResponseMapper, CardResponseMapper, ContentElementResponseFactory } from '../controller/mapper'; import { ColumnResponseMapper } from '../controller/mapper/column-response.mapper'; @@ -49,7 +48,6 @@ export class BoardCollaborationGateway { private readonly cardUc: CardUc, private readonly elementUc: ElementUc, private readonly metricsService: MetricsService, - private readonly userUc: UserUc, private readonly authorizableService: BoardDoAuthorizableService // to be removed ) {} @@ -59,10 +57,9 @@ export class BoardCollaborationGateway { return user; } - private async hasUserEditRights(socket: Socket) { - const { userId } = this.getCurrentUser(socket); - const [user] = await this.userUc.me(userId); - const isTeacher = user.getRoles().find((role) => role.name === RoleName.TEACHER); + private hasUserEditRights(socket: Socket) { + const { user } = socket.handshake; + const isTeacher = user ? user.roles.find((role) => role === RoleName.TEACHER) : false; return isTeacher; } @@ -79,12 +76,12 @@ export class BoardCollaborationGateway { this.metricsService.setNumberOfBoardRooms(roomCount); } - public async handleConnection(socket: Socket): Promise { + public handleConnection(socket: Socket): void { if (!socket) { throw new Error('Server is not initialized'); } this.updateRoomsAndUsersMetrics(); - const hasEditRights = await this.hasUserEditRights(socket); + const hasEditRights = this.hasUserEditRights(socket); if (hasEditRights) { this.metricsService.incrementNumberOfEditors(); } else { @@ -92,12 +89,12 @@ export class BoardCollaborationGateway { } } - public async handleDisconnect(socket: Socket): Promise { + public handleDisconnect(socket: Socket): void { if (!socket) { throw new Error('Server is not initialized'); } this.updateRoomsAndUsersMetrics(); - const hasEditRights = await this.hasUserEditRights(socket); + const hasEditRights = this.hasUserEditRights(socket); if (hasEditRights) { this.metricsService.decrementNumberOfEditors(); } else { From 89125bb3aa5e5c2143bdae4a5c7af430da119d68 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Thu, 6 Jun 2024 16:08:43 +0200 Subject: [PATCH 13/23] add tracking of role counts --- .../src/modules/board/board-ws-api.module.ts | 3 +- .../gateway/board-collaboration.gateway.ts | 79 +++++++++++++------ .../modules/board/metrics/metrics.service.ts | 36 ++++++--- 3 files changed, 81 insertions(+), 37 deletions(-) diff --git a/apps/server/src/modules/board/board-ws-api.module.ts b/apps/server/src/modules/board/board-ws-api.module.ts index 5ade81c90a8..716c222d375 100644 --- a/apps/server/src/modules/board/board-ws-api.module.ts +++ b/apps/server/src/modules/board/board-ws-api.module.ts @@ -2,13 +2,14 @@ import { forwardRef, Module } from '@nestjs/common'; import { CourseRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; import { AuthorizationModule } from '../authorization'; +import { UserModule } from '../user'; import { BoardModule } from './board.module'; import { BoardCollaborationGateway } from './gateway/board-collaboration.gateway'; import { MetricsService } from './metrics/metrics.service'; import { BoardUc, CardUc, ColumnUc, ElementUc } from './uc'; @Module({ - imports: [BoardModule, forwardRef(() => AuthorizationModule), LoggerModule], + imports: [BoardModule, forwardRef(() => AuthorizationModule), LoggerModule, UserModule], providers: [BoardCollaborationGateway, CardUc, ColumnUc, ElementUc, BoardUc, CourseRepo, MetricsService], exports: [], }) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 13e8ef61e82..96c0fe259e7 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -1,9 +1,18 @@ import { WsValidationPipe, Socket } from '@infra/socketio'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; import { UseGuards, UsePipes } from '@nestjs/common'; -import { SubscribeMessage, WebSocketGateway, WebSocketServer, WsException } from '@nestjs/websockets'; +import { + OnGatewayConnection, + OnGatewayDisconnect, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, + WsException, +} from '@nestjs/websockets'; import { RoleName } from '@shared/domain/interface'; +import { UserDO } from '@shared/domain/domainobject'; import { WsJwtAuthGuard } from '@src/modules/authentication/guard/ws-jwt-auth.guard'; +import { UserService } from '@modules/user'; import { Server } from 'socket.io'; import { BoardResponseMapper, CardResponseMapper, ContentElementResponseFactory } from '../controller/mapper'; import { ColumnResponseMapper } from '../controller/mapper/column-response.mapper'; @@ -35,8 +44,7 @@ import { UpdateContentElementMessageParams } from './dto/update-content-element. @UsePipes(new WsValidationPipe()) @WebSocketGateway(BoardCollaborationConfiguration.websocket) @UseGuards(WsJwtAuthGuard) -export class BoardCollaborationGateway { - // implements OnGatewayConnection, OnGatewayDisconnect { +export class BoardCollaborationGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server?: Server; @@ -48,6 +56,7 @@ export class BoardCollaborationGateway { private readonly cardUc: CardUc, private readonly elementUc: ElementUc, private readonly metricsService: MetricsService, + private readonly userService: UserService, private readonly authorizableService: BoardDoAuthorizableService // to be removed ) {} @@ -57,13 +66,17 @@ export class BoardCollaborationGateway { return user; } - private hasUserEditRights(socket: Socket) { - const { user } = socket.handshake; - const isTeacher = user ? user.roles.find((role) => role === RoleName.TEACHER) : false; - return isTeacher; + private mapRole(user: UserDO): 'editor' | 'viewer' | undefined { + if (user.roles.find((r) => r.name === RoleName.TEACHER)) { + return 'editor'; + } + if (user.roles.find((r) => r.name === RoleName.STUDENT)) { + return 'viewer'; + } + return undefined; } - private updateRoomsAndUsersMetrics() { + private async updateRoomsAndUsersMetrics(socket: Socket) { if (!this.server) { throw new Error('Server is not initialized'); } @@ -74,32 +87,34 @@ export class BoardCollaborationGateway { ).length; this.metricsService.setNumberOfUsers(userCount); this.metricsService.setNumberOfBoardRooms(roomCount); + await this.updateUserRoleCounters(socket); + } + + private async updateUserRoleCounters(socket: Socket) { + const { user } = socket.handshake; + if (user) { + if (!this.metricsService.isClientRoleKnown(socket.id)) { + const userDo = await this.userService.findById(user.userId); + const role = this.mapRole(userDo); + if (role) { + this.metricsService.trackClientRole(socket.id, role); + } + } + } } - public handleConnection(socket: Socket): void { + public handleConnection(socket: Socket): Promise { if (!socket) { throw new Error('Server is not initialized'); } - this.updateRoomsAndUsersMetrics(); - const hasEditRights = this.hasUserEditRights(socket); - if (hasEditRights) { - this.metricsService.incrementNumberOfEditors(); - } else { - this.metricsService.incrementNumberOfViewers(); - } + return this.updateRoomsAndUsersMetrics(socket); } public handleDisconnect(socket: Socket): void { if (!socket) { throw new Error('Server is not initialized'); } - this.updateRoomsAndUsersMetrics(); - const hasEditRights = this.hasUserEditRights(socket); - if (hasEditRights) { - this.metricsService.decrementNumberOfEditors(); - } else { - this.metricsService.decrementNumberOfViewers(); - } + this.metricsService.untrackClient(socket.id); } @SubscribeMessage('delete-board-request') @@ -114,6 +129,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('delete-board-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-board-title-request') @@ -128,6 +144,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-board-title-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-card-title-request') @@ -142,6 +159,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-card-title-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-card-height-request') @@ -156,6 +174,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-card-height-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('delete-card-request') @@ -170,6 +189,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('delete-card-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('create-card-request') @@ -188,6 +208,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('create-card-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('create-column-request') @@ -225,10 +246,10 @@ export class BoardCollaborationGateway { const responsePayload = BoardResponseMapper.mapToResponse(board); await emitter.emitToClient('fetch-board-success', responsePayload); - this.updateRoomsAndUsersMetrics(); } catch (err) { socket.emit('fetch-board-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('move-card-request') @@ -244,6 +265,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('move-card-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('move-column-request') @@ -259,6 +281,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('move-column-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-column-title-request') @@ -274,6 +297,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-column-title-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-board-visibility-request') @@ -289,6 +313,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-board-visibility-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('delete-column-request') @@ -304,6 +329,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('delete-column-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('fetch-card-request') @@ -320,6 +346,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('fetch-card-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('create-element-request') @@ -339,6 +366,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('create-element-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('update-element-request') @@ -354,6 +382,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('update-element-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('delete-element-request') @@ -369,6 +398,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('delete-element-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } @SubscribeMessage('move-element-request') @@ -384,6 +414,7 @@ export class BoardCollaborationGateway { } catch (err) { socket.emit('move-element-failure', data); } + await this.updateRoomsAndUsersMetrics(socket); } private async buildBoardSocketEmitter(client: Socket, id: string) { diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index eae092e0315..2b44e4e67eb 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -1,8 +1,13 @@ import { Injectable } from '@nestjs/common'; import { Gauge, register } from 'prom-client'; +type ClientId = string; +type Role = 'owner' | 'editor' | 'viewer'; + @Injectable() export class MetricsService { + private knownClientRoles: Map = new Map(); + private numberOfUsersOnServerCounter: Gauge; private numberOfBoardroomsOnServerCounter: Gauge; @@ -36,27 +41,34 @@ export class MetricsService { register.registerMetric(this.numberOfBoardroomsOnServerCounter); } - public setNumberOfUsers(value: number): void { - this.numberOfUsersOnServerCounter.set(value); + public isClientRoleKnown(clientId: ClientId): boolean { + return this.knownClientRoles.has(clientId); } - public setNumberOfBoardRooms(value: number): void { - this.numberOfBoardroomsOnServerCounter.set(value); + public trackClientRole(clientId: ClientId, role: Role): void { + this.knownClientRoles.set(clientId, role); + this.updateRoleCounts(); } - public incrementNumberOfEditors(): void { - this.numberOfEditorsOnServerCounter.inc(); + public untrackClient(clientId: ClientId): void { + this.knownClientRoles.delete(clientId); + this.updateRoleCounts(); } - public decrementNumberOfEditors(): void { - this.numberOfEditorsOnServerCounter.dec(); + private updateRoleCounts(): void { + this.numberOfEditorsOnServerCounter.set(this.countByRole('editor')); + this.numberOfViewersOnServerCounter.set(this.countByRole('viewer')); } - public incrementNumberOfViewers(): void { - this.numberOfViewersOnServerCounter.inc(); + private countByRole(role: Role) { + return Array.from(this.knownClientRoles.values()).filter((r) => r === role).length; } - public decrementNumberOfViewers(): void { - this.numberOfViewersOnServerCounter.dec(); + public setNumberOfUsers(value: number): void { + this.numberOfUsersOnServerCounter.set(value); + } + + public setNumberOfBoardRooms(value: number): void { + this.numberOfBoardroomsOnServerCounter.set(value); } } From 227541e92e531ad93d7463c49e7a942d7a05c062 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 7 Jun 2024 16:54:00 +0200 Subject: [PATCH 14/23] implement executionTime tracking --- .../gateway/board-collaboration.gateway.ts | 42 ++++++----------- .../modules/board/metrics/metrics.service.ts | 47 +++++++++++++++++-- .../metrics/track-execution-time.decorator.ts | 26 ++++++++++ 3 files changed, 84 insertions(+), 31 deletions(-) create mode 100644 apps/server/src/modules/board/metrics/track-execution-time.decorator.ts diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 11c1572a0f1..17efa334ac8 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -9,10 +9,7 @@ import { WebSocketServer, WsException, } from '@nestjs/websockets'; -import { RoleName } from '@shared/domain/interface'; -import { UserDO } from '@shared/domain/domainobject'; import { WsJwtAuthGuard } from '@src/modules/authentication/guard/ws-jwt-auth.guard'; -import { UserService } from '@modules/user'; import { Server } from 'socket.io'; import { BoardResponseMapper, CardResponseMapper, ContentElementResponseFactory } from '../controller/mapper'; import { ColumnResponseMapper } from '../controller/mapper/column-response.mapper'; @@ -40,6 +37,7 @@ import { UpdateBoardVisibilityMessageParams } from './dto/update-board-visibilit import { UpdateCardHeightMessageParams } from './dto/update-card-height.message.param'; import { UpdateCardTitleMessageParams } from './dto/update-card-title.message.param'; import { UpdateContentElementMessageParams } from './dto/update-content-element.message.param'; +import { TrackExecutionTime } from '../metrics/track-execution-time.decorator'; @UsePipes(new WsValidationPipe()) @WebSocketGateway(BoardCollaborationConfiguration.websocket) @@ -56,26 +54,21 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway private readonly cardUc: CardUc, private readonly elementUc: ElementUc, private readonly metricsService: MetricsService, - private readonly userService: UserService, private readonly authorizableService: BoardDoAuthorizableService // to be removed ) {} + trackExecutionTime(methodName: string, executionTimeMs: number) { + if (this.metricsService) { + this.metricsService.setExecutionTime(methodName, executionTimeMs); + } + } + private getCurrentUser(socket: Socket) { const { user } = socket.handshake; if (!user) throw new WsException('Not Authenticated.'); return user; } - private mapRole(user: UserDO): 'editor' | 'viewer' | undefined { - if (user.roles.find((r) => r.name === RoleName.TEACHER)) { - return 'editor'; - } - if (user.roles.find((r) => r.name === RoleName.STUDENT)) { - return 'viewer'; - } - return undefined; - } - private async updateRoomsAndUsersMetrics(socket: Socket) { if (!this.server) { throw new Error('Server is not initialized'); @@ -87,20 +80,8 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway ).length; this.metricsService.setNumberOfUsers(userCount); this.metricsService.setNumberOfBoardRooms(roomCount); - await this.updateUserRoleCounters(socket); - } - - private async updateUserRoleCounters(socket: Socket) { const { user } = socket.handshake; - if (user) { - if (!this.metricsService.isClientRoleKnown(socket.id)) { - const userDo = await this.userService.findById(user.userId); - const role = this.mapRole(userDo); - if (role) { - this.metricsService.trackClientRole(socket.id, role); - } - } - } + await this.metricsService.trackClientRole(socket.id, user?.userId); } public handleConnection(socket: Socket): Promise { @@ -133,6 +114,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('update-board-title-request') + @TrackExecutionTime() @UseRequestContext() async updateBoardTitle(socket: Socket, data: UpdateBoardTitleMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'update-board-title' }); @@ -148,6 +130,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('update-card-title-request') + @TrackExecutionTime() @UseRequestContext() async updateCardTitle(socket: Socket, data: UpdateCardTitleMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'update-card-title' }); @@ -193,6 +176,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('create-card-request') + @TrackExecutionTime() @UseRequestContext() async createCard(socket: Socket, data: CreateCardMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.columnId, action: 'create-card' }); @@ -236,6 +220,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('fetch-board-request') + @TrackExecutionTime() @UseRequestContext() async fetchBoard(socket: Socket, data: FetchBoardMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'fetch-board' }); @@ -282,6 +267,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('update-column-title-request') + @TrackExecutionTime() @UseRequestContext() async updateColumnTitle(socket: Socket, data: UpdateColumnTitleMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.columnId, action: 'update-column-title' }); @@ -327,6 +313,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('fetch-card-request') + @TrackExecutionTime() @UseRequestContext() async fetchCards(socket: Socket, data: FetchCardsMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardIds[0], action: 'fetch-card' }); @@ -362,6 +349,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway } @SubscribeMessage('update-element-request') + @TrackExecutionTime() @UseRequestContext() async updateElement(socket: Socket, data: UpdateContentElementMessageParams) { const emitter = await this.buildBoardSocketEmitter({ socket, id: data.elementId, action: 'update-element' }); diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 2b44e4e67eb..c9c665076e9 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -1,4 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { UserDO } from '@shared/domain/domainobject'; +import { RoleName } from '@shared/domain/interface'; +import { UserService } from '@src/modules/user'; import { Gauge, register } from 'prom-client'; type ClientId = string; @@ -16,7 +19,10 @@ export class MetricsService { private numberOfViewersOnServerCounter: Gauge; - constructor() { + private executionTimes: Map> = new Map(); + + // better percentile than avg + constructor(private readonly userService: UserService) { this.numberOfUsersOnServerCounter = new Gauge({ name: 'sc_boards_users', help: 'Number of active users per pod', @@ -45,9 +51,28 @@ export class MetricsService { return this.knownClientRoles.has(clientId); } - public trackClientRole(clientId: ClientId, role: Role): void { - this.knownClientRoles.set(clientId, role); - this.updateRoleCounts(); + private mapRole(user: UserDO): 'editor' | 'viewer' | undefined { + if (user.roles.find((r) => r.name === RoleName.TEACHER)) { + return 'editor'; + } + if (user.roles.find((r) => r.name === RoleName.STUDENT)) { + return 'viewer'; + } + return undefined; + } + + public async trackClientRole(clientId: ClientId, userId: string | undefined): Promise { + if (userId) { + // extract => metricsService + if (!this.knownClientRoles.has(clientId)) { + const userDo = await this.userService.findById(userId); + const role = this.mapRole(userDo); + if (role) { + this.knownClientRoles.set(clientId, role); + this.updateRoleCounts(); + } + } + } } public untrackClient(clientId: ClientId): void { @@ -71,4 +96,18 @@ export class MetricsService { public setNumberOfBoardRooms(value: number): void { this.numberOfBoardroomsOnServerCounter.set(value); } + + public setExecutionTime(actionName: string, value: number): void { + let gauge = this.executionTimes.get(actionName); + + if (!gauge) { + gauge = new Gauge({ + name: `sc_boards_execution_time_${actionName}`, + help: '...', + }); + this.executionTimes.set(actionName, gauge); + register.registerMetric(gauge); + } + gauge.set(value); + } } diff --git a/apps/server/src/modules/board/metrics/track-execution-time.decorator.ts b/apps/server/src/modules/board/metrics/track-execution-time.decorator.ts new file mode 100644 index 00000000000..2ae058a488d --- /dev/null +++ b/apps/server/src/modules/board/metrics/track-execution-time.decorator.ts @@ -0,0 +1,26 @@ +import { performance } from 'perf_hooks'; + +const CALLBACK_METHOD_NAME = 'trackExecutionTime'; + +export function TrackExecutionTime(): MethodDecorator { + return function track(target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { + if (typeof target[CALLBACK_METHOD_NAME] !== 'function') { + throw new Error( + `The class ${target.constructor.name} does not implement the required ${CALLBACK_METHOD_NAME} method.` + ); + } + + const originalMethod = descriptor.value as () => unknown; + descriptor.value = async function wrapper(...args: []) { + const startTime = performance.now(); + const result = await originalMethod.apply(this, args); + const executionTime = performance.now() - startTime; + + const callback = target[CALLBACK_METHOD_NAME] as (methodName: string, executionTime: number) => void; + callback.apply(this, [String(propertyKey), executionTime]); + + return result; + }; + return descriptor; + }; +} From 10d2ea1fbdba724dc744a427069dcf211369ca9e Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 12 Jun 2024 16:45:16 +0200 Subject: [PATCH 15/23] try using histogram for execution times --- .../modules/board/metrics/metrics.service.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index c9c665076e9..24a9fe32250 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { UserDO } from '@shared/domain/domainobject'; import { RoleName } from '@shared/domain/interface'; import { UserService } from '@src/modules/user'; -import { Gauge, register } from 'prom-client'; +import { Gauge, Histogram, linearBuckets, register } from 'prom-client'; type ClientId = string; type Role = 'owner' | 'editor' | 'viewer'; @@ -19,7 +19,7 @@ export class MetricsService { private numberOfViewersOnServerCounter: Gauge; - private executionTimes: Map> = new Map(); + private executionTimes: Map> = new Map(); // better percentile than avg constructor(private readonly userService: UserService) { @@ -98,16 +98,17 @@ export class MetricsService { } public setExecutionTime(actionName: string, value: number): void { - let gauge = this.executionTimes.get(actionName); + let histogram = this.executionTimes.get(actionName); - if (!gauge) { - gauge = new Gauge({ + if (!histogram) { + histogram = new Histogram({ name: `sc_boards_execution_time_${actionName}`, help: '...', + buckets: linearBuckets(0, 25, 40), }); - this.executionTimes.set(actionName, gauge); - register.registerMetric(gauge); + this.executionTimes.set(actionName, histogram); + register.registerMetric(histogram); } - gauge.set(value); + histogram.observe(value); } } From 8a2bfc2b2a83505d58dad880536b6dd3751df920 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 12 Jun 2024 17:16:25 +0200 Subject: [PATCH 16/23] switch execution time use Summary instead of histogram --- .../modules/board/metrics/metrics.service.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 24a9fe32250..9f1918ba874 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { UserDO } from '@shared/domain/domainobject'; import { RoleName } from '@shared/domain/interface'; import { UserService } from '@src/modules/user'; -import { Gauge, Histogram, linearBuckets, register } from 'prom-client'; +import { Gauge, Summary, register } from 'prom-client'; type ClientId = string; type Role = 'owner' | 'editor' | 'viewer'; @@ -19,7 +19,7 @@ export class MetricsService { private numberOfViewersOnServerCounter: Gauge; - private executionTimes: Map> = new Map(); + private executionTimesSummary: Map> = new Map(); // better percentile than avg constructor(private readonly userService: UserService) { @@ -98,17 +98,19 @@ export class MetricsService { } public setExecutionTime(actionName: string, value: number): void { - let histogram = this.executionTimes.get(actionName); + let summary = this.executionTimesSummary.get(actionName); - if (!histogram) { - histogram = new Histogram({ + if (!summary) { + summary = new Summary({ name: `sc_boards_execution_time_${actionName}`, help: '...', - buckets: linearBuckets(0, 25, 40), + maxAgeSeconds: 60, + ageBuckets: 5, + percentiles: [0.01, 0.1, 0.9, 0.99], }); - this.executionTimes.set(actionName, histogram); - register.registerMetric(histogram); + this.executionTimesSummary.set(actionName, summary); + register.registerMetric(summary); } - histogram.observe(value); + summary.observe(value); } } From 049cf5a1f81fbd8e921667d5c0b79159d115a308 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 12 Jun 2024 17:58:25 +0200 Subject: [PATCH 17/23] chore: cleanup --- .../modules/board/metrics/metrics.service.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 9f1918ba874..65e429fcbb9 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -11,8 +11,6 @@ type Role = 'owner' | 'editor' | 'viewer'; export class MetricsService { private knownClientRoles: Map = new Map(); - private numberOfUsersOnServerCounter: Gauge; - private numberOfBoardroomsOnServerCounter: Gauge; private numberOfEditorsOnServerCounter: Gauge; @@ -21,13 +19,7 @@ export class MetricsService { private executionTimesSummary: Map> = new Map(); - // better percentile than avg constructor(private readonly userService: UserService) { - this.numberOfUsersOnServerCounter = new Gauge({ - name: 'sc_boards_users', - help: 'Number of active users per pod', - }); - this.numberOfBoardroomsOnServerCounter = new Gauge({ name: 'sc_boards_rooms', help: 'Number of active boards per pod', @@ -43,7 +35,8 @@ export class MetricsService { help: 'Number of active viewers per pod', }); - register.registerMetric(this.numberOfUsersOnServerCounter); + register.registerMetric(this.numberOfEditorsOnServerCounter); + register.registerMetric(this.numberOfViewersOnServerCounter); register.registerMetric(this.numberOfBoardroomsOnServerCounter); } @@ -89,10 +82,6 @@ export class MetricsService { return Array.from(this.knownClientRoles.values()).filter((r) => r === role).length; } - public setNumberOfUsers(value: number): void { - this.numberOfUsersOnServerCounter.set(value); - } - public setNumberOfBoardRooms(value: number): void { this.numberOfBoardroomsOnServerCounter.set(value); } @@ -103,7 +92,7 @@ export class MetricsService { if (!summary) { summary = new Summary({ name: `sc_boards_execution_time_${actionName}`, - help: '...', + help: 'Average execution time of a specific action in milliseconds', maxAgeSeconds: 60, ageBuckets: 5, percentiles: [0.01, 0.1, 0.9, 0.99], From 1b552eb44868607e2fa6ff2668ba10045ab7514b Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Thu, 13 Jun 2024 21:24:13 +0200 Subject: [PATCH 18/23] implement tests for metricsService --- .../gateway/board-collaboration.gateway.ts | 4 +- .../board/metrics/metrics.service.spec.ts | 87 +++++++++++++++++++ .../modules/board/metrics/metrics.service.ts | 23 +++-- 3 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 apps/server/src/modules/board/metrics/metrics.service.spec.ts diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 17efa334ac8..07f7e202582 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -74,14 +74,12 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway throw new Error('Server is not initialized'); } - const userCount = Array.from(this.server.of('/').adapter.sids.keys()).length; const roomCount = Array.from(this.server.of('/').adapter.rooms.keys()).filter((key) => key.startsWith('board_') ).length; - this.metricsService.setNumberOfUsers(userCount); this.metricsService.setNumberOfBoardRooms(roomCount); const { user } = socket.handshake; - await this.metricsService.trackClientRole(socket.id, user?.userId); + await this.metricsService.trackRoleOfClient(socket.id, user?.userId); } public handleConnection(socket: Socket): Promise { diff --git a/apps/server/src/modules/board/metrics/metrics.service.spec.ts b/apps/server/src/modules/board/metrics/metrics.service.spec.ts new file mode 100644 index 00000000000..9da48af0baa --- /dev/null +++ b/apps/server/src/modules/board/metrics/metrics.service.spec.ts @@ -0,0 +1,87 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { UserService } from '@src/modules/user'; +import { RoleName } from '@shared/domain/interface'; +import { roleFactory, userDoFactory } from '@shared/testing'; +import { MetricsService } from './metrics.service'; + +describe(MetricsService.name, () => { + let module: TestingModule; + let service: MetricsService; + let userService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + MetricsService, + { + provide: UserService, + useValue: createMock(), + }, + ], + }).compile(); + + service = module.get(MetricsService); + userService = module.get(UserService); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('trackRoleOfClient', () => { + const setup = (roleName: RoleName) => { + const teacherRole = roleFactory.build({ name: roleName }); + const userDo = userDoFactory.buildWithId({ roles: [teacherRole] }); + userService.findById.mockResolvedValueOnce(userDo); + const clientId = Math.random().toString(); + return { userDo, clientId, userId: userDo.id }; + }; + + describe('when tracking a user with role teacher', () => { + it('should count one editor', async () => { + const { clientId, userId } = setup(RoleName.TEACHER); + + const role = await service.trackRoleOfClient(clientId, userId); + + expect(role).toEqual('editor'); + }); + }); + + describe('when tracking a user with role Course-Substition-Teacher', () => { + it('should count one editor', async () => { + const { clientId, userId } = setup(RoleName.COURSESUBSTITUTIONTEACHER); + + const role = await service.trackRoleOfClient(clientId, userId); + + expect(role).toEqual('editor'); + }); + }); + + describe('when tracking a user with role student', () => { + it('should count one viewer', async () => { + const { clientId, userId } = setup(RoleName.STUDENT); + + const role = await service.trackRoleOfClient(clientId, userId); + + expect(role).toEqual('viewer'); + }); + }); + + describe('when tracking a user with role teacher that is unknown', () => { + it('should not count for any role', async () => { + const teacherRole = roleFactory.build({ name: RoleName.TEACHER }); + const userDo = userDoFactory.buildWithId({ roles: [teacherRole] }); + const clientId = Math.random().toString(); + + const role = await service.trackRoleOfClient(clientId, userDo.id); + + expect(role).toEqual(undefined); + }); + }); + }); +}); diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 65e429fcbb9..8fdbfb87a27 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -13,7 +13,7 @@ export class MetricsService { private numberOfBoardroomsOnServerCounter: Gauge; - private numberOfEditorsOnServerCounter: Gauge; + public numberOfEditorsOnServerCounter: Gauge; private numberOfViewersOnServerCounter: Gauge; @@ -45,27 +45,26 @@ export class MetricsService { } private mapRole(user: UserDO): 'editor' | 'viewer' | undefined { - if (user.roles.find((r) => r.name === RoleName.TEACHER)) { + const EDITOR_ROLES = [RoleName.TEACHER, RoleName.COURSESUBSTITUTIONTEACHER, RoleName.COURSETEACHER]; + if (user.roles.find((r) => EDITOR_ROLES.includes(r.name))) { return 'editor'; } - if (user.roles.find((r) => r.name === RoleName.STUDENT)) { - return 'viewer'; - } - return undefined; + return 'viewer'; } - public async trackClientRole(clientId: ClientId, userId: string | undefined): Promise { - if (userId) { - // extract => metricsService - if (!this.knownClientRoles.has(clientId)) { - const userDo = await this.userService.findById(userId); - const role = this.mapRole(userDo); + public async trackRoleOfClient(clientId: ClientId, userId: string | undefined): Promise { + let role = this.knownClientRoles.get(clientId); + if (role === undefined && userId) { + const userDo = await this.userService.findById(userId); + if (userDo) { + role = this.mapRole(userDo); if (role) { this.knownClientRoles.set(clientId, role); this.updateRoleCounts(); } } } + return role; } public untrackClient(clientId: ClientId): void { From ec7de961929a6189f3c924cd3a2d4893db4f54d1 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Thu, 13 Jun 2024 21:46:00 +0200 Subject: [PATCH 19/23] test trackExecutionTimeDecorator --- .../track-execution-time.decorator.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 apps/server/src/modules/board/metrics/track-execution-time.decorator.spec.ts diff --git a/apps/server/src/modules/board/metrics/track-execution-time.decorator.spec.ts b/apps/server/src/modules/board/metrics/track-execution-time.decorator.spec.ts new file mode 100644 index 00000000000..76e3cb98624 --- /dev/null +++ b/apps/server/src/modules/board/metrics/track-execution-time.decorator.spec.ts @@ -0,0 +1,36 @@ +import { TrackExecutionTime } from './track-execution-time.decorator'; + +class MockClassWithTrackingFunction { + trackExecutionTime(methodName: string, executionTime: number) { + console.log(`Executing method ${methodName} took ${executionTime}ms`); + } +} +class MockClassWithoutTrackingFunction { + hello() { + console.log('Hello'); + } +} + +describe('TrackExecutionTimeDecorator', () => { + describe('track', () => { + describe('when a tracking function is defined in the target object', () => { + it('should not throw an exception', () => { + const decorator = TrackExecutionTime(); + + const target = new MockClassWithTrackingFunction(); + expect(() => decorator(target, 'nameOfFunctionBeingTracked', {})).not.toThrow(); + }); + }); + + describe('when no tracking function is defined in the target object', () => { + it('should throw an exception', () => { + const decorator = TrackExecutionTime(); + + const target = new MockClassWithoutTrackingFunction(); + expect(() => decorator(target, 'nameOfFunctionBeingTracked', {})).toThrowError( + `The class MockClassWithoutTrackingFunction does not implement the required trackExecutionTime method.` + ); + }); + }); + }); +}); From 35bff9fe6756626b6ea103a60bea8baf87e4e1c5 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Thu, 13 Jun 2024 22:22:35 +0200 Subject: [PATCH 20/23] remove unused method --- apps/server/src/modules/board/metrics/metrics.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/server/src/modules/board/metrics/metrics.service.ts b/apps/server/src/modules/board/metrics/metrics.service.ts index 8fdbfb87a27..8019220b40e 100644 --- a/apps/server/src/modules/board/metrics/metrics.service.ts +++ b/apps/server/src/modules/board/metrics/metrics.service.ts @@ -40,10 +40,6 @@ export class MetricsService { register.registerMetric(this.numberOfBoardroomsOnServerCounter); } - public isClientRoleKnown(clientId: ClientId): boolean { - return this.knownClientRoles.has(clientId); - } - private mapRole(user: UserDO): 'editor' | 'viewer' | undefined { const EDITOR_ROLES = [RoleName.TEACHER, RoleName.COURSESUBSTITUTIONTEACHER, RoleName.COURSETEACHER]; if (user.roles.find((r) => EDITOR_ROLES.includes(r.name))) { From 671c737afd12c1afe5dab29f44194964175939cb Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 14 Jun 2024 08:42:12 +0200 Subject: [PATCH 21/23] remove unneeded code --- .../board/gateway/board-collaboration.gateway.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 07f7e202582..56d4b969021 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -2,7 +2,6 @@ import { WsValidationPipe, Socket } from '@infra/socketio'; import { MikroORM, UseRequestContext } from '@mikro-orm/core'; import { UseGuards, UsePipes } from '@nestjs/common'; import { - OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, WebSocketGateway, @@ -42,7 +41,7 @@ import { TrackExecutionTime } from '../metrics/track-execution-time.decorator'; @UsePipes(new WsValidationPipe()) @WebSocketGateway(BoardCollaborationConfiguration.websocket) @UseGuards(WsJwtAuthGuard) -export class BoardCollaborationGateway implements OnGatewayConnection, OnGatewayDisconnect { +export class BoardCollaborationGateway implements OnGatewayDisconnect { @WebSocketServer() server?: Server; @@ -82,17 +81,7 @@ export class BoardCollaborationGateway implements OnGatewayConnection, OnGateway await this.metricsService.trackRoleOfClient(socket.id, user?.userId); } - public handleConnection(socket: Socket): Promise { - if (!socket) { - throw new Error('Server is not initialized'); - } - return this.updateRoomsAndUsersMetrics(socket); - } - public handleDisconnect(socket: Socket): void { - if (!socket) { - throw new Error('Server is not initialized'); - } this.metricsService.untrackClient(socket.id); } From bdb64ed00f0c576d1738a4ffa4a0f9e65af4930e Mon Sep 17 00:00:00 2001 From: Thomas Feldtkeller Date: Fri, 14 Jun 2024 15:47:33 +0200 Subject: [PATCH 22/23] minor changes --- apps/server/src/apps/board-collaboration.app.ts | 6 +++--- apps/server/src/modules/board/board-ws-api.module.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/server/src/apps/board-collaboration.app.ts b/apps/server/src/apps/board-collaboration.app.ts index 695d40a4d40..72a44d1e061 100644 --- a/apps/server/src/apps/board-collaboration.app.ts +++ b/apps/server/src/apps/board-collaboration.app.ts @@ -50,9 +50,9 @@ async function bootstrap() { }); console.log('##########################################'); - console.log(`### Start Board Collaboration Server`); - console.log(`### Port: ${port}`); - console.log(`### Base path: ${basePath}`); + console.log(`### Start Board Collaboration Server ###`); + console.log(`### Port: ${port} ###`); + console.log(`### Base path: ${basePath} ###`); console.log('##########################################'); } void bootstrap(); diff --git a/apps/server/src/modules/board/board-ws-api.module.ts b/apps/server/src/modules/board/board-ws-api.module.ts index 716c222d375..763773d165f 100644 --- a/apps/server/src/modules/board/board-ws-api.module.ts +++ b/apps/server/src/modules/board/board-ws-api.module.ts @@ -1,8 +1,8 @@ import { forwardRef, Module } from '@nestjs/common'; import { CourseRepo } from '@shared/repo'; import { LoggerModule } from '@src/core/logger'; -import { AuthorizationModule } from '../authorization'; -import { UserModule } from '../user'; +import { AuthorizationModule } from '@modules/authorization'; +import { UserModule } from '@modules/user'; import { BoardModule } from './board.module'; import { BoardCollaborationGateway } from './gateway/board-collaboration.gateway'; import { MetricsService } from './metrics/metrics.service'; From 0e8a5b05e1531240c6af5bc7303e50e2fb7a83b9 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 14 Jun 2024 15:49:56 +0200 Subject: [PATCH 23/23] ensure existence of server property --- .../modules/board/gateway/board-collaboration.gateway.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index 56d4b969021..57585f752bc 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -43,7 +43,7 @@ import { TrackExecutionTime } from '../metrics/track-execution-time.decorator'; @UseGuards(WsJwtAuthGuard) export class BoardCollaborationGateway implements OnGatewayDisconnect { @WebSocketServer() - server?: Server; + server!: Server; // TODO: use loggables instead of legacy logger constructor( @@ -69,10 +69,6 @@ export class BoardCollaborationGateway implements OnGatewayDisconnect { } private async updateRoomsAndUsersMetrics(socket: Socket) { - if (!this.server) { - throw new Error('Server is not initialized'); - } - const roomCount = Array.from(this.server.of('/').adapter.rooms.keys()).filter((key) => key.startsWith('board_') ).length;