mirror of
https://github.com/RPCS3/discord-bot.git
synced 2024-11-23 10:19:39 +00:00
Merge pull request #880 from 13xforever/vnext
Update handling of bot usage stats
This commit is contained in:
commit
6bfc26743e
@ -63,8 +63,7 @@ internal class BaseCommandModuleCustom : BaseCommandModule
|
||||
{
|
||||
if (ctx.Command?.QualifiedName is string qualifiedName)
|
||||
{
|
||||
StatsStorage.CmdStatCache.TryGetValue(qualifiedName, out int counter);
|
||||
StatsStorage.CmdStatCache.Set(qualifiedName, ++counter, StatsStorage.CacheTime);
|
||||
StatsStorage.IncCmdStat(qualifiedName);
|
||||
Config.TelemetryClient?.TrackRequest(qualifiedName, executionStart, DateTimeOffset.UtcNow - executionStart, HttpStatusCode.OK.ToString(), true);
|
||||
}
|
||||
|
||||
|
@ -171,12 +171,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
|
||||
|
||||
private static void AppendCmdStats(DiscordEmbedBuilder embed)
|
||||
{
|
||||
var commandStats = StatsStorage.CmdStatCache.GetCacheKeys<string>();
|
||||
var sortedCommandStats = commandStats
|
||||
.Select(c => (name: c, stat: StatsStorage.CmdStatCache.Get(c) as int?))
|
||||
.Where(c => c.stat.HasValue)
|
||||
.OrderByDescending(c => c.stat)
|
||||
.ToList();
|
||||
var sortedCommandStats = StatsStorage.GetCmdStats();
|
||||
var totalCalls = sortedCommandStats.Sum(c => c.stat);
|
||||
var top = sortedCommandStats.Take(5).ToList();
|
||||
if (top.Count == 0)
|
||||
@ -192,12 +187,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
|
||||
|
||||
private static void AppendExplainStats(DiscordEmbedBuilder embed)
|
||||
{
|
||||
var terms = StatsStorage.ExplainStatCache.GetCacheKeys<string>();
|
||||
var sortedTerms = terms
|
||||
.Select(t => (term: t, stat: StatsStorage.ExplainStatCache.Get(t) as int?))
|
||||
.Where(t => t.stat.HasValue)
|
||||
.OrderByDescending(t => t.stat)
|
||||
.ToList();
|
||||
var sortedTerms = StatsStorage.GetExplainStats();
|
||||
var totalExplains = sortedTerms.Sum(t => t.stat);
|
||||
var top = sortedTerms.Take(5).ToList();
|
||||
if (top.Count == 0)
|
||||
@ -213,12 +203,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
|
||||
|
||||
private static void AppendGameLookupStats(DiscordEmbedBuilder embed)
|
||||
{
|
||||
var gameTitles = StatsStorage.GameStatCache.GetCacheKeys<string>();
|
||||
var sortedTitles = gameTitles
|
||||
.Select(t => (title: t, stat: StatsStorage.GameStatCache.Get(t) as int?))
|
||||
.Where(t => t.stat.HasValue)
|
||||
.OrderByDescending(t => t.stat)
|
||||
.ToList();
|
||||
var sortedTitles = StatsStorage.GetGameStats();
|
||||
var totalLookups = sortedTitles.Sum(t => t.stat);
|
||||
var top = sortedTitles.Take(5).ToList();
|
||||
if (top.Count == 0)
|
||||
|
@ -508,10 +508,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
|
||||
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
|
||||
foreach (var title in searchHits.Select(t => t.info.Title).Distinct())
|
||||
{
|
||||
StatsStorage.GameStatCache.TryGetValue(title, out int stat);
|
||||
StatsStorage.GameStatCache.Set(title, ++stat, StatsStorage.CacheTime);
|
||||
}
|
||||
StatsStorage.IncGameStat(title);
|
||||
foreach (var resultInfo in sortedList.Take(request.AmountRequested))
|
||||
{
|
||||
var info = resultInfo.AsString();
|
||||
|
@ -397,8 +397,7 @@ internal sealed class Explain: BaseCommandModuleCustom
|
||||
}
|
||||
|
||||
var explain = termLookupResult.explanation;
|
||||
StatsStorage.ExplainStatCache.TryGetValue(explain.Keyword, out int stat);
|
||||
StatsStorage.ExplainStatCache.Set(explain.Keyword, ++stat, StatsStorage.CacheTime);
|
||||
StatsStorage.IncExplainStat(explain.Keyword);
|
||||
msgBuilder = new DiscordMessageBuilder().WithContent(explain.Text);
|
||||
if (!usedReply && useReply)
|
||||
msgBuilder.WithReply(sourceMessage.Id);
|
||||
|
@ -39,10 +39,10 @@
|
||||
<AdditionalFiles Include="..\win32_error_codes.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DSharpPlus" Version="4.3.0-nightly-01149" />
|
||||
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.3.0-nightly-01149" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="4.3.0-nightly-01149" />
|
||||
<PackageReference Include="DSharpPlus.SlashCommands" Version="4.3.0-nightly-01149" />
|
||||
<PackageReference Include="DSharpPlus" Version="4.3.0-nightly-01146" />
|
||||
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.3.0-nightly-01146" />
|
||||
<PackageReference Include="DSharpPlus.Interactivity" Version="4.3.0-nightly-01146" />
|
||||
<PackageReference Include="DSharpPlus.SlashCommands" Version="4.3.0-nightly-01146" />
|
||||
<PackageReference Include="Google.Apis.Drive.v3" Version="1.57.0.2684" />
|
||||
<PackageReference Include="ksemenenko.ColorThief" Version="1.1.1.4" />
|
||||
<PackageReference Include="MathParser.org-mXparser" Version="5.0.6" />
|
||||
|
@ -44,7 +44,7 @@ internal class BotDb: DbContext
|
||||
modelBuilder.Entity<DisabledCommand>().HasIndex(c => c.Command).IsUnique().HasDatabaseName("disabled_command_command");
|
||||
modelBuilder.Entity<WhitelistedInvite>().HasIndex(i => i.GuildId).IsUnique().HasDatabaseName("whitelisted_invite_guild_id");
|
||||
modelBuilder.Entity<EventSchedule>().HasIndex(e => new {e.Year, e.EventName}).HasDatabaseName("event_schedule_year_event_name");
|
||||
modelBuilder.Entity<Stats>().HasIndex(s => new { s.Category, s.Key }).IsUnique().HasDatabaseName("stats_category_key");
|
||||
modelBuilder.Entity<Stats>().HasIndex(s => new { s.Category, s.Bucket, s.Key }).IsUnique().HasDatabaseName("stats_category_bucket_key");
|
||||
modelBuilder.Entity<Kot>().HasIndex(k => k.UserId).IsUnique().HasDatabaseName("kot_user_id");
|
||||
modelBuilder.Entity<Doggo>().HasIndex(d => d.UserId).IsUnique().HasDatabaseName("doggo_user_id");
|
||||
modelBuilder.Entity<ForcedNickname>().HasIndex(d => new { d.GuildId, d.UserId }).IsUnique().HasDatabaseName("forced_nickname_guild_id_user_id");
|
||||
@ -169,6 +169,7 @@ internal class Stats
|
||||
public int Id { get; set; }
|
||||
[Required]
|
||||
public string Category { get; set; } = null!;
|
||||
public string? Bucket { get; set; }
|
||||
[Required]
|
||||
public string Key { get; set; } = null!;
|
||||
public int Value { get; set; }
|
||||
|
433
CompatBot/Database/Migrations/BotDb/20220704163631_AddStatsBucketColumn.Designer.cs
generated
Normal file
433
CompatBot/Database/Migrations/BotDb/20220704163631_AddStatsBucketColumn.Designer.cs
generated
Normal file
@ -0,0 +1,433 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CompatBot.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CompatBot.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(BotDb))]
|
||||
[Migration("20220704163631_AddStatsBucketColumn")]
|
||||
partial class AddStatsBucketColumn
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.BotState", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("key");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("value");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Key")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("bot_state_key");
|
||||
|
||||
b.ToTable("bot_state");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.DisabledCommand", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Command")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("command");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Command")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("disabled_command_command");
|
||||
|
||||
b.ToTable("disabled_commands");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Doggo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("doggo_user_id");
|
||||
|
||||
b.ToTable("doggo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.EventSchedule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<long>("End")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("end");
|
||||
|
||||
b.Property<string>("EventName")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("event_name");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<long>("Start")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("start");
|
||||
|
||||
b.Property<int>("Year")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("year");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Year", "EventName")
|
||||
.HasDatabaseName("event_schedule_year_event_name");
|
||||
|
||||
b.ToTable("event_schedule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Explanation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<byte[]>("Attachment")
|
||||
.HasMaxLength(7340032)
|
||||
.HasColumnType("BLOB")
|
||||
.HasColumnName("attachment");
|
||||
|
||||
b.Property<string>("AttachmentFilename")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("attachment_filename");
|
||||
|
||||
b.Property<string>("Keyword")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("keyword");
|
||||
|
||||
b.Property<string>("Text")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("text");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Keyword")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("explanation_keyword");
|
||||
|
||||
b.ToTable("explanation");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.ForcedNickname", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("nickname");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("GuildId", "UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("forced_nickname_guild_id_user_id");
|
||||
|
||||
b.ToTable("forced_nicknames");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Kot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("kot_user_id");
|
||||
|
||||
b.ToTable("kot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Moderator", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<bool>("Sudoer")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("sudoer");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("moderator_discord_id");
|
||||
|
||||
b.ToTable("moderator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Piracystring", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("Actions")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(11)
|
||||
.HasColumnName("actions");
|
||||
|
||||
b.Property<byte>("Context")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue((byte)3)
|
||||
.HasColumnName("context");
|
||||
|
||||
b.Property<string>("CustomMessage")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("custom_message");
|
||||
|
||||
b.Property<bool>("Disabled")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("disabled");
|
||||
|
||||
b.Property<string>("ExplainTerm")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("explain_term");
|
||||
|
||||
b.Property<string>("String")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(255)")
|
||||
.HasColumnName("string");
|
||||
|
||||
b.Property<string>("ValidatingRegex")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("validating_regex");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("String")
|
||||
.HasDatabaseName("piracystring_string");
|
||||
|
||||
b.ToTable("piracystring");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Stats", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Bucket")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("bucket");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("category");
|
||||
|
||||
b.Property<long>("ExpirationTimestamp")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("expiration_timestamp");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("key");
|
||||
|
||||
b.Property<int>("Value")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("value");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Category", "Bucket", "Key")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("stats_category_bucket_key");
|
||||
|
||||
b.ToTable("stats");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.SuspiciousString", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("String")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("string");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("String")
|
||||
.HasDatabaseName("suspicious_string_string");
|
||||
|
||||
b.ToTable("suspicious_string");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.Warning", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<string>("FullReason")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("full_reason");
|
||||
|
||||
b.Property<ulong>("IssuerId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("issuer_id");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("reason");
|
||||
|
||||
b.Property<bool>("Retracted")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("retracted");
|
||||
|
||||
b.Property<ulong?>("RetractedBy")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("retracted_by");
|
||||
|
||||
b.Property<string>("RetractionReason")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("retraction_reason");
|
||||
|
||||
b.Property<long?>("RetractionTimestamp")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("retraction_timestamp");
|
||||
|
||||
b.Property<long?>("Timestamp")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasDatabaseName("warning_discord_id");
|
||||
|
||||
b.ToTable("warning");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.WhitelistedInvite", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("guild_id");
|
||||
|
||||
b.Property<string>("InviteCode")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("invite_code");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("GuildId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("whitelisted_invite_guild_id");
|
||||
|
||||
b.ToTable("whitelisted_invites");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CompatBot.Database.Migrations
|
||||
{
|
||||
public partial class AddStatsBucketColumn : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "stats_category_key",
|
||||
table: "stats");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "bucket",
|
||||
table: "stats",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "stats_category_bucket_key",
|
||||
table: "stats",
|
||||
columns: new[] { "category", "bucket", "key" },
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "stats_category_bucket_key",
|
||||
table: "stats");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "bucket",
|
||||
table: "stats");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "stats_category_key",
|
||||
table: "stats",
|
||||
columns: new[] { "category", "key" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CompatBot.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(BotDb))]
|
||||
@ -13,8 +15,7 @@ namespace CompatBot.Database.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
|
||||
|
||||
modelBuilder.Entity("CompatBot.Database.BotState", b =>
|
||||
{
|
||||
@ -289,6 +290,10 @@ namespace CompatBot.Database.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Bucket")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("bucket");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
@ -310,9 +315,9 @@ namespace CompatBot.Database.Migrations
|
||||
b.HasKey("Id")
|
||||
.HasName("id");
|
||||
|
||||
b.HasIndex("Category", "Key")
|
||||
b.HasIndex("Category", "Bucket", "Key")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("stats_category_key");
|
||||
.HasDatabaseName("stats_category_bucket_key");
|
||||
|
||||
b.ToTable("stats");
|
||||
});
|
||||
|
@ -11,12 +11,14 @@ namespace CompatBot.Database.Providers;
|
||||
|
||||
internal static class StatsStorage
|
||||
{
|
||||
internal static readonly TimeSpan CacheTime = TimeSpan.FromDays(1);
|
||||
internal static readonly MemoryCache CmdStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
internal static readonly MemoryCache ExplainStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
internal static readonly MemoryCache GameStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
private static readonly TimeSpan CacheTime = TimeSpan.FromDays(1);
|
||||
private static readonly MemoryCache CmdStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
private static readonly MemoryCache ExplainStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
private static readonly MemoryCache GameStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
|
||||
private const char PrefixSeparator = '\0';
|
||||
|
||||
private static readonly SemaphoreSlim Barrier = new(1, 1);
|
||||
private static readonly SemaphoreSlim BucketLock = new(1, 1);
|
||||
private static readonly (string name, MemoryCache cache)[] AllCaches =
|
||||
{
|
||||
(nameof(CmdStatCache), CmdStatCache),
|
||||
@ -24,6 +26,50 @@ internal static class StatsStorage
|
||||
(nameof(GameStatCache), GameStatCache),
|
||||
};
|
||||
|
||||
private static ((int y, int m, int d, int h) Key, string Value) bucketPrefix = ((0, 0, 0, 0), "");
|
||||
|
||||
private static string Prefix
|
||||
{
|
||||
get
|
||||
{
|
||||
var ts = DateTime.UtcNow;
|
||||
var key = (ts.Year, ts.Month, ts.Day, ts.Hour);
|
||||
if (bucketPrefix.Key == key)
|
||||
return bucketPrefix.Value;
|
||||
|
||||
if (!BucketLock.Wait(0))
|
||||
return bucketPrefix.Value;
|
||||
|
||||
bucketPrefix = (key, ts.ToString("yyyyMMddHH") + PrefixSeparator);
|
||||
BucketLock.Release();
|
||||
return bucketPrefix.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void IncCmdStat(string qualifiedName) => IncStat(qualifiedName, CmdStatCache);
|
||||
public static void IncExplainStat(string term) => IncStat(term, ExplainStatCache);
|
||||
public static void IncGameStat(string title) => IncStat(title, GameStatCache);
|
||||
private static void IncStat(string key, MemoryCache cache)
|
||||
{
|
||||
var bucketKey = Prefix + key;
|
||||
cache.TryGetValue(bucketKey, out int stat);
|
||||
cache.Set(bucketKey, ++stat, CacheTime);
|
||||
}
|
||||
|
||||
public static List<(string name, int stat)> GetCmdStats() => GetStats(CmdStatCache);
|
||||
public static List<(string name, int stat)> GetExplainStats() => GetStats(ExplainStatCache);
|
||||
public static List<(string name, int stat)> GetGameStats() => GetStats(GameStatCache);
|
||||
private static List<(string name, int stat)> GetStats(MemoryCache cache)
|
||||
{
|
||||
return cache.GetCacheKeys<string>()
|
||||
.Select(c => (name: c.Split(PrefixSeparator, 2)[^1], stat: cache.Get(c) as int?))
|
||||
.Where(s => s.stat.HasValue)
|
||||
.GroupBy(s => s.name)
|
||||
.Select(g => (name: g.Key, stat: (int)g.Sum(s => s.stat)!))
|
||||
.OrderByDescending(s => s.stat)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static async Task SaveAsync(bool wait = false)
|
||||
{
|
||||
if (await Barrier.WaitAsync(0).ConfigureAwait(false))
|
||||
@ -32,21 +78,35 @@ internal static class StatsStorage
|
||||
{
|
||||
Config.Log.Debug("Got stats saving lock");
|
||||
await using var db = new BotDb();
|
||||
db.Stats.RemoveRange(db.Stats);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
foreach (var (category, cache) in AllCaches)
|
||||
{
|
||||
var entries = cache.GetCacheEntries<string>();
|
||||
var savedKeys = new HashSet<string>();
|
||||
foreach (var (key, value) in entries)
|
||||
if (savedKeys.Add(key))
|
||||
await db.Stats.AddAsync(new Stats
|
||||
{
|
||||
var keyParts = key.Split(PrefixSeparator, 2);
|
||||
var bucket = keyParts.Length == 2 ? keyParts[0] : null;
|
||||
var statKey = keyParts[^1];
|
||||
var statValue = (int?)value?.Value ?? 0;
|
||||
var ts = value?.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0;
|
||||
|
||||
var currentEntry = db.Stats.FirstOrDefault(e => e.Category == category && e.Bucket == bucket && e.Key == statKey);
|
||||
if (currentEntry is null)
|
||||
await db.Stats.AddAsync(new()
|
||||
{
|
||||
Category = category,
|
||||
Bucket = bucket,
|
||||
Key = statKey,
|
||||
Value = statValue,
|
||||
ExpirationTimestamp = ts
|
||||
}).ConfigureAwait(false);
|
||||
else
|
||||
{
|
||||
Category = category,
|
||||
Key = key,
|
||||
Value = (int?)value?.Value ?? 0,
|
||||
ExpirationTimestamp = value?.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0
|
||||
}).ConfigureAwait(false);
|
||||
currentEntry.Value = statValue;
|
||||
currentEntry.ExpirationTimestamp = ts;
|
||||
}
|
||||
}
|
||||
else
|
||||
Config.Log.Warn($"Somehow there's another '{key}' in the {category} cache");
|
||||
}
|
||||
@ -80,9 +140,19 @@ internal static class StatsStorage
|
||||
{
|
||||
var time = entry.ExpirationTimestamp.AsUtc();
|
||||
if (time > now)
|
||||
cache.Set(entry.Key, entry.Value, time);
|
||||
{
|
||||
var key = entry.Key;
|
||||
if (entry.Bucket is { Length: > 0 } bucket)
|
||||
key = bucket + PrefixSeparator + key;
|
||||
cache.Set(key, entry.Value, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
db.Stats.Remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task BackgroundSaveAsync()
|
||||
|
@ -32,7 +32,7 @@ internal static class IsTheGamePlayableHandler
|
||||
|
||||
public static async Task OnMessageCreated(DiscordClient c, MessageCreateEventArgs args)
|
||||
{
|
||||
if (DefaultHandlerFilter.IsFluff(args.Message))
|
||||
if (DefaultHandlerFilter.IsFluff(args.Message) || args.Channel is null)
|
||||
return;
|
||||
|
||||
#if !DEBUG
|
||||
@ -125,10 +125,7 @@ internal static class IsTheGamePlayableHandler
|
||||
return (null, null);
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Title))
|
||||
{
|
||||
StatsStorage.GameStatCache.TryGetValue(info.Title, out int stat);
|
||||
StatsStorage.GameStatCache.Set(info.Title, ++stat, StatsStorage.CacheTime);
|
||||
}
|
||||
StatsStorage.IncGameStat(info.Title);
|
||||
return (code, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -102,10 +102,7 @@ internal static class TitleInfoFormatter
|
||||
desc += " (cached)";
|
||||
var cacheTitle = info.Title ?? gameTitle;
|
||||
if (!string.IsNullOrEmpty(cacheTitle))
|
||||
{
|
||||
StatsStorage.GameStatCache.TryGetValue(cacheTitle, out int stat);
|
||||
StatsStorage.GameStatCache.Set(cacheTitle, ++stat, StatsStorage.CacheTime);
|
||||
}
|
||||
StatsStorage.IncGameStat(cacheTitle);
|
||||
var title = $"{productCodePart}{cacheTitle?.Trim(200)}{onlineOnlyPart}";
|
||||
if (string.IsNullOrEmpty(title))
|
||||
desc = "";
|
||||
@ -143,8 +140,7 @@ internal static class TitleInfoFormatter
|
||||
gameTitle = titleName;
|
||||
if (!string.IsNullOrEmpty(gameTitle))
|
||||
{
|
||||
StatsStorage.GameStatCache.TryGetValue(gameTitle, out int stat);
|
||||
StatsStorage.GameStatCache.Set(gameTitle, ++stat, StatsStorage.CacheTime);
|
||||
StatsStorage.IncGameStat(gameTitle);
|
||||
result.Title = $"{productCodePart}{gameTitle.Sanitize().Trim(200)}";
|
||||
}
|
||||
return result;
|
||||
|
Loading…
Reference in New Issue
Block a user