From f1f7803434b367bdf636eeb7c7191928bd2c4a0a Mon Sep 17 00:00:00 2001 From: Cara Date: Tue, 15 Dec 2020 02:04:00 +1030 Subject: [PATCH] Stat tracking for Framework Updates to help developers see if their plugin is slowing the game down or not removing their framework update events. --- Dalamud/Game/Internal/Framework.cs | 39 +++++++++++- Dalamud/Interface/DalamudPluginStatWindow.cs | 66 ++++++++++++++++++-- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs index e65ead3a0..e55f502ee 100644 --- a/Dalamud/Game/Internal/Framework.cs +++ b/Dalamud/Game/Internal/Framework.cs @@ -1,11 +1,13 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using Dalamud.Game.Internal.Gui; using Dalamud.Game.Internal.Libc; using Dalamud.Game.Internal.Network; using Dalamud.Hooking; using Serilog; -using Dalamud.Game.Internal.File; namespace Dalamud.Game.Internal { /// @@ -30,6 +32,11 @@ namespace Dalamud.Game.Internal { /// public FrameworkAddressResolver Address { get; } +#region Stats + public static bool StatsEnabled { get; set; } + public static Dictionary> StatsHistory = new Dictionary>(); + private static Stopwatch statsStopwatch = new Stopwatch(); +#endregion #region Subsystems /// @@ -108,7 +115,35 @@ namespace Dalamud.Game.Internal { } try { - OnUpdateEvent?.Invoke(this); + if (StatsEnabled && OnUpdateEvent != null) { + // Stat Tracking for Framework Updates + var invokeList = OnUpdateEvent.GetInvocationList(); + var notUpdated = StatsHistory.Keys.ToList(); + // Individually invoke OnUpdate handlers and time them. + foreach (var d in invokeList) { + statsStopwatch.Restart(); + d.Method.Invoke(d.Target, new object[]{ this }); + statsStopwatch.Stop(); + var key = $"{d.Target}::{d.Method.Name}"; + if (notUpdated.Contains(key)) notUpdated.Remove(key); + if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List()); + StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); + if (StatsHistory[key].Count > 1000) { + StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); + } + } + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) { + if (StatsHistory[key].Count > 0) { + StatsHistory[key].RemoveAt(0); + } else { + StatsHistory.Remove(key); + } + } + } else { + OnUpdateEvent?.Invoke(this); + } } catch (Exception ex) { Log.Error(ex, "Exception while dispatching Framework::Update event."); } diff --git a/Dalamud/Interface/DalamudPluginStatWindow.cs b/Dalamud/Interface/DalamudPluginStatWindow.cs index 7862075f1..0aad890fd 100644 --- a/Dalamud/Interface/DalamudPluginStatWindow.cs +++ b/Dalamud/Interface/DalamudPluginStatWindow.cs @@ -1,14 +1,15 @@ using System; using System.Linq; +using Dalamud.Game.Internal; using Dalamud.Plugin; using ImGuiNET; namespace Dalamud.Interface { - class DalamudPluginStatWindow : IDisposable { + internal class DalamudPluginStatWindow : IDisposable { - private PluginManager pm; - public DalamudPluginStatWindow(PluginManager pm) { - this.pm = pm; + private readonly PluginManager pluginManager; + public DalamudPluginStatWindow(PluginManager pluginManager) { + this.pluginManager = pluginManager; } public bool Draw() { @@ -29,7 +30,7 @@ namespace Dalamud.Interface { ImGui.SameLine(); if (ImGui.Button("Reset")) { - foreach (var a in this.pm.Plugins) { + foreach (var a in this.pluginManager.Plugins) { a.PluginInterface.UiBuilder.lastDrawTime = -1; a.PluginInterface.UiBuilder.maxDrawTime = -1; a.PluginInterface.UiBuilder.drawTimeHistory.Clear(); @@ -51,7 +52,7 @@ namespace Dalamud.Interface { ImGui.Text("Average"); ImGui.NextColumn(); ImGui.Separator(); - foreach (var a in this.pm.Plugins) { + foreach (var a in this.pluginManager.Plugins) { ImGui.Text(a.Definition.Name); ImGui.NextColumn(); ImGui.Text($"{a.PluginInterface.UiBuilder.lastDrawTime/10000f:F4}ms"); @@ -69,6 +70,59 @@ namespace Dalamud.Interface { ImGui.Columns(1); } + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Framework times")) { + + var doStats = Framework.StatsEnabled; + + if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) { + Framework.StatsEnabled = doStats; + } + + if (doStats) { + ImGui.SameLine(); + if (ImGui.Button("Reset")) { + Framework.StatsHistory.Clear(); + } + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); + ImGui.SetColumnWidth(1, 100f); + ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); + ImGui.Text("Method"); + ImGui.NextColumn(); + ImGui.Text("Last"); + ImGui.NextColumn(); + ImGui.Text("Longest"); + ImGui.NextColumn(); + ImGui.Text("Average"); + ImGui.NextColumn(); + ImGui.Separator(); + ImGui.Separator(); + + foreach (var handlerHistory in Framework.StatsHistory) { + if (handlerHistory.Value.Count == 0) continue; + ImGui.SameLine(); + ImGui.Text($"{handlerHistory.Key}"); + ImGui.NextColumn(); + ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); + ImGui.NextColumn(); + ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); + ImGui.NextColumn(); + ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); + ImGui.NextColumn(); + ImGui.Separator(); + } + ImGui.Columns(0); + } + + ImGui.EndTabItem(); + } + + } }