add performance logging on demand, fix minion issues
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Dalamud.Logging;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
@@ -20,13 +21,19 @@ internal class DalamudLogger : ILogger
|
||||
if (!IsEnabled(logLevel)) return;
|
||||
|
||||
if (exception == null)
|
||||
PluginLog.Information($"[{_name}]{{{(int)logLevel}}} {formatter(state, exception)}");
|
||||
else if (logLevel == LogLevel.Warning)
|
||||
PluginLog.Warning($"[{_name}]{{{(int)logLevel}}} {formatter(state, exception)}");
|
||||
else if (logLevel == LogLevel.Error)
|
||||
PluginLog.Error($"[{_name}]{{{(int)logLevel}}} {formatter(state, exception)}");
|
||||
PluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}");
|
||||
else
|
||||
PluginLog.Fatal($"[{_name}]{{{(int)logLevel}}} {formatter(state, exception)}");
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"[{_name}]{{{(int)logLevel}}} {state}: {exception.Message}");
|
||||
sb.AppendLine(exception.StackTrace);
|
||||
if (logLevel == LogLevel.Warning)
|
||||
PluginLog.Warning(sb.ToString());
|
||||
else if (logLevel == LogLevel.Error)
|
||||
PluginLog.Error(sb.ToString());
|
||||
else
|
||||
PluginLog.Fatal(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
|
||||
@@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using MareSynchronos.Mediator;
|
||||
using MareSynchronos.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
@@ -20,7 +21,7 @@ public class DalamudUtil : IDisposable
|
||||
private readonly Framework _framework;
|
||||
private readonly Condition _condition;
|
||||
private readonly MareMediator _mediator;
|
||||
|
||||
private readonly PerformanceCollector _performanceCollector;
|
||||
private uint? _classJobId = 0;
|
||||
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
|
||||
private bool _sentBetweenAreas = false;
|
||||
@@ -41,7 +42,7 @@ public class DalamudUtil : IDisposable
|
||||
}
|
||||
|
||||
public DalamudUtil(ILogger<DalamudUtil> logger, ClientState clientState, ObjectTable objectTable, Framework framework,
|
||||
Condition condition, Dalamud.Data.DataManager gameData, MareMediator mediator)
|
||||
Condition condition, Dalamud.Data.DataManager gameData, MareMediator mediator, PerformanceCollector performanceCollector)
|
||||
{
|
||||
_logger = logger;
|
||||
_clientState = clientState;
|
||||
@@ -49,6 +50,7 @@ public class DalamudUtil : IDisposable
|
||||
_framework = framework;
|
||||
_condition = condition;
|
||||
_mediator = mediator;
|
||||
_performanceCollector = performanceCollector;
|
||||
_framework.Update += FrameworkOnUpdate;
|
||||
if (IsLoggedIn)
|
||||
{
|
||||
@@ -64,7 +66,12 @@ public class DalamudUtil : IDisposable
|
||||
|
||||
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
|
||||
|
||||
private unsafe void FrameworkOnUpdate(Framework framework)
|
||||
private void FrameworkOnUpdate(Framework framework)
|
||||
{
|
||||
_performanceCollector.LogPerformance(this, "FrameworkOnUpdate", FrameworkOnUpdateInternal);
|
||||
}
|
||||
|
||||
private unsafe void FrameworkOnUpdateInternal()
|
||||
{
|
||||
if (GposeTarget != null && !IsInGpose)
|
||||
{
|
||||
@@ -171,9 +178,10 @@ public class DalamudUtil : IDisposable
|
||||
return obj != null && obj.IsValid();
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetMinion()
|
||||
public unsafe IntPtr GetMinion(IntPtr? playerPointer = null)
|
||||
{
|
||||
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
|
||||
playerPointer ??= PlayerPointer;
|
||||
return (IntPtr)((Character*)playerPointer)->CompanionObject;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
|
||||
@@ -190,6 +198,12 @@ public class DalamudUtil : IDisposable
|
||||
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetMinionOrMount(IntPtr? playerPointer = null)
|
||||
{
|
||||
playerPointer ??= PlayerPointer;
|
||||
return _objectTable.GetObjectAddress(((GameObject*)playerPointer)->ObjectIndex + 1);
|
||||
}
|
||||
|
||||
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
|
||||
public uint WorldId => _clientState.LocalPlayer!.HomeWorld.Id;
|
||||
|
||||
@@ -236,17 +250,6 @@ public class DalamudUtil : IDisposable
|
||||
return null;
|
||||
}
|
||||
|
||||
public unsafe IntPtr? GetMinionOrMount(IntPtr chara)
|
||||
{
|
||||
var minionOrMount = ((Character*)chara)->CompanionObject;
|
||||
if (minionOrMount != null)
|
||||
{
|
||||
return (IntPtr)minionOrMount;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
|
||||
{
|
||||
return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
|
||||
@@ -276,11 +279,11 @@ public class DalamudUtil : IDisposable
|
||||
}
|
||||
catch (NullReferenceException ex)
|
||||
{
|
||||
logger.LogWarning("Error accessing " + handler + ", object does not exist anymore?", ex);
|
||||
logger.LogWarning(ex, "Error accessing " + handler + ", object does not exist anymore?");
|
||||
}
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
logger.LogWarning("Error accessing " + handler + ", object does not exist anymore?", ex);
|
||||
logger.LogWarning(ex, "Error accessing " + handler + ", object does not exist anymore?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
168
MareSynchronos/Utils/PerformanceCollector.cs
Normal file
168
MareSynchronos/Utils/PerformanceCollector.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public class PerformanceCollector
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, RollingList<Tuple<TimeOnly, long>>> _performanceCounters = new(StringComparer.Ordinal);
|
||||
private readonly ILogger<PerformanceCollector> _logger;
|
||||
private readonly MareConfigService _mareConfigService;
|
||||
private const string counterSplit = "=>";
|
||||
|
||||
public PerformanceCollector(ILogger<PerformanceCollector> logger, MareConfigService mareConfigService)
|
||||
{
|
||||
_logger = logger;
|
||||
_mareConfigService = mareConfigService;
|
||||
}
|
||||
|
||||
public T LogPerformance<T>(object sender, string counterName, Func<T> func)
|
||||
{
|
||||
if (!_mareConfigService.Current.LogPerformance) return func.Invoke();
|
||||
|
||||
counterName = sender.GetType().Name + counterSplit + counterName;
|
||||
|
||||
if (!_performanceCounters.TryGetValue(counterName, out var list))
|
||||
{
|
||||
list = _performanceCounters[counterName] = new(10000);
|
||||
}
|
||||
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
return func.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
st.Stop();
|
||||
list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks));
|
||||
}
|
||||
}
|
||||
|
||||
public void LogPerformance(object sender, string counterName, Action act)
|
||||
{
|
||||
if (!_mareConfigService.Current.LogPerformance) { act.Invoke(); return; }
|
||||
|
||||
counterName = sender.GetType().Name + counterSplit + counterName;
|
||||
|
||||
if (!_performanceCounters.TryGetValue(counterName, out var list))
|
||||
{
|
||||
list = _performanceCounters[counterName] = new(10000);
|
||||
}
|
||||
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
act.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
st.Stop();
|
||||
list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void PrintPerformanceStats(int limitBySeconds = 0)
|
||||
{
|
||||
if (!_mareConfigService.Current.LogPerformance)
|
||||
{
|
||||
_logger.LogWarning("Performance counters are disabled");
|
||||
}
|
||||
|
||||
StringBuilder sb = new();
|
||||
if (limitBySeconds > 0)
|
||||
{
|
||||
sb.AppendLine($"Performance Metrics over the past {limitBySeconds} seconds of each counter");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("Performance metrics over total lifetime of each counter");
|
||||
}
|
||||
var data = _performanceCounters.ToList();
|
||||
var longestCounterName = data.OrderByDescending(d => d.Key.Length).First().Key.Length + 2;
|
||||
sb.Append("-Last".PadRight(15, '-'));
|
||||
sb.Append('|');
|
||||
sb.Append("-Max".PadRight(15, '-'));
|
||||
sb.Append('|');
|
||||
sb.Append("-Average".PadRight(15, '-'));
|
||||
sb.Append('|');
|
||||
sb.Append("-Last Update".PadRight(15, '-'));
|
||||
sb.Append('|');
|
||||
sb.Append("-Entries".PadRight(10, '-'));
|
||||
sb.Append('|');
|
||||
sb.Append("-Counter Name".PadRight(longestCounterName, '-'));
|
||||
sb.AppendLine();
|
||||
var orderedData = data.OrderBy(k => k.Key, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
var previousCaller = orderedData.First().Key.Split(counterSplit, StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
foreach (var entry in orderedData)
|
||||
{
|
||||
var newCaller = entry.Key.Split(counterSplit, StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
if (!string.Equals(previousCaller, newCaller, StringComparison.Ordinal))
|
||||
{
|
||||
DrawSeparator(sb, longestCounterName);
|
||||
}
|
||||
|
||||
var pastEntries = limitBySeconds > 0 ? entry.Value.Where(e => e.Item1.AddMinutes(limitBySeconds / 60.0d) >= TimeOnly.FromDateTime(DateTime.Now)).ToList() : entry.Value.ToList();
|
||||
|
||||
if (pastEntries.Any())
|
||||
{
|
||||
sb.Append((" " + TimeSpan.FromTicks(pastEntries.LastOrDefault()?.Item2 ?? 0).TotalMilliseconds.ToString("0.00000", CultureInfo.InvariantCulture)).PadRight(15));
|
||||
sb.Append('|');
|
||||
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('|');
|
||||
sb.Append(" -".PadRight(15));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
DrawSeparator(sb, longestCounterName);
|
||||
|
||||
_logger.LogInformation("{perf}", sb.ToString());
|
||||
}
|
||||
|
||||
private static void DrawSeparator(StringBuilder sb, int longestCounterName)
|
||||
{
|
||||
sb.Append("".PadRight(15, '-'));
|
||||
sb.Append('+');
|
||||
sb.Append("".PadRight(15, '-'));
|
||||
sb.Append('+');
|
||||
sb.Append("".PadRight(15, '-'));
|
||||
sb.Append('+');
|
||||
sb.Append("".PadRight(15, '-'));
|
||||
sb.Append('+');
|
||||
sb.Append("".PadRight(10, '-'));
|
||||
sb.Append('+');
|
||||
sb.Append("".PadRight(longestCounterName, '-'));
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
46
MareSynchronos/Utils/RollingList.cs
Normal file
46
MareSynchronos/Utils/RollingList.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public class RollingList<T> : IEnumerable<T>
|
||||
{
|
||||
private readonly LinkedList<T> _list = new();
|
||||
private readonly object _addLock = new();
|
||||
|
||||
public RollingList(int maximumCount)
|
||||
{
|
||||
if (maximumCount <= 0)
|
||||
throw new ArgumentException(message: null, nameof(maximumCount));
|
||||
|
||||
MaximumCount = maximumCount;
|
||||
}
|
||||
|
||||
public int MaximumCount { get; }
|
||||
public int Count => _list.Count;
|
||||
|
||||
public void Add(T value)
|
||||
{
|
||||
lock (_addLock)
|
||||
{
|
||||
if (_list.Count == MaximumCount)
|
||||
{
|
||||
_list.RemoveFirst();
|
||||
}
|
||||
_list.AddLast(value);
|
||||
}
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
|
||||
return _list.Skip(index).First();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
Reference in New Issue
Block a user