From 3715ca5ce3fbdf1a1d7931ace20815f26cbedda4 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sat, 29 Apr 2023 23:32:54 +0200 Subject: [PATCH] the need for speed --- MareSynchronos/Interop/IpcManager.cs | 50 ++++++++++++------- .../PlayerData/Pairs/CachedPlayer.cs | 32 ++++++------ MareSynchronos/PlayerData/Pairs/Pair.cs | 32 ++++++++---- .../PlayerData/Pairs/PairManager.cs | 27 ---------- MareSynchronos/Services/DalamudUtilService.cs | 17 +++---- .../Services/Mediator/MareMediator.cs | 2 +- MareSynchronos/Services/Mediator/Messages.cs | 2 +- .../Services/PerformanceCollectorService.cs | 22 +++----- 8 files changed, 82 insertions(+), 102 deletions(-) diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index 40d11e7..b587029 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services.Mediator; using MareSynchronos.Services; +using System; namespace MareSynchronos.Interop; @@ -24,6 +25,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter; private readonly DalamudUtilService _dalamudUtil; private readonly ICallGateSubscriber _glamourerApiVersion; + private readonly SemaphoreSlim _glamourerApplicationSemaphore = new(2); private readonly ICallGateSubscriber? _glamourerApplyAll; private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; @@ -212,30 +214,37 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task GlamourerApplyAll(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; - var gameObj = _dalamudUtil.CreateGameObject(handler.Address); - if (gameObj is Character c) + try { - await PenumbraRedrawAsync(logger, handler, applicationId, () => _glamourerApplyAll!.InvokeAction(customization, c), fireAndForget, token).ConfigureAwait(false); + await _glamourerApplicationSemaphore.WaitAsync().ConfigureAwait(true); + var gameObj = _dalamudUtil.CreateGameObject(handler.Address); + if (gameObj is Character c) + { + await PenumbraRedrawAsync(logger, handler, applicationId, () => _glamourerApplyAll!.InvokeAction(customization, c), fireAndForget, token).ConfigureAwait(false); + } + } + finally + { + _glamourerApplicationSemaphore.Release(); } } - public async Task GlamourerApplyOnlyCustomization(ILogger logger, GameObjectHandler handler, string customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) + public async Task GlamourerApplyCustomizationAndEquipment(ILogger logger, GameObjectHandler handler, string customization, string equipment, Guid applicationid, CancellationToken token, bool fireAndForget = false) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; - var gameObj = _dalamudUtil.CreateGameObject(handler.Address); - if (gameObj is Character c) + try { - await PenumbraRedrawAsync(logger, handler, applicationId, () => _glamourerApplyOnlyCustomization!.InvokeAction(customization, c), fireAndForget, token).ConfigureAwait(false); + await _glamourerApplicationSemaphore.WaitAsync().ConfigureAwait(false); + var gameObj = _dalamudUtil.CreateGameObject(handler.Address); + if (gameObj is Character c) + { + await PenumbraRedrawAsync(logger, handler, applicationid, () => _glamourerApplyOnlyEquipment!.InvokeAction(customization, c), fireAndForget, token).ConfigureAwait(false); + await PenumbraRedrawAsync(logger, handler, applicationid, () => _glamourerApplyOnlyEquipment!.InvokeAction(equipment, c), fireAndForget, token).ConfigureAwait(false); + } } - } - - public async Task GlamourerApplyOnlyEquipment(ILogger logger, GameObjectHandler handler, string customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) - { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; - var gameObj = _dalamudUtil.CreateGameObject(handler.Address); - if (gameObj is Character c) + finally { - await PenumbraRedrawAsync(logger, handler, applicationId, () => _glamourerApplyOnlyEquipment!.InvokeAction(customization, c), fireAndForget, token).ConfigureAwait(false); + _glamourerApplicationSemaphore.Release(); } } @@ -387,11 +396,14 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task PenumbraRedraw(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token, bool fireAndForget = false) { if (!CheckPenumbraApi() || _dalamudUtil.IsZoning) return; - var gameObj = _dalamudUtil.CreateGameObject(handler.Address); - if (gameObj is Character c) + await _dalamudUtil.RunOnFrameworkThread(async () => { - await PenumbraRedrawAsync(logger, handler, applicationId, () => _penumbraRedrawObject!.Invoke(c, RedrawType.Redraw), fireAndForget, token).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(handler.Address); + if (gameObj is Character c) + { + await PenumbraRedrawAsync(logger, handler, applicationId, () => _penumbraRedrawObject!.Invoke(c, RedrawType.Redraw), fireAndForget, token).ConfigureAwait(false); + } + }).ConfigureAwait(false); } public async Task PenumbraRemoveTemporaryCollection(ILogger logger, Guid applicationId, string characterName) diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs index 0629d00..9b30d76 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs @@ -78,7 +78,6 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase : ((GameObject*)_charaHandler!.Address)->ObjectID; public string? PlayerName { get; private set; } public string PlayerNameHash => OnlineUser.Ident; - public uint? PlayerWorld { get; private set; } public void ApplyCharacterData(CharacterData characterData, bool forced = false) { @@ -157,7 +156,6 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase SetUploading(false); _downloadManager.Dispose(); var name = PlayerName; - var world = PlayerWorld; Logger.LogDebug("Disposing {name} ({user})", name, OnlineUser); try { @@ -180,7 +178,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase { try { - RevertCustomizationData(item.Key, name, world ?? 0, applicationId).GetAwaiter().GetResult(); + RevertCustomizationData(item.Key, name, applicationId).GetAwaiter().GetResult(); } catch (InvalidOperationException ex) { @@ -469,6 +467,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase foreach (var kind in updatedData) { await ApplyCustomizationData(_applicationId, kind, charaData, token).ConfigureAwait(false); + token.ThrowIfCancellationRequested(); } Logger.LogDebug("[{applicationId}] Application finished", _applicationId); @@ -488,7 +487,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); if (pc == null) return; Logger.LogDebug("One-Time Initializing {this}", this); - Initialize(pc.Name.ToString(), pc.HomeWorld.Id); + Initialize(pc.Name.ToString()); Logger.LogDebug("One-Time Initialized {this}", this); } @@ -498,10 +497,13 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Mediator.Publish(new CachedPlayerVisibleMessage(this)); Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); _framesSinceNotVisible = 0; - _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).GetAwaiter().GetResult(); if (_cachedData != null) { - Task.Run(() => ApplyCharacterData(_cachedData, true)); + Task.Run(async () => + { + _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false); + ApplyCharacterData(_cachedData, true); + }); } } else if (_charaHandler?.Address == IntPtr.Zero && IsVisible) @@ -515,11 +517,10 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase } } - private void Initialize(string name, uint worldid) + private void Initialize(string name) { PlayerName = name; - PlayerWorld = worldid; - _charaHandler = _gameObjectHandlerFactory(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName, worldid), false); + _charaHandler = _gameObjectHandlerFactory(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByIdent(OnlineUser.Ident), false); _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult(); _lastGlamourerData = _originalGlamourerData; @@ -589,13 +590,13 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase } } - private async Task RevertCustomizationData(ObjectKind objectKind, string name, uint world, Guid applicationId) + private async Task RevertCustomizationData(ObjectKind objectKind, string name, Guid applicationId) { - nint address = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(name, world); + nint address = _dalamudUtil.GetPlayerCharacterFromObjectTableByIdent(OnlineUser.Ident); if (address == IntPtr.Zero) return; var cancelToken = new CancellationTokenSource(); - cancelToken.CancelAfter(TimeSpan.FromSeconds(10)); + cancelToken.CancelAfter(TimeSpan.FromSeconds(60)); Logger.LogDebug("[{applicationId}] Reverting all Customization for {alias}/{name} {objectKind}", applicationId, OnlineUser.User.AliasOrUID, name, objectKind); @@ -603,11 +604,8 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase { using GameObjectHandler tempHandler = _gameObjectHandlerFactory(ObjectKind.Player, () => address, false); CheckForNameAndThrow(tempHandler, name); - Logger.LogDebug("[{applicationId}] Restoring Customization for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData); - await _ipcManager.GlamourerApplyOnlyCustomization(Logger, tempHandler, _originalGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); - CheckForNameAndThrow(tempHandler, name); - Logger.LogDebug("[{applicationId}] Restoring Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _lastGlamourerData); - await _ipcManager.GlamourerApplyOnlyEquipment(Logger, tempHandler, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); + Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}: {data}", applicationId, OnlineUser.User.AliasOrUID, name, _originalGlamourerData); + await _ipcManager.GlamourerApplyCustomizationAndEquipment(Logger, tempHandler, _originalGlamourerData, _lastGlamourerData, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); CheckForNameAndThrow(tempHandler, name); Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); await _ipcManager.HeelsRestoreOffsetForPlayer(address).ConfigureAwait(false); diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index 89d8f82..3db557d 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -14,6 +14,7 @@ namespace MareSynchronos.PlayerData.Pairs; public class Pair { private readonly Func _cachedPlayerFactory; + private readonly SemaphoreSlim _creationSemaphore = new(1); private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ServerConfigurationManager _serverConfigurationManager; @@ -89,19 +90,28 @@ public class Pair public void CreateCachedPlayer(OnlineUserIdentDto? dto = null) { - if (dto == null && _onlineUserIdentDto == null) + try { - CachedPlayer?.Dispose(); - CachedPlayer = null; - return; - } - if (dto != null) - { - _onlineUserIdentDto = dto; - } + _creationSemaphore.Wait(); - CachedPlayer?.Dispose(); - CachedPlayer = _cachedPlayerFactory(_onlineUserIdentDto!); + if (dto == null && _onlineUserIdentDto == null) + { + CachedPlayer?.Dispose(); + CachedPlayer = null; + return; + } + if (dto != null) + { + _onlineUserIdentDto = dto; + } + + CachedPlayer?.Dispose(); + CachedPlayer = _cachedPlayerFactory(_onlineUserIdentDto!); + } + finally + { + _creationSemaphore.Release(); + } } public string? GetNote() diff --git a/MareSynchronos/PlayerData/Pairs/PairManager.cs b/MareSynchronos/PlayerData/Pairs/PairManager.cs index e30da4a..3bb6708 100644 --- a/MareSynchronos/PlayerData/Pairs/PairManager.cs +++ b/MareSynchronos/PlayerData/Pairs/PairManager.cs @@ -1,5 +1,4 @@ using Dalamud.ContextMenu; -using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Interface.Internal.Notifications; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Comparer; @@ -8,7 +7,6 @@ using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; -using MareSynchronos.Utils; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; @@ -20,7 +18,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase private readonly ConcurrentDictionary _allGroups = new(GroupDataComparer.Instance); private readonly MareConfigService _configurationService; private readonly DalamudContextMenu _dalamudContextMenu; - private readonly Dictionary _indexedPairs = new(StringComparer.Ordinal); private readonly Func _pairFactory; private Lazy> _directPairsInternal; private Lazy>> _groupPairsInternal; @@ -32,7 +29,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase _pairFactory = pairFactory; _configurationService = configurationService; _dalamudContextMenu = dalamudContextMenu; - Mediator.Subscribe(this, (_) => DalamudUtilOnDelayedFrameworkUpdate()); Mediator.Subscribe(this, (_) => ClearPairs()); Mediator.Subscribe(this, (_) => ReapplyPairData()); _directPairsInternal = DirectPairsLazy(); @@ -89,18 +85,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase RecreateLazy(); } - public List<(PlayerCharacter Character, Pair Pair)> FindAllPairs(List playerCharacters) - { - return playerCharacters.Select(p => (p, _indexedPairs.TryGetValue(p.GetHash256(), out var pair) ? pair : null)).Where(p => p.Item2 != null).ToList()!; - } - - public Pair? FindPair(PlayerCharacter? pChar) - { - if (pChar == null) return null; - var hash = pChar.GetHash256(); - return _allClientPairs.Values.FirstOrDefault(f => string.Equals(hash, f.GetPlayerNameHash())); - } - public List GetOnlineUserPairs() => _allClientPairs.Where(p => !string.IsNullOrEmpty(p.Value.GetPlayerNameHash())).Select(p => p.Value).ToList(); public List GetVisibleUsers() => _allClientPairs.Where(p => p.Value.IsVisible).Select(p => p.Key).ToList(); @@ -341,17 +325,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase } } - private void DalamudUtilOnDelayedFrameworkUpdate() - { - _indexedPairs.Clear(); - foreach (var pair in _allClientPairs.Values.Where(p => string.IsNullOrEmpty(p.PlayerName))) - { - var hash = pair.GetPlayerNameHash(); - if (string.IsNullOrEmpty(hash)) continue; - _indexedPairs[hash] = pair; - } - } - private Lazy> DirectPairsLazy() => new(() => _allClientPairs.Select(k => k.Value).Where(k => k.UserPair != null).ToList()); private void DisposePairs() diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index fb27421..ab53dec 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -11,7 +11,6 @@ using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; @@ -31,7 +30,7 @@ public class DalamudUtilService : IHostedService private readonly List ClassJobIdsIgnoredForPets = new() { 30 }; private uint? _classJobId = 0; private DateTime _delayedFrameworkUpdateCheck = DateTime.Now; - private Dictionary _playerCharas = new(StringComparer.Ordinal); + private Dictionary _playerCharas = new(StringComparer.Ordinal); private bool _sentBetweenAreas = false; public DalamudUtilService(ILogger logger, ClientState clientState, ObjectTable objectTable, Framework framework, @@ -113,17 +112,12 @@ public class DalamudUtilService : IHostedService return await RunOnFrameworkThread(() => GetPetInternal(playerPointer)).ConfigureAwait(false); } - public IntPtr GetPlayerCharacterFromObjectTableByName(string characterName, uint worldid) + public IntPtr GetPlayerCharacterFromObjectTableByIdent(string characterName) { - if (_playerCharas.TryGetValue(characterName + worldid.ToString(CultureInfo.InvariantCulture), out var pchar)) return pchar; + if (_playerCharas.TryGetValue(characterName, out var pchar)) return pchar.Address; return IntPtr.Zero; } - public List GetPlayerCharacters() - { - return _objectTable.OfType().ToList(); - } - public unsafe bool IsGameObjectPresent(IntPtr key) { return _objectTable.Any(f => f.Address == key); @@ -238,7 +232,8 @@ public class DalamudUtilService : IHostedService internal PlayerCharacter? FindPlayerByNameHash(string ident) { - return _objectTable.OfType().FirstOrDefault(p => p.GetHash256().Equals(ident, StringComparison.Ordinal)); + _playerCharas.TryGetValue(ident, out var result); + return result; } private void FrameworkOnUpdate(Framework framework) @@ -250,7 +245,7 @@ public class DalamudUtilService : IHostedService { if (_clientState.LocalPlayer?.IsDead ?? false) return; - _playerCharas = _objectTable.OfType().ToDictionary(p => p.Name.ToString() + p.HomeWorld.Id.ToString(), p => p.Address, StringComparer.Ordinal); + _playerCharas = _performanceCollector.LogPerformance(this, "ObjTableToCharas", () => _objectTable.OfType().ToDictionary(p => p.GetHash256(), p => p, StringComparer.Ordinal)); if (GposeTarget != null && !IsInGpose) { diff --git a/MareSynchronos/Services/Mediator/MareMediator.cs b/MareSynchronos/Services/Mediator/MareMediator.cs index 7ec0baa..45f6fa1 100644 --- a/MareSynchronos/Services/Mediator/MareMediator.cs +++ b/MareSynchronos/Services/Mediator/MareMediator.cs @@ -160,7 +160,7 @@ public sealed class MareMediator : IHostedService private void ExecuteSubscriber(SubscriberAction subscriber, T message) where T : MessageBase { var isSameThread = message.KeepThreadContext ? "$" : string.Empty; - _performanceCollector.LogPerformance(this, $"{isSameThread}Execute>{message.GetType().Name}+{subscriber.Subscriber.GetType().Name}", () => ((Action)subscriber.Action).Invoke(message)); + _performanceCollector.LogPerformance(this, $"{isSameThread}Execute>{message.GetType().Name}+{subscriber.Subscriber.GetType().Name}>{subscriber.Subscriber}", () => ((Action)subscriber.Action).Invoke(message)); } private sealed class SubscriberAction diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index c30da00..d8535af 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -30,7 +30,7 @@ public record DisconnectedMessage : SameThreadMessage; public record PenumbraModSettingChangedMessage : MessageBase; public record PenumbraInitializedMessage : MessageBase; public record PenumbraDisposedMessage : MessageBase; -public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx, bool WasRequested) : MessageBase; +public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx, bool WasRequested) : SameThreadMessage; public record HeelsOffsetMessage : MessageBase; public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : SameThreadMessage; public record CustomizePlusMessage : MessageBase; diff --git a/MareSynchronos/Services/PerformanceCollectorService.cs b/MareSynchronos/Services/PerformanceCollectorService.cs index 47700cc..4f08b26 100644 --- a/MareSynchronos/Services/PerformanceCollectorService.cs +++ b/MareSynchronos/Services/PerformanceCollectorService.cs @@ -130,22 +130,14 @@ public sealed class PerformanceCollectorService : IHostedService sb.Append((" " + TimeSpan.FromTicks(pastEntries.Max(m => m.Item2)).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15)); sb.Append('|'); sb.Append((" " + TimeSpan.FromTicks((long)pastEntries.Average(m => m.Item2)).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15)); - } - else - { - sb.Append(" -".PadRight(15)); sb.Append('|'); - sb.Append(" -".PadRight(15)); + sb.Append((" " + (pastEntries.LastOrDefault()?.Item1.ToString("HH:mm:ss.ffff", CultureInfo.InvariantCulture) ?? "-")).PadRight(15, ' ')); sb.Append('|'); - sb.Append(" -".PadRight(15)); + sb.Append((" " + pastEntries.Count).PadRight(10)); + sb.Append('|'); + sb.Append(' ').Append(entry.Key); + sb.AppendLine(); } - sb.Append('|'); - sb.Append((" " + (pastEntries.LastOrDefault()?.Item1.ToString("HH:mm:ss.ffff", CultureInfo.InvariantCulture) ?? "-")).PadRight(15, ' ')); - sb.Append('|'); - sb.Append((" " + pastEntries.Count).PadRight(10)); - sb.Append('|'); - sb.Append(' ').Append(entry.Key); - sb.AppendLine(); previousCaller = newCaller; } @@ -182,9 +174,9 @@ public sealed class PerformanceCollectorService : IHostedService try { var last = entries.Value.ToList()[^1]; - if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now)) + if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now) && !_performanceCounters.TryRemove(entries.Key, out _)) { - _performanceCounters.Remove(entries.Key, out _); + _logger.LogDebug("Could not remove performance counter {counter}", entries.Key); } } catch (Exception e)