1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-07 21:58:06 -05:00

Using Code from EFPerformanceBucket for references

This commit is contained in:
Ayymoss 2024-09-07 18:41:18 +01:00
parent 79bd6ca8e1
commit 98e2be8623
No known key found for this signature in database
GPG Key ID: 6F64388D52A78E9E
17 changed files with 154 additions and 103 deletions

View File

@ -912,9 +912,9 @@ namespace IW4MAdmin
context.Entry(gameServer).Property(property => property.HostName).IsModified = true; context.Entry(gameServer).Property(property => property.HostName).IsModified = true;
} }
if (gameServer.PerformanceBucket != PerformanceBucket) if (gameServer.PerformanceBucket.Code != PerformanceCode)
{ {
gameServer.PerformanceBucket = PerformanceBucket; gameServer.PerformanceBucket.Code = PerformanceCode;
context.Entry(gameServer).Property(property => property.PerformanceBucket).IsModified = true; context.Entry(gameServer).Property(property => property.PerformanceBucket).IsModified = true;
} }

View File

@ -29,11 +29,12 @@ namespace IW4MAdmin.Application.Misc
private readonly StatManager _statManager; private readonly StatManager _statManager;
private readonly TimeSpan? _cacheTimeSpan = private readonly TimeSpan? _cacheTimeSpan =
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10); Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?)TimeSpan.FromMinutes(10);
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache, public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
IDataValueCache<EFClient, (int, int)> serverStatsCache, IDataValueCache<EFClient, (int, int)> serverStatsCache,
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache, IDataValueCache<EFClientRankingHistory, int> rankedClientsCache, StatManager statManager) IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache,
IDataValueCache<EFClientRankingHistory, int> rankedClientsCache, StatManager statManager)
{ {
_logger = logger; _logger = logger;
_snapshotCache = snapshotCache; _snapshotCache = snapshotCache;
@ -43,16 +44,17 @@ namespace IW4MAdmin.Application.Misc
_statManager = statManager; _statManager = statManager;
} }
public async Task<(int?, DateTime?)> public async Task<(int?, DateTime?)>
MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null, MaxConcurrentClientsAsync(long? serverId = null, Reference.Game? gameCode = null, TimeSpan? overPeriod = null,
CancellationToken token = default) CancellationToken token = default)
{ {
_snapshotCache.SetCacheItem(async (snapshots, ids, cancellationToken) => _snapshotCache.SetCacheItem(async (snapshots, idsList, cancellationToken) =>
{ {
Reference.Game? game = null; Reference.Game? game = null;
long? id = null; long? id = null;
if (ids.Any()) var ids = idsList.ToList();
if (ids.Count is not 0)
{ {
game = (Reference.Game?)ids.First(); game = (Reference.Game?)ids.First();
id = (long?)ids.Last(); id = (long?)ids.Last();
@ -102,12 +104,11 @@ namespace IW4MAdmin.Application.Misc
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients); _logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
return (maxClients, maxClientsTime); return (maxClients, maxClientsTime);
}, nameof(MaxConcurrentClientsAsync), new object[] { gameCode, serverId }, _cacheTimeSpan, true); }, nameof(MaxConcurrentClientsAsync), [gameCode, serverId], _cacheTimeSpan, true);
try try
{ {
return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), return await _snapshotCache.GetCacheItem(nameof(MaxConcurrentClientsAsync), [gameCode, serverId], token);
new object[] { gameCode, serverId }, token);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -116,22 +117,24 @@ namespace IW4MAdmin.Application.Misc
} }
} }
public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null, CancellationToken token = default) public async Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, Reference.Game? gameCode = null,
CancellationToken token = default)
{ {
_serverStatsCache.SetCacheItem(async (set, ids, cancellationToken) => _serverStatsCache.SetCacheItem(async (set, ids, cancellationToken) =>
{ {
Reference.Game? game = null; Reference.Game? game = null;
if (ids.Any()) if (ids.Any())
{ {
game = (Reference.Game?)ids.First(); game = (Reference.Game?)ids.First();
} }
var count = await set.CountAsync(item => game == null || item.GameName == game, var count = await set.CountAsync(item => game == null || item.GameName == game,
cancellationToken); cancellationToken);
var startOfPeriod = var startOfPeriod =
DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24); DateTime.UtcNow.AddHours(-overPeriod?.TotalHours ?? -24);
var recentCount = await set.CountAsync(client => (game == null || client.GameName == game) && client.LastConnection >= startOfPeriod, var recentCount = await set.CountAsync(
client => (game == null || client.GameName == game) && client.LastConnection >= startOfPeriod,
cancellationToken); cancellationToken);
return (count, recentCount); return (count, recentCount);
@ -173,7 +176,10 @@ namespace IW4MAdmin.Application.Misc
{ {
ServerId = byServer.Key, ServerId = byServer.Key,
ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot
{ Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount, ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName}).ToList() {
Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount,
ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName
}).ToList()
}).ToList(); }).ToList();
}, nameof(_clientHistoryCache), TimeSpan.MaxValue); }, nameof(_clientHistoryCache), TimeSpan.MaxValue);
@ -184,35 +190,36 @@ namespace IW4MAdmin.Application.Misc
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientHistoryAsync)); _logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientHistoryAsync));
return Enumerable.Empty<ClientHistoryInfo>(); return [];
} }
} }
public async Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default) public async Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucketCode = null,
CancellationToken token = default)
{ {
_rankedClientsCache.SetCacheItem((set, ids, cancellationToken) => _rankedClientsCache.SetCacheItem((set, idsList, cancellationToken) =>
{ {
long? id = null; long? id = null;
string bucket = null; string bucket = null;
if (ids.Any()) var ids = idsList.ToList();
if (ids.Count is not 0)
{ {
id = (long?)ids.First(); id = (long?)ids.First();
} }
if (ids.Count() == 2) if (ids.Count is 2)
{ {
bucket = (string)ids.Last(); bucket = (string)ids.Last();
} }
return _statManager.GetBucketConfig(serverId) return _statManager.GetBucketConfig(serverId)
.ContinueWith(result => _statManager.GetTotalRankedPlayers(id, bucket), cancellationToken).Result; .ContinueWith(result => _statManager.GetTotalRankedPlayers(id, bucket), cancellationToken).Result;
}, nameof(_rankedClientsCache), [serverId, performanceBucketCode], _cacheTimeSpan);
}, nameof(_rankedClientsCache), new object[] { serverId, performanceBucket }, _cacheTimeSpan);
try try
{ {
return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), new object[] { serverId, performanceBucket }, token); return await _rankedClientsCache.GetCacheItem(nameof(_rankedClientsCache), [serverId, performanceBucketCode], token);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -25,6 +25,7 @@ namespace Data.Context
#region STATS #region STATS
public DbSet<EFPerformanceBucket> PerformanceBuckets { get; set; }
public DbSet<Models.Vector3> Vector3s { get; set; } public DbSet<Models.Vector3> Vector3s { get; set; }
public DbSet<EFACSnapshotVector3> SnapshotVector3s { get; set; } public DbSet<EFACSnapshotVector3> SnapshotVector3s { get; set; }
public DbSet<EFACSnapshot> ACSnapshots { get; set; } public DbSet<EFACSnapshot> ACSnapshots { get; set; }

View File

@ -4,12 +4,17 @@ namespace Data.Models.Client.Stats;
public class EFPerformanceBucket public class EFPerformanceBucket
{ {
[Key] [Key] public int PerformanceBucketId { get; set; }
public int PerformanceBucketId { get; set; }
/// <summary>
/// Identifier for Bucket
/// </summary>
[MaxLength(256)] [MaxLength(256)]
public string BucketCode { get; set; } public string Code { get; set; }
/// <summary>
/// Friendly name for Bucket
/// </summary>
[MaxLength(256)] [MaxLength(256)]
public string BucketName { get; set; } public string Name { get; set; }
} }

View File

@ -290,16 +290,16 @@ public class HitCalculator : IClientStatisticCalculator
var matchingLocation = await GetOrAddHitLocation(hitInfo.Location, hitInfo.Game); var matchingLocation = await GetOrAddHitLocation(hitInfo.Location, hitInfo.Game);
var meansOfDeath = await GetOrAddMeansOfDeath(hitInfo.MeansOfDeath, hitInfo.Game); var meansOfDeath = await GetOrAddMeansOfDeath(hitInfo.MeansOfDeath, hitInfo.Game);
var baseTasks = new[] List<Task<EFClientHitStatistic>> baseTasks =
{ [
// just the client // just the client
GetOrAddClientHit(hitInfo.EntityId, null), GetOrAddClientHit(hitInfo.EntityId),
// client and server // client and server
GetOrAddClientHit(hitInfo.EntityId, serverId), GetOrAddClientHit(hitInfo.EntityId, serverId),
// just the location // just the location
GetOrAddClientHit(hitInfo.EntityId, null, matchingLocation.HitLocationId), GetOrAddClientHit(hitInfo.EntityId, hitLocationId: matchingLocation.HitLocationId),
// location and server // location and server
GetOrAddClientHit(hitInfo.EntityId, serverId, matchingLocation.HitLocationId), GetOrAddClientHit(hitInfo.EntityId, serverId, hitLocationId: matchingLocation.HitLocationId),
// per weapon // per weapon
GetOrAddClientHit(hitInfo.EntityId, null, null, weapon.WeaponId), GetOrAddClientHit(hitInfo.EntityId, null, null, weapon.WeaponId),
// per weapon and server // per weapon and server
@ -309,7 +309,7 @@ public class HitCalculator : IClientStatisticCalculator
// means of death per server aggregate // means of death per server aggregate
GetOrAddClientHit(hitInfo.EntityId, serverId, GetOrAddClientHit(hitInfo.EntityId, serverId,
meansOfDeathId: meansOfDeath.MeansOfDeathId) meansOfDeathId: meansOfDeath.MeansOfDeathId)
}; ];
var allTasks = baseTasks.AsEnumerable(); var allTasks = baseTasks.AsEnumerable();
@ -413,7 +413,7 @@ public class HitCalculator : IClientStatisticCalculator
} }
} }
private async Task<EFClientHitStatistic> GetOrAddClientHit(int clientId, long? serverId = null, string performanceBucket = null, private async Task<EFClientHitStatistic> GetOrAddClientHit(int clientId, long? serverId = null, string performanceBucketCode = null,
int? hitLocationId = null, int? weaponId = null, int? attachmentComboId = null, int? hitLocationId = null, int? weaponId = null, int? attachmentComboId = null,
int? meansOfDeathId = null) int? meansOfDeathId = null)
{ {
@ -425,7 +425,7 @@ public class HitCalculator : IClientStatisticCalculator
&& hit.WeaponId == weaponId && hit.WeaponId == weaponId
&& hit.WeaponAttachmentComboId == attachmentComboId && hit.WeaponAttachmentComboId == attachmentComboId
&& hit.MeansOfDeathId == meansOfDeathId && hit.MeansOfDeathId == meansOfDeathId
&& (performanceBucket is not null && performanceBucket == hit.Server.PerformanceBucket || (performanceBucket is null && hit.ServerId == serverId))); && (performanceBucketCode is not null && performanceBucketCode == hit.Server.PerformanceBucket.Code || (performanceBucketCode is null && hit.ServerId == serverId)));
if (hitStat != null) if (hitStat != null)
{ {

View File

@ -63,7 +63,7 @@ namespace Stats.Client
{ {
var bucketConfig = var bucketConfig =
_configuration.PerformanceBuckets.FirstOrDefault(bucket => _configuration.PerformanceBuckets.FirstOrDefault(bucket =>
bucket.Name == performanceBucket) ?? new PerformanceBucketConfiguration(); bucket.Code == performanceBucket) ?? new PerformanceBucketConfiguration();
var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration; var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration;
var performances = await iqPerformances.Where(s => s.ServerId == serverId) var performances = await iqPerformances.Where(s => s.ServerId == serverId)
@ -76,42 +76,43 @@ namespace Stats.Client
distributions.Add(serverId.ToString(), distributionParams); distributions.Add(serverId.ToString(), distributionParams);
} }
foreach (var performanceBucketGroup in _appConfig.Servers.Select(server => server.PerformanceBucket).Distinct()) foreach (var performanceBucket in _appConfig.Servers.Select(server => server.PerformanceBucketCode).Distinct())
{ {
var performanceBucket = performanceBucketGroup ?? "null"; // TODO: ?
var performanceBucketCode = performanceBucket ?? "null";
var bucketConfig = var bucketConfig =
_configuration.PerformanceBuckets.FirstOrDefault(bucket => _configuration.PerformanceBuckets.FirstOrDefault(bucket =>
bucket.Name == performanceBucket) ?? new PerformanceBucketConfiguration(); bucket.Code == performanceBucketCode) ?? new PerformanceBucketConfiguration();
var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration; var oldestPerf = DateTime.UtcNow - bucketConfig.RankingExpiration;
var performances = await iqPerformances var performances = await iqPerformances
.Where(perf => perf.Server.PerformanceBucket == performanceBucket) .Where(perf => perf.Server.PerformanceBucket.Code == performanceBucketCode)
.Where(perf => perf.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds) .Where(perf => perf.TimePlayed >= bucketConfig.ClientMinPlayTime.TotalSeconds)
.Where(perf => perf.UpdatedAt >= oldestPerf) .Where(perf => perf.UpdatedAt >= oldestPerf)
.Where(perf => perf.Skill < 999999) .Where(perf => perf.Skill < 999999)
.Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0) .Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0)
.ToListAsync(token); .ToListAsync(token);
var distributionParams = performances.GenerateDistributionParameters(); var distributionParams = performances.GenerateDistributionParameters();
distributions.Add(performanceBucket, distributionParams); distributions.Add(performanceBucketCode, distributionParams);
} }
return distributions; return distributions;
}, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1)); }, DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(1));
foreach (var performanceBucket in _appConfig.Servers.Select(s => s.PerformanceBucket).Distinct()) foreach (var performanceBucket in _appConfig.Servers.Select(s => s.PerformanceBucketCode).Distinct())
{ {
_maxZScoreCache.SetCacheItem(async (set, ids, token) => _maxZScoreCache.SetCacheItem(async (set, ids, token) =>
{ {
var validPlayTime = _configuration.TopPlayersMinPlayTime; var validPlayTime = _configuration.TopPlayersMinPlayTime;
var oldestStat = DateTime.UtcNow - Extensions.FifteenDaysAgo(); var oldestStat = DateTime.UtcNow - Extensions.FifteenDaysAgo();
var perfBucket = (string)ids.FirstOrDefault(); var localPerformanceBucket = (string)ids.FirstOrDefault();
if (!string.IsNullOrEmpty(perfBucket)) if (!string.IsNullOrEmpty(localPerformanceBucket))
{ {
var bucketConfig = var bucketConfig =
_configuration.PerformanceBuckets.FirstOrDefault(cfg => _configuration.PerformanceBuckets.FirstOrDefault(cfg =>
cfg.Name == perfBucket) ?? new PerformanceBucketConfiguration(); cfg.Code == localPerformanceBucket) ?? new PerformanceBucketConfiguration();
validPlayTime = (int)bucketConfig.ClientMinPlayTime.TotalSeconds; validPlayTime = (int)bucketConfig.ClientMinPlayTime.TotalSeconds;
oldestStat = bucketConfig.RankingExpiration; oldestStat = bucketConfig.RankingExpiration;
@ -121,7 +122,7 @@ namespace Stats.Client
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime, oldestStat)) .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime, oldestStat))
.Where(s => s.Skill > 0) .Where(s => s.Skill > 0)
.Where(s => s.EloRating >= 0) .Where(s => s.EloRating >= 0)
.Where(stat => perfBucket == stat.Server.PerformanceBucket) .Where(stat => localPerformanceBucket == stat.Server.PerformanceBucket.Code)
.GroupBy(stat => stat.ClientId) .GroupBy(stat => stat.ClientId)
.Select(group => .Select(group =>
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed)) group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed))
@ -170,7 +171,7 @@ namespace Stats.Client
await using var context = _contextFactory.CreateContext(false); await using var context = _contextFactory.CreateContext(false);
_serverIds.AddRange(await context.Servers _serverIds.AddRange(await context.Servers
.Where(s => s.EndPoint != null && s.HostName != null) .Where(s => s.EndPoint != null && s.HostName != null)
.Select(s => new Tuple<long, string>(s.ServerId, s.PerformanceBucket)) .Select(s => new Tuple<long, string>(s.ServerId, s.PerformanceBucket.Code))
.ToListAsync()); .ToListAsync());
} }
} }

