diff --git a/package.json b/package.json
index e79ab464d4999714c85ef4d7c379aadcb088b918..60894a217ea7ef37461fb92a331d9ba5775a8a8d 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
     "node-html-parser": "^1.1.11",
     "p-queue": "^6.0.1",
     "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 d88c10bffb5d22c5275948faf90d584151740e5d..a4ad3f165b76522bae8c7a28d2929214da578c02 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 2f0eb76241cfca553350e116ea39b9b45e72d171..2da5b7734e721eca3d77126750cf16957349deb4 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -90,6 +90,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 c04230e0fa56c75046a628935f598454b7c6fc7c..72677cc9b70b66cce86abdfd690a9f45f7093851 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 cb1251e28c1f08d86e74ba47aa7695da00319474..c85420745df27117040de49a3c2f464ee9b2b5eb 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 1f350c3819a8f6c70c924bbc1f289b7af6f92276..574f3968621279fd5933391089082ca943d1ccf6 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");
 
@@ -94,13 +95,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: {},
@@ -112,7 +116,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 16fa4a974510cf405e931a02a278789b3f89e08a..91343e609d39341754bfc8353ab350934bc9bbf0 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);
             }
         }
     }