1.7.7-alpha-small-optimizations

- Clear bans cache on plugin reload
- Changed ip history to int
This commit is contained in:
Dawid Bepierszcz 2025-05-23 03:28:10 +02:00
parent f654d6b085
commit 3ab63c05db
11 changed files with 284 additions and 169 deletions

View file

@ -5,25 +5,29 @@ using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class CacheManager
internal class CacheManager: IDisposable
{
private readonly ConcurrentDictionary<int, BanRecord> _banCache = new();
private readonly ConcurrentDictionary<ulong, (HashSet<string> ips, DateTime usedAt, string playerName)> _playerIpsCache = new();
private HashSet<string> _cachedIgnoredIps;
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
private HashSet<uint> _cachedIgnoredIps = [];
private DateTime _lastUpdateTime = DateTime.MinValue;
private bool _isInitialized;
private bool _disposed;
public async Task InitializeCacheAsync()
{
if (CS2_SimpleAdmin.Database == null) return;
if (!CS2_SimpleAdmin.ServerLoaded) return;
if (_isInitialized) return;
_cachedIgnoredIps = [..CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps];
try
{
Clear();
_cachedIgnoredIps = new HashSet<uint>(
CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
.Select(IpHelper.IpToUint));
await using var connection = await CS2_SimpleAdmin.Database.GetConnectionAsync();
var bans = await connection.QueryAsync<BanRecord>(
"""
@ -44,26 +48,49 @@ internal class CacheManager
FROM sa_bans
""");
var ipHistory =
await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>(
await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC");
foreach (var ban in bans)
{
_banCache.TryAdd(ban.Id, ban);
}
foreach (var group in ipHistory.GroupBy(x => x.steamid))
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ips = new HashSet<string>(group.Select(x => x.address));
var lastUsed = group.Max(x => x.used_at);
var playerName = group.FirstOrDefault(x => !string.IsNullOrEmpty(x.name)).name
?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var ipSet = new HashSet<IpRecord>(
group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
!string.IsNullOrEmpty(latest.name)
? latest.name
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
);
}),
new IpRecordComparer()
);
_playerIpsCache[group.Key] = (ips, lastUsed, playerName);
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var ip in ipSet)
{
existingSet.Remove(ip);
existingSet.Add(ip);
}
return existingSet;
});
}
_lastUpdateTime = DateTime.Now;
_lastUpdateTime = DateTime.Now.AddSeconds(-1);
_isInitialized = true;
}
catch (Exception e)
@ -95,53 +122,44 @@ internal class CacheManager
var updatedBans = (await connection.QueryAsync<BanRecord>(
"SELECT * FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC",
new { lastUpdate = _lastUpdateTime }
)).ToList().AsValueEnumerable();
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, string address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC LIMIT 500")).ToList();
)).ToList();
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300", new {lastUpdate = _lastUpdateTime})).ToList();
// foreach (var group in ipHistory.GroupBy(x => x.steamid))
// {
// var ips = new HashSet<string>(group.Select(x => x.address));
// var lastUsed = group.Max(x => x.used_at);
// _playerIpsCache[group.Key] = (ips, lastUsed);
// }
var groupedData = ipHistory.AsValueEnumerable()
.GroupBy(x => x.steamid)
.ToList();
groupedData.ForEach(group =>
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ips = new HashSet<string>(
group.Select(x => x.address),
StringComparer.OrdinalIgnoreCase
var ipSet = new HashSet<IpRecord>(
group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
!string.IsNullOrEmpty(latest.name)
? latest.name
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
);
}),
new IpRecordComparer()
);
var lastUsed = group.Max(x => x.used_at);
var playerName = group
.OrderByDescending(x => x.used_at) // Prefer newer records
.Select(x => x.name)
.FirstOrDefault(name => !string.IsNullOrEmpty(name))
?? CS2_SimpleAdmin._localizer?["sa_unknown"]
?? "Unknown";
_playerIpsCache.AddOrUpdate(
group.Key,
// Add new entry
_ => (ips, lastUsed, playerName),
// Update existing
(_, existing) =>
_ => ipSet,
(_, existingSet) =>
{
existing.ips.UnionWith(ips);
return (
existing.ips,
lastUsed > existing.usedAt ? lastUsed : existing.usedAt,
string.IsNullOrEmpty(existing.playerName) ? playerName : existing.playerName
);
foreach (var newEntry in ipSet)
{
existingSet.Remove(newEntry);
existingSet.Add(newEntry);
}
return existingSet;
});
});
if (updatedBans.Count() == 0)
}
if (updatedBans.Count == 0)
return;
foreach (var ban in updatedBans)
@ -149,7 +167,7 @@ internal class CacheManager
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
}
_lastUpdateTime = DateTime.Now;
_lastUpdateTime = DateTime.Now.AddSeconds(-1);
}
catch (Exception e)
{
@ -160,52 +178,119 @@ internal class CacheManager
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.Status == "ACTIVE").ToList();
public List<BanRecord> GetPlayerBansBySteamId(string steamId) => _banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList();
public List<(ulong SteamId, string PlayerName)> GetAccountsByIp(string ipAddress)
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
{
var ipAsUint = IpHelper.IpToUint(ipAddress);
return _playerIpsCache.AsValueEnumerable()
.Where(kvp => kvp.Value.ips.Contains(ipAddress))
.Select(kvp => (kvp.Key, kvp.Value.playerName))
.SelectMany(kvp => kvp.Value
.Where(entry => entry.Ip == ipAsUint)
.Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName)))
.ToList();
}
private bool IsIpBanned(string ipAddress)
{
return _banCache.Values.Any(b =>
b.Status == "ACTIVE" &&
!string.IsNullOrEmpty(b.PlayerIp) &&
b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) &&
!_cachedIgnoredIps.Contains(ipAddress));
var ipUInt = IpHelper.IpToUint(ipAddress);
return _banCache.Values.Any(b =>
b is { Status: "ACTIVE", PlayerIp: not null } &&
IpHelper.IpToUint(b.PlayerIp) == ipUInt &&
!_cachedIgnoredIps.Contains(ipUInt));
}
public bool IsPlayerBanned(string? steamId, string? ipAddress) =>
_banCache.Values.Any(b =>
b.Status == "ACTIVE" && (
(steamId != null &&
b.PlayerSteamId != null &&
b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase)) ||
(ipAddress != null &&
b.PlayerIp != null &&
b.PlayerIp.Equals(ipAddress, StringComparison.OrdinalIgnoreCase) &&
!_cachedIgnoredIps.Contains(ipAddress))
));
public bool IsPlayerBanned(string? steamId, string? ipAddress)
{
if (ipAddress == null)
return _banCache.Values.Any(b =>
b.Status == "ACTIVE" &&
steamId != null &&
b.PlayerSteamId != null &&
b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase));
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
return false;
return _banCache.Values.Any(b =>
b is { Status: "ACTIVE", PlayerIp: not null } &&
(
(steamId != null &&
b.PlayerSteamId != null &&
b.PlayerSteamId.Equals(steamId, StringComparison.OrdinalIgnoreCase))
||
(IpHelper.TryConvertIpToUint(b.PlayerIp, out var bIpUint) &&
bIpUint == ipUInt &&
!_cachedIgnoredIps.Contains(ipUInt))
)
);
}
public bool IsPlayerOrAnyIpBanned(ulong steamId)
public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
{
var steamIdStr = steamId.ToString();
if (_banCache.Values.Any(b =>
if (_banCache.Values.Any(b =>
b.Status == "ACTIVE" &&
b.PlayerSteamId?.Equals(steamIdStr, StringComparison.OrdinalIgnoreCase) == true))
{
return true;
}
return _playerIpsCache.TryGetValue(steamId, out var ipList) && ipList.ips.Any(ip =>
!_cachedIgnoredIps.Contains(ip) && IsIpBanned(ip));
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
return false;
var now = DateTime.Now;
var cutoff = now.AddDays(-7);
if (ipAddress != null)
{
var ipAsUint = IpHelper.IpToUint(ipAddress);
ipData.Add(new IpRecord(
ipAsUint,
now.AddSeconds(-2),
CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
));
}
return ipData.Any(x =>
x.UsedAt >= cutoff &&
!_cachedIgnoredIps.Contains(x.Ip) &&
_banCache.Values.Any(b =>
b is { Status: "ACTIVE", PlayerIp: not null } &&
IpHelper.TryConvertIpToUint(b.PlayerIp, out var banIpUint) &&
banIpUint == x.Ip
));
}
public bool HasIpForPlayer(ulong steamId, string ipAddress)
{
return _playerIpsCache.TryGetValue(steamId, out var ipList)
&& ipList.ips.Contains(ipAddress);
if (string.IsNullOrWhiteSpace(ipAddress))
return false;
return _playerIpsCache.TryGetValue(steamId, out var ipData)
&& ipData.Any(x => x.Ip == IpHelper.IpToUint(ipAddress));
}
private void Clear()
{
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps.Clear();
}
public void Dispose()
{
if (_disposed) return;
Clear();
_disposed = true;
}
}
public class IpRecordComparer : IEqualityComparer<IpRecord>
{
public bool Equals(IpRecord x, IpRecord y)
=> x.Ip == y.Ip;
public int GetHashCode(IpRecord obj)
=> obj.Ip.GetHashCode();
}