View File

@ -5,7 +5,8 @@ namespace Stats.Config;
public class PerformanceBucketConfiguration public class PerformanceBucketConfiguration
{ {
public string Name { get; set; } public string FriendlyNameRENAME { get; set; }
public string Code { get; set; }
public TimeSpan ClientMinPlayTime { get; set; } = Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(3); public TimeSpan ClientMinPlayTime { get; set; } = Utilities.IsDevelopment ? TimeSpan.FromMinutes(1) : TimeSpan.FromHours(3);
public TimeSpan RankingExpiration { get; set; } = TimeSpan.FromDays(15); public TimeSpan RankingExpiration { get; set; } = TimeSpan.FromDays(15);
} }

View File

@ -7,6 +7,6 @@
/// </summary> /// </summary>
public int? ClientId { get; set; } public int? ClientId { get; set; }
public string ServerEndpoint { get; set; } public string ServerEndpoint { get; set; }
public string PerformanceBucket { get; set; } public string PerformanceBucketCode { get; set; }
} }
} }

View File

@ -66,8 +66,8 @@ namespace Stats.Helpers
.ThenInclude(attachment => attachment.Attachment3) .ThenInclude(attachment => attachment.Attachment3)
.Where(stat => stat.ClientId == query.ClientId); .Where(stat => stat.ClientId == query.ClientId);
iqHitStats = !string.IsNullOrEmpty(query.PerformanceBucket) iqHitStats = !string.IsNullOrEmpty(query.PerformanceBucketCode)
? iqHitStats.Where(stat => stat.Server.PerformanceBucket == query.PerformanceBucket) ? iqHitStats.Where(stat => stat.Server.PerformanceBucket.Code == query.PerformanceBucketCode)
: iqHitStats.Where(stat => stat.ServerId == serverId); : iqHitStats.Where(stat => stat.ServerId == serverId);
var hitStats = await iqHitStats.ToListAsync(); var hitStats = await iqHitStats.ToListAsync();
@ -76,7 +76,7 @@ namespace Stats.Helpers
.Where(r => r.ClientId == clientInfo.ClientId) .Where(r => r.ClientId == clientInfo.ClientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.Where(r => r.Ranking != null) .Where(r => r.Ranking != null)
.Where(r => r.PerformanceBucket == query.PerformanceBucket) .Where(r => r.PerformanceBucket.Code == query.PerformanceBucketCode)
.OrderByDescending(r => r.CreatedDateTime) .OrderByDescending(r => r.CreatedDateTime)
.Take(250) .Take(250)
.ToListAsync(); .ToListAsync();
@ -85,7 +85,7 @@ namespace Stats.Helpers
{ {
ClientId = query.ClientId, ClientId = query.ClientId,
ServerEndpoint = query.ServerEndpoint, ServerEndpoint = query.ServerEndpoint,
PerformanceBucket = query.PerformanceBucket PerformanceBucketCode = query.PerformanceBucketCode
})).Results.First(); })).Results.First();
var mostRecentRanking = ratings.FirstOrDefault(ranking => ranking.Newest); var mostRecentRanking = ratings.FirstOrDefault(ranking => ranking.Newest);
@ -95,7 +95,7 @@ namespace Stats.Helpers
var legacyStats = await context.Set<EFClientStatistics>() var legacyStats = await context.Set<EFClientStatistics>()
.Where(stat => stat.ClientId == query.ClientId) .Where(stat => stat.ClientId == query.ClientId)
.Where(stat => serverId == null || stat.ServerId == serverId) .Where(stat => serverId == null || stat.ServerId == serverId)
.Where(stat => stat.Server.PerformanceBucket == query.PerformanceBucket) .Where(stat => stat.Server.PerformanceBucket.Code == query.PerformanceBucketCode)
.ToListAsync(); .ToListAsync();
var bucketConfig = await statManager.GetBucketConfig(serverId); var bucketConfig = await statManager.GetBucketConfig(serverId);
@ -126,12 +126,12 @@ namespace Stats.Helpers
{ {
Name = server.Hostname, IPAddress = server.ListenAddress, Port = server.ListenPort, Name = server.Hostname, IPAddress = server.ListenAddress, Port = server.ListenPort,
Game = (Reference.Game)server.GameName, Game = (Reference.Game)server.GameName,
PerformanceBucket = server.PerformanceBucket PerformanceBucket = server.PerformanceCode
}) })
.Where(server => server.Game == clientInfo.GameName) .Where(server => server.Game == clientInfo.GameName)
.ToList(), .ToList(),
Aggregate = hitStats.FirstOrDefault(hit => Aggregate = hitStats.FirstOrDefault(hit =>
hit.HitLocationId == null && (string.IsNullOrEmpty(query.PerformanceBucket) || hit.ServerId == serverId) && hit.WeaponId == null && hit.HitLocationId == null && (string.IsNullOrEmpty(query.PerformanceBucketCode) || hit.ServerId == serverId) && hit.WeaponId == null &&
hit.MeansOfDeathId == null), hit.MeansOfDeathId == null),
ByHitLocation = hitStats ByHitLocation = hitStats
.Where(hit => hit.HitLocationId != null) .Where(hit => hit.HitLocationId != null)
@ -186,9 +186,9 @@ namespace Stats.Helpers
var currentRanking = 0; var currentRanking = 0;
int totalRankedClients; int totalRankedClients;
string performanceBucket; string performanceBucketCode;
if (string.IsNullOrEmpty(query.PerformanceBucket) && serverId is null) if (string.IsNullOrEmpty(query.PerformanceBucketCode) && serverId is null)
{ {
var maxPerformance = await context.Set<EFClientRankingHistory>() var maxPerformance = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == query.ClientId) .Where(r => r.ClientId == query.ClientId)
@ -204,27 +204,27 @@ namespace Stats.Helpers
{ {
currentRanking = 0; currentRanking = 0;
totalRankedClients = 0; totalRankedClients = 0;
performanceBucket = null; performanceBucketCode = null;
} }
else else
{ {
currentRanking = currentRanking =
await statManager.GetClientOverallRanking(query.ClientId!.Value, null, maxPerformance.Key); await statManager.GetClientOverallRanking(query.ClientId!.Value, null, maxPerformance.Key.Code);
totalRankedClients = await serverDataViewer.RankedClientsCountAsync(null, maxPerformance.Key); totalRankedClients = await serverDataViewer.RankedClientsCountAsync(null, maxPerformance.Key.Code);
performanceBucket = maxPerformance.Key; performanceBucketCode = maxPerformance.Key.Code;
} }
} }
else else
{ {
performanceBucket = query.PerformanceBucket; performanceBucketCode = query.PerformanceBucketCode;
currentRanking = currentRanking =
await statManager.GetClientOverallRanking(query.ClientId!.Value, serverId, performanceBucket); await statManager.GetClientOverallRanking(query.ClientId!.Value, serverId, performanceBucketCode);
totalRankedClients = await serverDataViewer.RankedClientsCountAsync(serverId, performanceBucket); totalRankedClients = await serverDataViewer.RankedClientsCountAsync(serverId, performanceBucketCode);
} }
return new ResourceQueryHelperResult<ClientRankingInfo> return new ResourceQueryHelperResult<ClientRankingInfo>
{ {
Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucket)] Results = [new ClientRankingInfo(currentRanking, totalRankedClients, performanceBucketCode)]
}; };
} }
} }

