From e4add3c1cf14633c239f139d0191584d9a2ee041 Mon Sep 17 00:00:00 2001 From: Kristoffer T Date: Sat, 30 Dec 2023 00:55:26 +0100 Subject: [PATCH] Added metrics to commands --- Hanekawa.Application/Metrics.cs | 40 +++++++++++++++ .../Administration/AdministrationCommands.cs | 49 ++++++++++++++----- .../Commands/Slash/Club/ClubCommands.cs | 16 ++++++ .../Commands/Slash/Setting/GreetCommands.cs | 18 +++++++ .../Commands/Slash/Setting/LevelCommands.cs | 14 ++++++ Hanekawa.Bot/Program.cs | 15 +++++- 6 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 Hanekawa.Application/Metrics.cs diff --git a/Hanekawa.Application/Metrics.cs b/Hanekawa.Application/Metrics.cs new file mode 100644 index 00000000..319c5c52 --- /dev/null +++ b/Hanekawa.Application/Metrics.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Metrics; + +namespace Hanekawa.Application; + +public class Metrics where T : class +{ + private readonly Counter _counter; + private readonly Histogram _histogram; + + public Metrics(IMeterFactory meterFactory) + { + var meter = meterFactory.Create(nameof(T)); + + _counter = meter.CreateCounter($"hanekawa.{nameof(T)}.request.counter"); + _histogram = meter.CreateHistogram($"hanekawa.{nameof(T)}.request.duration"); + } + + public void IncrementCounter() => _counter.Add(1); + public TrackedDuration MeasureDuration() => new(TimeProvider.System, _histogram); +} + +public class TrackedDuration : IDisposable +{ + private readonly long _requestStartTime; + private readonly Histogram _histogram; + private readonly TimeProvider _timeProvider; + + public TrackedDuration(TimeProvider timeProvider, Histogram histogram) + { + _requestStartTime = timeProvider.GetTimestamp(); + _timeProvider = timeProvider; + _histogram = histogram; + } + + public void Dispose() + { + var elapsed = _timeProvider.GetElapsedTime(_requestStartTime); + _histogram.Record(elapsed.TotalMilliseconds); + } +} \ No newline at end of file diff --git a/Hanekawa.Bot/Commands/Slash/Administration/AdministrationCommands.cs b/Hanekawa.Bot/Commands/Slash/Administration/AdministrationCommands.cs index e3b53c1a..78d33db0 100644 --- a/Hanekawa.Bot/Commands/Slash/Administration/AdministrationCommands.cs +++ b/Hanekawa.Bot/Commands/Slash/Administration/AdministrationCommands.cs @@ -4,6 +4,7 @@ using Disqord.Bot.Commands.Interaction; using Disqord.Gateway; using Disqord.Rest; +using Hanekawa.Application; using Hanekawa.Application.Commands.Administration; using Hanekawa.Application.Handlers.Warnings; using Hanekawa.Bot.Mapper; @@ -16,8 +17,12 @@ namespace Hanekawa.Bot.Commands.Slash.Administration; [Description("Administration commands")] public class AdministrationCommands : DiscordApplicationGuildModuleBase { - private readonly IServiceProvider _serviceProvider; - public AdministrationCommands(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + private readonly Metrics _metrics; + + public AdministrationCommands(Metrics metrics) + { + _metrics = metrics; + } [SlashCommand("ban")] [RequireBotPermissions(Permissions.BanMembers)] @@ -25,8 +30,10 @@ public class AdministrationCommands : DiscordApplicationGuildModuleBase [Description("Bans a user from the server")] public async Task BanAsync(AutoComplete member, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var result = await _serviceProvider.GetRequiredService() + var result = await Bot.Services.GetRequiredService() .BanUserAsync(user.ToDiscordMember(), Context.AuthorId, reason); return Response(result.ToLocalInteractionMessageResponse()); } @@ -37,8 +44,10 @@ public async Task BanAsync(AutoComplete [Description("Unbans a user from the server")] public async Task UnbanAsync(AutoComplete member, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var result = await _serviceProvider.GetRequiredService() + var result = await Bot.Services.GetRequiredService() .UnbanUserAsync(user.ToGuild(),user.Id, Context.AuthorId.RawValue, reason); return Response(result.ToLocalInteractionMessageResponse()); } @@ -49,8 +58,10 @@ public async Task UnbanAsync(AutoComple [Description("Kick a user from the server")] public async Task KickAsync(AutoComplete member, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var result = await _serviceProvider.GetRequiredService() + var result = await Bot.Services.GetRequiredService() .KickUserAsync(user.ToDiscordMember(), Context.AuthorId, reason); return Response(result.ToLocalInteractionMessageResponse()); } @@ -62,8 +73,10 @@ public async Task KickAsync(AutoComplet public async Task MuteAsync(AutoComplete member, TimeSpan duration, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var result = await _serviceProvider.GetRequiredService() + var result = await Bot.Services.GetRequiredService() .MuteUserAsync(user.ToDiscordMember(), Context.AuthorId, reason, duration); return Response(result.ToLocalInteractionMessageResponse()); } @@ -74,8 +87,10 @@ public async Task MuteAsync(AutoComplet [Description("Un-mutes a user in the server")] public async Task UnmuteAsync(AutoComplete member, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var result = await _serviceProvider.GetRequiredService() + var result = await Bot.Services.GetRequiredService() .UnmuteUserAsync(user.ToDiscordMember(), Context.AuthorId, reason); return Response(result.ToLocalInteractionMessageResponse()); } @@ -85,8 +100,10 @@ public async Task UnmuteAsync(AutoCompl [Description("Warns a user in the server")] public async Task WarnAsync(AutoComplete member, string reason) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var response = await _serviceProvider.GetRequiredService() + var response = await Bot.Services.GetRequiredService() .Send(new WarningReceived(user.ToDiscordMember(), reason, Context.AuthorId)); return Response(response.ToLocalInteractionMessageResponse()); } @@ -96,8 +113,10 @@ public async Task WarnAsync(AutoComplet [Description("Voids a warn from a user in the server")] public async Task VoidWarnAsync(AutoComplete member) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var response = await _serviceProvider.GetRequiredService() + var response = await Bot.Services.GetRequiredService() .Send(new WarningClear(user.ToDiscordMember(), Context.AuthorId, "Voided by moderator")); return Response(response.ToLocalInteractionMessageResponse()); } @@ -107,8 +126,10 @@ public async Task VoidWarnAsync(AutoCom [Description("List all warnings from a user in the server")] public async Task WarnsAsync(AutoComplete member) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var response = await _serviceProvider.GetRequiredService() + var response = await Bot.Services.GetRequiredService() .Send(new WarningList(user.GuildId, user.Id)); return Response(response.ToLocalInteractionMessageResponse()); } @@ -118,8 +139,10 @@ public async Task WarnsAsync(AutoComple [Description("Clears all warnings from a user in the server")] public async Task ClearWarnsAsync(AutoComplete member) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var user = member.Argument.Value; - var response = await _serviceProvider.GetRequiredService() + var response = await Bot.Services.GetRequiredService() .Send(new WarningClear(user.ToDiscordMember(), Context.AuthorId, "Cleared by moderator", true)); return Response(response.ToLocalInteractionMessageResponse()); @@ -131,6 +154,8 @@ public async Task ClearWarnsAsync(AutoC [Description("Prunes a number of messages from a channel")] public async Task Prune(int messageCount = 100) { + _metrics.IncrementCounter(); + using var _ = _metrics.MeasureDuration(); var channel = Bot.GetChannel(Context.GuildId, Context.ChannelId) as ITextChannel; var messagesAsync = await channel.FetchMessagesAsync(messageCount); var messageIds = new ulong[messagesAsync.Count]; @@ -142,7 +167,7 @@ public async Task Prune(int messageCoun messageIds[i] = msg.Id.RawValue; } - var result= await _serviceProvider.GetRequiredService() + var result= await Bot.Services.GetRequiredService() .PruneAsync(Context.GuildId, Context.ChannelId, messageIds, Context.AuthorId, ""); diff --git a/Hanekawa.Bot/Commands/Slash/Club/ClubCommands.cs b/Hanekawa.Bot/Commands/Slash/Club/ClubCommands.cs index dfc90993..7fffb3fd 100644 --- a/Hanekawa.Bot/Commands/Slash/Club/ClubCommands.cs +++ b/Hanekawa.Bot/Commands/Slash/Club/ClubCommands.cs @@ -2,6 +2,7 @@ using Disqord.Bot.Commands; using Disqord.Bot.Commands.Application; using Disqord.Bot.Commands.Interaction; +using Hanekawa.Application; using Hanekawa.Application.Interfaces.Commands; using Hanekawa.Bot.Mapper; using Qmmands; @@ -11,10 +12,17 @@ namespace Hanekawa.Bot.Commands.Slash.Club; [SlashGroup("club")] public class ClubCommands : DiscordApplicationGuildModuleBase { + private readonly Metrics _metrics; + + public ClubCommands(Metrics metrics) + => _metrics = metrics; + [SlashCommand("create")] [Description("Create a club")] public async Task Create(string name, string description) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.Create(Context.GuildId, name, description, Context.AuthorId); @@ -36,6 +44,8 @@ public async Task Delete(string name) [Description("List all clubs")] public async Task List() { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.List(Context.GuildId); @@ -46,6 +56,8 @@ public async Task List() [Description("Join a club")] public async Task Join(string name) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.Join(Context.GuildId, name, Context.AuthorId); @@ -56,6 +68,8 @@ public async Task Join(string name) [Description("Leave a club")] public async Task Leave(string name) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.Leave(Context.GuildId, name, Context.AuthorId); @@ -66,6 +80,8 @@ public async Task Leave(string name) [Description("Get club info")] public async Task Info(string name) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.Info(Context.GuildId, name); diff --git a/Hanekawa.Bot/Commands/Slash/Setting/GreetCommands.cs b/Hanekawa.Bot/Commands/Slash/Setting/GreetCommands.cs index ced9f159..06687eb4 100644 --- a/Hanekawa.Bot/Commands/Slash/Setting/GreetCommands.cs +++ b/Hanekawa.Bot/Commands/Slash/Setting/GreetCommands.cs @@ -4,6 +4,7 @@ using Disqord.Bot.Commands.Interaction; using Disqord.Extensions.Interactivity.Menus.Paged; using Disqord.Gateway; +using Hanekawa.Application; using Hanekawa.Application.Interfaces.Commands; using Hanekawa.Bot.Mapper; using Hanekawa.Entities.Configs; @@ -17,10 +18,17 @@ namespace Hanekawa.Bot.Commands.Slash.Setting; [RequireAuthorPermissions(Permissions.ManageChannels)] public class GreetCommands : DiscordApplicationGuildModuleBase { + private readonly Metrics _metrics; + + public GreetCommands(Metrics metrics) + => _metrics = metrics; + [SlashCommand("channel")] [Description("Set the greet channel")] public async Task Set(IChannel channel) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); if (channel is not TransientInteractionChannel { Type: ChannelType.Text } textChannel) return Response(Localization.ChannelMustBeTextChannel); await using var scope = Bot.Services.CreateAsyncScope(); @@ -33,6 +41,8 @@ public async Task Set(IChannel channel) [Description("Set the greet message")] public async Task Set(string message) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.SetMessage(Context.GuildId, message); @@ -43,6 +53,8 @@ public async Task Set(string message) [Description("Set the greet image url")] public async Task SetImage(string url) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.SetImage(Context.GuildId, url, Context.AuthorId); @@ -53,6 +65,8 @@ public async Task SetImage(string url) [Description("List the greet images")] public async Task ListImages() { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.ListImages(Context.GuildId); @@ -76,6 +90,8 @@ public async Task ListImages() [Description("Remove the greet image")] public async Task RemoveImage(int id) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.RemoveImage(Context.GuildId, id); @@ -88,6 +104,8 @@ public async Task RemoveImage(int id) [Description("Toggle the greet image")] public async Task ToggleImage() { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.ToggleImage(Context.GuildId); diff --git a/Hanekawa.Bot/Commands/Slash/Setting/LevelCommands.cs b/Hanekawa.Bot/Commands/Slash/Setting/LevelCommands.cs index dd9d0555..767084f0 100644 --- a/Hanekawa.Bot/Commands/Slash/Setting/LevelCommands.cs +++ b/Hanekawa.Bot/Commands/Slash/Setting/LevelCommands.cs @@ -5,6 +5,7 @@ using Disqord.Bot.Commands.Interaction; using Disqord.Extensions.Interactivity.Menus.Paged; using Disqord.Gateway; +using Hanekawa.Application; using Hanekawa.Application.Interfaces.Commands; using Hanekawa.Entities.Levels; using Hanekawa.Localize; @@ -16,10 +17,17 @@ namespace Hanekawa.Bot.Commands.Slash.Setting; [RequireAuthorPermissions(Permissions.ManageGuild)] public class LevelCommands : DiscordApplicationGuildModuleBase { + private readonly Metrics _metrics; + + public LevelCommands(Metrics metrics) + => _metrics = metrics; + [SlashCommand("add")] [Description("Add a level role")] public async Task Add(int level, IRole role) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); await service.AddAsync(Context.GuildId, role.Id, level, Context.CancellationToken); @@ -30,6 +38,8 @@ public async Task Add(int level, IRole [Description("Remove a level role")] public async Task Remove(IRole role) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); await service.RemoveAsync(Context.GuildId, role.Id, Context.CancellationToken); @@ -40,6 +50,8 @@ public async Task Remove(IRole role) [Description("List all level roles")] public async Task List() { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); var response = await service.ListAsync(Context.GuildId, Context.CancellationToken); @@ -51,6 +63,8 @@ public async Task List() [Description("Modify a level role")] public async Task Modify(int level, IRole role) { + using var _ = _metrics.MeasureDuration(); + _metrics.IncrementCounter(); await using var scope = Bot.Services.CreateAsyncScope(); var service = scope.ServiceProvider.GetRequiredService(); await service.ModifyAsync(Context.GuildId, role.Id, level, Context.CancellationToken); diff --git a/Hanekawa.Bot/Program.cs b/Hanekawa.Bot/Program.cs index f272d955..e65f8404 100644 --- a/Hanekawa.Bot/Program.cs +++ b/Hanekawa.Bot/Program.cs @@ -3,6 +3,9 @@ using Disqord.Gateway; using Hanekawa.Application; using Hanekawa.Application.Interfaces; +using Hanekawa.Bot.Commands.Slash.Administration; +using Hanekawa.Bot.Commands.Slash.Club; +using Hanekawa.Bot.Commands.Slash.Setting; using Hanekawa.Bot.Services.Grpc; using Hanekawa.Infrastructure; using Serilog; @@ -10,8 +13,14 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(); -builder.Services.AddInfrastructureLayer(builder.Configuration); + +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(); +builder.Services.AddSingleton>(); + builder.Services.AddApplicationLayer(); +builder.Services.AddInfrastructureLayer(builder.Configuration); builder.Host.ConfigureDiscordBot((_, bot) => { @@ -19,8 +28,9 @@ bot.ApplicationId = new Snowflake(ulong.Parse(builder.Configuration["applicationId"]!)); bot.UseMentionPrefix = true; bot.ReadyEventDelayMode = ReadyEventDelayMode.Guilds; - bot.Intents |= GatewayIntents.Unprivileged | GatewayIntents.Members; + bot.Intents |= GatewayIntents.All; }); + builder.Host.UseDefaultServiceProvider(x => { x.ValidateScopes = true; @@ -30,6 +40,7 @@ Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); + builder.Logging.ClearProviders(); builder.Host.UseSerilog();