discord-bot/CompatBot/Watchdog.cs

127 lines
5.8 KiB
C#
Raw Normal View History

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
2020-03-21 10:16:03 +00:00
using System.Linq;
2020-09-09 17:17:54 +00:00
using System.Runtime;
using System.Threading.Tasks;
using CompatBot.Commands;
2020-03-21 10:16:03 +00:00
using CompatBot.Database.Providers;
using CompatBot.EventHandlers;
using DSharpPlus;
2020-07-03 08:57:27 +00:00
using Microsoft.ApplicationInsights;
using NLog;
namespace CompatBot
{
internal static class Watchdog
{
private static readonly TimeSpan CheckInterval = TimeSpan.FromSeconds(10);
public static readonly ConcurrentQueue<DateTime> DisconnectTimestamps = new ConcurrentQueue<DateTime>();
public static readonly Stopwatch TimeSinceLastIncomingMessage = Stopwatch.StartNew();
private static bool IsOk => DisconnectTimestamps.IsEmpty && TimeSinceLastIncomingMessage.Elapsed < Config.IncomingMessageCheckIntervalInMin;
private static DiscordClient discordClient = null;
public static async Task Watch(DiscordClient client)
{
discordClient = client;
do
{
await Task.Delay(CheckInterval, Config.Cts.Token).ConfigureAwait(false);
2020-03-21 10:16:03 +00:00
foreach (var sudoer in ModProvider.Mods.Values.Where(m => m.Sudoer))
{
var user = await client.GetUserAsync(sudoer.DiscordId).ConfigureAwait(false);
if (user?.Presence?.Activity?.CustomStatus?.Name is string cmd && cmd.StartsWith("restart"))
{
var instance = cmd.Split(' ', StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
if (ulong.TryParse(instance, out var botId) && botId == client.CurrentUser.Id)
{
Config.Log.Warn($"Found request to restart on {user.Username}#{user.Discriminator}'s custom status");
Sudo.Bot.Restart(Program.InvalidChannelId, $"Restarted by request from {user.Username}#{user.Discriminator}'s custom status");
}
}
}
2020-01-03 10:15:19 +00:00
if (IsOk)
continue;
try
{
2020-07-03 08:57:27 +00:00
Config.TelemetryClient?.TrackEvent("socket-deadlock-potential");
2019-04-28 18:08:18 +00:00
Config.Log.Warn("Potential socket deadlock detected, reconnecting...");
2019-04-22 22:49:46 +00:00
await client.ReconnectAsync(true).ConfigureAwait(false);
await Task.Delay(CheckInterval, Config.Cts.Token).ConfigureAwait(false);
2020-01-03 10:15:19 +00:00
if (IsOk)
{
2019-04-28 18:08:18 +00:00
Config.Log.Info("Looks like we're back in business");
2019-04-22 22:49:46 +00:00
continue;
}
2020-01-03 10:15:19 +00:00
2020-07-03 08:57:27 +00:00
Config.TelemetryClient?.TrackEvent("socket-deadlock-for-sure");
2019-04-28 18:08:18 +00:00
Config.Log.Error("Hard reconnect failed, restarting...");
2020-03-21 11:45:04 +00:00
Sudo.Bot.Restart(Program.InvalidChannelId, $@"Restarted to reset potential socket deadlock (last incoming message event: {TimeSinceLastIncomingMessage.Elapsed:h\:mm\:ss} ago)");
}
catch (Exception e)
{
Config.Log.Error(e);
}
} while (!Config.Cts.IsCancellationRequested);
}
2020-07-03 08:57:27 +00:00
public static void OnLogHandler(string level, string message)
{
if (level == nameof(LogLevel.Info))
{
if (message?.Contains("Session resumed") ?? false)
DisconnectTimestamps.Clear();
}
else if (level == nameof(LogLevel.Warn))
{
if (message?.Contains("Dispatch:PRESENCES_REPLACE") ?? false)
BotStatusMonitor.RefreshAsync(discordClient).ConfigureAwait(false).GetAwaiter().GetResult();
else if (message?.Contains("Pre-emptive ratelimit triggered") ?? false)
Config.TelemetryClient?.TrackEvent("preemptive-rate-limit");
}
else if (level == nameof(LogLevel.Fatal))
{
if ((message?.Contains("Socket connection terminated") ?? false)
|| (message?.Contains("heartbeats were skipped. Issuing reconnect.") ?? false))
DisconnectTimestamps.Enqueue(DateTime.UtcNow);
}
}
2020-07-03 08:57:27 +00:00
public static async Task SendMetrics(DiscordClient client)
{
do
{
await Task.Delay(Config.MetricsIntervalInSec).ConfigureAwait(false);
2020-09-09 17:17:54 +00:00
using var process = Process.GetCurrentProcess();
2020-07-03 08:57:27 +00:00
if (Config.TelemetryClient is TelemetryClient tc)
{
tc.TrackMetric("gw-latency", client.Ping);
tc.TrackMetric("time-since-last-incoming-message", TimeSinceLastIncomingMessage.ElapsedMilliseconds);
tc.TrackMetric("gc-total-memory", GC.GetTotalMemory(false));
2020-09-09 17:17:54 +00:00
tc.TrackMetric("process-total-memory", process.PrivateMemorySize64);
2020-07-03 08:57:27 +00:00
tc.TrackMetric("github-limit-remaining", GithubClient.Client.RateLimitRemaining);
tc.Flush();
2020-07-03 08:57:27 +00:00
}
} while (!Config.Cts.IsCancellationRequested);
}
2020-09-09 17:17:54 +00:00
public static async Task CheckGCStats()
{
do
{
await Task.Delay(TimeSpan.FromHours(1)).ConfigureAwait(false);
using var process = Process.GetCurrentProcess();
var processMemory = process.PrivateMemorySize64;
var gcMemory = GC.GetTotalMemory(false);
if (processMemory / (double)gcMemory > 2)
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(2, GCCollectionMode.Optimized, true, true); // force LOH compaction
}
} while (!Config.Cts.IsCancellationRequested);
}
}
}