View File

@ -115,7 +115,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return 0; return 0;
} }
private Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null, string performanceBucket = null) private Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(TimeSpan oldestStat, TimeSpan minPlayTime, long? serverId = null, string performanceBucketCode = null)
{ {
var oldestDate = DateTime.UtcNow - oldestStat; var oldestDate = DateTime.UtcNow - oldestStat;
return ranking => ranking.ServerId == serverId return ranking => ranking.ServerId == serverId
@ -124,7 +124,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
&& ranking.ZScore != null && ranking.ZScore != null
&& ranking.PerformanceMetric != null && ranking.PerformanceMetric != null
&& ranking.Newest && ranking.Newest
&& ranking.PerformanceBucket == performanceBucket && ranking.PerformanceBucket.Code == performanceBucketCode
&& ranking.Client.TotalConnectionTime >= (int)minPlayTime.TotalSeconds; && ranking.Client.TotalConnectionTime >= (int)minPlayTime.TotalSeconds;
} }
@ -150,13 +150,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public DateTime CreatedDateTime { get; set; } public DateTime CreatedDateTime { get; set; }
} }
public async Task<List<TopStatsInfo>> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucket = null) public async Task<List<TopStatsInfo>> GetNewTopStats(int start, int count, long? serverId = null, string performanceBucketCode = null)
{ {
var bucketConfig = await GetBucketConfig(serverId); var bucketConfig = await GetBucketConfig(serverId);
await using var context = _contextFactory.CreateContext(false); await using var context = _contextFactory.CreateContext(false);
var clientIdsList = await context.Set<EFClientRankingHistory>() var clientIdsList = await context.Set<EFClientRankingHistory>()
.Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId, performanceBucket)) .Where(GetNewRankingFunc(bucketConfig.RankingExpiration, bucketConfig.ClientMinPlayTime, serverId: serverId, performanceBucketCode))
.OrderByDescending(ranking => ranking.PerformanceMetric) .OrderByDescending(ranking => ranking.PerformanceMetric)
.Select(ranking => ranking.ClientId) .Select(ranking => ranking.ClientId)
.Skip(start) .Skip(start)
@ -170,7 +170,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var eachRank = await context.Set<EFClientRankingHistory>() var eachRank = await context.Set<EFClientRankingHistory>()
.Where(ranking => ranking.ClientId == clientId) .Where(ranking => ranking.ClientId == clientId)
.Where(ranking => ranking.ServerId == serverId) .Where(ranking => ranking.ServerId == serverId)
.Where(ranking => ranking.PerformanceBucket == performanceBucket) .Where(ranking => ranking.PerformanceBucket.Code == performanceBucketCode)
.OrderByDescending(ranking => ranking.CreatedDateTime) .OrderByDescending(ranking => ranking.CreatedDateTime)
.Select(ranking => new RankingSnapshot .Select(ranking => new RankingSnapshot
{ {
@ -278,14 +278,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
foreach (var customMetricFunc in Plugin.ServerManager.CustomStatsMetrics) foreach (var customMetricFunc in Plugin.ServerManager.CustomStatsMetrics)
{ {
await customMetricFunc(finished.ToDictionary(kvp => kvp.ClientId, kvp => kvp.Metrics), serverId, await customMetricFunc(finished.ToDictionary(kvp => kvp.ClientId, kvp => kvp.Metrics), serverId,
performanceBucket, true); performanceBucketCode, true);
} }
return finished; return finished;
} }
public async Task<PerformanceBucketConfiguration> GetBucketConfig(long? serverId = null, public async Task<PerformanceBucketConfiguration> GetBucketConfig(long? serverId = null,
string bucketName = null) string performanceBucketCode = null)
{ {
var defaultConfig = new PerformanceBucketConfiguration var defaultConfig = new PerformanceBucketConfiguration
{ {
@ -293,26 +293,26 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
RankingExpiration = DateTime.UtcNow - Extensions.FifteenDaysAgo() RankingExpiration = DateTime.UtcNow - Extensions.FifteenDaysAgo()
}; };
if (serverId is null && bucketName is null) if (serverId is null && performanceBucketCode is null)
{ {
return defaultConfig; return defaultConfig;
} }
if (bucketName is not null) if (performanceBucketCode is not null)
{ {
return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Name == bucketName) ?? return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Code == performanceBucketCode) ??
defaultConfig; defaultConfig;
} }
var performanceBucket = var performanceBucket =
(await _serverCache.FirstAsync(server => server.Id == serverId)).PerformanceBucket; (await _serverCache.FirstAsync(server => server.Id == serverId))?.PerformanceBucket?.Code;
if (string.IsNullOrEmpty(performanceBucket)) if (string.IsNullOrEmpty(performanceBucket))
{ {
return defaultConfig; return defaultConfig;
} }
return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Name == performanceBucket) ?? return _config.PerformanceBuckets.FirstOrDefault(bucket => bucket.Code == performanceBucket) ??
defaultConfig; defaultConfig;
} }
@ -1268,7 +1268,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Include(stat => stat.Server) .Include(stat => stat.Server)
.Where(stat => stat.ClientId == clientId) .Where(stat => stat.ClientId == clientId)
.Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking .Where(stat => stat.ServerId != serverId) // ignore the one we're currently tracking
.Where(stat => stat.Server.PerformanceBucket == bucketConfig.Name) .Where(stat => stat.Server.PerformanceBucket.Code == bucketConfig.Code)
.Where(stats => stats.UpdatedAt >= oldestStateDate) .Where(stats => stats.UpdatedAt >= oldestStateDate)
.Where(stats => stats.TimePlayed >= (int)bucketConfig.ClientMinPlayTime.TotalSeconds) .Where(stats => stats.TimePlayed >= (int)bucketConfig.ClientMinPlayTime.TotalSeconds)
.ToListAsync(); .ToListAsync();
@ -1293,7 +1293,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var aggregateRanking = await context.Set<EFClientStatistics>() var aggregateRanking = await context.Set<EFClientStatistics>()
.Where(stat => stat.ClientId != clientId) .Where(stat => stat.ClientId != clientId)
.Where(stat => bucketConfig.Name == stat.Server.PerformanceBucket) .Where(stat => bucketConfig.Code == stat.Server.PerformanceBucket.Code)
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc((int)bucketConfig.ClientMinPlayTime.TotalSeconds, bucketConfig.RankingExpiration)) .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc((int)bucketConfig.ClientMinPlayTime.TotalSeconds, bucketConfig.RankingExpiration))
.GroupBy(stat => stat.ClientId) .GroupBy(stat => stat.ClientId)
.Where(group => .Where(group =>
@ -1302,7 +1302,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
.Select(c => c.Key) .Select(c => c.Key)
.CountAsync(); .CountAsync();
var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, bucketConfig.Name); var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore, bucketConfig.Code);
if (newPerformanceMetric == null) if (newPerformanceMetric == null)
{ {
@ -1311,19 +1311,23 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return; return;
} }
// TODO: CACHE THE EFPERFORMANCEBUCKET SO WE DON'T NEED TO LOOK IT UP - THERE WILL BE A REALISTIC LIMIT TO HOW MANY BUCKETS SHOULD BE LIVE
var performanceBucketId = (await context.PerformanceBuckets.FirstOrDefaultAsync(x => x.Code == bucketConfig.Code))
?.PerformanceBucketId;
var aggregateRankingSnapshot = new EFClientRankingHistory var aggregateRankingSnapshot = new EFClientRankingHistory
{ {
ClientId = clientId, ClientId = clientId,
ZScore = aggregateZScore, ZScore = aggregateZScore,
Ranking = aggregateRanking, Ranking = aggregateRanking,
PerformanceMetric = newPerformanceMetric, PerformanceMetric = newPerformanceMetric,
PerformanceBucket = bucketConfig.Name, PerformanceBucketId = performanceBucketId,
Newest = true, Newest = true,
}; };
context.Add(aggregateRankingSnapshot); context.Add(aggregateRankingSnapshot);
await PruneOldRankings(context, clientId, performanceBucket: bucketConfig.Name); await PruneOldRankings(context, clientId, performanceBucketCode: bucketConfig.Code);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
@ -1354,18 +1358,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null, string performanceBucket = null) private async Task PruneOldRankings(DatabaseContext context, int clientId, long? serverId = null, string performanceBucketCode = null)
{ {
var totalRankingEntries = await context.Set<EFClientRankingHistory>() var totalRankingEntries = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == clientId) .Where(r => r.ClientId == clientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.Where(r => r.PerformanceBucket == performanceBucket) .Where(r => r.PerformanceBucket.Code == performanceBucketCode)
.CountAsync(); .CountAsync();
var mostRecent = await context.Set<EFClientRankingHistory>() var mostRecent = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == clientId) .Where(r => r.ClientId == clientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.Where(r => r.PerformanceBucket == performanceBucket) .Where(r => r.PerformanceBucket.Code == performanceBucketCode)
.FirstOrDefaultAsync(r => r.Newest); .FirstOrDefaultAsync(r => r.Newest);
if (mostRecent != null) if (mostRecent != null)
@ -1381,7 +1385,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var lastRating = await context.Set<EFClientRankingHistory>() var lastRating = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == clientId) .Where(r => r.ClientId == clientId)
.Where(r => r.ServerId == serverId) .Where(r => r.ServerId == serverId)
.Where(r => r.PerformanceBucket == performanceBucket) .Where(r => r.PerformanceBucket.Code == performanceBucketCode)
.OrderBy(r => r.CreatedDateTime) .OrderBy(r => r.CreatedDateTime)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
@ -1397,6 +1401,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary> /// </summary>
/// <param name="attackerStats">Stats of the attacker</param> /// <param name="attackerStats">Stats of the attacker</param>
/// <param name="victimStats">Stats of the victim</param> /// <param name="victimStats">Stats of the victim</param>
/// <param name="attacker">Attacker</param>
/// <param name="victim">Victim</param>
public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats, public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats,
EFClient attacker, EFClient victim) EFClient attacker, EFClient victim)
{ {
@ -1438,8 +1444,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
attackerStats.EloRating = attackerStats.EloRating =
attackerEloRatingFunc?.Invoke(attacker, attackerStats) ?? attackerStats.EloRating; attackerEloRatingFunc?.Invoke(attacker, attackerStats) ?? attackerStats.EloRating;
var victimEloRatingFunc = // Unused? New code. TODO: Check if needed?
victim.GetAdditionalProperty<Func<EFClient, EFClientStatistics, double>>("EloRatingFunction"); //var victimEloRatingFunc =
// victim.GetAdditionalProperty<Func<EFClient, EFClientStatistics, double>>("EloRatingFunction");
victimStats.EloRating = victimStats.EloRating =
attackerEloRatingFunc?.Invoke(victim, victimStats) ?? victimStats.EloRating; attackerEloRatingFunc?.Invoke(victim, victimStats) ?? victimStats.EloRating;

View File

@ -620,7 +620,7 @@ public class ZombieClientStateManager(
} }
public async Task GetAdvancedStatsMetrics(Dictionary<int, List<EFMeta>> meta, long? serverId, public async Task GetAdvancedStatsMetrics(Dictionary<int, List<EFMeta>> meta, long? serverId,
string performanceBucket, string performanceBucketCode,
bool isTopStats) bool isTopStats)
{ {
if (isTopStats || !meta.Any()) if (isTopStats || !meta.Any())
@ -634,8 +634,8 @@ public class ZombieClientStateManager(
var iqStats = context.ZombieClientStatAggregates var iqStats = context.ZombieClientStatAggregates
.Where(stat => stat.ClientId == clientId); .Where(stat => stat.ClientId == clientId);
iqStats = !string.IsNullOrEmpty(performanceBucket) iqStats = !string.IsNullOrEmpty(performanceBucketCode)
? iqStats.Where(stat => stat.Server.PerformanceBucket == performanceBucket) ? iqStats.Where(stat => stat.Server.PerformanceBucket.Code == performanceBucketCode)
: iqStats.Where(stat => stat.ServerId == serverId); : iqStats.Where(stat => stat.ServerId == serverId);
var stats = await iqStats.Select(stat => new var stats = await iqStats.Select(stat => new

View File

@ -0,0 +1,29 @@
### Server reference PerformanceBucket
EFPerformanceBucket adding as DbSet to DatabaseContext
Create migration for EFClientStat
### Leaderboard logic
Using individual stats - but groups them by the bucket
Determines what the performance is by all the stats in that bucket / per server and for global stats. (BUCKET, SERVER, GLOBAL)
- Isn't properly calculating, when it goes to try and find where a person ranked in a performance bucket, it was returning null(?) so they never got a rank
- Potentially resolved by using EFPerformanceBucketId instead of arbitrary string
If new buckets are added to config, check on start and add to DB (PerformanceBucketConfgiuration)
- Needs to update to use the code
- CODE LOC `EnsureServerAdded()`
LOG OnPlayerRoundDataGameEvent ZOMBIES `CID - 848637`
### 2024-09-05
- Fixed references
- Added DbSet
- MIGRATION NOT CREATED
- StatManager Line 1313 needs checking.

View File

@ -47,7 +47,7 @@ namespace SharedLibraryCore.Configuration
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")] [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")]
[ConfigurationOptional] [ConfigurationOptional]
public string CustomHostname { get; set; } public string CustomHostname { get; set; }
public string PerformanceBucket { get; set; } public string PerformanceBucketCode { get; set; }
public IBaseConfiguration Generate() public IBaseConfiguration Generate()
{ {

View File

@ -45,9 +45,9 @@ namespace SharedLibraryCore.Interfaces
/// Retrieves the number of ranked clients for given server id /// Retrieves the number of ranked clients for given server id
/// </summary> /// </summary>
/// <param name="serverId">ServerId to query on</param> /// <param name="serverId">ServerId to query on</param>
/// <param name="performanceBucket"></param> /// <param name="performanceBucketCode"></param>
/// <param name="token">CancellationToken</param> /// <param name="token">CancellationToken</param>
/// <returns></returns> /// <returns></returns>
Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucket = null, CancellationToken token = default); Task<int> RankedClientsCountAsync(long? serverId = null, string performanceBucketCode = null, CancellationToken token = default);
} }
} }

View File

@ -83,7 +83,7 @@ namespace SharedLibraryCore
RConConnectionFactory = rconConnectionFactory; RConConnectionFactory = rconConnectionFactory;
ServerLogger = logger; ServerLogger = logger;
DefaultSettings = serviceProvider.GetRequiredService<DefaultSettings>(); DefaultSettings = serviceProvider.GetRequiredService<DefaultSettings>();
PerformanceBucket = ServerConfig.PerformanceBucket; PerformanceCode = ServerConfig.PerformanceBucketCode;
InitializeTokens(); InitializeTokens();
InitializeAutoMessages(); InitializeAutoMessages();
} }
@ -164,7 +164,7 @@ namespace SharedLibraryCore
public bool IsInitialized { get; set; } public bool IsInitialized { get; set; }
public int Port { get; protected set; } public int Port { get; protected set; }
public int ListenPort => Port; public int ListenPort => Port;
public string PerformanceBucket { get; init; } public string PerformanceCode { get; init; }
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty); public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
public abstract Task<string[]> ExecuteCommandAsync(string command, CancellationToken token = default); public abstract Task<string[]> ExecuteCommandAsync(string command, CancellationToken token = default);
public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default); public abstract Task SetDvarAsync(string name, object value, CancellationToken token = default);

View File

@ -35,7 +35,7 @@ namespace WebfrontCore.Controllers
{ {
ClientId = id, ClientId = id,
ServerEndpoint = serverId, ServerEndpoint = serverId,
PerformanceBucket = performanceBucket PerformanceBucketCode = performanceBucket
}))?.Results?.First(); }))?.Results?.First();
if (hitInfo is null) if (hitInfo is null)

View File

@ -72,7 +72,7 @@ namespace WebfrontCore.Controllers.Client.Legacy
IPAddress = selectedServer.ListenAddress, IPAddress = selectedServer.ListenAddress,
Port = selectedServer.ListenPort, Port = selectedServer.ListenPort,
Game = selectedServer.GameCode, Game = selectedServer.GameCode,
PerformanceBucket = selectedServer.PerformanceBucket PerformanceBucket = selectedServer.PerformanceCode
})); }));
} }