From 75ad95a357171353bd4e33fdd8fd8ec2f8efec4c Mon Sep 17 00:00:00 2001 From: janderedev Date: Sat, 12 Feb 2022 15:25:24 +0100 Subject: [PATCH] add basic prometheus metrics --- .env.example | 13 +++++ bot/package.json | 1 + bot/src/bot/modules/command_handler.ts | 3 ++ bot/src/bot/modules/metrics.ts | 73 ++++++++++++++++++++++++++ bot/src/index.ts | 1 + bot/yarn.lock | 19 +++++++ docker-compose.yml.example | 5 ++ 7 files changed, 115 insertions(+) create mode 100644 bot/src/bot/modules/metrics.ts diff --git a/.env.example b/.env.example index f9275b9..622a543 100644 --- a/.env.example +++ b/.env.example @@ -31,6 +31,19 @@ BOT_OWNERS= # used to log certain events (bot started, etc). LOG_WEBHOOK= +# Optional: If set, enables Prometheus metrics +# on the specified port (Under /metrics). +# Note that no authentication can be configured; +# you should use a reverse proxy if you need +# BasicAuth or a different form of authentication. +BOT_METRICS_PORT= + +# Optional: Set this to a channel ID if you +# want Prometheus metrics to return `msg_ping`. +# The bot will regularly send a message in that +# channel. +BOT_METRICS_MSG_PING_CHANNEL= + # The URL from which your API and Web app are # publicly reachable. Do not add a trailing # slash to the URLs. diff --git a/bot/package.json b/bot/package.json index 85f4188..7938f3a 100644 --- a/bot/package.json +++ b/bot/package.json @@ -22,6 +22,7 @@ "form-data": "^4.0.0", "log75": "^2.2.0", "monk": "^7.3.4", + "prom-client": "^14.0.1", "ulid": "^2.3.0", "xlsx": "^0.17.3" }, diff --git a/bot/src/bot/modules/command_handler.ts b/bot/src/bot/modules/command_handler.ts index 3f3d180..b56f2f5 100644 --- a/bot/src/bot/modules/command_handler.ts +++ b/bot/src/bot/modules/command_handler.ts @@ -10,6 +10,7 @@ import MessageCommandContext from "../../struct/MessageCommandContext"; import { fileURLToPath } from 'url'; import { getOwnMemberInServer, hasPermForChannel } from "../util"; import { isSudo, updateSudoTimeout } from "../commands/botadm"; +import { metrics } from "./metrics"; // thanks a lot esm const filename = fileURLToPath(import.meta.url); @@ -72,6 +73,8 @@ let commands: Command[]; let cmd = commands.find(c => c.name == cmdName || (c.aliases?.indexOf(cmdName!) ?? -1) > -1); if (!cmd) return; + metrics.commands.inc(); + if (isSudo(msg.author!)) updateSudoTimeout(msg.author!); if (cmd.restrict == 'BOTOWNER' && ownerIDs.indexOf(msg.author_id) == -1) { diff --git a/bot/src/bot/modules/metrics.ts b/bot/src/bot/modules/metrics.ts new file mode 100644 index 0000000..dabfb7d --- /dev/null +++ b/bot/src/bot/modules/metrics.ts @@ -0,0 +1,73 @@ +import prom from 'prom-client'; +import http from 'http'; +import logger from '../logger'; +import { client } from '../..'; +import { decodeTime } from 'ulid'; + +const PORT = Number(process.env.BOT_METRICS_PORT); + +prom.collectDefaultMetrics({ prefix: 'automod_' }); + +const metrics = { + commands: new prom.Counter({ name: 'commands_executed', help: 'Amount of executed commands' }), + servers: new prom.Gauge({ name: 'server_count', help: 'Amount of servers the bot is in' }), + wsPing: new prom.Gauge({ name: 'ws_ping', help: 'WebSocket ping as returned by revolt.js' }), + msgPing: new prom.Gauge({ name: 'msg_ping', help: 'Amount of time it takes for the bot to send a message' }), +} + +if (!isNaN(PORT)) { + logger.info(`Enabling Prometheus metrics on :${PORT}`); + + const server = new http.Server(); + + server.on('request', async (req, res) => { + if (req.url == '/metrics') { + res.write(await prom.register.metrics()); + res.end(); + } else { + res.statusCode = 404; + res.write('404 not found'); + res.end(); + } + }); + + const setServerCount = () => metrics.servers.set(client.servers.size); + + client.once('ready', setServerCount); + client.on('server/update', setServerCount); + client.on('server/delete', setServerCount); + + const measureLatency = async () => { + const wsPing = client.websocket.ping; + if (wsPing != undefined) metrics.wsPing.set(wsPing); + } + + client.once('ready', () => { + measureLatency(); + setInterval(measureLatency, 10000); + + if (process.env.BOT_METRICS_MSG_PING_CHANNEL) { + logger.info('BOT_METRICS_MSG_PING_CHANNEL is set, enabling message latency measuring'); + + const getMsgPing = async () => { + const channel = client.channels.get(process.env.BOT_METRICS_MSG_PING_CHANNEL!); + try { + const now = Date.now(); + const msg = await channel?.sendMessage('Ping?'); + if (!msg) return; + + const delay = decodeTime(msg._id) - now; + metrics.msgPing.set(delay); + await msg.edit({ content: `Pong! ${delay}ms` }); + } catch(e) { console.error(e) } + } + + getMsgPing(); + setInterval(getMsgPing, 30000); + } + }); + + server.listen(PORT, () => logger.done(`Prometheus metrics ready`)); +} + +export { metrics }; diff --git a/bot/src/index.ts b/bot/src/index.ts index c6fd57a..5520177 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -31,4 +31,5 @@ export { client } import('./bot/modules/tempbans'); import('./bot/modules/user_scan'); import('./bot/modules/api_communication'); + import('./bot/modules/metrics'); })(); diff --git a/bot/yarn.lock b/bot/yarn.lock index b199bc1..f573d06 100644 --- a/bot/yarn.lock +++ b/bot/yarn.lock @@ -150,6 +150,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bintrees@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" + integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= + bl@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" @@ -498,6 +503,13 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +prom-client@^14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.0.1.tgz#bdd9583e02ec95429677c0e013712d42ef1f86a8" + integrity sha512-HxTArb6fkOntQHoRGvv4qd/BkorjliiuO2uSWC2KC17MUTKYttWdDoXX/vxOhQdkoECEM9BBH0pj2l8G8kev6w== + dependencies: + tdigest "^0.1.1" + readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -559,6 +571,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.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" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 41af434..9884f23 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -19,6 +19,11 @@ services: - API_WS_TOKEN=${INTERNAL_API_TOKEN} - WEB_UI_URL=${PUBLIC_WEB_URL} - BOT_PREFIX + - BOT_METRICS_PORT + - BOT_METRICS_MSG_PING_CHANNEL + # Uncomment if you enabled Prometheus metrics + #ports: + # - 127.0.0.1:${BOT_METRICS_PORT}:${BOT_METRICS_PORT} depends_on: - mongo - api