Adding Prometheus metrics
This commit adds a '/metrics' endpoint in the API that can be exploited by Prometheus. This endpoint returns: - the number of connected sockets - the number of users per room - common NodeJS and system metrics WARNING: this endpoint is public right now and should be protected
This commit is contained in:
parent
bdb01f3103
commit
af6924a27c
@ -32,6 +32,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-status-codes": "^1.4.0",
|
"http-status-codes": "^1.4.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"prom-client": "^12.0.0",
|
||||||
"socket.io": "^2.3.0",
|
"socket.io": "^2.3.0",
|
||||||
"systeminformation": "^4.26.5",
|
"systeminformation": "^4.26.5",
|
||||||
"ts-node-dev": "^1.0.0-pre.44",
|
"ts-node-dev": "^1.0.0-pre.44",
|
||||||
|
@ -6,6 +6,7 @@ import {Application, Request, Response} from 'express';
|
|||||||
import bodyParser = require('body-parser');
|
import bodyParser = require('body-parser');
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import {MapController} from "./Controller/MapController";
|
import {MapController} from "./Controller/MapController";
|
||||||
|
import {PrometheusController} from "./Controller/PrometheusController";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: Application;
|
public app: Application;
|
||||||
@ -13,6 +14,7 @@ class App {
|
|||||||
public ioSocketController: IoSocketController;
|
public ioSocketController: IoSocketController;
|
||||||
public authenticateController: AuthenticateController;
|
public authenticateController: AuthenticateController;
|
||||||
public mapController: MapController;
|
public mapController: MapController;
|
||||||
|
public prometheusController: PrometheusController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
@ -29,6 +31,7 @@ class App {
|
|||||||
this.ioSocketController = new IoSocketController(this.server);
|
this.ioSocketController = new IoSocketController(this.server);
|
||||||
this.authenticateController = new AuthenticateController(this.app);
|
this.authenticateController = new AuthenticateController(this.app);
|
||||||
this.mapController = new MapController(this.app);
|
this.mapController = new MapController(this.app);
|
||||||
|
this.prometheusController = new PrometheusController(this.app, this.ioSocketController);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add session user
|
// TODO add session user
|
||||||
|
@ -12,6 +12,8 @@ import {SetPlayerDetailsMessage} from "_Model/Websocket/SetPlayerDetailsMessage"
|
|||||||
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
|
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
|
||||||
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
|
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
|
||||||
import si from "systeminformation";
|
import si from "systeminformation";
|
||||||
|
import {Gauge} from "prom-client";
|
||||||
|
import os from 'os';
|
||||||
|
|
||||||
enum SockerIoEvent {
|
enum SockerIoEvent {
|
||||||
CONNECTION = "connection",
|
CONNECTION = "connection",
|
||||||
@ -31,12 +33,24 @@ enum SockerIoEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class IoSocketController {
|
export class IoSocketController {
|
||||||
Io: socketIO.Server;
|
public readonly Io: socketIO.Server;
|
||||||
Worlds: Map<string, World> = new Map<string, World>();
|
private Worlds: Map<string, World> = new Map<string, World>();
|
||||||
sockets: Map<string, ExSocketInterface> = new Map<string, ExSocketInterface>();
|
private sockets: Map<string, ExSocketInterface> = new Map<string, ExSocketInterface>();
|
||||||
|
private nbClientsGauge: Gauge<string>;
|
||||||
|
private nbClientsPerRoomGauge: Gauge<string>;
|
||||||
|
|
||||||
constructor(server: http.Server) {
|
constructor(server: http.Server) {
|
||||||
this.Io = socketIO(server);
|
this.Io = socketIO(server);
|
||||||
|
this.nbClientsGauge = new Gauge({
|
||||||
|
name: 'workadventure_nb_sockets',
|
||||||
|
help: 'Number of connected sockets',
|
||||||
|
labelNames: [ 'host' ]
|
||||||
|
});
|
||||||
|
this.nbClientsPerRoomGauge = new Gauge({
|
||||||
|
name: 'workadventure_nb_clients_per_room',
|
||||||
|
help: 'Number of clients per room',
|
||||||
|
labelNames: [ 'host', 'room' ]
|
||||||
|
});
|
||||||
|
|
||||||
// Authentication with token. it will be decoded and stored in the socket.
|
// Authentication with token. it will be decoded and stored in the socket.
|
||||||
// Completely commented for now, as we do not use the "/login" route at all.
|
// Completely commented for now, as we do not use the "/login" route at all.
|
||||||
@ -103,6 +117,7 @@ export class IoSocketController {
|
|||||||
|
|
||||||
// Let's log server load when a user joins
|
// Let's log server load when a user joins
|
||||||
let srvSockets = this.Io.sockets.sockets;
|
let srvSockets = this.Io.sockets.sockets;
|
||||||
|
this.nbClientsGauge.inc({ host: os.hostname() });
|
||||||
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)');
|
console.log(new Date().toISOString() + ' A user joined (', Object.keys(srvSockets).length, ' connected users)');
|
||||||
si.currentLoad().then(data => console.log(' Current load: ', data.avgload));
|
si.currentLoad().then(data => console.log(' Current load: ', data.avgload));
|
||||||
si.currentLoad().then(data => console.log(' CPU: ', data.currentload, '%'));
|
si.currentLoad().then(data => console.log(' CPU: ', data.currentload, '%'));
|
||||||
@ -231,6 +246,7 @@ export class IoSocketController {
|
|||||||
|
|
||||||
// Let's log server load when a user leaves
|
// Let's log server load when a user leaves
|
||||||
let srvSockets = this.Io.sockets.sockets;
|
let srvSockets = this.Io.sockets.sockets;
|
||||||
|
this.nbClientsGauge.dec({ host: os.hostname() });
|
||||||
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)');
|
console.log('A user left (', Object.keys(srvSockets).length, ' connected users)');
|
||||||
si.currentLoad().then(data => console.log('Current load: ', data.avgload));
|
si.currentLoad().then(data => console.log('Current load: ', data.avgload));
|
||||||
si.currentLoad().then(data => console.log('CPU: ', data.currentload, '%'));
|
si.currentLoad().then(data => console.log('CPU: ', data.currentload, '%'));
|
||||||
@ -267,6 +283,7 @@ export class IoSocketController {
|
|||||||
}
|
}
|
||||||
//user leave previous room
|
//user leave previous room
|
||||||
Client.leave(Client.roomId);
|
Client.leave(Client.roomId);
|
||||||
|
this.nbClientsPerRoomGauge.inc({ host: os.hostname(), room: Client.roomId });
|
||||||
delete Client.roomId;
|
delete Client.roomId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,6 +291,7 @@ export class IoSocketController {
|
|||||||
private joinRoom(Client : ExSocketInterface, roomId: string, position: Point): World {
|
private joinRoom(Client : ExSocketInterface, roomId: string, position: Point): World {
|
||||||
//join user in room
|
//join user in room
|
||||||
Client.join(roomId);
|
Client.join(roomId);
|
||||||
|
this.nbClientsPerRoomGauge.inc({ host: os.hostname(), room: roomId });
|
||||||
Client.roomId = roomId;
|
Client.roomId = roomId;
|
||||||
Client.position = position;
|
Client.position = position;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export class MapController {
|
|||||||
// Returns a map mapping map name to file name of the map
|
// Returns a map mapping map name to file name of the map
|
||||||
getStartMap() {
|
getStartMap() {
|
||||||
this.App.get("/start-map", (req: Request, res: Response) => {
|
this.App.get("/start-map", (req: Request, res: Response) => {
|
||||||
return res.status(OK).send({
|
res.status(OK).send({
|
||||||
mapUrlStart: req.headers.host + "/map/files" + URL_ROOM_STARTED,
|
mapUrlStart: req.headers.host + "/map/files" + URL_ROOM_STARTED,
|
||||||
startInstance: "global"
|
startInstance: "global"
|
||||||
});
|
});
|
||||||
|
20
back/src/Controller/PrometheusController.ts
Normal file
20
back/src/Controller/PrometheusController.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Application, Request, Response} from "express";
|
||||||
|
import {IoSocketController} from "_Controller/IoSocketController";
|
||||||
|
const register = require('prom-client').register;
|
||||||
|
const collectDefaultMetrics = require('prom-client').collectDefaultMetrics;
|
||||||
|
|
||||||
|
export class PrometheusController {
|
||||||
|
constructor(private App: Application, private ioSocketController: IoSocketController) {
|
||||||
|
collectDefaultMetrics({
|
||||||
|
timeout: 10000,
|
||||||
|
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5], // These are the default buckets.
|
||||||
|
});
|
||||||
|
|
||||||
|
this.App.get("/metrics", this.metrics.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private metrics(req: Request, res: Response): void {
|
||||||
|
res.set('Content-Type', register.contentType);
|
||||||
|
res.end(register.metrics());
|
||||||
|
}
|
||||||
|
}
|
@ -266,6 +266,11 @@ better-assert@~1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
callsite "1.0.0"
|
callsite "1.0.0"
|
||||||
|
|
||||||
|
bintrees@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
|
||||||
|
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
|
||||||
|
|
||||||
blob@0.0.5:
|
blob@0.0.5:
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
||||||
@ -1333,6 +1338,13 @@ progress@^2.0.0:
|
|||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||||
|
|
||||||
|
prom-client@^12.0.0:
|
||||||
|
version "12.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-12.0.0.tgz#9689379b19bd3f6ab88a9866124db9da3d76c6ed"
|
||||||
|
integrity sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ==
|
||||||
|
dependencies:
|
||||||
|
tdigest "^0.1.1"
|
||||||
|
|
||||||
proxy-addr@~2.0.5:
|
proxy-addr@~2.0.5:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||||
@ -1683,6 +1695,13 @@ table@^5.2.3:
|
|||||||
slice-ansi "^2.1.0"
|
slice-ansi "^2.1.0"
|
||||||
string-width "^3.0.0"
|
string-width "^3.0.0"
|
||||||
|
|
||||||
|
tdigest@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021"
|
||||||
|
integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=
|
||||||
|
dependencies:
|
||||||
|
bintrees "1.0.1"
|
||||||
|
|
||||||
text-table@^0.2.0:
|
text-table@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
|
Loading…
Reference in New Issue
Block a user