mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
feat: add task debugger
This commit is contained in:
parent
85affedaa8
commit
f8597415c0
4 changed files with 322 additions and 3 deletions
|
|
@ -104,6 +104,11 @@ namespace Dalamud
|
||||||
|
|
||||||
Service<ServiceContainer>.Set();
|
Service<ServiceContainer>.Set();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Service<TaskTracker>.Set();
|
||||||
|
Log.Information("[T1] TaskTracker OK!");
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialize the process information.
|
// Initialize the process information.
|
||||||
Service<SigScanner>.Set(new SigScanner(true));
|
Service<SigScanner>.Set(new SigScanner(true));
|
||||||
Service<HookManager>.Set();
|
Service<HookManager>.Set();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
|
@ -27,6 +30,7 @@ using Dalamud.Game.Text;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using Dalamud.Plugin.Ipc.Internal;
|
using Dalamud.Plugin.Ipc.Internal;
|
||||||
|
|
@ -137,6 +141,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
KeyState,
|
KeyState,
|
||||||
Gamepad,
|
Gamepad,
|
||||||
Configuration,
|
Configuration,
|
||||||
|
TaskSched,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -304,6 +309,10 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
case DataKind.Configuration:
|
case DataKind.Configuration:
|
||||||
this.DrawConfiguration();
|
this.DrawConfiguration();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DataKind.TaskSched:
|
||||||
|
this.DrawTaskSched();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -1256,6 +1265,127 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
Util.ShowObject(config);
|
Util.ShowObject(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawTaskSched()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Clear list"))
|
||||||
|
{
|
||||||
|
TaskTracker.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Short Task.Run"))
|
||||||
|
{
|
||||||
|
Task.Run(() => { Thread.Sleep(500); });
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Task in task(Delay)"))
|
||||||
|
{
|
||||||
|
Task.Run(async () => await this.TestTaskInTaskDelay());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Task in task(Sleep)"))
|
||||||
|
{
|
||||||
|
Task.Run(async () => await this.TestTaskInTaskSleep());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Faulting task"))
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(200);
|
||||||
|
|
||||||
|
string a = null;
|
||||||
|
a.Contains("dalamud");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(20);
|
||||||
|
|
||||||
|
// Needed to init the task tracker, if we're not on a debug build
|
||||||
|
var tracker = Service<TaskTracker>.GetNullable() ?? Service<TaskTracker>.Set();
|
||||||
|
|
||||||
|
for (var i = 0; i < TaskTracker.Tasks.Count; i++)
|
||||||
|
{
|
||||||
|
var task = TaskTracker.Tasks[i];
|
||||||
|
var subTime = DateTime.Now;
|
||||||
|
if (task.Task == null)
|
||||||
|
subTime = task.FinishTime;
|
||||||
|
|
||||||
|
switch (task.Status)
|
||||||
|
{
|
||||||
|
case TaskStatus.Created:
|
||||||
|
case TaskStatus.WaitingForActivation:
|
||||||
|
case TaskStatus.WaitingToRun:
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey);
|
||||||
|
break;
|
||||||
|
case TaskStatus.Running:
|
||||||
|
case TaskStatus.WaitingForChildrenToComplete:
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue);
|
||||||
|
break;
|
||||||
|
case TaskStatus.RanToCompletion:
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen);
|
||||||
|
break;
|
||||||
|
case TaskStatus.Canceled:
|
||||||
|
case TaskStatus.Faulted:
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}"))
|
||||||
|
{
|
||||||
|
if (ImGui.Button("CANCEL (May not work)"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cancelFunc =
|
||||||
|
typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
cancelFunc.Invoke(task, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not cancel task.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(task.StackTrace.ToString());
|
||||||
|
|
||||||
|
if (task.Exception != null)
|
||||||
|
{
|
||||||
|
ImGuiHelpers.ScaledDummy(15);
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:");
|
||||||
|
ImGui.TextUnformatted(task.Exception.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopStyleColor(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task TestTaskInTaskDelay()
|
||||||
|
{
|
||||||
|
await Task.Delay(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable 1998
|
||||||
|
private async Task TestTaskInTaskSleep()
|
||||||
|
#pragma warning restore 1998
|
||||||
|
{
|
||||||
|
Thread.Sleep(5000);
|
||||||
|
}
|
||||||
|
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
{
|
||||||
var dataManager = Service<DataManager>.Get();
|
var dataManager = Service<DataManager>.Get();
|
||||||
|
|
|
||||||
184
Dalamud/Logging/Internal/TaskTracker.cs
Normal file
184
Dalamud/Logging/Internal/TaskTracker.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Game;
|
||||||
|
using MonoMod.RuntimeDetour;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Dalamud.Logging.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class responsible for tracking asynchronous tasks.
|
||||||
|
/// </summary>
|
||||||
|
internal class TaskTracker : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly List<TaskInfo> TrackedTasksInternal = new();
|
||||||
|
|
||||||
|
private Hook? scheduleAndStartHook;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="TaskTracker"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public TaskTracker()
|
||||||
|
{
|
||||||
|
this.ApplyPatch();
|
||||||
|
|
||||||
|
var framework = Service<Framework>.Get();
|
||||||
|
framework.Update += this.FrameworkOnUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a read-only list of tracked tasks.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<TaskInfo> Tasks => TrackedTasksInternal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the list of tracked tasks.
|
||||||
|
/// </summary>
|
||||||
|
public static void Clear() => TrackedTasksInternal.Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the tracked data.
|
||||||
|
/// </summary>
|
||||||
|
public static void UpdateData()
|
||||||
|
{
|
||||||
|
foreach (var taskInfo in TrackedTasksInternal)
|
||||||
|
{
|
||||||
|
if (taskInfo.Task == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
taskInfo.IsCompleted = taskInfo.Task.IsCompleted;
|
||||||
|
taskInfo.IsFaulted = taskInfo.Task.IsFaulted;
|
||||||
|
taskInfo.IsCanceled = taskInfo.Task.IsCanceled;
|
||||||
|
taskInfo.IsCompletedSuccessfully = taskInfo.Task.IsCompletedSuccessfully;
|
||||||
|
taskInfo.Status = taskInfo.Task.Status;
|
||||||
|
|
||||||
|
if (taskInfo.IsCompleted || taskInfo.IsFaulted || taskInfo.IsCanceled ||
|
||||||
|
taskInfo.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
taskInfo.Exception = taskInfo.Task.Exception;
|
||||||
|
|
||||||
|
taskInfo.Task = null;
|
||||||
|
taskInfo.FinishTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.scheduleAndStartHook?.Dispose();
|
||||||
|
|
||||||
|
var framework = Service<Framework>.Get();
|
||||||
|
framework.Update -= this.FrameworkOnUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AddToActiveTasksHook(Func<Task, bool> orig, Task self)
|
||||||
|
{
|
||||||
|
orig(self);
|
||||||
|
|
||||||
|
var trace = new StackTrace();
|
||||||
|
TrackedTasksInternal.Add(new TaskInfo
|
||||||
|
{
|
||||||
|
Task = self,
|
||||||
|
Id = self.Id,
|
||||||
|
StackTrace = trace,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkOnUpdate(Framework framework)
|
||||||
|
{
|
||||||
|
UpdateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPatch()
|
||||||
|
{
|
||||||
|
var targetType = typeof(Task);
|
||||||
|
|
||||||
|
var debugField = targetType.GetField("s_asyncDebuggingEnabled", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
debugField.SetValue(null, true);
|
||||||
|
|
||||||
|
Log.Information("s_asyncDebuggingEnabled: {0}", debugField.GetValue(null));
|
||||||
|
|
||||||
|
var targetMethod = targetType.GetMethod("AddToActiveTasks", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
var patchMethod =
|
||||||
|
typeof(TaskTracker).GetMethod("AddToActiveTasksHook", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
|
||||||
|
if (targetMethod == null)
|
||||||
|
Log.Error("TargetMethod null!");
|
||||||
|
|
||||||
|
if (patchMethod == null)
|
||||||
|
Log.Error("PatchMethod null!");
|
||||||
|
|
||||||
|
this.scheduleAndStartHook = new Hook(targetMethod, patchMethod);
|
||||||
|
|
||||||
|
Log.Information("AddToActiveTasks Hooked!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Class representing a tracked task.
|
||||||
|
/// </summary>
|
||||||
|
internal class TaskInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the tracked task.
|
||||||
|
/// </summary>
|
||||||
|
public Task? Task { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ID of the task.
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the stack trace of where the task was started.
|
||||||
|
/// </summary>
|
||||||
|
public StackTrace? StackTrace { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the task was completed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the task faulted.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFaulted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the task was canceled.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCanceled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether or not the task was completed successfully.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompletedSuccessfully { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the status of the task.
|
||||||
|
/// </summary>
|
||||||
|
public TaskStatus Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the start time of the task.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime StartTime { get; } = DateTime.Now;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the end time of the task.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime FinishTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the exception that occurred within the task.
|
||||||
|
/// </summary>
|
||||||
|
public AggregateException? Exception { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1064,15 +1064,15 @@ namespace Dalamud.Plugin.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal partial class PluginManager
|
internal partial class PluginManager
|
||||||
{
|
{
|
||||||
private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook;
|
|
||||||
private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading
|
/// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading
|
||||||
/// plugins via byte[].
|
/// plugins via byte[].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static readonly Dictionary<string, PluginPatchData> PluginLocations = new();
|
internal static readonly Dictionary<string, PluginPatchData> PluginLocations = new();
|
||||||
|
|
||||||
|
private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook;
|
||||||
|
private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location.
|
/// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location.
|
||||||
/// This patch facilitates resolving the assembly location for plugins that are loaded via byte[].
|
/// This patch facilitates resolving the assembly location for plugins that are loaded via byte[].
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue