add performance logging on demand, fix minion issues

This commit is contained in:
rootdarkarchon
2023-02-19 16:11:40 +01:00
parent 6cf0ecdef1
commit 44450b24b4
29 changed files with 580 additions and 294 deletions

View File

@@ -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)

View File

@@ -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?");
}
}

View 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();
}
}

View 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();
}