From 0419ced0d29cc9e1f1c36ef2ab0dd27b0d60ee85 Mon Sep 17 00:00:00 2001 From: spiral Date: Sat, 22 Jan 2022 03:52:52 -0500 Subject: [PATCH] feat: store shard status in Redis --- PluralKit.API/APIJsonExt.cs | 24 --- .../Controllers/PrivateController.cs | 40 +++- PluralKit.API/PluralKit.API.csproj | 5 + PluralKit.API/Program.cs | 5 +- PluralKit.API/packages.lock.json | 22 +++ PluralKit.Bot/Commands/Misc.cs | 59 +++--- PluralKit.Bot/Init.cs | 4 +- PluralKit.Bot/PluralKit.Bot.csproj | 7 + PluralKit.Bot/Services/ShardInfoService.cs | 153 ++++++++-------- PluralKit.Bot/packages.lock.json | 68 +++++++ .../Repository/ModelRepository.Shards.cs | 28 --- PluralKit.Core/Models/PKShardInfo.cs | 18 -- PluralKit.Core/PluralKit.Core.csproj | 1 + PluralKit.Core/Utils/ProtobufUtils.cs | 22 +++ PluralKit.Core/packages.lock.json | 15 ++ PluralKit.ScheduledTasks/packages.lock.json | 123 ++++++++++++- PluralKit.Tests/packages.lock.json | 172 +++++++++++++++++- proto/discord.proto | 19 ++ 18 files changed, 602 insertions(+), 183 deletions(-) delete mode 100644 PluralKit.Core/Database/Repository/ModelRepository.Shards.cs delete mode 100644 PluralKit.Core/Models/PKShardInfo.cs create mode 100644 PluralKit.Core/Utils/ProtobufUtils.cs create mode 100644 proto/discord.proto diff --git a/PluralKit.API/APIJsonExt.cs b/PluralKit.API/APIJsonExt.cs index 953e6cfe..a3d4f3db 100644 --- a/PluralKit.API/APIJsonExt.cs +++ b/PluralKit.API/APIJsonExt.cs @@ -9,30 +9,6 @@ namespace PluralKit.API; public static class APIJsonExt { - public static JArray ToJSON(this IEnumerable shards) - { - var o = new JArray(); - - foreach (var shard in shards) - { - var s = new JObject(); - s.Add("id", shard.Id); - - if (shard.Status == PKShardInfo.ShardStatus.Down) - s.Add("status", "down"); - else - s.Add("status", "up"); - - s.Add("ping", shard.Ping); - s.Add("last_heartbeat", shard.LastHeartbeat.ToString()); - s.Add("last_connection", shard.LastConnection.ToString()); - - o.Add(s); - } - - return o; - } - public static JObject ToJson(this ModelRepository.Counts counts) { var o = new JObject(); diff --git a/PluralKit.API/Controllers/PrivateController.cs b/PluralKit.API/Controllers/PrivateController.cs index 1dacbc7c..10f86587 100644 --- a/PluralKit.API/Controllers/PrivateController.cs +++ b/PluralKit.API/Controllers/PrivateController.cs @@ -17,19 +17,53 @@ namespace PluralKit.API; [Route("private")] public class PrivateController: PKControllerBase { - public PrivateController(IServiceProvider svc) : base(svc) { } + private readonly RedisService _redis; + public PrivateController(IServiceProvider svc) : base(svc) + { + _redis = svc.GetRequiredService(); + } [HttpGet("meta")] public async Task> Meta() { - var shards = await _repo.GetShards(); + var db = _redis.Connection.GetDatabase(); + var redisInfo = await db.HashGetAllAsync("pluralkit:shardstatus"); + var shards = redisInfo.Select(x => Proto.Unmarshal(x.Value)); + var stats = await _repo.GetStats(); var o = new JObject(); - o.Add("shards", shards.ToJSON()); + o.Add("shards", shards.ToJson()); o.Add("stats", stats.ToJson()); o.Add("version", BuildInfoService.Version); return Ok(o); } +} + +public static class PrivateJsonExt +{ + public static JArray ToJson(this IEnumerable shards) + { + var o = new JArray(); + + foreach (var shard in shards) + { + var s = new JObject(); + s.Add("id", shard.ShardId); + + if (!shard.Up) + s.Add("status", "down"); + else + s.Add("status", "up"); + + s.Add("ping", shard.Latency); + s.Add("last_heartbeat", shard.LastHeartbeat.ToString()); + s.Add("last_connection", shard.LastConnection.ToString()); + + o.Add(s); + } + + return o; + } } \ No newline at end of file diff --git a/PluralKit.API/PluralKit.API.csproj b/PluralKit.API/PluralKit.API.csproj index f25a6989..8b7f1fad 100644 --- a/PluralKit.API/PluralKit.API.csproj +++ b/PluralKit.API/PluralKit.API.csproj @@ -28,6 +28,8 @@ + + @@ -40,4 +42,7 @@ + + + diff --git a/PluralKit.API/Program.cs b/PluralKit.API/Program.cs index 5c4f970a..d3466d4f 100644 --- a/PluralKit.API/Program.cs +++ b/PluralKit.API/Program.cs @@ -12,7 +12,10 @@ public class Program { InitUtils.InitStatic(); await BuildInfoService.LoadVersion(); - await CreateHostBuilder(args).Build().RunAsync(); + var host = CreateHostBuilder(args).Build(); + var config = host.Services.GetRequiredService(); + await host.Services.GetRequiredService().InitAsync(config); + await host.RunAsync(); } public static IHostBuilder CreateHostBuilder(string[] args) => diff --git a/PluralKit.API/packages.lock.json b/PluralKit.API/packages.lock.json index ff86f324..40997e0d 100644 --- a/PluralKit.API/packages.lock.json +++ b/PluralKit.API/packages.lock.json @@ -2,6 +2,22 @@ "version": 1, "dependencies": { "net6.0": { + "Google.Protobuf": { + "type": "Direct", + "requested": "[3.13.0, )", + "resolved": "3.13.0", + "contentHash": "/6VgKCh0P59x/rYsBkCvkUanF0TeUYzwV9hzLIWgt23QRBaKHoxaaMkidEWhKibLR88c3PVCXyyrx9Xlb+Ne6w==", + "dependencies": { + "System.Memory": "4.5.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, + "Grpc.Tools": { + "type": "Direct", + "requested": "[2.37.0, )", + "resolved": "2.37.0", + "contentHash": "cud/urkbw3QoQ8+kNeCy2YI0sHrh7td/1cZkVbH6hDLIXX7zzmJbV/KjYSiqiYtflQf+S5mJPLzDQWScN/QdDg==" + }, "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "type": "Direct", "requested": "[3.1.0, )", @@ -1167,6 +1183,11 @@ "System.Threading": "4.3.0" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.2", + "contentHash": "fvq1GNmUFwbKv+aLVYYdgu/+gc8Nu9oFujOxIjPrsf+meis9JBzTPDL6aP/eeGOz9yPj6rRLUbOjKMpsMEWpNg==" + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", @@ -1685,6 +1706,7 @@ "Autofac.Extensions.DependencyInjection": "7.1.0", "Dapper": "2.0.35", "Dapper.Contrib": "2.0.35", + "Google.Protobuf": "3.13.0", "Microsoft.Extensions.Caching.Memory": "3.1.10", "Microsoft.Extensions.Configuration": "3.1.10", "Microsoft.Extensions.Configuration.Binder": "3.1.10", diff --git a/PluralKit.Bot/Commands/Misc.cs b/PluralKit.Bot/Commands/Misc.cs index 4a184847..5845cd51 100644 --- a/PluralKit.Bot/Commands/Misc.cs +++ b/PluralKit.Bot/Commands/Misc.cs @@ -21,17 +21,16 @@ public class Misc private readonly IDiscordCache _cache; private readonly CpuStatService _cpu; private readonly IMetrics _metrics; - private readonly ModelRepository _repo; private readonly ShardInfoService _shards; + private readonly ModelRepository _repo; - public Misc(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ShardInfoService shards, - ModelRepository repo, IDiscordCache cache) + public Misc(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ModelRepository repo, ShardInfoService shards, IDiscordCache cache) { _botConfig = botConfig; _metrics = metrics; _cpu = cpu; - _shards = shards; _repo = repo; + _shards = shards; _cache = cache; } @@ -64,6 +63,8 @@ public class Misc var embed = new EmbedBuilder(); + // todo: these will be inaccurate when the bot is actually multi-process + var messagesReceived = _metrics.Snapshot.GetForContext("Bot").Meters .FirstOrDefault(m => m.MultidimensionalName == BotMetrics.MessagesReceived.Name)?.Value; if (messagesReceived != null) @@ -85,38 +86,52 @@ public class Misc $"{commandsRun.OneMinuteRate * 60:F1}/m ({commandsRun.FifteenMinuteRate * 60:F1}/m over 15m)", true)); + var isCluster = _botConfig.Cluster != null && _botConfig.Cluster.TotalShards != ctx.Cluster.Shards.Count; + var counts = await _repo.GetStats(); + var shards = await _shards.GetShards(); - var shardId = ctx.ShardId; - var shardTotal = ctx.Cluster.Shards.Count; - var shardUpTotal = _shards.Shards.Where(x => x.Connected).Count(); - var shardInfo = _shards.GetShardInfo(ctx.ShardId); + var shardInfo = shards.Where(s => s.ShardId == ctx.ShardId).First(); + // todo: if we're running multiple processes, it is not useful to get the CPU/RAM usage of just the current one var process = Process.GetCurrentProcess(); var memoryUsage = process.WorkingSet64; - var now = SystemClock.Instance.GetCurrentInstant(); - var shardUptime = now - shardInfo.LastConnectionTime; + var now = SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds(); + var shardUptime = Duration.FromSeconds(now - shardInfo.LastConnection); + + var shardTotal = shards.Count(); + int shardClusterTotal = ctx.Cluster.Shards.Count; + var shardUpTotal = shards.Where(x => x.Up && now - x.LastConnection > 60).Count(); embed .Field(new Embed.Field("Current shard", - $"Shard #{shardId} (of {shardTotal} total, {shardUpTotal} are up)", true)) + $"Shard #{ctx.ShardId} (of {shardTotal} total," + + (isCluster ? $" {shardClusterTotal} in this cluster," : "") + $" {shardUpTotal} are up)" + , true)) .Field(new Embed.Field("Shard uptime", $"{shardUptime.FormatDuration()} ({shardInfo.DisconnectionCount} disconnections)", true)) .Field(new Embed.Field("CPU usage", $"{_cpu.LastCpuMeasure:P1}", true)) .Field(new Embed.Field("Memory usage", $"{memoryUsage / 1024 / 1024} MiB", true)) .Field(new Embed.Field("Latency", - $"API: {apiLatency.TotalMilliseconds:F0} ms, shard: {shardInfo.ShardLatency.Milliseconds} ms", - true)) - .Field(new Embed.Field("Total numbers", $" {counts.SystemCount:N0} systems," - + $" {counts.MemberCount:N0} members," - + $" {counts.GroupCount:N0} groups," - + $" {counts.SwitchCount:N0} switches," - + $" {counts.MessageCount:N0} messages")) - .Timestamp(process.StartTime.ToString("O")) - .Footer(new Embed.EmbedFooter( - $"PluralKit {BuildInfoService.Version} • https://github.com/xSke/PluralKit • Last restarted: ")); - ; + $"API: {apiLatency.TotalMilliseconds:F0} ms, shard: {shardInfo.Latency} ms", + true)); + + embed.Field(new("Total numbers", $" {counts.SystemCount:N0} systems," + + $" {counts.MemberCount:N0} members," + + $" {counts.GroupCount:N0} groups," + + $" {counts.SwitchCount:N0} switches," + + $" {counts.MessageCount:N0} messages")); + + embed + .Footer(new(String.Join(" \u2022 ", new[] { + $"PluralKit {BuildInfoService.Version}", + (isCluster ? $"Cluster {_botConfig.Cluster.NodeIndex}" : ""), + "https://github.com/xSke/PluralKit", + "Last restarted:", + }))) + .Timestamp(process.StartTime.ToString("O")); + await ctx.Rest.EditMessage(msg.ChannelId, msg.Id, new MessageEditRequest { Content = "", Embed = embed.Build() }); } diff --git a/PluralKit.Bot/Init.cs b/PluralKit.Bot/Init.cs index aa866a4c..77c1422d 100644 --- a/PluralKit.Bot/Init.cs +++ b/PluralKit.Bot/Init.cs @@ -54,8 +54,8 @@ public class Init logger.Information("Connecting to database"); await services.Resolve().ApplyMigrations(); - // if we're running single-process, clear any existing shard status from the database - await services.Resolve().ClearShardStatus(); + // Clear shard status from Redis + await redis.Connection.GetDatabase().KeyDeleteAsync("pluralkit:shardstatus"); } // Init the bot instance itself, register handlers and such to the client before beginning to connect diff --git a/PluralKit.Bot/PluralKit.Bot.csproj b/PluralKit.Bot/PluralKit.Bot.csproj index 9ebee85a..d55c7b88 100644 --- a/PluralKit.Bot/PluralKit.Bot.csproj +++ b/PluralKit.Bot/PluralKit.Bot.csproj @@ -22,8 +22,15 @@ + + + + + + + diff --git a/PluralKit.Bot/Services/ShardInfoService.cs b/PluralKit.Bot/Services/ShardInfoService.cs index 3f1ec7d1..3f7c86ac 100644 --- a/PluralKit.Bot/Services/ShardInfoService.cs +++ b/PluralKit.Bot/Services/ShardInfoService.cs @@ -1,11 +1,12 @@ using System.Net.WebSockets; -using App.Metrics; +using Google.Protobuf; using Myriad.Gateway; using NodaTime; -using NodaTime.Extensions; + +using StackExchange.Redis; using PluralKit.Core; @@ -13,30 +14,20 @@ using Serilog; namespace PluralKit.Bot; -// TODO: how much of this do we need now that we have logging in the shard library? -// A lot could probably be cleaned up... public class ShardInfoService { - private readonly Cluster _client; - - private readonly IDatabase _db; private readonly ILogger _logger; - - private readonly IMetrics _metrics; - private readonly ModelRepository _repo; + private readonly Cluster _client; + private readonly RedisService _redis; private readonly Dictionary _shardInfo = new(); - public ShardInfoService(ILogger logger, Cluster client, IMetrics metrics, IDatabase db, ModelRepository repo) + public ShardInfoService(ILogger logger, Cluster client, RedisService redis) { - _client = client; - _metrics = metrics; - _db = db; - _repo = repo; _logger = logger.ForContext(); + _client = client; + _redis = redis; } - public ICollection Shards => _shardInfo.Values; - public void Init() { // We initialize this before any shards are actually created and connected @@ -44,109 +35,109 @@ public class ShardInfoService _client.ShardCreated += InitializeShard; } - private void ReportShardStatus() + public async Task> GetShards() { - foreach (var (id, shard) in _shardInfo) - _metrics.Measure.Gauge.SetValue(BotMetrics.ShardLatency, new MetricTags("shard", id.ToString()), - shard.ShardLatency.TotalMilliseconds); - _metrics.Measure.Gauge.SetValue(BotMetrics.ShardsConnected, _shardInfo.Count(s => s.Value.Connected)); + var db = _redis.Connection.GetDatabase(); + var redisInfo = await db.HashGetAllAsync("pluralkit:shardstatus"); + return redisInfo.Select(x => Proto.Unmarshal(x.Value)); } private void InitializeShard(Shard shard) { - // Get or insert info in the client dict - if (_shardInfo.TryGetValue(shard.ShardId, out var info)) + _ = Inner(); + + async Task Inner() { + var db = _redis.Connection.GetDatabase(); + var redisInfo = await db.HashGetAsync("pluralkit::shardstatus", shard.ShardId); + // Skip adding listeners if we've seen this shard & already added listeners to it - if (info.HasAttachedListeners) + if (redisInfo.HasValue) return; + + // latency = 0 because otherwise shard 0 would serialize to an empty array, thanks protobuf + var state = new ShardState() { ShardId = shard.ShardId, Up = false, Latency = 1 }; + + // Register listeners for new shard + shard.Resumed += () => ReadyOrResumed(shard); + shard.Ready += () => ReadyOrResumed(shard); + shard.SocketClosed += (closeStatus, message) => SocketClosed(shard, closeStatus, message); + shard.HeartbeatReceived += latency => Heartbeated(shard, latency); + + // Register that we've seen it + await db.HashSetAsync("pluralkit:shardstatus", state.HashWrapper()); } - else - { - _shardInfo[shard.ShardId] = info = new ShardInfo(); - } - - // Call our own SocketOpened listener manually (and then attach the listener properly) - - // Register listeners for new shards - shard.Resumed += () => ReadyOrResumed(shard); - shard.Ready += () => ReadyOrResumed(shard); - shard.SocketClosed += (closeStatus, message) => SocketClosed(shard, closeStatus, message); - shard.HeartbeatReceived += latency => Heartbeated(shard, latency); - - // Register that we've seen it - info.HasAttachedListeners = true; } - private ShardInfo TryGetShard(Shard shard) + private async Task TryGetShard(Shard shard) { - // If we haven't seen this shard before, add it to the dict! - // I don't think this will ever occur since the shard number is constant up-front and we handle those - // in the RefreshShardList handler above but you never know, I guess~ - if (!_shardInfo.TryGetValue(shard.ShardId, out var info)) - _shardInfo[shard.ShardId] = info = new ShardInfo(); - return info; + var db = _redis.Connection.GetDatabase(); + var redisInfo = await db.HashGetAsync("pluralkit:shardstatus", shard.ShardId); + if (redisInfo.HasValue) + return Proto.Unmarshal(redisInfo); + return null; } private void ReadyOrResumed(Shard shard) { - var info = TryGetShard(shard); - info.LastConnectionTime = SystemClock.Instance.GetCurrentInstant(); - info.Connected = true; - ReportShardStatus(); - - _ = ExecuteWithDatabase(async c => + _ = DoAsync(async () => { - await _repo.SetShardStatus(c, shard.ShardId, PKShardInfo.ShardStatus.Up); - await _repo.RegisterShardConnection(c, shard.ShardId); + var info = await TryGetShard(shard); + + info.LastConnection = (int)SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds(); + info.Up = true; + + var db = _redis.Connection.GetDatabase(); + await db.HashSetAsync("pluralkit:shardstatus", info.HashWrapper()); }); } private void SocketClosed(Shard shard, WebSocketCloseStatus? closeStatus, string message) { - var info = TryGetShard(shard); - info.DisconnectionCount++; - info.Connected = false; - ReportShardStatus(); + _ = DoAsync(async () => + { + var info = await TryGetShard(shard); - _ = ExecuteWithDatabase(c => - _repo.SetShardStatus(c, shard.ShardId, PKShardInfo.ShardStatus.Down)); + info.DisconnectionCount++; + info.Up = false; + + var db = _redis.Connection.GetDatabase(); + await db.HashSetAsync("pluralkit:shardstatus", info.HashWrapper()); + }); } private void Heartbeated(Shard shard, TimeSpan latency) { - var info = TryGetShard(shard); - info.LastHeartbeatTime = SystemClock.Instance.GetCurrentInstant(); - info.Connected = true; - info.ShardLatency = latency.ToDuration(); + _ = DoAsync(async () => + { + var info = await TryGetShard(shard); - _ = ExecuteWithDatabase(c => - _repo.RegisterShardHeartbeat(c, shard.ShardId, latency.ToDuration())); + info.LastHeartbeat = (int)SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds(); + info.Up = true; + info.Latency = (int)latency.TotalMilliseconds; + + var db = _redis.Connection.GetDatabase(); + await db.HashSetAsync("pluralkit:shardstatus", info.HashWrapper()); + }); } - private async Task ExecuteWithDatabase(Func fn) + private async Task DoAsync(Func fn) { // wrapper function to log errors because we "async void" it at call site :( try { - await using var conn = await _db.Obtain(); - await fn(conn); + await fn(); } catch (Exception e) { _logger.Error(e, "Error persisting shard status"); } } +} - public ShardInfo GetShardInfo(int shardId) => _shardInfo[shardId]; - - public class ShardInfo - { - public bool Connected; - public int DisconnectionCount; - public bool HasAttachedListeners; - public Instant LastConnectionTime; - public Instant LastHeartbeatTime; - public Duration ShardLatency; - } +public static class RedisExt +{ + // convenience method + public static HashEntry[] HashWrapper(this ShardState state) + => new[] { new HashEntry(state.ShardId, state.ToByteArray()) }; } \ No newline at end of file diff --git a/PluralKit.Bot/packages.lock.json b/PluralKit.Bot/packages.lock.json index a0114f78..b8f7eefb 100644 --- a/PluralKit.Bot/packages.lock.json +++ b/PluralKit.Bot/packages.lock.json @@ -2,6 +2,32 @@ "version": 1, "dependencies": { "net6.0": { + "Google.Protobuf": { + "type": "Direct", + "requested": "[3.13.0, )", + "resolved": "3.13.0", + "contentHash": "/6VgKCh0P59x/rYsBkCvkUanF0TeUYzwV9hzLIWgt23QRBaKHoxaaMkidEWhKibLR88c3PVCXyyrx9Xlb+Ne6w==", + "dependencies": { + "System.Memory": "4.5.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Direct", + "requested": "[2.32.0, )", + "resolved": "2.32.0", + "contentHash": "ixqSWxPK49P+5z6M2dDBHca0k+sXFe2KHHTJK3P+YXp6QOTHv5CHxNdaW8GrFF34Eh1FJ56Q2ADe383+FEAp6Q==", + "dependencies": { + "Grpc.Net.Client": "2.32.0", + "Microsoft.Extensions.Http": "3.0.3" + } + }, + "Grpc.Tools": { + "type": "Direct", + "requested": "[2.37.0, )", + "resolved": "2.37.0", + "contentHash": "cud/urkbw3QoQ8+kNeCy2YI0sHrh7td/1cZkVbH6hDLIXX7zzmJbV/KjYSiqiYtflQf+S5mJPLzDQWScN/QdDg==" + }, "Humanizer.Core": { "type": "Direct", "requested": "[2.8.26, )", @@ -135,6 +161,32 @@ "System.Diagnostics.DiagnosticSource": "4.5.1" } }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "t9H6P/oYA4ZQI4fWq4eEwq2GmMNqmOSRfz5+YIat7pQuFmz1hRC2Vq/fL9ZVV1mjd5kHqBlhupMdlsBOsaxeEw==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "T4lKl51ahaSprLcgoZvgn8zYwh834DpaPnrDs6jBRdipL2NHIAC0rPeE7UyzDp/lzv4Xll2tw1u65Fg9ckvErg==", + "dependencies": { + "Grpc.Net.Common": "2.32.0", + "Microsoft.Extensions.Logging.Abstractions": "3.0.3", + "System.Diagnostics.DiagnosticSource": "4.5.1" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "vDsgy6fs+DlsylppjK9FBGTMMUe8vfAmaURV7ZTurM27itr8qBwymgqmwnVB2hcP1q35NqKx2NvPGe5S2IEnDw==", + "dependencies": { + "Grpc.Core.Api": "2.32.0" + } + }, "IPNetwork2": { "type": "Transitive", "resolved": "2.5.381", @@ -262,6 +314,16 @@ "resolved": "3.1.10", "contentHash": "TzHIUBWnzsViPS/20DnC6wf5kXdRAUZlIYwTYOT9S6heuOA4Re//UmHWsDR3PusAzly5dkdDW0RV0dDZ2vEebQ==" }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "3.0.3", + "contentHash": "dcyB8szIcSynjVZRuFgqkZpPgTc5zeRSj1HMXSmNqWbHYKiPYJl8ZQgBHz6wmZNSUUNGpCs5uxUg8DZHHDC1Ew==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.3", + "Microsoft.Extensions.Logging": "3.0.3", + "Microsoft.Extensions.Options": "3.0.3" + } + }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "3.1.10", @@ -941,6 +1003,11 @@ "System.Threading": "4.3.0" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", @@ -1469,6 +1536,7 @@ "Autofac.Extensions.DependencyInjection": "7.1.0", "Dapper": "2.0.35", "Dapper.Contrib": "2.0.35", + "Google.Protobuf": "3.13.0", "Microsoft.Extensions.Caching.Memory": "3.1.10", "Microsoft.Extensions.Configuration": "3.1.10", "Microsoft.Extensions.Configuration.Binder": "3.1.10", diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs b/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs deleted file mode 100644 index 1006b0f9..00000000 --- a/PluralKit.Core/Database/Repository/ModelRepository.Shards.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Dapper; - -using NodaTime; - -namespace PluralKit.Core; - -public partial class ModelRepository -{ - public Task> GetShards() => - _db.Execute(conn => conn.QueryAsync("select * from shards order by id")); - - public Task SetShardStatus(IPKConnection conn, int shard, PKShardInfo.ShardStatus status) => - conn.ExecuteAsync( - "insert into shards (id, status) values (@Id, @Status) on conflict (id) do update set status = @Status", - new { Id = shard, Status = status }); - - public Task ClearShardStatus() => _db.Execute(conn => conn.ExecuteAsync("update shards set status = 0")); - - public Task RegisterShardHeartbeat(IPKConnection conn, int shard, Duration ping) => - conn.ExecuteAsync( - "insert into shards (id, last_heartbeat, ping) values (@Id, now(), @Ping) on conflict (id) do update set last_heartbeat = now(), ping = @Ping", - new { Id = shard, Ping = ping.TotalSeconds }); - - public Task RegisterShardConnection(IPKConnection conn, int shard) => - conn.ExecuteAsync( - "insert into shards (id, last_connection) values (@Id, now()) on conflict (id) do update set last_connection = now()", - new { Id = shard }); -} \ No newline at end of file diff --git a/PluralKit.Core/Models/PKShardInfo.cs b/PluralKit.Core/Models/PKShardInfo.cs deleted file mode 100644 index 93b35955..00000000 --- a/PluralKit.Core/Models/PKShardInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NodaTime; - -namespace PluralKit.Core; - -public class PKShardInfo -{ - public enum ShardStatus - { - Down = 0, - Up = 1 - } - - public int Id { get; } - public ShardStatus Status { get; } - public float? Ping { get; } - public Instant? LastHeartbeat { get; } - public Instant? LastConnection { get; } -} \ No newline at end of file diff --git a/PluralKit.Core/PluralKit.Core.csproj b/PluralKit.Core/PluralKit.Core.csproj index a5a5ea70..a22155b8 100644 --- a/PluralKit.Core/PluralKit.Core.csproj +++ b/PluralKit.Core/PluralKit.Core.csproj @@ -22,6 +22,7 @@ + diff --git a/PluralKit.Core/Utils/ProtobufUtils.cs b/PluralKit.Core/Utils/ProtobufUtils.cs new file mode 100644 index 00000000..9ab45dd4 --- /dev/null +++ b/PluralKit.Core/Utils/ProtobufUtils.cs @@ -0,0 +1,22 @@ +using Google.Protobuf; + +namespace PluralKit.Core; + +public static class Proto +{ + private static Dictionary _parser = new(); + + public static byte[] Marshal(this IMessage message) => message.ToByteArray(); + + public static T Unmarshal(this byte[] message) where T : IMessage, new() + { + var type = typeof(T).ToString(); + if (_parser.ContainsKey(type)) + return (T)_parser[type].ParseFrom(message); + else + { + _parser.Add(type, new MessageParser(() => new T())); + return Unmarshal(message); + } + } +} \ No newline at end of file diff --git a/PluralKit.Core/packages.lock.json b/PluralKit.Core/packages.lock.json index 7f20b6d4..f3d1653d 100644 --- a/PluralKit.Core/packages.lock.json +++ b/PluralKit.Core/packages.lock.json @@ -61,6 +61,16 @@ "System.Reflection.Emit": "4.7.0" } }, + "Google.Protobuf": { + "type": "Direct", + "requested": "[3.13.0, )", + "resolved": "3.13.0", + "contentHash": "/6VgKCh0P59x/rYsBkCvkUanF0TeUYzwV9hzLIWgt23QRBaKHoxaaMkidEWhKibLR88c3PVCXyyrx9Xlb+Ne6w==", + "dependencies": { + "System.Memory": "4.5.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "IPNetwork2": { "type": "Direct", "requested": "[2.5.381, )", @@ -950,6 +960,11 @@ "System.Threading": "4.3.0" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.2", + "contentHash": "fvq1GNmUFwbKv+aLVYYdgu/+gc8Nu9oFujOxIjPrsf+meis9JBzTPDL6aP/eeGOz9yPj6rRLUbOjKMpsMEWpNg==" + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", diff --git a/PluralKit.ScheduledTasks/packages.lock.json b/PluralKit.ScheduledTasks/packages.lock.json index 1c876ed5..572a17f6 100644 --- a/PluralKit.ScheduledTasks/packages.lock.json +++ b/PluralKit.ScheduledTasks/packages.lock.json @@ -117,6 +117,15 @@ "System.Diagnostics.DiagnosticSource": "4.5.1" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.13.0", + "contentHash": "/6VgKCh0P59x/rYsBkCvkUanF0TeUYzwV9hzLIWgt23QRBaKHoxaaMkidEWhKibLR88c3PVCXyyrx9Xlb+Ne6w==", + "dependencies": { + "System.Memory": "4.5.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.8.26", @@ -281,8 +290,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -299,6 +308,23 @@ "System.Runtime": "4.3.0" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Bh6blKG8VAKvXiLe2L+sEsn62nc1Ij34MrNxepD2OCrS5cpCwQa9MeLyhVQPQ/R4Wlzwuy6wMK8hLb11QPDRsQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", @@ -389,6 +415,14 @@ "Npgsql": "4.1.5" } }, + "Pipelines.Sockets.Unofficial": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "7hzHplEIVOGBl5zOQZGX/DiJDHjq+RVRVrYgDiqXb6RriqWAdacXxp+XO9WSrATCEXyNOUOQg9aqQArsjase/A==", + "dependencies": { + "System.IO.Pipelines": "5.0.0" + } + }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.0", @@ -608,6 +642,15 @@ "dapper": "1.50.5" } }, + "StackExchange.Redis": { + "type": "Transitive", + "resolved": "2.2.88", + "contentHash": "JJi1jcO3/ZiamBhlsC/TR8aZmYf+nqpGzMi0HRRCy5wJkUPmMnRp0kBA6V84uhU8b531FHSdTDaFCAyCUJomjA==", + "dependencies": { + "Pipelines.Sockets.Unofficial": "2.2.0", + "System.Diagnostics.PerformanceCounter": "5.0.0" + } + }, "System.AppContext": { "type": "Transitive", "resolved": "4.3.0", @@ -648,6 +691,15 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "aM7cbfEfVNlEEOj3DsZP+2g9NRwbkyiAv2isQEzw7pnkDg9ekCU2m1cdJLM02Uq691OaCS91tooaxcEn8d0q5w==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "5.0.0", + "System.Security.Permissions": "5.0.0" + } + }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", @@ -675,6 +727,17 @@ "resolved": "4.7.1", "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" }, + "System.Diagnostics.PerformanceCounter": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "kcQWWtGVC3MWMNXdMDWfrmIlFZZ2OdoeT6pSNVRtk9+Sa7jwdPiMlNwb0ZQcS7NRlT92pCfmjRtkSWUW3RAKwg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "Microsoft.Win32.Registry": "5.0.0", + "System.Configuration.ConfigurationManager": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", @@ -695,6 +758,14 @@ "System.Runtime": "4.3.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "5.0.0" + } + }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", @@ -810,6 +881,11 @@ "System.Runtime": "4.3.0" } }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "irMYm3vhVgRsYvHTU5b2gsT2CwT/SMM6LZFzuJjpIvT5Z4CshxNsaoBC1X/LltwuR3Opp8d6jOS/60WwOb7Q2Q==" + }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", @@ -851,6 +927,11 @@ "System.Threading": "4.3.0" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.2", + "contentHash": "fvq1GNmUFwbKv+aLVYYdgu/+gc8Nu9oFujOxIjPrsf+meis9JBzTPDL6aP/eeGOz9yPj6rRLUbOjKMpsMEWpNg==" + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", @@ -1066,6 +1147,15 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", @@ -1178,6 +1268,11 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "HGxMSAFAPLNoxBvSfW08vHde0F9uh7BjASwu6JF9JnXuEPhCY3YUqURn0+bQV/4UWeaqymmrHWV+Aw9riQCtCA==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1210,6 +1305,20 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uE8juAhEkp7KDBCdjDIE3H9R1HJuEHqeqX8nLX9gmYKWwsqk3T5qZlPx8qle5DPKimC/Fy3AFTdV7HamgCh9qQ==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Windows.Extensions": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -1283,6 +1392,14 @@ "System.Runtime": "4.3.0" } }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "c1ho9WU9ZxMZawML+ssPKZfdnrg/OjR3pe0m9v8230z3acqphwvPJqzAkH54xRYm5ntZHGG1EPP3sux9H3qSPg==", + "dependencies": { + "System.Drawing.Common": "5.0.0" + } + }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", @@ -1333,6 +1450,7 @@ "Autofac.Extensions.DependencyInjection": "7.1.0", "Dapper": "2.0.35", "Dapper.Contrib": "2.0.35", + "Google.Protobuf": "3.13.0", "Microsoft.Extensions.Caching.Memory": "3.1.10", "Microsoft.Extensions.Configuration": "3.1.10", "Microsoft.Extensions.Configuration.Binder": "3.1.10", @@ -1356,6 +1474,7 @@ "Serilog.Sinks.File": "4.1.0", "SqlKata": "2.3.7", "SqlKata.Execution": "2.3.7", + "StackExchange.Redis": "2.2.88", "System.Interactive.Async": "5.0.0", "ipnetwork2": "2.5.381" } diff --git a/PluralKit.Tests/packages.lock.json b/PluralKit.Tests/packages.lock.json index 7c114f5b..8b5407e9 100644 --- a/PluralKit.Tests/packages.lock.json +++ b/PluralKit.Tests/packages.lock.json @@ -153,6 +153,50 @@ "System.Diagnostics.DiagnosticSource": "4.5.1" } }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.13.0", + "contentHash": "/6VgKCh0P59x/rYsBkCvkUanF0TeUYzwV9hzLIWgt23QRBaKHoxaaMkidEWhKibLR88c3PVCXyyrx9Xlb+Ne6w==", + "dependencies": { + "System.Memory": "4.5.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "t9H6P/oYA4ZQI4fWq4eEwq2GmMNqmOSRfz5+YIat7pQuFmz1hRC2Vq/fL9ZVV1mjd5kHqBlhupMdlsBOsaxeEw==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "T4lKl51ahaSprLcgoZvgn8zYwh834DpaPnrDs6jBRdipL2NHIAC0rPeE7UyzDp/lzv4Xll2tw1u65Fg9ckvErg==", + "dependencies": { + "Grpc.Net.Common": "2.32.0", + "Microsoft.Extensions.Logging.Abstractions": "3.0.3", + "System.Diagnostics.DiagnosticSource": "4.5.1" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "ixqSWxPK49P+5z6M2dDBHca0k+sXFe2KHHTJK3P+YXp6QOTHv5CHxNdaW8GrFF34Eh1FJ56Q2ADe383+FEAp6Q==", + "dependencies": { + "Grpc.Net.Client": "2.32.0", + "Microsoft.Extensions.Http": "3.0.3" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.32.0", + "contentHash": "vDsgy6fs+DlsylppjK9FBGTMMUe8vfAmaURV7ZTurM27itr8qBwymgqmwnVB2hcP1q35NqKx2NvPGe5S2IEnDw==", + "dependencies": { + "Grpc.Core.Api": "2.32.0" + } + }, "Humanizer.Core": { "type": "Transitive", "resolved": "2.8.26", @@ -365,6 +409,16 @@ "Microsoft.Extensions.Logging.Abstractions": "2.1.0" } }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "3.0.3", + "contentHash": "dcyB8szIcSynjVZRuFgqkZpPgTc5zeRSj1HMXSmNqWbHYKiPYJl8ZQgBHz6wmZNSUUNGpCs5uxUg8DZHHDC1Ew==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "3.0.3", + "Microsoft.Extensions.Logging": "3.0.3", + "Microsoft.Extensions.Options": "3.0.3" + } + }, "Microsoft.Extensions.Logging": { "type": "Transitive", "resolved": "3.1.10", @@ -408,8 +462,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -448,6 +502,23 @@ "System.Runtime": "4.3.0" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Bh6blKG8VAKvXiLe2L+sEsn62nc1Ij34MrNxepD2OCrS5cpCwQa9MeLyhVQPQ/R4Wlzwuy6wMK8hLb11QPDRsQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, "NETStandard.Library": { "type": "Transitive", "resolved": "1.6.1", @@ -551,6 +622,14 @@ "resolved": "5.0.0", "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" }, + "Pipelines.Sockets.Unofficial": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "7hzHplEIVOGBl5zOQZGX/DiJDHjq+RVRVrYgDiqXb6RriqWAdacXxp+XO9WSrATCEXyNOUOQg9aqQArsjase/A==", + "dependencies": { + "System.IO.Pipelines": "5.0.0" + } + }, "Polly": { "type": "Transitive", "resolved": "7.2.1", @@ -846,6 +925,15 @@ "dapper": "1.50.5" } }, + "StackExchange.Redis": { + "type": "Transitive", + "resolved": "2.2.88", + "contentHash": "JJi1jcO3/ZiamBhlsC/TR8aZmYf+nqpGzMi0HRRCy5wJkUPmMnRp0kBA6V84uhU8b531FHSdTDaFCAyCUJomjA==", + "dependencies": { + "Pipelines.Sockets.Unofficial": "2.2.0", + "System.Diagnostics.PerformanceCounter": "5.0.0" + } + }, "Swashbuckle.AspNetCore": { "type": "Transitive", "resolved": "5.0.0", @@ -948,6 +1036,15 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "aM7cbfEfVNlEEOj3DsZP+2g9NRwbkyiAv2isQEzw7pnkDg9ekCU2m1cdJLM02Uq691OaCS91tooaxcEn8d0q5w==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "5.0.0", + "System.Security.Permissions": "5.0.0" + } + }, "System.Console": { "type": "Transitive", "resolved": "4.3.0", @@ -975,6 +1072,17 @@ "resolved": "4.7.1", "contentHash": "j81Lovt90PDAq8kLpaJfJKV/rWdWuEk6jfV+MBkee33vzYLEUsy4gXK8laa9V2nZlLM9VM9yA/OOQxxPEJKAMw==" }, + "System.Diagnostics.PerformanceCounter": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "kcQWWtGVC3MWMNXdMDWfrmIlFZZ2OdoeT6pSNVRtk9+Sa7jwdPiMlNwb0ZQcS7NRlT92pCfmjRtkSWUW3RAKwg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "Microsoft.Win32.Registry": "5.0.0", + "System.Configuration.ConfigurationManager": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Diagnostics.Tools": { "type": "Transitive", "resolved": "4.3.0", @@ -995,6 +1103,14 @@ "System.Runtime": "4.3.0" } }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "5.0.0" + } + }, "System.Dynamic.Runtime": { "type": "Transitive", "resolved": "4.0.11", @@ -1132,6 +1248,11 @@ "System.Runtime": "4.3.0" } }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "irMYm3vhVgRsYvHTU5b2gsT2CwT/SMM6LZFzuJjpIvT5Z4CshxNsaoBC1X/LltwuR3Opp8d6jOS/60WwOb7Q2Q==" + }, "System.Linq": { "type": "Transitive", "resolved": "4.3.0", @@ -1173,6 +1294,11 @@ "System.Threading": "4.3.0" } }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.0", @@ -1388,6 +1514,15 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Security.Cryptography.Algorithms": { "type": "Transitive", "resolved": "4.3.0", @@ -1500,6 +1635,11 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "HGxMSAFAPLNoxBvSfW08vHde0F9uh7BjASwu6JF9JnXuEPhCY3YUqURn0+bQV/4UWeaqymmrHWV+Aw9riQCtCA==" + }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", "resolved": "4.3.0", @@ -1532,6 +1672,20 @@ "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" } }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uE8juAhEkp7KDBCdjDIE3H9R1HJuEHqeqX8nLX9gmYKWwsqk3T5qZlPx8qle5DPKimC/Fy3AFTdV7HamgCh9qQ==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Windows.Extensions": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -1605,6 +1759,14 @@ "System.Runtime": "4.3.0" } }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "c1ho9WU9ZxMZawML+ssPKZfdnrg/OjR3pe0m9v8230z3acqphwvPJqzAkH54xRYm5ntZHGG1EPP3sux9H3qSPg==", + "dependencies": { + "System.Drawing.Common": "5.0.0" + } + }, "System.Xml.ReaderWriter": { "type": "Transitive", "resolved": "4.3.0", @@ -1697,12 +1859,14 @@ "Polly": "7.2.1", "Polly.Contrib.WaitAndRetry": "1.1.1", "Serilog": "2.10.0", + "StackExchange.Redis": "2.2.88", "System.Linq.Async": "5.0.0" } }, "pluralkit.api": { "type": "Project", "dependencies": { + "Google.Protobuf": "3.13.0", "Microsoft.AspNetCore.Mvc.NewtonsoftJson": "3.1.0", "Microsoft.AspNetCore.Mvc.Versioning": "4.2.0", "Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer": "4.2.0", @@ -1719,6 +1883,8 @@ "pluralkit.bot": { "type": "Project", "dependencies": { + "Google.Protobuf": "3.13.0", + "Grpc.Net.ClientFactory": "2.32.0", "Humanizer.Core": "2.8.26", "Myriad": "1.0.0", "PluralKit.Core": "1.0.0", @@ -1735,6 +1901,7 @@ "Autofac.Extensions.DependencyInjection": "7.1.0", "Dapper": "2.0.35", "Dapper.Contrib": "2.0.35", + "Google.Protobuf": "3.13.0", "Microsoft.Extensions.Caching.Memory": "3.1.10", "Microsoft.Extensions.Configuration": "3.1.10", "Microsoft.Extensions.Configuration.Binder": "3.1.10", @@ -1758,6 +1925,7 @@ "Serilog.Sinks.File": "4.1.0", "SqlKata": "2.3.7", "SqlKata.Execution": "2.3.7", + "StackExchange.Redis": "2.2.88", "System.Interactive.Async": "5.0.0", "ipnetwork2": "2.5.381" } diff --git a/proto/discord.proto b/proto/discord.proto new file mode 100644 index 00000000..f3bd765b --- /dev/null +++ b/proto/discord.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +message ShardState { + int32 shard_id = 1; + bool up = 2; + int32 disconnection_count = 3; + + // milliseconds + int32 latency = 4; + + // unix timestamp + int32 last_heartbeat = 5; + int32 last_connection = 6; +} + +message GatewayEvent { + int32 shard_id = 1; + bytes payload = 2; +}