Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics #480

Merged
merged 50 commits into from
Jun 13, 2019
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
639623e
Add support for metrics
Half-Shot May 22, 2019
c4ae6be
Dammit metrics.ts
Half-Shot May 22, 2019
3d9a677
Enable the metrics
Half-Shot May 22, 2019
4397227
Wait for bridge to start first
Half-Shot May 22, 2019
663eebc
Expire old requests in metrics
Half-Shot May 22, 2019
d617346
Store prefixes
Half-Shot May 22, 2019
11a548f
Merge branch 'develop' into release/0.5.0
Sorunome May 26, 2019
2370b4d
Add support for remote send calls in metrics
Half-Shot May 31, 2019
95cb6af
Linting
Half-Shot May 31, 2019
3a90721
Add copyright header
Half-Shot May 31, 2019
04f3007
Bind metrics safely
Half-Shot May 31, 2019
b0810a8
Fix docker image for appservice bridge dependency
turt2live Jun 2, 2019
a78de0b
Merge branch 'travis/fix-docker' of https://github.com/t2bot/matrix-a…
Sorunome Jun 2, 2019
e57769e
Merge branch 'develop' into release/0.5.0
Sorunome Jun 2, 2019
c6e41d9
Merge branch 'release/0.5.0'
Sorunome Jun 4, 2019
0267969
fix version
Sorunome Jun 4, 2019
8c09564
fix some test assertion calls
pacien Jun 4, 2019
45d2bf8
add config override using env variables
pacien Jun 4, 2019
9827b98
add note for config override using env vars
pacien Jun 5, 2019
28ecd27
update p-queue lib
pacien Jun 6, 2019
d02603c
disambiguate promise type
pacien Jun 7, 2019
831f922
Merge pull request #489 from pacien/pqueue-ts-fix
Half-Shot Jun 7, 2019
90ea8c8
Merge pull request #485 from pacien/config-from-env
Half-Shot Jun 7, 2019
de2e92d
Add non-warning to readme for installation
Half-Shot Jun 7, 2019
dab3561
Merge pull request #492 from Half-Shot/hs/readme-install-warnings
Half-Shot Jun 7, 2019
6ed4eb1
add support for animated discord guild icons
Sorunome Jun 13, 2019
fda7d34
remove unneeded type setting
Sorunome Jun 13, 2019
d2642ef
Merge branch 'soru/animated-guild-icons' into develop
Sorunome Jun 13, 2019
a6cbac2
fix some test assertion calls
pacien Jun 4, 2019
eb89fc1
add config override using env variables
pacien Jun 4, 2019
37d76af
add note for config override using env vars
pacien Jun 5, 2019
9a47c09
update p-queue lib
pacien Jun 6, 2019
ef1872d
disambiguate promise type
pacien Jun 7, 2019
c3a142a
Add non-warning to readme for installation
Half-Shot Jun 7, 2019
1b9d062
add support for animated discord guild icons
Sorunome Jun 13, 2019
1391753
remove unneeded type setting
Sorunome Jun 13, 2019
0fdb9dc
Merge branch 'develop' of github.com:Half-Shot/matrix-appservice-disc…
Half-Shot Jun 13, 2019
980a1a2
Add support for metrics
Half-Shot May 22, 2019
b336eda
Dammit metrics.ts
Half-Shot May 22, 2019
bdc84f7
Enable the metrics
Half-Shot May 22, 2019
c9cad35
Wait for bridge to start first
Half-Shot May 22, 2019
641edc7
Expire old requests in metrics
Half-Shot May 22, 2019
5f245e1
Store prefixes
Half-Shot May 22, 2019
062d5b1
Add support for remote send calls in metrics
Half-Shot May 31, 2019
2243030
Linting
Half-Shot May 31, 2019
cff2bc1
Add copyright header
Half-Shot May 31, 2019
55a1248
Bind metrics safely
Half-Shot May 31, 2019
f64340b
More binds
Half-Shot Jun 13, 2019
8405046
Merge branch 'hs/metrics' of github.com:Half-Shot/matrix-appservice-d…
Half-Shot Jun 13, 2019
c3eaaa8
Add a note about metrics
Half-Shot Jun 13, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 14 additions & 3 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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);
}
})();
Expand Down Expand Up @@ -389,6 +392,7 @@ export class DiscordBot {
}
const channel = guild.channels.get(room);
if (channel && channel.type === "text") {
this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we always bind the channel on message processing? Or even better, right when the message enters via the event listeners?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, because we only call the send function when sending stuff that came via matrix. Though I guess we should also bind in the event listeners too.

const lookupResult = new ChannelLookupResult();
lookupResult.channel = channel as Discord.TextChannel;
lookupResult.botUser = this.bot.user.id === client.user.id;
Expand Down Expand Up @@ -448,6 +452,7 @@ export class DiscordBot {
// NOTE: Don't send replies to discord if we are a puppet.
msg = await chan.send(embed.description, opts);
} else if (hook) {
MetricPeg.get.remoteCall("hook.send");
msg = await hook.send(embed.description, {
avatarURL: embed!.author!.icon_url,
embeds: embedSet.replyEmbed ? [embedSet.replyEmbed] : undefined,
Expand Down Expand Up @@ -539,6 +544,7 @@ export class DiscordBot {
if (guild) {
const channel = client.channels.get(entry.remote!.get("discord_channel") as string);
if (channel) {
this.ClientFactory.bindMetricsToChannel(channel as Discord.TextChannel);
return channel;
}
throw Error("Channel given in room entry not found");
Expand Down Expand Up @@ -738,11 +744,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
Expand All @@ -751,13 +759,15 @@ 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;
}
}

// 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;
}

Expand All @@ -766,14 +776,13 @@ export class DiscordBot {
let rooms;
try {
rooms = await this.channelSync.GetRoomIdsFromChannel(msg.channel);
if (rooms === null) { throw Error(); }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new lines for if-condition due to {}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was being lazy, sorry.

} 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) => {
Expand Down Expand Up @@ -854,7 +863,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);
}
}
Expand Down
14 changes: 10 additions & 4 deletions src/clientfactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@ limitations under the License.

import { DiscordBridgeConfigAuth } from "./config";
import { DiscordStore } from "./store";
import { Client as DiscordClient } from "discord.js";
import { Client as DiscordClient, TextChannel } from "discord.js";
import { Log } from "./log";
import { Util } from "./util";
import { MetricPeg } from "./metrics";

const log = new Log("ClientFactory");

const READY_TIMEOUT = 30000;

export class DiscordClientFactory {
private config: DiscordBridgeConfigAuth;
private store: DiscordStore;
Expand Down Expand Up @@ -110,4 +108,12 @@ export class DiscordClientFactory {
return this.botClient;
}
}

public bindMetricsToChannel(channel: TextChannel) {
// tslint:disable-next-line:only-arrow-functions
channel.send = function() {
MetricPeg.get.remoteCall("channel.send");
return channel.send.apply(channel, arguments);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this recurse? What if we call bindMetricsToChannel twice on a channel object, won't it do the metrics twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes hello I'm dumb, I wonder how we do this sanely.

};
}
}
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/db/roomstore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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("RoomStore.getEntriesByMatrixId", true);
return cached.e;
}
MetricPeg.get.storeCall("RoomStore.getEntriesByMatrixId", false);
const entries = await this.db.All(
"SELECT * FROM room_entries WHERE matrix_id = $id", {id: matrixId},
);
Expand Down Expand Up @@ -190,6 +193,7 @@ export class DbRoomStore {
}

public async getEntriesByMatrixIds(matrixIds: string[]): Promise<IRoomStoreEntry[]> {
MetricPeg.get.storeCall("RoomStore.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(", ")})`;
Expand Down Expand Up @@ -222,6 +226,7 @@ export class DbRoomStore {
}

public async linkRooms(matrixRoom: MatrixStoreRoom, remoteRoom: RemoteStoreRoom) {
MetricPeg.get.storeCall("RoomStore.linkRooms", false);
await this.upsertRoom(remoteRoom);

const values = {
Expand All @@ -244,6 +249,7 @@ export class DbRoomStore {
}

public async getEntriesByRemoteRoomData(data: IRemoteRoomDataLazy): Promise<IRoomStoreEntry[]> {
MetricPeg.get.storeCall("RoomStore.getEntriesByRemoteRoomData", false);
Object.keys(data).filter((k) => typeof(data[k]) === "boolean").forEach((k) => {
data[k] = Number(data[k]);
});
Expand All @@ -270,11 +276,13 @@ export class DbRoomStore {
}

public async removeEntriesByRemoteRoomId(remoteId: string) {
MetricPeg.get.storeCall("RoomStore.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("RoomStore.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) {
Expand All @@ -286,6 +294,7 @@ export class DbRoomStore {
}

private async upsertRoom(room: RemoteStoreRoom) {
MetricPeg.get.storeCall("RoomStore.upsertRoom", false);
if (!room.data) {
throw new Error("Tried to upsert a room with undefined data");
}
Expand Down
7 changes: 6 additions & 1 deletion src/db/userstore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("UserStore.getRemoteUser", true);
return cached.e;
}
MetricPeg.get.storeCall("UserStore.getRemoteUser", false);

const row = await this.db.Get(
"SELECT * FROM user_entries WHERE remote_id = $id", {id: remoteId},
);
Expand Down Expand Up @@ -86,6 +89,7 @@ export class DbUserStore {
}

public async setRemoteUser(user: RemoteUser) {
MetricPeg.get.storeCall("UserStore.setRemoteUser", false);
this.remoteUserCache.delete(user.id);
const existingData = await this.db.Get(
"SELECT * FROM remote_user_data WHERE remote_id = $remoteId",
Expand Down Expand Up @@ -156,6 +160,7 @@ AND guild_id = $guild_id`,
}

public async linkUsers(matrixId: string, remoteId: string) {
MetricPeg.get.storeCall("UserStore.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)`, {
Expand Down
13 changes: 12 additions & 1 deletion src/discordas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DiscordBot } from "./bot";
import { DiscordStore } from "./store";
import { Log } from "./log";
import "source-map-support/register";
import { MetricPeg, PrometheusBridgeMetrics } from "./metrics";

const log = new Log("DiscordAS");

Expand Down Expand Up @@ -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: {},
Expand All @@ -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"));
}
Expand Down Expand Up @@ -159,6 +165,11 @@ async function run(port: number, fileConfig: DiscordBridgeConfig) {
await bridge.run(port, config);
log.info(`Started listening on port ${port}`);

if (config.bridge.enableMetrics) {
log.info("Enabled metrics");
MetricPeg.set(new PrometheusBridgeMetrics().init(bridge));
}

try {
await store.init(undefined, bridge.getRoomStore(), bridge.getUserStore());
} catch (ex) {
Expand Down
Loading