using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using App.Metrics; using Discord; using Discord.WebSocket; using NodaTime.Extensions; using PluralKit.Core; using Serilog; namespace PluralKit.Bot { public class PeriodicStatCollector { private DiscordShardedClient _client; private IMetrics _metrics; private IDataStore _data; private WebhookCacheService _webhookCache; private DbConnectionCountHolder _countHolder; private ILogger _logger; public PeriodicStatCollector(IDiscordClient client, IMetrics metrics, ILogger logger, WebhookCacheService webhookCache, DbConnectionCountHolder countHolder, IDataStore data) { _client = (DiscordShardedClient) client; _metrics = metrics; _webhookCache = webhookCache; _countHolder = countHolder; _data = data; _logger = logger.ForContext(); } public async Task CollectStats() { var stopwatch = new Stopwatch(); stopwatch.Start(); // Aggregate guild/channel stats _metrics.Measure.Gauge.SetValue(BotMetrics.Guilds, _client.Guilds.Count); _metrics.Measure.Gauge.SetValue(BotMetrics.Channels, _client.Guilds.Sum(g => g.TextChannels.Count)); _metrics.Measure.Gauge.SetValue(BotMetrics.ShardsConnected, _client.Shards.Count(shard => shard.ConnectionState == ConnectionState.Connected)); // Aggregate member stats var usersKnown = new HashSet(); var usersOnline = new HashSet(); foreach (var guild in _client.Guilds) foreach (var user in guild.Users) { usersKnown.Add(user.Id); if (user.Status == UserStatus.Online) usersOnline.Add(user.Id); } _metrics.Measure.Gauge.SetValue(BotMetrics.MembersTotal, usersKnown.Count); _metrics.Measure.Gauge.SetValue(BotMetrics.MembersOnline, usersOnline.Count); // Aggregate DB stats _metrics.Measure.Gauge.SetValue(CoreMetrics.SystemCount, await _data.GetTotalSystems()); _metrics.Measure.Gauge.SetValue(CoreMetrics.MemberCount, await _data.GetTotalMembers()); _metrics.Measure.Gauge.SetValue(CoreMetrics.SwitchCount, await _data.GetTotalSwitches()); _metrics.Measure.Gauge.SetValue(CoreMetrics.MessageCount, await _data.GetTotalMessages()); // Process info var process = Process.GetCurrentProcess(); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessPhysicalMemory, process.WorkingSet64); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessVirtualMemory, process.VirtualMemorySize64); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessPrivateMemory, process.PrivateMemorySize64); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessThreads, process.Threads.Count); _metrics.Measure.Gauge.SetValue(CoreMetrics.ProcessHandles, process.HandleCount); _metrics.Measure.Gauge.SetValue(CoreMetrics.CpuUsage, await EstimateCpuUsage()); // Database info _metrics.Measure.Gauge.SetValue(CoreMetrics.DatabaseConnections, _countHolder.ConnectionCount); // Other shiz _metrics.Measure.Gauge.SetValue(BotMetrics.WebhookCacheSize, _webhookCache.CacheSize); stopwatch.Stop(); _logger.Information("Updated metrics in {Time}", stopwatch.ElapsedDuration()); } private async Task EstimateCpuUsage() { // We get the current processor time, wait 5 seconds, then compare // https://medium.com/@jackwild/getting-cpu-usage-in-net-core-7ef825831b8b _logger.Information("Estimating CPU usage..."); var stopwatch = new Stopwatch(); stopwatch.Start(); var cpuTimeBefore = Process.GetCurrentProcess().TotalProcessorTime; await Task.Delay(5000); stopwatch.Stop(); var cpuTimeAfter = Process.GetCurrentProcess().TotalProcessorTime; var cpuTimePassed = cpuTimeAfter - cpuTimeBefore; var timePassed = stopwatch.Elapsed; var percent = cpuTimePassed / timePassed; _logger.Information("CPU usage measured as {Percent:P}", percent); return percent; } } }