Skip to content

Commit d667f3c

Browse files
committed
Added metrics (again)
1 parent c1483ba commit d667f3c

File tree

9 files changed

+138
-91
lines changed

9 files changed

+138
-91
lines changed

Hanekawa.Application/DependencyInjection.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Hanekawa.Application.Commands.Settings;
1+
using Hanekawa.Application.Handlers.Commands.Settings;
2+
using Hanekawa.Application.Interfaces;
23
using Hanekawa.Application.Interfaces.Commands;
34
using Hanekawa.Application.Interfaces.Services;
45
using Hanekawa.Application.Services;
@@ -25,6 +26,7 @@ public static IServiceCollection AddApplicationLayer(this IServiceCollection ser
2526
serviceCollection.AddSingleton(fontCollection);
2627

2728
serviceCollection.AddMetricFactory(new CollectorRegistry());
29+
serviceCollection.AddSingleton<IMetrics, Metrics>();
2830

2931
return serviceCollection;
3032
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Hanekawa.Application.Interfaces;
2+
3+
public interface IMetrics
4+
{
5+
public TrackedDuration All<T>(ulong? guildId = null);
6+
7+
public TrackedDuration All(string name, ulong? guildId = null);
8+
9+
public void IncrementCounter<T>(ulong? guildId = null);
10+
public void IncrementCounter(string name, ulong? guildId = null);
11+
public TrackedDuration MeasureDuration<T>(ulong? guildId = null);
12+
public TrackedDuration MeasureDuration(string name, ulong? guildId = null);
13+
}

Hanekawa.Application/Metrics.cs

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,71 @@
11
using System.Diagnostics.Metrics;
2+
using Hanekawa.Application.Interfaces;
23

34
namespace Hanekawa.Application;
45

5-
public class Metrics<T> where T : class
6+
public class Metrics : IMetrics
67
{
7-
private readonly Counter<long> _counter;
8-
private readonly Histogram<double> _histogram;
8+
private readonly Dictionary<string, MetricCollection> _metrics = new();
9+
private readonly IMeterFactory _meterFactory;
910

1011
public Metrics(IMeterFactory meterFactory)
12+
=> _meterFactory = meterFactory;
13+
14+
15+
/// <inheritdoc />
16+
public TrackedDuration All<T>(ulong? guildId = null) => All(nameof(T), guildId);
17+
18+
/// <inheritdoc />
19+
public TrackedDuration All(string name, ulong? guildId = null)
1120
{
12-
var meter = meterFactory.Create(nameof(T));
21+
if(!_metrics.TryGetValue(name, out var metric)) metric = CreateMetric(name, guildId);
22+
metric.GlobalCounter.Add(1);
23+
if(guildId is not null) metric.GuildCounter[guildId.Value].Add(1);
24+
return new (TimeProvider.System, metric.Histogram);
25+
}
26+
27+
/// <inheritdoc />
28+
public void IncrementCounter<T>(ulong? guildId = null) => IncrementCounter(nameof(T));
1329

14-
_counter = meter.CreateCounter<long>($"hanekawa.{nameof(T)}.request.counter");
15-
_histogram = meter.CreateHistogram<double>($"hanekawa.{nameof(T)}.request.duration");
30+
/// <inheritdoc />
31+
public void IncrementCounter(string name, ulong? guildId = null)
32+
{
33+
if(_metrics.TryGetValue(name, out var metric))
34+
{
35+
metric.GlobalCounter.Add(1);
36+
return;
37+
}
38+
metric = CreateMetric(name);
39+
metric.GlobalCounter.Add(1);
40+
}
41+
42+
/// <inheritdoc />
43+
public TrackedDuration MeasureDuration<T>(ulong? guildId = null) => MeasureDuration(nameof(T));
44+
45+
/// <inheritdoc />
46+
public TrackedDuration MeasureDuration(string name, ulong? guildId = null)
47+
{
48+
if (_metrics.TryGetValue(name, out var metric))
49+
return new(TimeProvider.System, metric.Histogram);
50+
var collection = CreateMetric(name);
51+
return new(TimeProvider.System, collection.Histogram);
52+
}
53+
54+
private MetricCollection CreateMetric(string name, ulong? guildId = null)
55+
{
56+
var meter = _meterFactory.Create($"hanekawa.{name}");
57+
var counter = meter.CreateCounter<long>($"hanekawa.{name}.request.counter");
58+
var guildCounter = new Dictionary<ulong, Counter<long>>();
59+
if (guildId != null)
60+
{
61+
guildCounter.Add(guildId.Value,
62+
meter.CreateCounter<long>($"hanekawa.{name}-{guildId.Value}.request.counter"));
63+
}
64+
var histogram = meter.CreateHistogram<double>($"hanekawa.{name}.request.duration");
65+
var collection = new MetricCollection(counter, guildCounter, histogram);
66+
_metrics.TryAdd(name, collection);
67+
return collection;
1668
}
17-
18-
public void IncrementCounter() => _counter.Add(1);
19-
public TrackedDuration MeasureDuration() => new(TimeProvider.System, _histogram);
2069
}
2170

2271
public class TrackedDuration : IDisposable
@@ -37,4 +86,18 @@ public void Dispose()
3786
var elapsed = _timeProvider.GetElapsedTime(_requestStartTime);
3887
_histogram.Record(elapsed.TotalMilliseconds);
3988
}
89+
}
90+
91+
internal class MetricCollection
92+
{
93+
public MetricCollection(Counter<long> globalCounter, Dictionary<ulong, Counter<long>> guildCounter, Histogram<double> histogram)
94+
{
95+
GlobalCounter = globalCounter;
96+
GuildCounter = guildCounter;
97+
Histogram = histogram;
98+
}
99+
100+
public Counter<long> GlobalCounter { get; }
101+
public Dictionary<ulong, Counter<long>> GuildCounter { get; }
102+
public Histogram<double> Histogram { get; }
40103
}

