hopeful topstats fixes

This commit is contained in:
RaidMax 2022-01-24 15:08:31 -06:00
parent fb11bf54a6
commit 686b297d32
2 changed files with 47 additions and 22 deletions

View File

@ -22,7 +22,7 @@ namespace Stats.Client
private readonly IDataValueCache<EFClientStatistics, Dictionary<long, Extensions.LogParams>> private readonly IDataValueCache<EFClientStatistics, Dictionary<long, Extensions.LogParams>>
_distributionCache; _distributionCache;
private readonly IDataValueCache<EFClientStatistics, double> private readonly IDataValueCache<EFClientStatistics, double>
_maxZScoreCache; _maxZScoreCache;
@ -51,9 +51,9 @@ namespace Stats.Client
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3; var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
var distributions = new Dictionary<long, Extensions.LogParams>(); var distributions = new Dictionary<long, Extensions.LogParams>();
await LoadServers(); await LoadServers();
foreach (var serverId in _serverIds) foreach (var serverId in _serverIds)
{ {
var performance = await set var performance = await set
@ -63,14 +63,14 @@ namespace Stats.Client
.Where(s => s.Client.Level != EFClient.Permission.Banned) .Where(s => s.Client.Level != EFClient.Permission.Banned)
.Where(s => s.TimePlayed >= validPlayTime) .Where(s => s.TimePlayed >= validPlayTime)
.Where(s => s.UpdatedAt >= Extensions.FifteenDaysAgo()) .Where(s => s.UpdatedAt >= Extensions.FifteenDaysAgo())
.Select(s => s.EloRating * 1/3.0 + s.Skill * 2/3.0).ToListAsync(); .Select(s => s.EloRating * 1 / 3.0 + s.Skill * 2 / 3.0).ToListAsync();
var distributionParams = performance.GenerateDistributionParameters(); var distributionParams = performance.GenerateDistributionParameters();
distributions.Add(serverId, distributionParams); distributions.Add(serverId, distributionParams);
} }
return distributions; return distributions;
}), DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(1)); }), DistributionCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(1));
_maxZScoreCache.SetCacheItem(async (set, token) => _maxZScoreCache.SetCacheItem(async (set, token) =>
{ {
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3; var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
@ -79,13 +79,16 @@ namespace Stats.Client
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime)) .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(validPlayTime))
.Where(s => s.Skill > 0) .Where(s => s.Skill > 0)
.Where(s => s.EloRating > 0) .Where(s => s.EloRating > 0)
.MaxAsync(s => (double?)s.ZScore, token); .GroupBy(stat => stat.ClientId)
.Select(group =>
group.Sum(stat => stat.ZScore * stat.TimePlayed) / group.Sum(stat => stat.TimePlayed))
.MaxAsync(avgZScore => (double?) avgZScore, token);
return zScore ?? 0; return zScore ?? 0;
}, MaxZScoreCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30)); }, MaxZScoreCacheKey, Utilities.IsDevelopment ? TimeSpan.FromMinutes(5) : TimeSpan.FromMinutes(30));
await _distributionCache.GetCacheItem(DistributionCacheKey); await _distributionCache.GetCacheItem(DistributionCacheKey);
await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey); await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey);
/*foreach (var serverId in _serverIds) /*foreach (var serverId in _serverIds)
{ {
await using var ctx = _contextFactory.CreateContext(enableTracking: true); await using var ctx = _contextFactory.CreateContext(enableTracking: true);
@ -138,6 +141,7 @@ namespace Stats.Client
{ {
return 0.0; return 0.0;
} }
var zScore = (Math.Log(value) - sdParams.Mean) / sdParams.Sigma; var zScore = (Math.Log(value) - sdParams.Mean) / sdParams.Sigma;
return zScore; return zScore;
} }
@ -145,7 +149,7 @@ namespace Stats.Client
public async Task<double?> GetRatingForZScore(double? value) public async Task<double?> GetRatingForZScore(double? value)
{ {
var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey); var maxZScore = await _maxZScoreCache.GetCacheItem(MaxZScoreCacheKey);
return maxZScore == 0 ? 0 : value.GetRatingForZScore(maxZScore); return maxZScore == 0 ? null : value.GetRatingForZScore(maxZScore);
} }
} }
} }

View File

@ -121,7 +121,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{ {
return (ranking) => ranking.ServerId == serverId return (ranking) => ranking.ServerId == serverId
&& ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned && ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned
&& ranking.Client.LastConnection >= Extensions.FifteenDaysAgo() && ranking.CreatedDateTime >= Extensions.FifteenDaysAgo()
&& ranking.ZScore != null && ranking.ZScore != null
&& ranking.PerformanceMetric != null && ranking.PerformanceMetric != null
&& ranking.Newest && ranking.Newest
@ -895,7 +895,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task AddStandardKill(EFClient attacker, EFClient victim) public async Task AddStandardKill(EFClient attacker, EFClient victim)
{ {
long serverId = GetIdForServer(attacker.CurrentServer); var serverId = GetIdForServer(attacker.CurrentServer);
var attackerStats = attacker.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY); var attackerStats = attacker.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
var victimStats = victim.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY); var victimStats = victim.GetAdditionalProperty<EFClientStatistics>(CLIENT_STATS_KEY);
@ -903,6 +903,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
_servers[serverId].ServerStatistics.TotalKills += 1; _servers[serverId].ServerStatistics.TotalKills += 1;
if (attackerStats == null)
{
_log.LogWarning("Stats for {Client} are not yet initialized", attacker.ToString());
return;
}
if (victimStats == null)
{
_log.LogWarning("Stats for {Client} are not yet initialized", victim.ToString());
return;
}
// this happens when the round has changed // this happens when the round has changed
if (attackerStats.SessionScore == 0) if (attackerStats.SessionScore == 0)
{ {
@ -961,7 +973,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update their performance // update their performance
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >=
(Utilities.IsDevelopment ? 0.5 : _configHandler.Configuration().EnableAdvancedMetrics ? 10.0 : 2.5)) (Utilities.IsDevelopment ? 0.5 : _configHandler.Configuration().EnableAdvancedMetrics ? 5.0 : 2.5))
{ {
try try
{ {
@ -1178,16 +1190,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task UpdateHistoricalRanking(int clientId, EFClientStatistics clientStats, long serverId) public async Task UpdateHistoricalRanking(int clientId, EFClientStatistics clientStats, long serverId)
{ {
await using var context = _contextFactory.CreateContext(); await using var context = _contextFactory.CreateContext();
var minPlayTime = _configHandler.Configuration().TopPlayersMinPlayTime;
var performances = await context.Set<EFClientStatistics>() var performances = await context.Set<EFClientStatistics>()
.AsNoTracking() .AsNoTracking()
.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(stats => stats.UpdatedAt >= Extensions.FifteenDaysAgo()) .Where(stats => stats.UpdatedAt >= Extensions.FifteenDaysAgo())
.Where(stats => stats.TimePlayed >= _configHandler.Configuration().TopPlayersMinPlayTime) .Where(stats => stats.TimePlayed >= minPlayTime)
.ToListAsync(); .ToListAsync();
if (clientStats.TimePlayed >= _configHandler.Configuration().TopPlayersMinPlayTime) if (clientStats.TimePlayed >= minPlayTime)
{ {
clientStats.ZScore = await _serverDistributionCalculator.GetZScoreForServer(serverId, clientStats.ZScore = await _serverDistributionCalculator.GetZScoreForServer(serverId,
clientStats.Performance); clientStats.Performance);
@ -1198,7 +1211,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
_configHandler.Configuration().TopPlayersMinPlayTime, clientStats.ZScore, serverId)) _configHandler.Configuration().TopPlayersMinPlayTime, clientStats.ZScore, serverId))
.CountAsync(); .CountAsync();
var serverRankingSnapshot = new EFClientRankingHistory() var serverRankingSnapshot = new EFClientRankingHistory
{ {
ClientId = clientId, ClientId = clientId,
ServerId = serverId, ServerId = serverId,
@ -1215,27 +1228,35 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
performances.Add(clientStats); performances.Add(clientStats);
} }
if (performances.Any(performance => performance.TimePlayed >= _configHandler.Configuration().TopPlayersMinPlayTime)) if (performances.Any(performance => performance.TimePlayed >= minPlayTime))
{ {
var aggregateZScore = performances.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), _configHandler.Configuration().TopPlayersMinPlayTime); var aggregateZScore = performances.WeightValueByPlaytime(nameof(EFClientStatistics.ZScore), minPlayTime);
int? aggregateRanking = await context.Set<EFClientStatistics>() int? aggregateRanking = await context.Set<EFClientStatistics>()
.Where(stat => stat.ClientId != clientId) .Where(stat => stat.ClientId != clientId)
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(_configHandler.Configuration() .Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(minPlayTime))
.TopPlayersMinPlayTime))
.GroupBy(stat => stat.ClientId) .GroupBy(stat => stat.ClientId)
.Where(group => .Where(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) >
aggregateZScore) aggregateZScore)
.Select(c => c.Key) .Select(c => c.Key)
.CountAsync(); .CountAsync();
var newPerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore);
if (newPerformanceMetric == null)
{
_log.LogWarning("Could not determine performance metric for {Client} {AggregateZScore}",
clientStats.Client?.ToString(), aggregateZScore);
return;
}
var aggregateRankingSnapshot = new EFClientRankingHistory() var aggregateRankingSnapshot = new EFClientRankingHistory
{ {
ClientId = clientId, ClientId = clientId,
ZScore = aggregateZScore, ZScore = aggregateZScore,
Ranking = aggregateRanking, Ranking = aggregateRanking,
PerformanceMetric = await _serverDistributionCalculator.GetRatingForZScore(aggregateZScore), PerformanceMetric = newPerformanceMetric,
Newest = true, Newest = true,
}; };