From f1ad221b0f034833e9b957025f14999a159fb08c Mon Sep 17 00:00:00 2001 From: alyssa Date: Sun, 13 Oct 2024 05:08:12 +0900 Subject: [PATCH] feat(bot): run both local and http cache to log differences --- Myriad/Cache/DiscordCacheExtensions.cs | 5 +- Myriad/Cache/HTTPDiscordCache.cs | 140 +++++++++++++++++++++---- PluralKit.Bot/Bot.cs | 10 +- PluralKit.Bot/BotMetrics.cs | 7 ++ PluralKit.Bot/Modules.cs | 17 ++- 5 files changed, 150 insertions(+), 29 deletions(-) diff --git a/Myriad/Cache/DiscordCacheExtensions.cs b/Myriad/Cache/DiscordCacheExtensions.cs index 0168a904..cdf701a7 100644 --- a/Myriad/Cache/DiscordCacheExtensions.cs +++ b/Myriad/Cache/DiscordCacheExtensions.cs @@ -102,8 +102,9 @@ public static class DiscordCacheExtensions public static async Task BotPermissionsIn(this IDiscordCache cache, ulong guildId, ulong channelId) { - if (cache is HttpDiscordCache) - return await ((HttpDiscordCache)cache).BotChannelPermissions(guildId, channelId); + // disable this for now + //if (cache is HttpDiscordCache) + // return await ((HttpDiscordCache)cache).BotChannelPermissions(guildId, channelId); var channel = await cache.GetRootChannel(guildId, channelId); diff --git a/Myriad/Cache/HTTPDiscordCache.cs b/Myriad/Cache/HTTPDiscordCache.cs index b8d3f21f..29f0f014 100644 --- a/Myriad/Cache/HTTPDiscordCache.cs +++ b/Myriad/Cache/HTTPDiscordCache.cs @@ -15,8 +15,12 @@ public class HttpDiscordCache: IDiscordCache private readonly int _shardCount; private readonly ulong _ownUserId; + private readonly MemoryDiscordCache _innerCache; + private readonly JsonSerializerOptions _jsonSerializerOptions; + public EventHandler<(bool?, string)> OnDebug; + public HttpDiscordCache(ILogger logger, HttpClient client, string cacheEndpoint, int shardCount, ulong ownUserId) { _logger = logger; @@ -25,18 +29,19 @@ public class HttpDiscordCache: IDiscordCache _shardCount = shardCount; _ownUserId = ownUserId; _jsonSerializerOptions = new JsonSerializerOptions().ConfigureForMyriad(); + _innerCache = new MemoryDiscordCache(ownUserId); } - public ValueTask SaveGuild(Guild guild) => default; - public ValueTask SaveChannel(Channel channel) => default; + public ValueTask SaveGuild(Guild guild) => _innerCache.SaveGuild(guild); + public ValueTask SaveChannel(Channel channel) => _innerCache.SaveChannel(channel); public ValueTask SaveUser(User user) => default; - public ValueTask SaveSelfMember(ulong guildId, GuildMemberPartial member) => default; - public ValueTask SaveRole(ulong guildId, Myriad.Types.Role role) => default; - public ValueTask SaveDmChannelStub(ulong channelId) => default; - public ValueTask RemoveGuild(ulong guildId) => default; - public ValueTask RemoveChannel(ulong channelId) => default; - public ValueTask RemoveUser(ulong userId) => default; - public ValueTask RemoveRole(ulong guildId, ulong roleId) => default; + public ValueTask SaveSelfMember(ulong guildId, GuildMemberPartial member) => _innerCache.SaveSelfMember(guildId, member); + public ValueTask SaveRole(ulong guildId, Myriad.Types.Role role) => _innerCache.SaveRole(guildId, role); + public ValueTask SaveDmChannelStub(ulong channelId) => _innerCache.SaveDmChannelStub(channelId); + public ValueTask RemoveGuild(ulong guildId) => _innerCache.RemoveGuild(guildId); + public ValueTask RemoveChannel(ulong channelId) => _innerCache.RemoveChannel(channelId); + public ValueTask RemoveUser(ulong userId) => _innerCache.RemoveUser(userId); + public ValueTask RemoveRole(ulong guildId, ulong roleId) => _innerCache.RemoveRole(guildId, roleId); public ulong GetOwnUser() => _ownUserId; @@ -59,23 +64,120 @@ public class HttpDiscordCache: IDiscordCache return JsonSerializer.Deserialize(plaintext, _jsonSerializerOptions); } - public Task TryGetGuild(ulong guildId) - => QueryCache($"/guilds/{guildId}", guildId); + public async Task TryGetGuild(ulong guildId) + { + var lres = await _innerCache.TryGetGuild(guildId); + var hres = await QueryCache($"/guilds/{guildId}", guildId); - public Task TryGetChannel(ulong guildId, ulong channelId) - => QueryCache($"/guilds/{guildId}/channels/{channelId}", guildId); + if (lres == null && hres == null) return null; + if (lres == null) + { + _logger.Warning($"TryGetGuild({guildId}) was only successful on remote cache"); + OnDebug(null, (true, "guild")); + return hres; + } + if (hres == null) + { + _logger.Warning($"TryGetGuild({guildId}) was only successful on local cache"); + OnDebug(null, (false, "guild")); + return lres; + } + return hres; + } + + public async Task TryGetChannel(ulong guildId, ulong channelId) + { + var lres = await _innerCache.TryGetChannel(guildId, channelId); + var hres = await QueryCache($"/guilds/{guildId}/channels/{channelId}", guildId); + if (lres == null && hres == null) return null; + if (lres == null) + { + _logger.Warning($"TryGetChannel({guildId}, {channelId}) was only successful on remote cache"); + OnDebug(null, (true, "channel")); + return hres; + } + if (hres == null) + { + _logger.Warning($"TryGetChannel({guildId}, {channelId}) was only successful on local cache"); + OnDebug(null, (false, "channel")); + return lres; + } + return hres; + } // this should be a GetUserCached method on nirn-proxy (it's always called as GetOrFetchUser) // so just return nothing public Task TryGetUser(ulong userId) => Task.FromResult(null); - public Task TryGetSelfMember(ulong guildId) - => QueryCache($"/guilds/{guildId}/members/@me", guildId); + public async Task TryGetSelfMember(ulong guildId) + { + var lres = await _innerCache.TryGetSelfMember(guildId); + var hres = await QueryCache($"/guilds/{guildId}/members/@me", guildId); + if (lres == null && hres == null) return null; + if (lres == null) + { + _logger.Warning($"TryGetSelfMember({guildId}) was only successful on remote cache"); + OnDebug(null, (true, "self_member")); + return hres; + } + if (hres == null) + { + _logger.Warning($"TryGetSelfMember({guildId}) was only successful on local cache"); + OnDebug(null, (false, "self_member")); + return lres; + } + return hres; + } - public Task BotChannelPermissions(ulong guildId, ulong channelId) - => QueryCache($"/guilds/{guildId}/channels/{channelId}/permissions/@me", guildId); + // public async Task BotChannelPermissions(ulong guildId, ulong channelId) + // { + // // todo: local cache throws rather than returning null + // // we need to throw too, and try/catch local cache here + // var lres = await _innerCache.BotPermissionsIn(guildId, channelId); + // var hres = await QueryCache($"/guilds/{guildId}/channels/{channelId}/permissions/@me", guildId); + // if (lres == null && hres == null) return null; + // if (lres == null) + // { + // _logger.Warning($"TryGetChannel({guildId}, {channelId}) was only successful on remote cache"); + // OnDebug(null, (true, "botchannelperms")); + // return hres; + // } + // if (hres == null) + // { + // _logger.Warning($"TryGetChannel({guildId}, {channelId}) was only successful on local cache"); + // OnDebug(null, (false, "botchannelperms")); + // return lres; + // } + // + // // this one is easy to check, so let's check it + // if ((int)lres != (int)hres) + // { + // // trust local + // _logger.Warning($"got different permissions for {channelId} (local {(int)lres}, remote {(int)hres})"); + // OnDebug(null, (null, "botchannelperms")); + // return lres; + // } + // return hres; + // } - public Task> GetGuildChannels(ulong guildId) - => QueryCache>($"/guilds/{guildId}/channels", guildId); + public async Task> GetGuildChannels(ulong guildId) + { + var lres = await _innerCache.GetGuildChannels(guildId); + var hres = await QueryCache>($"/guilds/{guildId}/channels", guildId); + if (lres == null && hres == null) return null; + if (lres == null) + { + _logger.Warning($"GetGuildChannels({guildId}) was only successful on remote cache"); + OnDebug(null, (true, "guild_channels")); + return hres; + } + if (hres == null) + { + _logger.Warning($"GetGuildChannels({guildId}) was only successful on local cache"); + OnDebug(null, (false, "guild_channels")); + return lres; + } + return hres; + } } \ No newline at end of file diff --git a/PluralKit.Bot/Bot.cs b/PluralKit.Bot/Bot.cs index ec33705a..96f1b568 100644 --- a/PluralKit.Bot/Bot.cs +++ b/PluralKit.Bot/Bot.cs @@ -99,13 +99,9 @@ public class Bot private async Task OnEventReceived(int shardId, IGatewayEvent evt) { - if (_cache is MemoryDiscordCache) - { - // we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent - await _cache.HandleGatewayEvent(evt); - - await _cache.TryUpdateSelfMember(_config.ClientId, evt); - } + // we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent + await _cache.HandleGatewayEvent(evt); + await _cache.TryUpdateSelfMember(_config.ClientId, evt); await OnEventReceivedInner(shardId, evt); } diff --git a/PluralKit.Bot/BotMetrics.cs b/PluralKit.Bot/BotMetrics.cs index bc59642f..eef96ac0 100644 --- a/PluralKit.Bot/BotMetrics.cs +++ b/PluralKit.Bot/BotMetrics.cs @@ -136,4 +136,11 @@ public static class BotMetrics DurationUnit = TimeUnit.Seconds, Context = "Bot" }; + + public static MeterOptions CacheDebug => new() + { + Name = "Bad responses to cache lookups", + Context = "Bot", + MeasurementUnit = Unit.Calls + }; } \ No newline at end of file diff --git a/PluralKit.Bot/Modules.cs b/PluralKit.Bot/Modules.cs index 07faf18a..11f40a0a 100644 --- a/PluralKit.Bot/Modules.cs +++ b/PluralKit.Bot/Modules.cs @@ -49,9 +49,24 @@ public class BotModule: Module var botConfig = c.Resolve(); if (botConfig.HttpCacheUrl != null) - return new HttpDiscordCache(c.Resolve(), + { + var cache = new HttpDiscordCache(c.Resolve(), c.Resolve(), botConfig.HttpCacheUrl, botConfig.Cluster?.TotalShards ?? 1, botConfig.ClientId); + var metrics = c.Resolve(); + + cache.OnDebug += (_, ev) => + { + var (remote, key) = ev; + metrics.Measure.Meter.Mark(BotMetrics.CacheDebug, new MetricTags( + new[] { "remote", "key" }, + new[] { remote.ToString(), key } + )); + }; + + return cache; + } + return new MemoryDiscordCache(botConfig.ClientId); }).AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance();