add metrics (without reporting, for now)
This commit is contained in:
parent
5585ffd6ea
commit
14b132e466
9 changed files with 183 additions and 9 deletions
|
|
@ -1,12 +1,12 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using App.Metrics;
|
||||
using Catalogger.Backend.Cache;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Extensions;
|
||||
using Humanizer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using Remora.Commands.Attributes;
|
||||
using Remora.Commands.Groups;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
|
@ -15,6 +15,7 @@ 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;
|
||||
|
|
@ -22,11 +23,11 @@ namespace Catalogger.Backend.Bot.Commands;
|
|||
[Group("catalogger")]
|
||||
public class MetaCommands(
|
||||
IClock clock,
|
||||
DatabaseContext db,
|
||||
IMetrics metrics,
|
||||
DiscordGatewayClient client,
|
||||
IFeedbackService feedbackService,
|
||||
GuildCacheService guildCache,
|
||||
ChannelCacheService channelCache,
|
||||
IFeedbackService feedbackService,
|
||||
IDiscordRestChannelAPI channelApi) : CommandGroup
|
||||
{
|
||||
[Command("ping")]
|
||||
|
|
@ -37,7 +38,6 @@ public class MetaCommands(
|
|||
var msg = await feedbackService.SendContextualAsync("...").GetOrThrow();
|
||||
var elapsed = clock.GetCurrentInstant() - t1;
|
||||
|
||||
var messageCount = await db.Messages.CountAsync();
|
||||
var process = Process.GetCurrentProcess();
|
||||
var memoryUsage = process.WorkingSet64;
|
||||
|
||||
|
|
@ -49,11 +49,19 @@ 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;
|
||||
|
||||
embed.AddField("Numbers",
|
||||
$"{messageCount:N0} messages from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels",
|
||||
inline: false);
|
||||
|
||||
List<IEmbed> embeds = [embed.Build().GetOrThrow()];
|
||||
IEmbed[] embeds = [embed.Build().GetOrThrow()];
|
||||
|
||||
return (Result)await channelApi.EditMessageAsync(msg.ChannelID, msg.ID, content: "", embeds: embeds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using App.Metrics;
|
||||
using Catalogger.Backend.Cache;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Database.Models;
|
||||
|
|
@ -20,7 +21,8 @@ public class MessageCreateResponder(
|
|||
DatabaseContext db,
|
||||
MessageRepository messageRepository,
|
||||
UserCacheService userCache,
|
||||
PkMessageHandler pkMessageHandler)
|
||||
PkMessageHandler pkMessageHandler,
|
||||
IMetrics metrics)
|
||||
: IResponder<IMessageCreate>
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<MessageCreateResponder>();
|
||||
|
|
@ -29,6 +31,7 @@ public class MessageCreateResponder(
|
|||
public async Task<Result> RespondAsync(IMessageCreate msg, CancellationToken ct = default)
|
||||
{
|
||||
userCache.UpdateUser(msg.Author);
|
||||
metrics.Measure.Meter.Mark(CataloggerMetrics.MessagesReceived);
|
||||
|
||||
if (!msg.GuildID.IsDefined())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ namespace Catalogger.Backend.Cache;
|
|||
public class UserCacheService(IDiscordRestUserAPI userApi)
|
||||
{
|
||||
private readonly ConcurrentDictionary<Snowflake, IUser> _cache = new();
|
||||
|
||||
public int Size => _cache.Count;
|
||||
|
||||
public async Task<IUser?> GetUserAsync(Snowflake userId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="App.Metrics" Version="4.3.0" />
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="EntityFrameworkCore.Exceptions.PostgreSQL" Version="8.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.7" />
|
||||
|
|
@ -27,6 +28,8 @@
|
|||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.0" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.System.Text.Json" Version="10.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
88
Catalogger.Backend/CataloggerMetrics.cs
Normal file
88
Catalogger.Backend/CataloggerMetrics.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using App.Metrics;
|
||||
using App.Metrics.Gauge;
|
||||
using App.Metrics.Meter;
|
||||
using App.Metrics.Timer;
|
||||
|
||||
namespace Catalogger.Backend;
|
||||
|
||||
public static class CataloggerMetrics
|
||||
{
|
||||
public static MeterOptions MessagesReceived => new()
|
||||
{
|
||||
Name = "Messages received",
|
||||
MeasurementUnit = Unit.Events,
|
||||
RateUnit = TimeUnit.Seconds,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static GaugeOptions GuildsCached => new()
|
||||
{
|
||||
Name = "Guilds cached",
|
||||
MeasurementUnit = Unit.Items,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static GaugeOptions ChannelsCached => new()
|
||||
{
|
||||
Name = "Channels cached",
|
||||
MeasurementUnit = Unit.Items,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static GaugeOptions UsersCached => new()
|
||||
{
|
||||
Name = "Users cached",
|
||||
MeasurementUnit = Unit.Items,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static GaugeOptions MessagesStored => new()
|
||||
{
|
||||
Name = "Messages stored",
|
||||
MeasurementUnit = Unit.Items,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static TimerOptions MetricsCollectionTime => new()
|
||||
{
|
||||
Name = "Metrics collection time",
|
||||
MeasurementUnit = Unit.Events,
|
||||
DurationUnit = TimeUnit.Milliseconds,
|
||||
Context = "Bot"
|
||||
};
|
||||
|
||||
public static GaugeOptions ProcessPhysicalMemory => new()
|
||||
{
|
||||
Name = "Process physical memory",
|
||||
MeasurementUnit = Unit.Bytes,
|
||||
Context = "Process"
|
||||
};
|
||||
|
||||
public static GaugeOptions ProcessVirtualMemory => new()
|
||||
{
|
||||
Name = "Process virtual memory",
|
||||
MeasurementUnit = Unit.Bytes,
|
||||
Context = "Process"
|
||||
};
|
||||
|
||||
public static GaugeOptions ProcessPrivateMemory => new()
|
||||
{
|
||||
Name = "Process private memory",
|
||||
MeasurementUnit = Unit.Bytes,
|
||||
Context = "Process"
|
||||
};
|
||||
|
||||
public static GaugeOptions ProcessThreads => new()
|
||||
{
|
||||
Name = "Process thread count",
|
||||
MeasurementUnit = Unit.Threads,
|
||||
Context = "Process"
|
||||
};
|
||||
|
||||
public static GaugeOptions ProcessHandles => new()
|
||||
{
|
||||
Name = "Process handle count",
|
||||
MeasurementUnit = Unit.Items,
|
||||
Context = "Process"
|
||||
};
|
||||
}
|
||||
|
|
@ -8,13 +8,13 @@ public class Config
|
|||
public DatabaseConfig Database { get; init; } = new();
|
||||
public DiscordConfig Discord { get; init; } = new();
|
||||
public WebConfig Web { get; init; } = new();
|
||||
|
||||
|
||||
public class LoggingConfig
|
||||
{
|
||||
public LogEventLevel LogEventLevel { get; init; } = LogEventLevel.Debug;
|
||||
public bool LogQueries { get; init; } = false;
|
||||
}
|
||||
|
||||
|
||||
public class DatabaseConfig
|
||||
{
|
||||
public string Url { get; init; } = string.Empty;
|
||||
|
|
@ -37,7 +37,9 @@ public class Config
|
|||
{
|
||||
public string Host { get; init; } = "localhost";
|
||||
public int Port { get; init; } = 5000;
|
||||
public int? MetricsPort { get; init; }
|
||||
public string BaseUrl { get; init; } = null!;
|
||||
public string Address => $"http://{Host}:{Port}";
|
||||
public string MetricsAddress => $"http://{Host}:{MetricsPort ?? Port}";
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,8 @@ public static class StartupExtensions
|
|||
.AddScoped<MessageRepository>()
|
||||
.AddSingleton<WebhookExecutorService>()
|
||||
.AddSingleton<PkMessageHandler>()
|
||||
.AddSingleton(InMemoryDataService<Snowflake, ChannelCommandData>.Instance);
|
||||
.AddSingleton(InMemoryDataService<Snowflake, ChannelCommandData>.Instance)
|
||||
.AddHostedService<MetricsCollectionService>();
|
||||
|
||||
public static async Task Initialize(this WebApplication app)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using App.Metrics;
|
||||
using Catalogger.Backend.Bot.Commands;
|
||||
using Catalogger.Backend.Database;
|
||||
using Catalogger.Backend.Extensions;
|
||||
|
|
@ -49,6 +50,11 @@ builder.Host
|
|||
.AddInteractionGroup<ChannelCommandsComponents>()
|
||||
);
|
||||
|
||||
// Add metrics
|
||||
// TODO: add actual reporter
|
||||
var metricsBuilder = AppMetrics.CreateDefaultBuilder();
|
||||
builder.Services.AddSingleton<IMetrics>(metricsBuilder.Build());
|
||||
|
||||
builder.Services
|
||||
.AddDbContext<DatabaseContext>()
|
||||
.AddCustomServices()
|
||||
|
|
|
|||
61
Catalogger.Backend/Services/MetricsCollectionService.cs
Normal file
61
Catalogger.Backend/Services/MetricsCollectionService.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System.Diagnostics;
|
||||
using App.Metrics;
|
||||
using Catalogger.Backend.Cache;
|
||||
using Catalogger.Backend.Database;
|
||||
using Humanizer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime.Extensions;
|
||||
|
||||
namespace Catalogger.Backend.Services;
|
||||
|
||||
public class MetricsCollectionService(
|
||||
ILogger logger,
|
||||
GuildCacheService guildCache,
|
||||
ChannelCacheService channelCache,
|
||||
UserCacheService userCache,
|
||||
IMetrics metrics,
|
||||
IServiceProvider services) : BackgroundService
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<MetricsCollectionService>();
|
||||
|
||||
private async Task CollectMetricsAsync()
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
await using var scope = services.CreateAsyncScope();
|
||||
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||
|
||||
var messageCount = await db.Messages.CountAsync();
|
||||
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.GuildsCached, guildCache.Size);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ChannelsCached, channelCache.Size);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.UsersCached, userCache.Size);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.MessagesStored, messageCount);
|
||||
|
||||
var process = Process.GetCurrentProcess();
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ProcessPhysicalMemory, process.WorkingSet64);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ProcessVirtualMemory, process.VirtualMemorySize64);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ProcessPrivateMemory, process.PrivateMemorySize64);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ProcessThreads, process.Threads.Count);
|
||||
metrics.Measure.Gauge.SetValue(CataloggerMetrics.ProcessHandles, process.HandleCount);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
metrics.Measure.Timer.Time(CataloggerMetrics.MetricsCollectionTime, stopwatch.ElapsedMilliseconds);
|
||||
_logger.Information("Collected metrics in {Duration}", stopwatch.ElapsedDuration());
|
||||
|
||||
await Task.WhenAll(((IMetricsRoot)metrics).ReportRunner.RunAllAsync());
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
using var timer = new PeriodicTimer(1.Minutes());
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
_logger.Debug("Collecting periodic metrics");
|
||||
await CollectMetricsAsync();
|
||||
_logger.Debug("Reported metrics to backend");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue