Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ public enum DisplayKind
Strings
}

public void PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, bool statsOnly, bool printFragmentation, bool sortByCount = false)
/// <summary>
/// Prints objects in a standardized format similar to how !dumpheap does, with MT, size, and type information. Can optionally print only statistics about the heap, and can optionally print fragmentation information.
/// </summary>
/// <param name="objects">The objects to print.</param>
/// <param name="displayKind">The display kind.</param>
/// <param name="statsOnly">Whether to print only statistics.</param>
/// <param name="printFragmentation">Whether to print fragmentation information.</param>
/// <param name="sortByCount">Whether to sort by count.</param>
/// <returns>True if any objects in the enumeration, false otherwise.</returns>
public bool PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, bool statsOnly, bool printFragmentation, bool sortByCount = false)
{
bool hadAny = false;
List<(ClrObject Free, ClrObject Next)> fragmentation = null;
Dictionary<(string String, ulong Size), uint> stringTable = null;
Dictionary<ulong, (int Count, ulong Size, string TypeName)> stats = new();
Expand All @@ -61,11 +71,14 @@ public void PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, b
}

thinLockOutput.WriteRow(obj, thinLock.Thread, thinLock.Thread?.OSThreadId ?? 0, thinLock.Recursion);
hadAny = true;
}

continue;
}

// Only thinlocks are further filtered by displayKind, everything else that reaches here is printed, so set hadAny = true.
hadAny = true;
if (displayKind == DisplayKind.Short)
{
Console.WriteLine(obj.Address.ToString("x12"));
Expand Down Expand Up @@ -247,6 +260,8 @@ orderby TotalSize

// Print fragmentation if we calculated it
PrintFragmentation(fragmentation);

return hadAny;
}

private void PrintFragmentation(List<(ClrObject Free, ClrObject Next)> fragmentation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.ExtensionCommands.Output;
using Microsoft.Diagnostics.Runtime;
Expand All @@ -20,6 +21,12 @@ public sealed class ThreadPoolCommand : ClrRuntimeCommandBase
[Option(Name = "-wi", Help = "Print all work items that are queued.")]
public bool PrintWorkItems { get; set; }

[Option(Name = "-stat", Aliases = new[] { "-summary" }, Help = "Print a summary of queued work items grouped by type (DumpHeap -stat style).")]
public bool PrintWorkItemStats { get; set; }

[ServiceImport]
public DumpHeapService DumpHeap { get; set; }

public override void Invoke()
{
// Runtime.ThreadPool shouldn't be null unless there was a problem with the dump.
Expand Down Expand Up @@ -127,9 +134,22 @@ It is the only option in .NET 6 and below. The UsePortableThreadPoolForIO field
}

// We can print managed work items even if we failed to request the ThreadPool.
if (PrintWorkItems && (threadPool is null || threadPool.UsingPortableThreadPool || threadPool.UsingWindowsThreadPool))
if ((PrintWorkItems || PrintWorkItemStats) && (threadPool is null || threadPool.UsingPortableThreadPool || threadPool.UsingWindowsThreadPool))
{
DumpWorkItems();
List<(ClrObject Obj, bool IsHighPri)> workItems = EnumerateAllWorkItemsWithPriority().ToList();

if (PrintWorkItems && workItems.Count > 0)
{
DumpWorkItems(workItems);
}

if (PrintWorkItemStats)
{
if (!DumpHeap.PrintHeap(workItems.Select(r => r.Obj), DumpHeapService.DisplayKind.Normal, statsOnly: true, printFragmentation: false))
{
Console.WriteLine("No queued work items.");
}
}
}
Comment thread
leculver marked this conversation as resolved.
}

Expand All @@ -138,11 +158,22 @@ public static string GetDetailedHelp() =>
@"This command lists basic information about the ThreadPool, including the number
of work requests in the queue, number of completion port threads, and number of
timers.

Use -stat to display a DumpHeap-style summary of queued work items grouped by
type, including MethodTable, Count, TotalSize, and Class Name. Use -wi -stat
together to display individual items followed by the statistics summary.
";
private void DumpWorkItems()
private void DumpWorkItems(List<(ClrObject Obj, bool IsHighPri)> workItems)
{
Table output = null;
foreach ((ClrObject obj, bool isHighPri) in workItems)
{
WriteEntry(ref output, obj, isHighPri);
}
}

private IEnumerable<(ClrObject Obj, bool IsHighPri)> EnumerateAllWorkItemsWithPriority()
{
ClrType workQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue");
ClrType workStealingQueueType = Runtime.BaseClassLibrary.GetTypeByName("System.Threading.ThreadPoolWorkQueue+WorkStealingQueue");

Expand All @@ -156,15 +187,15 @@ private void DumpWorkItems()
{
foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
{
WriteEntry(ref output, entry, isHighPri: true);
yield return (entry, true);
}
}

if (obj.TryReadObjectField("workItems", out workItems))
{
foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
{
WriteEntry(ref output, entry, isHighPri: false);
yield return (entry, false);
}
}

Expand All @@ -174,7 +205,7 @@ private void DumpWorkItems()
{
foreach (ClrObject entry in EnumerateConcurrentQueue(workItems))
{
WriteEntry(ref output, entry, isHighPri: false);
yield return (entry, false);
}
}
}
Expand All @@ -196,7 +227,7 @@ private void DumpWorkItems()
ClrObject entry = Runtime.Heap.GetObject(buffer[i]);
if (entry.IsValid && !entry.IsNull)
{
WriteEntry(ref output, entry, isHighPri: false);
yield return (entry, false);
}
}
}
Expand Down