Add basic WIP metrics system

This commit is contained in:
Ske 2019-07-16 21:59:06 +02:00
parent a2040f959d
commit 2d58705e85
6 changed files with 90 additions and 9 deletions

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using App.Metrics;
using Dapper;
using Discord;
using Discord.Commands;
@ -16,6 +17,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NodaTime;
using Npgsql;
using Npgsql.Logging;
using Sentry;
using Sentry.Extensibility;
@ -92,6 +94,8 @@ namespace PluralKit.Bot
.AddTransient<MessageStore>()
.AddTransient<SwitchStore>()
.AddSingleton<IMetrics>(_ => AppMetrics.CreateDefaultBuilder().Build())
.BuildServiceProvider();
}
class Bot
@ -101,13 +105,15 @@ namespace PluralKit.Bot
private CommandService _commands;
private ProxyService _proxy;
private Timer _updateTimer;
private IMetrics _metrics;
public Bot(IServiceProvider services, IDiscordClient client, CommandService commands, ProxyService proxy)
public Bot(IServiceProvider services, IDiscordClient client, CommandService commands, ProxyService proxy, IMetrics metrics)
{
this._services = services;
this._client = client as DiscordShardedClient;
this._commands = commands;
this._proxy = proxy;
_metrics = metrics;
}
public async Task Init()
@ -142,6 +148,8 @@ namespace PluralKit.Bot
private async Task CommandExecuted(Optional<CommandInfo> cmd, ICommandContext ctx, IResult _result)
{
_metrics.Measure.Meter.Mark(BotMetrics.CommandsRun);
// TODO: refactor this entire block, it's fugly.
if (!_result.IsSuccess) {
if (_result.Error == CommandError.Unsuccessful || _result.Error == CommandError.Exception) {
@ -168,6 +176,8 @@ namespace PluralKit.Bot
private async Task MessageReceived(SocketMessage _arg)
{
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
// _client.CurrentUser will be null if we've connected *some* shards but not shard #0 yet
// This will cause an error in WebhookCacheServices so we just workaround and don't process any messages
// until we properly connect. TODO: can we do this without chucking away a bunch of messages?

View File

@ -0,0 +1,16 @@
using App.Metrics;
using App.Metrics.Gauge;
using App.Metrics.Meter;
namespace PluralKit.Bot
{
public static class BotMetrics
{
public static MeterOptions MessagesReceived => new MeterOptions {Name = "Messages processed", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot"};
public static MeterOptions MessagesProxied => new MeterOptions {Name = "Messages proxied", MeasurementUnit = Unit.Events, RateUnit = TimeUnit.Seconds, Context = "Bot"};
public static MeterOptions CommandsRun => new MeterOptions {Name = "Commands run", MeasurementUnit = Unit.Commands, RateUnit = TimeUnit.Seconds, Context = "Bot"};
public static GaugeOptions MembersOnline => new GaugeOptions {Name = "Members online", MeasurementUnit = Unit.None, Context = "Bot"};
public static GaugeOptions DatabasePoolSize => new GaugeOptions { Name = "Database pool size", Context = "Database" };
}
}

View File

@ -1,10 +1,14 @@
using System.Linq;
using System.Threading.Tasks;
using App.Metrics;
using App.Metrics.Meter;
using Discord;
using Discord.Commands;
namespace PluralKit.Bot.Commands {
public class MiscCommands: ModuleBase<PKCommandContext> {
public BotConfig BotConfig { get; set; }
public IMetrics Metrics { get; set; }
[Command("invite")]
[Alias("inv")]
@ -32,5 +36,21 @@ namespace PluralKit.Bot.Commands {
[Command("freeze")] public Task Freeze() => Context.Channel.SendMessageAsync("*A giant crystal ball of ice is charged and hurled toward your opponent, bursting open and freezing them solid on contact.*");
[Command("starstorm")] public Task Starstorm() => Context.Channel.SendMessageAsync("*Vibrant colours burst forth from the sky as meteors rain down upon your opponent.*");
[Command("stats")]
public async Task Stats()
{
var messagesReceived = Metrics.Snapshot.GetForContext("Bot").Meters.First(m => m.MultidimensionalName == BotMetrics.MessagesReceived.Name).Value;
var messagesProxied = Metrics.Snapshot.GetForContext("Bot").Meters.First(m => m.MultidimensionalName == BotMetrics.MessagesProxied.Name).Value;
var proxySuccessRate = messagesProxied.Items.First(i => i.Item == "success");
var commandsRun = Metrics.Snapshot.GetForContext("Bot").Meters.First(m => m.MultidimensionalName == BotMetrics.CommandsRun.Name).Value;
await Context.Channel.SendMessageAsync(embed: new EmbedBuilder()
.AddField("Messages processed", $"{messagesReceived.OneMinuteRate:F1}/s ({messagesReceived.FifteenMinuteRate:F1}/s over 15m)")
.AddField("Messages proxied", $"{messagesProxied.OneMinuteRate:F1}/s ({messagesProxied.FifteenMinuteRate:F1}/s over 15m)")
.AddField("Commands executed", $"{commandsRun.OneMinuteRate:F1}/s ({commandsRun.FifteenMinuteRate:F1}/s over 15m)")
.AddField("Proxy success rate", $"{proxySuccessRate.Percent/100:P1}")
.Build());
}
}
}

View File

@ -6,6 +6,7 @@ using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using App.Metrics;
using Dapper;
using Discord;
using Discord.Net;
@ -35,8 +36,9 @@ namespace PluralKit.Bot
private WebhookCacheService _webhookCache;
private MessageStore _messageStorage;
private EmbedService _embeds;
private IMetrics _metrics;
public ProxyService(IDiscordClient client, WebhookCacheService webhookCache, DbConnectionFactory conn, LogChannelService logger, MessageStore messageStorage, EmbedService embeds)
public ProxyService(IDiscordClient client, WebhookCacheService webhookCache, DbConnectionFactory conn, LogChannelService logger, MessageStore messageStorage, EmbedService embeds, IMetrics metrics)
{
_client = client;
_webhookCache = webhookCache;
@ -44,6 +46,7 @@ namespace PluralKit.Bot
_logger = logger;
_messageStorage = messageStorage;
_embeds = embeds;
_metrics = metrics;
}
private ProxyMatch GetProxyTagMatch(string message, IEnumerable<ProxyDatabaseResult> potentials)
@ -159,15 +162,33 @@ namespace PluralKit.Bot
}
ulong messageId;
if (attachment != null) {
using (var http = new HttpClient())
using (var stream = await http.GetStreamAsync(attachment.Url)) {
messageId = await client.SendFileAsync(stream, filename: attachment.Filename, text: text, username: username, avatarUrl: avatarUrl);
try
{
if (attachment != null)
{
using (var http = new HttpClient())
using (var stream = await http.GetStreamAsync(attachment.Url))
{
messageId = await client.SendFileAsync(stream, filename: attachment.Filename, text: text,
username: username, avatarUrl: avatarUrl);
}
}
} else {
messageId = await client.SendMessageAsync(text, username: username, avatarUrl: avatarUrl);
else
{
messageId = await client.SendMessageAsync(text, username: username, avatarUrl: avatarUrl);
}
// Log it in the metrics
_metrics.Measure.Meter.Mark(BotMetrics.MessagesProxied, "success");
}
catch (HttpException)
{
// Log failure in metrics and rethrow (we still need to cancel everything else)
_metrics.Measure.Meter.Mark(BotMetrics.MessagesProxied, "failure");
throw;
}
// TODO: figure out a way to return the full message object (without doing a GetMessageAsync call, which
// doesn't work if there's no permission to)
return messageId;

View File

@ -0,0 +1,13 @@
using App.Metrics;
using App.Metrics.Gauge;
namespace PluralKit.Core
{
public static class CoreMetrics
{
public static GaugeOptions SystemCount => new GaugeOptions { Name = "Systems", MeasurementUnit = Unit.Items};
public static GaugeOptions MemberCount => new GaugeOptions { Name = "Members", MeasurementUnit = Unit.Items };
public static GaugeOptions MessageCount => new GaugeOptions { Name = "Messages", MeasurementUnit = Unit.Items };
public static GaugeOptions SwitchCount => new GaugeOptions { Name = "Switches", MeasurementUnit = Unit.Items };
}
}

View File

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="App.Metrics" Version="3.1.0" />
<PackageReference Include="Dapper" Version="1.60.6" />
<PackageReference Include="Dapper.Contrib" Version="1.60.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />