feat: replace App.Metrics with prometheus-net

This commit is contained in:
sam 2024-08-20 20:19:24 +02:00
parent df8af75dd4
commit be01fb1d53
8 changed files with 113 additions and 137 deletions

View file

@ -1,8 +1,8 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using App.Metrics;
using Catalogger.Backend.Cache;
using System.Text.Json;
using System.Web;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Extensions;
using Humanizer;
@ -10,10 +10,7 @@ using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Commands.Contexts;
using Remora.Discord.Commands.Extensions;
using Remora.Discord.Commands.Feedback.Services;
using Remora.Discord.Commands.Services;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway;
using Remora.Results;
@ -24,16 +21,18 @@ namespace Catalogger.Backend.Bot.Commands;
[Group("catalogger")]
public class MetaCommands(
ILogger logger,
IClock clock,
IMetrics metrics,
Config config,
DiscordGatewayClient client,
IFeedbackService feedbackService,
ContextInjectionService contextInjection,
IInviteCache inviteCache,
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()
@ -53,16 +52,15 @@ public class MetaCommands(
inline: true);
embed.AddField("Memory usage", memoryUsage.Bytes().Humanize(), inline: true);
var messagesReceived = metrics.Snapshot.GetForContext("Bot").Meters
.FirstOrDefault(m => m.MultidimensionalName == CataloggerMetrics.MessagesReceived.Name)?.Value;
if (messagesReceived != null)
embed.AddField("Messages received", $"{messagesReceived.OneMinuteRate * 60:F1}/m", true);
var messageCount = metrics.Snapshot.GetForContext("Bot").Gauges
.FirstOrDefault(m => m.MultidimensionalName == CataloggerMetrics.MessagesStored.Name)?.Value ?? 0;
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",
$"{messageCount:N0} messages from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels",
$"{CataloggerMetrics.MessagesStored.Value:N0} messages from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels",
inline: false);
IEmbed[] embeds = [embed.Build().GetOrThrow()];
@ -70,16 +68,35 @@ public class MetaCommands(
return (Result)await channelApi.EditMessageAsync(msg.ChannelID, msg.ID, content: "", embeds: embeds);
}
[Command("debug-invites")]
[Description("Show a representation of this server's invites")]
public async Task<IResult> DebugInvitesAsync()
// TODO: add more checks around response format, configurable prometheus endpoint
private async Task<double?> MessagesRate()
{
if (contextInjection.Context is not IInteractionCommandContext ctx) throw new CataloggerError("No context");
if (!ctx.TryGetGuildID(out var guildId)) throw new CataloggerError("No guild ID in context");
if (!config.Logging.EnableMetrics) return null;
var invites = await inviteCache.TryGetAsync(guildId);
var text = invites.Select(i => $"{i.Code} in {i.Channel?.ID.Value}");
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();
return await feedbackService.SendContextualAsync(string.Join("\n", text));
var data = await resp.Content.ReadFromJsonAsync<PrometheusResponse>();
_logger.Debug("Raw json: {Data}", JsonSerializer.Serialize(data));
var rawNumber = (data?.data.result[0].value[1] as JsonElement?)?.GetString();
_logger.Debug("Raw data: {Raw}", rawNumber);
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
}