From 639623eb75dfc7abef0cdb3e5f5be8104e917c56 Mon Sep 17 00:00:00 2001 From: Will Hunt <will@half-shot.uk> Date: Wed, 22 May 2019 21:42:16 +0100 Subject: [PATCH] Add support for metrics --- package.json | 1 + src/bot.ts | 14 +++++++++++--- src/config.ts | 1 + src/db/roomstore.ts | 9 +++++++++ src/db/userstore.ts | 7 ++++++- src/discordas.ts | 8 +++++++- src/presencehandler.ts | 4 ++++ 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4e19eaa..aa0b475 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "node-html-parser": "^1.1.11", "p-queue": "^5.0.0", "pg-promise": "^8.5.1", + "prom-client": "^11.3.0", "tslint": "^5.11.0", "typescript": "^3.1.3", "winston": "^3.0.0", diff --git a/src/bot.ts b/src/bot.ts index 2c7ec05..13b0ecf 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -37,6 +37,7 @@ import * as Discord from "discord.js"; import * as mime from "mime"; import { IMatrixEvent, IMatrixMediaInfo } from "./matrixtypes"; import { DiscordCommandHandler } from "./discordcommandhandler"; +import { MetricPeg } from "./metrics"; const log = new Log("DiscordBot"); @@ -288,12 +289,14 @@ export class DiscordBot { }); client.on("message", async (msg: Discord.Message) => { try { + MetricPeg.get.registerRequest(msg.id); await this.waitUnlock(msg.channel); this.discordMessageQueue[msg.channel.id] = (async () => { await (this.discordMessageQueue[msg.channel.id] || Promise.resolve()); try { await this.OnMessage(msg); } catch (err) { + MetricPeg.get.requestOutcome(msg.id, true, "fail"); log.error("Caught while handing 'message'", err); } })(); @@ -738,11 +741,13 @@ export class DiscordBot { if (indexOfMsg !== -1) { log.verbose("Got repeated message, ignoring."); delete this.sentMessages[indexOfMsg]; + MetricPeg.get.requestOutcome(msg.id, true, "dropped"); return; // Skip *our* messages } const chan = msg.channel as Discord.TextChannel; if (msg.author.id === this.bot.user.id) { // We don't support double bridging. + MetricPeg.get.requestOutcome(msg.id, true, "dropped"); return; } // Test for webhooks @@ -751,6 +756,7 @@ export class DiscordBot { .filterArray((h) => h.name === "_matrix").pop(); if (webhook && msg.webhookID === webhook.id) { // Filter out our own webhook messages. + MetricPeg.get.requestOutcome(msg.id, true, "dropped"); return; } } @@ -758,6 +764,7 @@ export class DiscordBot { // check if it is a command to process by the bot itself if (msg.content.startsWith("!matrix")) { await this.discordCommandHandler.Process(msg); + MetricPeg.get.requestOutcome(msg.id, true, "success"); return; } @@ -766,14 +773,13 @@ export class DiscordBot { let rooms; try { rooms = await this.channelSync.GetRoomIdsFromChannel(msg.channel); + if (rooms === null) { throw Error() } } catch (err) { log.verbose("No bridged rooms to send message to. Oh well."); + MetricPeg.get.requestOutcome(msg.id, true, "dropped"); return null; } try { - if (rooms === null) { - return null; - } const intent = this.GetIntentFromDiscordMember(msg.author, msg.webhookID); // Check Attachements await Util.AsyncForEach(msg.attachments.array(), async (attachment) => { @@ -854,7 +860,9 @@ export class DiscordBot { await afterSend(res); } }); + MetricPeg.get.requestOutcome(msg.id, true, "success"); } catch (err) { + MetricPeg.get.requestOutcome(msg.id, true, "fail"); log.verbose("Failed to send message into room.", err); } } diff --git a/src/config.ts b/src/config.ts index 1666043..0fd5c6d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,7 @@ class DiscordBridgeConfigBridge { public disableEveryoneMention: boolean = false; public disableHereMention: boolean = false; public disableJoinLeaveNotifications: boolean = false; + public enableMetrics: boolean = false; } export class DiscordBridgeConfigDatabase { diff --git a/src/db/roomstore.ts b/src/db/roomstore.ts index c04230e..72677cc 100644 --- a/src/db/roomstore.ts +++ b/src/db/roomstore.ts @@ -19,6 +19,7 @@ import { Util } from "../util"; import * as uuid from "uuid/v4"; import { Postgres } from "./postgres"; +import { MetricPeg } from "../metrics"; const log = new Log("DbRoomStore"); @@ -155,8 +156,10 @@ export class DbRoomStore { public async getEntriesByMatrixId(matrixId: string): Promise<IRoomStoreEntry[]> { const cached = this.entriesMatrixIdCache.get(matrixId); if (cached && cached.ts + ENTRY_CACHE_LIMETIME > Date.now()) { + MetricPeg.get.storeCall("getEntriesByMatrixId", true); return cached.e; } + MetricPeg.get.storeCall("getEntriesByMatrixId", false); const entries = await this.db.All( "SELECT * FROM room_entries WHERE matrix_id = $id", {id: matrixId}, ); @@ -190,6 +193,7 @@ export class DbRoomStore { } public async getEntriesByMatrixIds(matrixIds: string[]): Promise<IRoomStoreEntry[]> { + MetricPeg.get.storeCall("getEntriesByMatrixIds", false); const mxIdMap = { }; matrixIds.forEach((mxId, i) => mxIdMap[i] = mxId); const sql = `SELECT * FROM room_entries WHERE matrix_id IN (${matrixIds.map((_, id) => `\$${id}`).join(", ")})`; @@ -222,6 +226,7 @@ export class DbRoomStore { } public async linkRooms(matrixRoom: MatrixStoreRoom, remoteRoom: RemoteStoreRoom) { + MetricPeg.get.storeCall("linkRooms", false); await this.upsertRoom(remoteRoom); const values = { @@ -244,6 +249,7 @@ export class DbRoomStore { } public async getEntriesByRemoteRoomData(data: IRemoteRoomDataLazy): Promise<IRoomStoreEntry[]> { + MetricPeg.get.storeCall("getEntriesByRemoteRoomData", false); Object.keys(data).filter((k) => typeof(data[k]) === "boolean").forEach((k) => { data[k] = Number(data[k]); }); @@ -270,11 +276,13 @@ export class DbRoomStore { } public async removeEntriesByRemoteRoomId(remoteId: string) { + MetricPeg.get.storeCall("removeEntriesByRemoteRoomId", false); await this.db.Run(`DELETE FROM room_entries WHERE remote_id = $remoteId`, {remoteId}); await this.db.Run(`DELETE FROM remote_room_data WHERE room_id = $remoteId`, {remoteId}); } public async removeEntriesByMatrixRoomId(matrixId: string) { + MetricPeg.get.storeCall("removeEntriesByMatrixRoomId", false); const entries = (await this.db.All(`SELECT * FROM room_entries WHERE matrix_id = $matrixId`, {matrixId})) || []; await Util.AsyncForEach(entries, async (entry) => { if (entry.remote_id) { @@ -286,6 +294,7 @@ export class DbRoomStore { } private async upsertRoom(room: RemoteStoreRoom) { + MetricPeg.get.storeCall("upsertRoom", false); if (!room.data) { throw new Error("Tried to upsert a room with undefined data"); } diff --git a/src/db/userstore.ts b/src/db/userstore.ts index cb1251e..c854207 100644 --- a/src/db/userstore.ts +++ b/src/db/userstore.ts @@ -15,8 +15,8 @@ limitations under the License. */ import { IDatabaseConnector } from "./connector"; -import * as uuid from "uuid/v4"; import { Log } from "../log"; +import { MetricPeg } from "../metrics"; /** * A UserStore compatible with @@ -54,8 +54,11 @@ export class DbUserStore { public async getRemoteUser(remoteId: string): Promise<RemoteUser|null> { const cached = this.remoteUserCache.get(remoteId); if (cached && cached.ts + ENTRY_CACHE_LIMETIME > Date.now()) { + MetricPeg.get.storeCall("getRemoteUser", true); return cached.e; } + MetricPeg.get.storeCall("getRemoteUser", false); + const row = await this.db.Get( "SELECT * FROM user_entries WHERE remote_id = $id", {id: remoteId}, ); @@ -86,6 +89,7 @@ export class DbUserStore { } public async setRemoteUser(user: RemoteUser) { + MetricPeg.get.storeCall("setRemoteUser", false); this.remoteUserCache.delete(user.id); const existingData = await this.db.Get( "SELECT * FROM remote_user_data WHERE remote_id = $remoteId", @@ -156,6 +160,7 @@ AND guild_id = $guild_id`, } public async linkUsers(matrixId: string, remoteId: string) { + MetricPeg.get.storeCall("linkUsers", false); // This is used ONCE in the bridge to link two IDs, so do not UPSURT data. try { await this.db.Run(`INSERT INTO user_entries VALUES ($matrixId, $remoteId)`, { diff --git a/src/discordas.ts b/src/discordas.ts index 0f4b5e1..6b74b7d 100644 --- a/src/discordas.ts +++ b/src/discordas.ts @@ -22,6 +22,7 @@ import { DiscordBot } from "./bot"; import { DiscordStore } from "./store"; import { Log } from "./log"; import "source-map-support/register"; +import { MetricPeg } from "./metrics"; const log = new Log("DiscordAS"); @@ -93,13 +94,16 @@ async function run(port: number, fileConfig: DiscordBridgeConfig) { } catch (err) { log.error("Exception thrown while handling \"onAliasQuery\" event", err); } }, onEvent: async (request) => { + const data = request.getData(); try { + MetricPeg.get.registerRequest(data.event_id); // Build our own context. if (!store.roomStore) { log.warn("Discord store not ready yet, dropping message"); + MetricPeg.get.requestOutcome(data.event_id, false, "dropped"); return; } - const roomId = request.getData().room_id; + const roomId = data.room_id; const context: BridgeContext = { rooms: {}, @@ -111,7 +115,9 @@ async function run(port: number, fileConfig: DiscordBridgeConfig) { } await request.outcomeFrom(callbacks.onEvent(request, context)); + MetricPeg.get.requestOutcome(data.event_id, false, "success"); } catch (err) { + MetricPeg.get.requestOutcome(data.event_id, false, "fail"); log.error("Exception thrown while handling \"onEvent\" event", err); await request.outcomeFrom(Promise.reject("Failed to handle")); } diff --git a/src/presencehandler.ts b/src/presencehandler.ts index 16fa4a9..91343e6 100644 --- a/src/presencehandler.ts +++ b/src/presencehandler.ts @@ -17,6 +17,7 @@ limitations under the License. import { User, Presence } from "discord.js"; import { DiscordBot } from "./bot"; import { Log } from "./log"; +import { MetricPeg } from "./metrics"; const log = new Log("PresenceHandler"); export class PresenceHandlerStatus { @@ -66,6 +67,7 @@ export class PresenceHandler { if (user.id !== this.bot.GetBotId() && this.presenceQueue.find((u) => u.id === user.id) === undefined) { log.info(`Adding ${user.id} (${user.username}) to the presence queue`); this.presenceQueue.push(user); + MetricPeg.get.setPresenceCount(this.presenceQueue.length); } } @@ -75,6 +77,7 @@ export class PresenceHandler { }); if (index !== -1) { this.presenceQueue.splice(index, 1); + MetricPeg.get.setPresenceCount(this.presenceQueue.length); } else { log.warn( `Tried to remove ${user.id} from the presence queue but it could not be found`, @@ -96,6 +99,7 @@ export class PresenceHandler { this.presenceQueue.push(user); } else { log.info(`Dropping ${user.id} from the presence queue.`); + MetricPeg.get.setPresenceCount(this.presenceQueue.length); } } } -- GitLab