optimize player history retrieval

This commit is contained in:
RaidMax 2023-05-30 18:12:57 -05:00
parent 81e2a2f6d4
commit 84ed9c8d8f
5 changed files with 78 additions and 50 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace SharedLibraryCore.Dtos namespace SharedLibraryCore.Dtos
{ {
@ -11,11 +12,17 @@ namespace SharedLibraryCore.Dtos
public class ClientCountSnapshot public class ClientCountSnapshot
{ {
[JsonIgnore]
public DateTime Time { get; set; } public DateTime Time { get; set; }
[JsonPropertyName("ts")]
public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ"); public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ");
[JsonPropertyName("cc")]
public int ClientCount { get; set; } public int ClientCount { get; set; }
[JsonPropertyName("ci")]
public bool ConnectionInterrupted { get;set; } public bool ConnectionInterrupted { get;set; }
[JsonIgnore]
public string Map { get; set; } public string Map { get; set; }
[JsonPropertyName("ma")]
public string MapAlias { get; set; } public string MapAlias { get; set; }
} }
} }

View File

@ -1,8 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using WebfrontCore.Controllers.API.Models; using WebfrontCore.Controllers.API.Models;
@ -12,9 +16,14 @@ namespace WebfrontCore.Controllers.API
[Route("api/[controller]")] [Route("api/[controller]")]
public class Server : BaseController public class Server : BaseController
{ {
private readonly IServerDataViewer _serverDataViewer;
public Server(IManager manager) : base(manager) private readonly ApplicationConfiguration _applicationConfiguration;
public Server(IManager manager, IServerDataViewer serverDataViewer,
ApplicationConfiguration applicationConfiguration) : base(manager)
{ {
_serverDataViewer = serverDataViewer;
_applicationConfiguration = applicationConfiguration;
} }
[HttpGet] [HttpGet]
@ -110,5 +119,48 @@ namespace WebfrontCore.Controllers.API
completedEvent.Output completedEvent.Output
}); });
} }
[HttpGet("{id}/history")]
public async Task<IActionResult> GetClientHistory(string id)
{
var foundServer = Manager.GetServers().FirstOrDefault(server => server.Id == id);
if (foundServer == null)
{
return new NotFoundResult();
}
var clientHistory = (await _serverDataViewer.ClientHistoryAsync(_applicationConfiguration.MaxClientHistoryTime,
CancellationToken.None))?
.FirstOrDefault(history => history.ServerId == foundServer.LegacyDatabaseId) ??
new ClientHistoryInfo
{
ServerId = foundServer.LegacyDatabaseId,
ClientCounts = new List<ClientCountSnapshot>()
};
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
if (foundServer.ClientHistory.ClientCounts.Any())
{
counts = counts.Union(foundServer.ClientHistory.ClientCounts.Where(history =>
history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue)))
.Where(history => history.Time >= DateTime.UtcNow - _applicationConfiguration.MaxClientHistoryTime);
}
if (ViewBag.Maps?.Count == 0)
{
return Json(counts.ToList());
}
var clientCountSnapshots = counts.ToList();
foreach (var count in clientCountSnapshots)
{
count.MapAlias = foundServer.Maps.FirstOrDefault(map => map.Name == count.Map)?.Alias ??
count.Map;
}
return Json(clientCountSnapshots);
}
} }
} }

View File

