From 0145d92e4348be6174c8ef25986d9c97939d7948 Mon Sep 17 00:00:00 2001 From: jvyden Date: Tue, 22 Aug 2023 20:45:17 -0400 Subject: [PATCH] Add request statistics tracking functionality --- .../Configuration/GameServerConfig.cs | 3 +- .../Database/GameDatabaseContext.Activity.cs | 2 + .../GameDatabaseContext.Statistics.cs | 45 +++++++++++++++++++ .../Database/GameDatabaseProvider.cs | 3 +- .../Response/ApiRequestStatisticsResponse.cs | 27 +++++++++++ .../Response/ApiStatisticsResponse.cs | 3 ++ .../Endpoints/ApiV3/InstanceApiEndpoints.cs | 26 +++++++++-- Refresh.GameServer/RefreshGameServer.cs | 3 ++ .../RequestStatisticTrackingService.cs | 37 +++++++++++++++ Refresh.GameServer/Types/RequestStatistics.cs | 11 +++++ 10 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs create mode 100644 Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiRequestStatisticsResponse.cs create mode 100644 Refresh.GameServer/Services/RequestStatisticTrackingService.cs create mode 100644 Refresh.GameServer/Types/RequestStatistics.cs diff --git a/Refresh.GameServer/Configuration/GameServerConfig.cs b/Refresh.GameServer/Configuration/GameServerConfig.cs index 51e34556..1987dd17 100644 --- a/Refresh.GameServer/Configuration/GameServerConfig.cs +++ b/Refresh.GameServer/Configuration/GameServerConfig.cs @@ -8,7 +8,7 @@ namespace Refresh.GameServer.Configuration; [SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")] public class GameServerConfig : Config { - public override int CurrentConfigVersion => 7; + public override int CurrentConfigVersion => 8; public override int Version { get; set; } = 0; protected override void Migrate(int oldVer, dynamic oldConfig) {} @@ -25,4 +25,5 @@ protected override void Migrate(int oldVer, dynamic oldConfig) {} public string InstanceDescription { get; set; } = "A server running Refresh!"; public bool MaintenanceMode { get; set; } = false; public bool RequireGameLoginToRegister { get; set; } = false; + public bool TrackRequestStatistics { get; set; } = false; } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Activity.cs b/Refresh.GameServer/Database/GameDatabaseContext.Activity.cs index cd1f38f4..863e8e18 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Activity.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Activity.cs @@ -18,4 +18,6 @@ public DatabaseList GetRecentActivityForLevel(GameLevel level, int count, .Where(e => e._StoredDataType == 1 && e.StoredSequentialId == level.LevelId) .Where(e => e.Timestamp < timestamp && e.Timestamp >= endTimestamp) .OrderByDescending(e => e.Timestamp), skip, count); + + public int GetTotalEventCount() => this._realm.All().Count(); } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs new file mode 100644 index 00000000..519cfbed --- /dev/null +++ b/Refresh.GameServer/Database/GameDatabaseContext.Statistics.cs @@ -0,0 +1,45 @@ +using Refresh.GameServer.Types; + +namespace Refresh.GameServer.Database; + +public partial class GameDatabaseContext // Statistics +{ + public RequestStatistics GetRequestStatistics() + { + RequestStatistics? statistics = this._realm.All().FirstOrDefault(); + if (statistics != null) return statistics; + + statistics = new RequestStatistics(); + this._realm.Write(() => + { + this._realm.Add(statistics); + }); + + return statistics; + } + + public void IncrementApiRequests() + { + RequestStatistics statistics = this.GetRequestStatistics(); + this._realm.Write(() => { + statistics.TotalRequests++; + statistics.ApiRequests++; + }); + } + public void IncrementLegacyApiRequests() + { + RequestStatistics statistics = this.GetRequestStatistics(); + this._realm.Write(() => { + statistics.TotalRequests++; + statistics.LegacyApiRequests++; + }); + } + public void IncrementGameRequests() + { + RequestStatistics statistics = this.GetRequestStatistics(); + this._realm.Write(() => { + statistics.TotalRequests++; + statistics.GameRequests++; + }); + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index b3b99193..2a09ef03 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -32,7 +32,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 75; + protected override ulong SchemaVersion => 76; protected override string Filename => "refreshGameServer.realm"; @@ -61,6 +61,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(GameAnnouncement), typeof(QueuedRegistration), typeof(EmailVerificationCode), + typeof(RequestStatistics), //grief report items typeof(GameReport), typeof(InfoBubble), diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiRequestStatisticsResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiRequestStatisticsResponse.cs new file mode 100644 index 00000000..41098c8b --- /dev/null +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiRequestStatisticsResponse.cs @@ -0,0 +1,27 @@ +using Refresh.GameServer.Types; + +namespace Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; + +[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] +public class ApiRequestStatisticsResponse : IApiResponse, IDataConvertableFrom +{ + public required long TotalRequests { get; set; } + public required long ApiRequests { get; set; } + public required long LegacyApiRequests { get; set; } + public required long GameRequests { get; set; } + + public static ApiRequestStatisticsResponse? FromOld(RequestStatistics? old) + { + if (old == null) return null; + + return new ApiRequestStatisticsResponse + { + TotalRequests = old.TotalRequests, + ApiRequests = old.ApiRequests, + LegacyApiRequests = old.LegacyApiRequests, + GameRequests = old.GameRequests, + }; + } + + public static IEnumerable FromOldList(IEnumerable oldList) => oldList.Select(FromOld)!; +} \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiStatisticsResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiStatisticsResponse.cs index 1f5e07e8..a5d3a5d5 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiStatisticsResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiStatisticsResponse.cs @@ -6,6 +6,9 @@ public class ApiStatisticsResponse : IApiResponse public required int TotalLevels { get; set; } public required int TotalUsers { get; set; } public required int TotalPhotos { get; set; } + public required int TotalEvents { get; set; } public required int CurrentRoomCount { get; set; } public required int CurrentIngamePlayersCount { get; set; } + + public required ApiRequestStatisticsResponse RequestStatistics { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/InstanceApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/InstanceApiEndpoints.cs index 7c3a366e..22941992 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/InstanceApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/InstanceApiEndpoints.cs @@ -14,16 +14,36 @@ public class InstanceApiEndpoints : EndpointGroup { [ApiV3Endpoint("statistics"), Authentication(false)] [DocSummary("Retrieves various statistics about the Refresh instance.")] - public ApiResponse GetStatistics(RequestContext context, GameDatabaseContext database, MatchService match) - => new ApiStatisticsResponse + public ApiResponse GetStatistics(RequestContext context, GameDatabaseContext database, MatchService match, GameServerConfig config) + { + ApiRequestStatisticsResponse requestStatistics; + if (!config.TrackRequestStatistics) + { + requestStatistics = new ApiRequestStatisticsResponse + { + TotalRequests = -1, + ApiRequests = -1, + LegacyApiRequests = -1, + GameRequests = -1, + }; + } + else + { + requestStatistics = ApiRequestStatisticsResponse.FromOld(database.GetRequestStatistics())!; + } + + return new ApiStatisticsResponse { TotalLevels = database.GetTotalLevelCount(), TotalUsers = database.GetTotalUserCount(), TotalPhotos = database.GetTotalPhotoCount(), + TotalEvents = database.GetTotalEventCount(), CurrentRoomCount = match.Rooms.Count(), CurrentIngamePlayersCount = match.TotalPlayers, + RequestStatistics = requestStatistics, }; - + } + [ApiV3Endpoint("instance"), Authentication(false), AllowDuringMaintenance] [DocSummary("Retrieves various information and metadata about the Refresh instance.")] public ApiResponse GetInstanceInformation(RequestContext context, diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 5a4aa41d..eceaab1e 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -125,6 +125,9 @@ protected virtual void SetupServices() this._server.AddService(); this._server.AddService(); + + if (this._config!.TrackRequestStatistics) + this._server.AddService(); } protected virtual void SetupWorkers() diff --git a/Refresh.GameServer/Services/RequestStatisticTrackingService.cs b/Refresh.GameServer/Services/RequestStatisticTrackingService.cs new file mode 100644 index 00000000..ab08f857 --- /dev/null +++ b/Refresh.GameServer/Services/RequestStatisticTrackingService.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using Bunkum.CustomHttpListener.Request; +using Bunkum.HttpServer; +using Bunkum.HttpServer.Database; +using Bunkum.HttpServer.Responses; +using Bunkum.HttpServer.Services; +using NotEnoughLogs; +using Refresh.GameServer.Database; +using Refresh.GameServer.Endpoints; + +namespace Refresh.GameServer.Services; + +public class RequestStatisticTrackingService : Service +{ + internal RequestStatisticTrackingService(LoggerContainer logger) : base(logger) + {} + + public override Response? OnRequestHandled(ListenerContext context, MethodInfo method, Lazy database) + { + GameDatabaseContext gameDatabase = (GameDatabaseContext)database.Value; + + if (context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute)) + { + gameDatabase.IncrementGameRequests(); + } + else if(context.Uri.AbsolutePath.StartsWith(LegacyApiEndpointAttribute.BaseRoute)) + { + gameDatabase.IncrementLegacyApiRequests(); + } + else + { + gameDatabase.IncrementApiRequests(); + } + + return null; + } +} \ No newline at end of file diff --git a/Refresh.GameServer/Types/RequestStatistics.cs b/Refresh.GameServer/Types/RequestStatistics.cs new file mode 100644 index 00000000..a68c4900 --- /dev/null +++ b/Refresh.GameServer/Types/RequestStatistics.cs @@ -0,0 +1,11 @@ +using Realms; + +namespace Refresh.GameServer.Types; + +public partial class RequestStatistics : IRealmObject +{ + public long TotalRequests { get; set; } + public long ApiRequests { get; set; } + public long LegacyApiRequests { get; set; } + public long GameRequests { get; set; } +} \ No newline at end of file