107 lines
No EOL
4 KiB
C#
107 lines
No EOL
4 KiB
C#
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.Json;
|
|
using System.Web;
|
|
using Catalogger.Backend.Cache.InMemoryCache;
|
|
using Catalogger.Backend.Extensions;
|
|
using Humanizer;
|
|
using Humanizer.Localisation;
|
|
using Remora.Commands.Attributes;
|
|
using Remora.Commands.Groups;
|
|
using Remora.Discord.API.Abstractions.Objects;
|
|
using Remora.Discord.API.Abstractions.Rest;
|
|
using Remora.Discord.Commands.Feedback.Services;
|
|
using Remora.Discord.Extensions.Embeds;
|
|
using Remora.Discord.Gateway;
|
|
using Remora.Results;
|
|
using IClock = NodaTime.IClock;
|
|
using IResult = Remora.Results.IResult;
|
|
|
|
namespace Catalogger.Backend.Bot.Commands;
|
|
|
|
[Group("catalogger")]
|
|
public class MetaCommands(
|
|
ILogger logger,
|
|
IClock clock,
|
|
Config config,
|
|
DiscordGatewayClient client,
|
|
IFeedbackService feedbackService,
|
|
GuildCache guildCache,
|
|
ChannelCache channelCache,
|
|
IDiscordRestChannelAPI channelApi) : CommandGroup
|
|
{
|
|
private readonly ILogger _logger = logger.ForContext<MetaCommands>();
|
|
private readonly HttpClient _client = new();
|
|
|
|
[Command("ping")]
|
|
[Description("Ping pong! See the bot's latency")]
|
|
public async Task<IResult> PingAsync()
|
|
{
|
|
var t1 = clock.GetCurrentInstant();
|
|
var msg = await feedbackService.SendContextualAsync("...").GetOrThrow();
|
|
var elapsed = clock.GetCurrentInstant() - t1;
|
|
|
|
var process = Process.GetCurrentProcess();
|
|
var memoryUsage = process.WorkingSet64;
|
|
|
|
var embed = new EmbedBuilder()
|
|
.WithColour(DiscordUtils.Purple)
|
|
.WithFooter($"{RuntimeInformation.FrameworkDescription} on {RuntimeInformation.RuntimeIdentifier}")
|
|
.WithCurrentTimestamp();
|
|
embed.AddField("Ping", $"Gateway: {client.Latency.Humanize()}\nAPI: {elapsed.ToTimeSpan().Humanize()}",
|
|
inline: true);
|
|
embed.AddField("Memory usage", memoryUsage.Bytes().Humanize(), inline: true);
|
|
|
|
var messageRate = await MessagesRate();
|
|
embed.AddField("Messages received",
|
|
messageRate != null
|
|
? $"{messageRate / 5:F1}/m\n({CataloggerMetrics.MessagesReceived.Value:N0} since last restart)"
|
|
: $"{CataloggerMetrics.MessagesReceived.Value:N0} since last restart",
|
|
true);
|
|
|
|
embed.AddField("Numbers",
|
|
$"{CataloggerMetrics.MessagesStored.Value:N0} messages " +
|
|
$"from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels",
|
|
true);
|
|
|
|
embed.AddField("Uptime",
|
|
$"{(CataloggerMetrics.Startup - clock.GetCurrentInstant()).Prettify(TimeUnit.Second)}\n" +
|
|
$"since <t:{CataloggerMetrics.Startup.ToUnixTimeSeconds()}:F>",
|
|
true);
|
|
|
|
IEmbed[] embeds = [embed.Build().GetOrThrow()];
|
|
|
|
return (Result)await channelApi.EditMessageAsync(msg.ChannelID, msg.ID, content: "", embeds: embeds);
|
|
}
|
|
|
|
// TODO: add more checks around response format, configurable prometheus endpoint
|
|
private async Task<double?> MessagesRate()
|
|
{
|
|
if (!config.Logging.EnableMetrics) return null;
|
|
|
|
try
|
|
{
|
|
var query = HttpUtility.UrlEncode("delta(catalogger_received_messages[5m])");
|
|
var resp = await _client.GetAsync($"http://localhost:9090/api/v1/query?query={query}");
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
var data = await resp.Content.ReadFromJsonAsync<PrometheusResponse>();
|
|
var rawNumber = (data?.data.result[0].value[1] as JsonElement?)?.GetString();
|
|
return double.TryParse(rawNumber, out var rate) ? rate : null;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Warning(e, "Failed querying Prometheus for message rate");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ReSharper disable InconsistentNaming, ClassNeverInstantiated.Local
|
|
private record PrometheusResponse(PrometheusData data);
|
|
|
|
private record PrometheusData(PrometheusResult[] result);
|
|
|
|
private record PrometheusResult(object[] value);
|
|
// ReSharper restore InconsistentNaming, ClassNeverInstantiated.Local
|
|
} |