@ -1,29 +1,21 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Dtos; using SharedLibraryCore.Dtos;
using System.Linq; using System.Linq;
using System.Threading;
using Data.Models; using Data.Models;
using Data.Models.Client.Stats; using Data.Models.Client.Stats;
using IW4MAdmin.Plugins.Stats.Helpers; using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace WebfrontCore.ViewComponents namespace WebfrontCore.ViewComponents
{ {
public class ServerListViewComponent : ViewComponent public class ServerListViewComponent : ViewComponent
{ {
private readonly IServerDataViewer _serverDataViewer;
private readonly ApplicationConfiguration _appConfig;
private readonly DefaultSettings _defaultSettings; private readonly DefaultSettings _defaultSettings;
public ServerListViewComponent(IServerDataViewer serverDataViewer, public ServerListViewComponent(DefaultSettings defaultSettings)
ApplicationConfiguration applicationConfiguration, DefaultSettings defaultSettings)
{ {
_serverDataViewer = serverDataViewer;
_appConfig = applicationConfiguration;
_defaultSettings = defaultSettings; _defaultSettings = defaultSettings;
} }
@ -46,25 +38,6 @@ namespace WebfrontCore.ViewComponents
foreach (var server in servers) foreach (var server in servers)
{ {
var serverId = server.GetIdForServer().Result;
var clientHistory = _serverDataViewer.ClientHistoryAsync(_appConfig.MaxClientHistoryTime,
CancellationToken.None).Result?
.FirstOrDefault(history => history.ServerId == serverId) ??
new ClientHistoryInfo
{
ServerId = serverId,
ClientCounts = new List<ClientCountSnapshot>()
};
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
if (server.ClientHistory.ClientCounts.Any())
{
counts = counts.Union(server.ClientHistory.ClientCounts.Where(history =>
history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue)))
.Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime);
}
serverInfo.Add(new ServerInfo serverInfo.Add(new ServerInfo
{ {
Name = server.Hostname, Name = server.Hostname,
@ -76,11 +49,7 @@ namespace WebfrontCore.ViewComponents
MaxClients = server.MaxClients, MaxClients = server.MaxClients,
PrivateClientSlots = server.PrivateClientSlots, PrivateClientSlots = server.PrivateClientSlots,
GameType = server.GametypeName, GameType = server.GametypeName,
ClientHistory = new ClientHistoryInfo ClientHistory = new ClientHistoryInfo(),
{
ServerId = server.EndPoint,
ClientCounts = counts.ToList()
},
Players = server.GetClientsAsList() Players = server.GetClientsAsList()
.Select(client => new PlayerInfo .Select(client => new PlayerInfo
{ {

View File

@ -82,8 +82,7 @@
} }
<div class="server-history"> <div class="server-history">
<div class="server-history-row m-auto bg-dark-dm bg-light-lm rounded-bottom" style="position:relative; width: 100%" id="server_history_@Model.ID" data-serverid="@Model.ID" <div class="server-history-row m-auto bg-dark-dm bg-light-lm rounded-bottom" style="position:relative; width: 100%" id="server_history_@Model.ID" data-serverid="@Model.ID" data-server-endpoint="@Model.Endpoint"
data-clienthistory='@Html.Raw(Json.Serialize(Model.ClientHistory))'
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientHistory.ClientCounts))' data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientHistory.ClientCounts))'
data-online="@Model.Online"> data-online="@Model.Online">
<canvas id="server_history_canvas_@Model.ID" class="rounded-bottom" height="100"></canvas> <canvas id="server_history_canvas_@Model.ID" class="rounded-bottom" height="100"></canvas>

View File

@ -27,25 +27,25 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) {
let lastMap = ''; let lastMap = '';
playerHistory.forEach((elem, i) => { playerHistory.forEach((elem, i) => {
if (elem.map !== lastMap) { if (elem.ma !== lastMap) {
mapChange.push(i); mapChange.push(i);
lastMap = elem.map; lastMap = elem;
} }
if (elem.connectionInterrupted) { if (elem.connectionInterrupted) {
offlineTime.push({ offlineTime.push({
clientCount: maxClients, clientCount: maxClients,
timeString: elem.timeString timeString: elem.ts
}); });
onlineTime.push({ onlineTime.push({
clientCount: 0, clientCount: 0,
timeString: elem.timeString timeString: elem.ts
}) })
} else { } else {
offlineTime.push({ offlineTime.push({
clientCount: 0, clientCount: 0,
timeString: elem.timeString timeString: elem.ts
}); });
onlineTime.push(elem) onlineTime.push(elem)
@ -60,9 +60,9 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) {
return new Chart(document.getElementById(`server_history_canvas_${i}`), { return new Chart(document.getElementById(`server_history_canvas_${i}`), {
type: 'line', type: 'line',
data: { data: {
labels: playerHistory.map(history => history.timeString), labels: playerHistory.map(history => history.ts),
datasets: [{ datasets: [{
data: onlineTime.map(history => history.clientCount), data: onlineTime.map(history => history.cc),
backgroundColor: fillColor, backgroundColor: fillColor,
borderColor: primaryColor, borderColor: primaryColor,
borderWidth: 2, borderWidth: 2,
@ -70,7 +70,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) {
hoverBorderWidth: 2 hoverBorderWidth: 2
}, },
{ {
data: offlineTime.map(history => history.clientCount), data: offlineTime.map(history => history.cc),
backgroundColor: createDiagonalPattern(offlineFillColor), backgroundColor: createDiagonalPattern(offlineFillColor),
borderColor: offlineFillColor, borderColor: offlineFillColor,
borderWidth: 2, borderWidth: 2,
@ -88,7 +88,7 @@ function getPlayerHistoryChart(playerHistory, i, width, maxClients) {
callbacks: { callbacks: {
// todo: localization at some point // todo: localization at some point
title: context => moment(context[0].label).local().calendar(), title: context => moment(context[0].label).local().calendar(),
label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].mapAlias}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'], label: context => context.datasetIndex !== 1 ? `${context.value} ${_localization['WEBFRONT_SCRIPT_SERVER_PLAYERS']} | ${playerHistory[context.index].ma}` : context.value === '0' ? '' : _localization['WEBFRONT_SCRIPT_SERVER_UNREACHABLE'],
}, },
mode: 'nearest', mode: 'nearest',
intersect: false, intersect: false,
@ -153,13 +153,14 @@ $(document).ready(function () {
$(this).parent().parent().find('.server-header-ip-address').show(); $(this).parent().parent().find('.server-header-ip-address').show();
}); });
$('.server-history-row').each(function (index, element) { $('.server-history-row').each(async function (index, element) {
let clientHistory = $(this).data('clienthistory-ex');
const serverId = $(this).data('serverid'); const serverId = $(this).data('serverid');
const serverEp = $(this).data('server-endpoint');
setInterval(() => refreshClientActivity(serverId), 2000 + (index * 100)); setInterval(() => refreshClientActivity(serverId), 2000 + (index * 100));
let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text()); let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text());
let width = $('.server-header').first().width(); let width = $('.server-header').first().width();
getPlayerHistoryChart(clientHistory, serverId, width, maxClients); const clientHistory = await fetch(`/api/server/${serverEp}/history`);
getPlayerHistoryChart(await clientHistory.json(), serverId, width, maxClients);
}); });
$('.moment-date').each((index, element) => { $('.moment-date').each((index, element) => {