Hanekawa.Application/Pipelines/MetricPipeline.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
using System.Diagnostics;
2+
using Hanekawa.Application.Interfaces;
23
using MediatR;
34
using Microsoft.Extensions.Logging;
4-
using Prometheus.Client;
5-
using IMetric = Hanekawa.Application.Interfaces.IMetric;
65

76
namespace Hanekawa.Application.Pipelines;
87

98
public class MetricPipeline<TRequest, TResult> : IPipelineBehavior<IMetric, TResult> where TRequest : notnull
109
{
1110
private readonly ILogger<MetricPipeline<TRequest, TResult>> _logger;
12-
private readonly IMetricFactory _metricFactory;
11+
private readonly Metrics _metrics;
1312

14-
public MetricPipeline(ILogger<MetricPipeline<TRequest, TResult>> logger, IMetricFactory metricFactory)
13+
public MetricPipeline(ILogger<MetricPipeline<TRequest, TResult>> logger, Metrics metrics)
1514
{
1615
_logger = logger;
17-
_metricFactory = metricFactory;
16+
_metrics = metrics;
1817
}
1918

2019
public async Task<TResult> Handle(IMetric request, RequestHandlerDelegate<TResult> next, CancellationToken cancellationToken)
2120
{
2221
var type = request.GetType();
23-
_metricFactory.CreateCounter(type.Name, "Global events", "Request").Inc();
24-
_metricFactory.CreateCounter(request.GuildId + "-" + type.Name, "Guild specific events",
25-
$"{request.GuildId}", type.Name, "Request").Inc();
22+
_metrics.IncrementCounter(type.Name);
23+
using var _ = _metrics.MeasureDuration(type.Name);
2624
var start = Stopwatch.GetTimestamp();
27-
var response = await next();
25+
var response = await next().ConfigureAwait(false);
2826
var elapsedTime = Stopwatch.GetElapsedTime(start);
29-
3027
_logger.LogInformation("Request {Request} executed in {Elapsed}ms", nameof(request), elapsedTime);
3128
return response;
3229
}

Hanekawa.Bot/Commands/Slash/Administration/AdministrationCommands.cs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
using Disqord.Gateway;
66
using Disqord.Rest;
77
using Hanekawa.Application;
8-
using Hanekawa.Application.Commands.Administration;
9-
using Hanekawa.Application.Handlers.Warnings;
8+
using Hanekawa.Application.Handlers.Commands.Administration;
9+
using Hanekawa.Application.Handlers.Services.Warnings;
1010
using Hanekawa.Bot.Mapper;
1111
using MediatR;
1212
using Qmmands;
@@ -17,21 +17,18 @@ namespace Hanekawa.Bot.Commands.Slash.Administration;
1717
[Description("Administration commands")]
1818
public class AdministrationCommands : DiscordApplicationGuildModuleBase
1919
{
20-
private readonly Metrics<AdministrationCommands> _metrics;
20+
private readonly Metrics _metrics;
2121

22-
public AdministrationCommands(Metrics<AdministrationCommands> metrics)
23-
{
24-
_metrics = metrics;
25-
}
22+
public AdministrationCommands(Metrics metrics)
23+
=> _metrics = metrics;
2624

2725
[SlashCommand("ban")]
2826
[RequireBotPermissions(Permissions.BanMembers)]
2927
[RequireAuthorPermissions(Permissions.BanMembers)]
3028
[Description("Bans a user from the server")]
3129
public async Task<DiscordInteractionResponseCommandResult> BanAsync(AutoComplete<IMember> member, string reason)
3230
{
33-
_metrics.IncrementCounter();
34-
using var _ = _metrics.MeasureDuration();
31+
using var _ = _metrics.All<AdministrationCommands>();
3532
var user = member.Argument.Value;
3633
var result = await Bot.Services.GetRequiredService<AdministrationCommandService>()
3734
.BanUserAsync(user.ToDiscordMember(), Context.AuthorId, reason);
@@ -44,8 +41,7 @@ public async Task<DiscordInteractionResponseCommandResult> BanAsync(AutoComplete
4441
[Description("Unbans a user from the server")]
4542
public async Task<DiscordInteractionResponseCommandResult> UnbanAsync(AutoComplete<IMember> member, string reason)
4643
{
47-
_metrics.IncrementCounter();
48-
using var _ = _metrics.MeasureDuration();
44+
using var _ = _metrics.All<AdministrationCommands>();
4945
var user = member.Argument.Value;
5046
var result = await Bot.Services.GetRequiredService<AdministrationCommandService>()
5147
.UnbanUserAsync(user.ToGuild(),user.Id, Context.AuthorId.RawValue, reason);
@@ -58,8 +54,7 @@ public async Task<DiscordInteractionResponseCommandResult> UnbanAsync(AutoComple
5854
[Description("Kick a user from the server")]
5955
public async Task<DiscordInteractionResponseCommandResult> KickAsync(AutoComplete<IMember> member, string reason)
6056
{
61-
_metrics.IncrementCounter();
62-
using var _ = _metrics.MeasureDuration();
57+
using var _ = _metrics.All<AdministrationCommands>();
6358
var user = member.Argument.Value;
6459
var result = await Bot.Services.GetRequiredService<AdministrationCommandService>()
6560
.KickUserAsync(user.ToDiscordMember(), Context.AuthorId, reason);
@@ -73,8 +68,7 @@ public async Task<DiscordInteractionResponseCommandResult> KickAsync(AutoComplet
7368
public async Task<DiscordInteractionResponseCommandResult> MuteAsync(AutoComplete<IMember> member,
7469
TimeSpan duration, string reason)
7570
{
76-
_metrics.IncrementCounter();
77-
using var _ = _metrics.MeasureDuration();
71+
using var _ = _metrics.All<AdministrationCommands>();
7872
var user = member.Argument.Value;
7973
var result = await Bot.Services.GetRequiredService<AdministrationCommandService>()
8074
.MuteUserAsync(user.ToDiscordMember(), Context.AuthorId, reason, duration);
@@ -87,8 +81,7 @@ public async Task<DiscordInteractionResponseCommandResult> MuteAsync(AutoComplet
8781
[Description("Un-mutes a user in the server")]
8882
public async Task<DiscordInteractionResponseCommandResult> UnmuteAsync(AutoComplete<IMember> member, string reason)
8983
{
90-
_metrics.IncrementCounter();
91-
using var _ = _metrics.MeasureDuration();
84+
using var _ = _metrics.All<AdministrationCommands>();
9285
var user = member.Argument.Value;
9386
var result = await Bot.Services.GetRequiredService<AdministrationCommandService>()
9487
.UnmuteUserAsync(user.ToDiscordMember(), Context.AuthorId, reason);
@@ -100,8 +93,7 @@ public async Task<DiscordInteractionResponseCommandResult> UnmuteAsync(AutoCompl
10093
[Description("Warns a user in the server")]
10194
public async Task<DiscordInteractionResponseCommandResult> WarnAsync(AutoComplete<IMember> member, string reason)
10295
{
103-
_metrics.IncrementCounter();
104-
using var _ = _metrics.MeasureDuration();
96+
using var _ = _metrics.All<AdministrationCommands>();
10597
var user = member.Argument.Value;
10698
var response = await Bot.Services.GetRequiredService<IMediator>()
10799
.Send(new WarningReceived(user.ToDiscordMember(), reason, Context.AuthorId));
@@ -113,8 +105,7 @@ public async Task<DiscordInteractionResponseCommandResult> WarnAsync(AutoComplet
113105
[Description("Voids a warn from a user in the server")]
114106
public async Task<DiscordInteractionResponseCommandResult> VoidWarnAsync(AutoComplete<IMember> member)
115107
{
116-
_metrics.IncrementCounter();
117-
using var _ = _metrics.MeasureDuration();
108+
using var _ = _metrics.All<AdministrationCommands>();
118109
var user = member.Argument.Value;
119110
var response = await Bot.Services.GetRequiredService<IMediator>()
120111
.Send(new WarningClear(user.ToDiscordMember(), Context.AuthorId, "Voided by moderator"));
@@ -126,8 +117,7 @@ public async Task<DiscordInteractionResponseCommandResult> VoidWarnAsync(AutoCom
126117
[Description("List all warnings from a user in the server")]
127118
public async Task<DiscordInteractionResponseCommandResult> WarnsAsync(AutoComplete<IMember> member)
128119
{
129-
_metrics.IncrementCounter();
130-
using var _ = _metrics.MeasureDuration();
120+
using var _ = _metrics.All<AdministrationCommands>();
131121
var user = member.Argument.Value;
132122
var response = await Bot.Services.GetRequiredService<IMediator>()
133123
.Send(new WarningList(user.GuildId, user.Id));
@@ -139,8 +129,7 @@ public async Task<DiscordInteractionResponseCommandResult> WarnsAsync(AutoComple
139129
[Description("Clears all warnings from a user in the server")]
140130
public async Task<DiscordInteractionResponseCommandResult> ClearWarnsAsync(AutoComplete<IMember> member)
141131
{
142-
_metrics.IncrementCounter();
143-
using var _ = _metrics.MeasureDuration();
132+
using var _ = _metrics.All<AdministrationCommands>();
144133
var user = member.Argument.Value;
145134
var response = await Bot.Services.GetRequiredService<IMediator>()
146135
.Send(new WarningClear(user.ToDiscordMember(), Context.AuthorId,
@@ -154,8 +143,7 @@ public async Task<DiscordInteractionResponseCommandResult> ClearWarnsAsync(AutoC
154143
[Description("Prunes a number of messages from a channel")]
155144
public async Task<DiscordInteractionResponseCommandResult> Prune(int messageCount = 100)
156145
{
157-
_metrics.IncrementCounter();
158-
using var _ = _metrics.MeasureDuration();
146+
using var _ = _metrics.All<AdministrationCommands>();
159147
var channel = Bot.GetChannel(Context.GuildId, Context.ChannelId) as ITextChannel;
160148
var messagesAsync = await channel.FetchMessagesAsync(messageCount);
161149
var messageIds = new ulong[messagesAsync.Count];

Hanekawa.Bot/Commands/Slash/Club/ClubCommands.cs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@ namespace Hanekawa.Bot.Commands.Slash.Club;
1212
[SlashGroup("club")]
1313
public class ClubCommands : DiscordApplicationGuildModuleBase
1414
{
15-
private readonly Metrics<ClubCommands> _metrics;
15+
private readonly Metrics _metrics;
1616

17-
public ClubCommands(Metrics<ClubCommands> metrics)
17+
public ClubCommands(Metrics metrics)
1818
=> _metrics = metrics;
1919

2020
[SlashCommand("create")]
2121
[Description("Create a club")]
2222
public async Task<DiscordInteractionResponseCommandResult> Create(string name, string description)
2323
{
24-
using var _ = _metrics.MeasureDuration();
25-
_metrics.IncrementCounter();
24+
using var _ = _metrics.All<ClubCommands>();
2625
await using var scope = Bot.Services.CreateAsyncScope();
2726
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
2827
var response = await service.Create(Context.GuildId, name, description, Context.AuthorId);
@@ -34,6 +33,7 @@ public async Task<DiscordInteractionResponseCommandResult> Create(string name, s
3433
[RequireAuthorPermissions(Permissions.ManageGuild)]
3534
public async Task<DiscordInteractionResponseCommandResult> Delete(string name)
3635
{
36+
using var _ = _metrics.All<ClubCommands>();
3737
await using var scope = Bot.Services.CreateAsyncScope();
3838
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
3939
var response = await service.Delete(Context.GuildId, name, Context.AuthorId);
@@ -44,8 +44,7 @@ public async Task<DiscordInteractionResponseCommandResult> Delete(string name)
4444
[Description("List all clubs")]
4545
public async Task<DiscordInteractionResponseCommandResult> List()
4646
{
47-
using var _ = _metrics.MeasureDuration();
48-
_metrics.IncrementCounter();
47+
using var _ = _metrics.All<ClubCommands>();
4948
await using var scope = Bot.Services.CreateAsyncScope();
5049
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
5150
var response = await service.List(Context.GuildId);
@@ -56,8 +55,7 @@ public async Task<DiscordInteractionResponseCommandResult> List()
5655
[Description("Join a club")]
5756
public async Task<DiscordInteractionResponseCommandResult> Join(string name)
5857
{
59-
using var _ = _metrics.MeasureDuration();
60-
_metrics.IncrementCounter();
58+
using var _ = _metrics.All<ClubCommands>();
6159
await using var scope = Bot.Services.CreateAsyncScope();
6260
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
6361
var response = await service.Join(Context.GuildId, name, Context.AuthorId);
@@ -68,8 +66,7 @@ public async Task<DiscordInteractionResponseCommandResult> Join(string name)
6866
[Description("Leave a club")]
6967
public async Task<DiscordInteractionResponseCommandResult> Leave(string name)
7068
{
71-
using var _ = _metrics.MeasureDuration();
72-
_metrics.IncrementCounter();
69+
using var _ = _metrics.All<ClubCommands>();
7370
await using var scope = Bot.Services.CreateAsyncScope();
7471
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
7572
var response = await service.Leave(Context.GuildId, name, Context.AuthorId);
@@ -80,8 +77,7 @@ public async Task<DiscordInteractionResponseCommandResult> Leave(string name)
8077
[Description("Get club info")]
8178
public async Task<DiscordInteractionResponseCommandResult> Info(string name)
8279
{
83-
using var _ = _metrics.MeasureDuration();
84-
_metrics.IncrementCounter();
80+
using var _ = _metrics.All<ClubCommands>();
8581
await using var scope = Bot.Services.CreateAsyncScope();
8682
var service = scope.ServiceProvider.GetRequiredService<IClubCommandService>();
8783
var response = await service.Info(Context.GuildId, name);

0 commit comments

Comments
 (0)