From 57ed43b0183f130a75f6c23890d9c9907838c039 Mon Sep 17 00:00:00 2001
From: Dragory <2606411+Dragory@users.noreply.github.com>
Date: Sun, 12 Sep 2021 00:17:26 +0300
Subject: [PATCH] Add rate limit logging

---
 backend/src/index.ts                          |  5 +++
 .../plugins/BotControl/BotControlPlugin.ts    |  2 ++
 .../commands/RateLimitPerformanceCmd.ts       | 36 +++++++++++++++++++
 backend/src/rateLimitStats.ts                 | 24 +++++++++++++
 4 files changed, 67 insertions(+)
 create mode 100644 backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts
 create mode 100644 backend/src/rateLimitStats.ts

diff --git a/backend/src/index.ts b/backend/src/index.ts
index 829f89ac..6b552733 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -22,6 +22,7 @@ import { loadYamlSafely } from "./utils/loadYamlSafely";
 import { DecayingCounter } from "./utils/DecayingCounter";
 import { PluginNotLoadedError } from "knub/dist/plugins/PluginNotLoadedError";
 import { logRestCall } from "./restCallStats";
+import { logRateLimit } from "./rateLimitStats";
 
 if (!process.env.KEY) {
   // tslint:disable-next-line:no-console
@@ -317,6 +318,10 @@ connect().then(async () => {
     startUptimeCounter();
   });
 
+  client.on(Constants.Events.RATE_LIMIT, (data) => {
+    logRateLimit(data);
+  });
+
   bot.initialize();
   logger.info("Bot Initialized");
   logger.info("Logging in...");
diff --git a/backend/src/plugins/BotControl/BotControlPlugin.ts b/backend/src/plugins/BotControl/BotControlPlugin.ts
index bef25d24..9c6b0123 100644
--- a/backend/src/plugins/BotControl/BotControlPlugin.ts
+++ b/backend/src/plugins/BotControl/BotControlPlugin.ts
@@ -22,6 +22,7 @@ import { PluginPerformanceCmd } from "./commands/PluginPerformanceCmd";
 import { AddServerFromInviteCmd } from "./commands/AddServerFromInviteCmd";
 import { ChannelToServerCmd } from "./commands/ChannelToServerCmd";
 import { RestPerformanceCmd } from "./commands/RestPerformanceCmd";
+import { RateLimitPerformanceCmd } from "./commands/RateLimitPerformanceCmd";
 
 const defaultOptions = {
   config: {
@@ -54,6 +55,7 @@ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
     EligibleCmd,
     PluginPerformanceCmd,
     RestPerformanceCmd,
+    RateLimitPerformanceCmd,
     AddServerFromInviteCmd,
     ChannelToServerCmd,
   ],
diff --git a/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts
new file mode 100644
index 00000000..fdf81993
--- /dev/null
+++ b/backend/src/plugins/BotControl/commands/RateLimitPerformanceCmd.ts
@@ -0,0 +1,36 @@
+import { botControlCmd } from "../types";
+import { getRateLimitStats } from "../../../rateLimitStats";
+import moment from "moment-timezone";
+import { GuildArchives } from "../../../data/GuildArchives";
+import { getBaseUrl, sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
+import { TextChannel } from "discord.js";
+
+export const RateLimitPerformanceCmd = botControlCmd({
+  trigger: ["rate_limit_performance"],
+  permission: "can_performance",
+
+  signature: {},
+
+  async run({ pluginData, message: msg, args }) {
+    const logItems = getRateLimitStats();
+    if (logItems.length === 0) {
+      sendSuccessMessage(pluginData, msg.channel as TextChannel, `No rate limits hit`);
+      return;
+    }
+
+    logItems.reverse();
+    const formatted = logItems.map((item) => {
+      const formattedTime = moment.utc(item.timestamp).format("YYYY-MM-DD HH:mm:ss.SSS");
+      return `${item.data.global ? "GLOBAL " : ""}${item.data.method} ${item.data.route} stalled for ${
+        item.data.timeout
+      }ms`;
+    });
+
+    const fullText = `Last ${logItems.length} rate limits hit:\n\n${formatted.join("\n")}`;
+
+    const archives = GuildArchives.getGuildInstance("0");
+    const archiveId = await archives.create(fullText, moment().add(1, "hour"));
+    const archiveUrl = archives.getUrl(getBaseUrl(pluginData), archiveId);
+    msg.channel.send(`Link: ${archiveUrl}`);
+  },
+});
diff --git a/backend/src/rateLimitStats.ts b/backend/src/rateLimitStats.ts
new file mode 100644
index 00000000..eb3c142f
--- /dev/null
+++ b/backend/src/rateLimitStats.ts
@@ -0,0 +1,24 @@
+import { RateLimitData } from "discord.js";
+
+type RateLimitLogItem = {
+  timestamp: number;
+  data: RateLimitData;
+};
+
+const rateLimitLog: RateLimitLogItem[] = [];
+
+const MAX_RATE_LIMIT_LOG_ITEMS = 100;
+
+export function logRateLimit(data: RateLimitData) {
+  rateLimitLog.push({
+    timestamp: Date.now(),
+    data,
+  });
+  if (rateLimitLog.length > MAX_RATE_LIMIT_LOG_ITEMS) {
+    rateLimitLog.splice(0, rateLimitLog.length - MAX_RATE_LIMIT_LOG_ITEMS);
+  }
+}
+
+export function getRateLimitStats(): RateLimitLogItem[] {
+  return Array.from(rateLimitLog);
+}