using System.Text;
using Microsoft.Diagnostics.DebugServices;
using Microsoft.Diagnostics.Runtime;
+using static Microsoft.Diagnostics.ExtensionCommands.TableOutput;
namespace Microsoft.Diagnostics.ExtensionCommands
{
- [Command(Name = "eeheap", Help = "Displays information about native memory that CLR has allocated.")]
+ [Command(Name = CommandName, Help = "Displays information about native memory that CLR has allocated.")]
public class EEHeapCommand : CommandBase
{
+ private const string CommandName = "eeheap";
+
+ private HeapWithFilters HeapWithFilters { get; set; }
+
+ // Don't use the word "Total" if we have filtered out entries
+ private string TotalString => HeapWithFilters.HasFilters ? "Partial" : "Total";
+
[ServiceImport]
- public IRuntimeService RuntimeService { get; set; }
+ public ClrRuntime Runtime { get; set; }
[ServiceImport]
public IMemoryService MemoryService { get; set; }
[Option(Name = "-loader", Help = "Only display the Loader.")]
public bool ShowLoader { get; set; }
+ [Option(Name = "-heap")]
+ public int GCHeap { get; set; } = -1;
+
+ [Option(Name = "-segment")]
+ public string Segment { get; set; }
+
+ [Argument(Help = "Optional memory ranges in the form of: [Start [End]]")]
+ public string[] MemoryRange { get; set; }
+
public override void Invoke()
{
- IRuntime[] runtimes = RuntimeService.EnumerateRuntimes().ToArray();
+ HeapWithFilters = new(Runtime.Heap)
+ {
+ // The user may want to filter loader regions by address
+ ThrowIfNoMatchingGCRegions = false
+ };
- ulong totalBytes = 0;
- StringBuilder stringBuilder = null;
- foreach (IRuntime iRuntime in runtimes)
+ if (GCHeap >= 0)
{
- if (runtimes.Length > 1)
- {
- WriteDivider($"{iRuntime.RuntimeType} {iRuntime.RuntimeModule?.GetVersionData()}");
- }
+ HeapWithFilters.GCHeap = GCHeap;
+ }
- ClrRuntime clrRuntime = iRuntime.Services.GetService<ClrRuntime>();
- totalBytes += PrintOneRuntime(ref stringBuilder, clrRuntime);
+ if (!string.IsNullOrWhiteSpace(Segment))
+ {
+ HeapWithFilters.FilterBySegmentHex(Segment);
}
- // Only print the total bytes if we walked everything.
- if (runtimes.Length > 1 && !ShowGC && !ShowLoader)
+ if (MemoryRange is not null)
{
- WriteLine($"Total bytes consumed by all CLRs: {FormatMemorySize(totalBytes, "0")}");
+ HeapWithFilters.FilterByStringMemoryRange(MemoryRange, CommandName);
}
+
+ PrintOneRuntime(Runtime);
}
- private ulong PrintOneRuntime(ref StringBuilder stringBuilder, ClrRuntime clrRuntime)
+ private ulong PrintOneRuntime(ClrRuntime clrRuntime)
{
+ StringBuilder stringBuilder = null;
TableOutput output = new(Console, (21, "x12"), (0, "x12"))
{
AlignLeft = true
if (!ShowGC && !ShowLoader)
{
WriteLine();
- WriteLine($"Total bytes consumed by CLR: {FormatMemorySize(totalSize, "0")}");
+ WriteLine($"{TotalString} bytes consumed by CLR: {FormatMemorySize(totalSize, "0")}");
WriteLine();
}
}
}
- IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> heapsByKind = from heap in appDomain.EnumerateLoaderAllocatorHeaps()
- where loaderAllocatorsSeen.Add(heap.Address)
- group heap by heap.Kind into g
- orderby GetSortOrder(g.Key)
- select g;
+ IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> filteredHeapsByKind = from heap in appDomain.EnumerateLoaderAllocatorHeaps()
+ where IsIncludedInFilter(heap)
+ where loaderAllocatorsSeen.Add(heap.Address)
+ group heap by heap.Kind into g
+ orderby GetSortOrder(g.Key)
+ select g;
- return PrintAppDomainHeapsByKind(output, heapsByKind);
+ return PrintAppDomainHeapsByKind(output, filteredHeapsByKind);
}
private static int GetSortOrder(NativeHeapKind key)
};
}
- private ulong PrintAppDomainHeapsByKind(TableOutput output, IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> heapsByKind)
+ private ulong PrintAppDomainHeapsByKind(TableOutput output, IOrderedEnumerable<IGrouping<NativeHeapKind, ClrNativeHeapInfo>> filteredHeapsByKind)
{
// Just build and print the table.
ulong totalSize = 0;
ulong totalWasted = 0;
StringBuilder text = new(512);
- foreach (IGrouping<NativeHeapKind, ClrNativeHeapInfo> item in heapsByKind)
+ foreach (IGrouping<NativeHeapKind, ClrNativeHeapInfo> item in filteredHeapsByKind)
{
text.Clear();
NativeHeapKind kind = item.Key;
{
WriteSizeAndWasted(text, totalSize, totalWasted);
text.Append('.');
- output.WriteRow("Total size:", text);
+ output.WriteRow($"{TotalString} size:", text);
}
else
{
private ulong PrintCodeHeaps(TableOutput output, ClrRuntime clrRuntime)
{
ulong totalSize = 0;
-
StringBuilder text = new(512);
foreach (ClrJitManager jitManager in clrRuntime.EnumerateJitManagers())
{
output.WriteRow("JIT Manager:", jitManager.Address);
- IEnumerable<ClrNativeHeapInfo> heaps = jitManager.EnumerateNativeHeaps().OrderBy(r => r.Kind).ThenBy(r => r.Address);
+ IEnumerable<ClrNativeHeapInfo> heaps = jitManager.EnumerateNativeHeaps().Where(IsIncludedInFilter).OrderBy(r => r.Kind).ThenBy(r => r.Address);
ulong jitMgrSize = 0, jitMgrWasted = 0;
foreach (ClrNativeHeapInfo heap in heaps)
WriteSizeAndWasted(text, jitMgrSize, jitMgrWasted);
text.Append('.');
- output.WriteRow("Total size:", text);
+ output.WriteRow($"{TotalString} size:", text);
WriteDivider();
totalSize += jitMgrSize;
return totalSize;
}
+ private bool IsIncludedInFilter(ClrNativeHeapInfo info)
+ {
+ // ClrNativeHeapInfo is only filtered by memory range (not heap or segment).
+ if (HeapWithFilters.MemoryRange is not MemoryRange filterRange)
+ {
+ // no filter, so include everything
+ return true;
+ }
+
+ if (filterRange.Contains(info.Address))
+ {
+ return true;
+ }
+
+ if (info.Size is ulong size && size > 0)
+ {
+ // Check for the last valid address in the range
+ return filterRange.Contains(info.Address + size - 1);
+ }
+
+ return false;
+ }
+
private (ulong Size, ulong Wasted) CalculateSizeAndWasted(StringBuilder sb, ClrNativeHeapInfo heap)
{
sb.Append(heap.Address.ToString("x12"));
ulong moduleSize = 0, moduleWasted = 0;
text.Clear();
- foreach (ClrNativeHeapInfo info in module.EnumerateThunkHeap())
+ foreach (ClrNativeHeapInfo info in module.EnumerateThunkHeap().Where(IsIncludedInFilter))
{
if (text.Length > 0)
{
text.Clear();
WriteSizeAndWasted(text, totalSize, totalWasted);
- output.WriteRow("Total size:", text);
+ output.WriteRow($"{TotalString} size:", text);
return totalSize;
}
Console.WriteLine($"Number of GC Heaps: {heap.SubHeaps.Length}");
WriteDivider();
- foreach (ClrSubHeap gc_heap in heap.SubHeaps)
+ foreach (ClrSubHeap gc_heap in HeapWithFilters.EnumerateFilteredSubHeaps())
{
if (heap.IsServer)
{
- Console.WriteLine($"Heap {gc_heap.Index} ({gc_heap.Address:x16})");
+ Console.Write("Heap ");
+ Console.WriteDmlExec(gc_heap.Index.ToString(), $"!dumpheap -heap {gc_heap.Index}");
+ Console.WriteLine($" ({gc_heap.Address:x16})");
}
if (!gc_heap.HasRegions)
{
for (int i = 0; i <= 2 && i < gc_heap.GenerationTable.Length; i++)
{
- Console.WriteLine($"generation {i} starts at {gc_heap.GenerationTable[i].AllocationStart:x}");
+ ClrSegment seg = heap.GetSegmentByAddress(gc_heap.GenerationTable[i].AllocationStart);
+ MemoryRange range = default;
+ if (seg is not null)
+ {
+ range = i switch
+ {
+ 0 => seg.Generation0,
+ 1 => seg.Generation1,
+ 2 => seg.Generation2,
+ _ => default
+ };
+ }
+
+ if (range.Length > 0)
+ {
+ Console.Write($"generation {i} starts at ");
+ Console.WriteDmlExec(gc_heap.GenerationTable[i].AllocationStart.ToString("x"), $"!dumpheap {range.Start:x} {range.End:x}");
+ Console.WriteLine();
+ }
+ else
+ {
+ Console.WriteLine($"generation {i} starts at {gc_heap.GenerationTable[i].AllocationStart:x}");
+ }
}
Console.Write("ephemeral segment allocation context: ");
WriteSegmentHeader(gcOutput);
bool[] needToPrintGen = new bool[] { gc_heap.HasRegions, gc_heap.HasRegions, gc_heap.HasRegions };
- IEnumerable<ClrSegment> ephemeralSegments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Ephemeral || (seg.Kind >= GCSegmentKind.Generation0 && seg.Kind <= GCSegmentKind.Generation2));
+ IEnumerable<ClrSegment> ephemeralSegments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Ephemeral || (seg.Kind >= GCSegmentKind.Generation0 && seg.Kind <= GCSegmentKind.Generation2));
IEnumerable<ClrSegment> segments = ephemeralSegments.OrderBy(seg => seg.Kind).ThenBy(seg => seg.Start);
foreach (ClrSegment segment in segments)
{
}
// print frozen object heap
- segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Frozen).OrderBy(seg => seg.Start);
+ segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Frozen).OrderBy(seg => seg.Start);
if (segments.Any())
{
Console.WriteLine("Frozen object heap");
Console.WriteLine($"Large object heap starts at {gc_heap.GenerationTable[3].AllocationStart:x}");
}
- segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Large).OrderBy(seg => seg.Start);
+ segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Large).OrderBy(seg => seg.Start);
WriteSegmentHeader(gcOutput);
foreach (ClrSegment segment in segments)
}
// print pinned object heap
- segments = gc_heap.Segments.Where(seg => seg.Kind == GCSegmentKind.Pinned).OrderBy(seg => seg.Start);
+ segments = HeapWithFilters.EnumerateFilteredSegments(gc_heap).Where(seg => seg.Kind == GCSegmentKind.Pinned).OrderBy(seg => seg.Start);
if (segments.Any())
{
if (gc_heap.HasRegions || gc_heap.GenerationTable.Length <= 3)
}
}
- Console.WriteLine($"Total Allocated Size: Size: {FormatMemorySize((ulong)gc_heap.Segments.Sum(r => (long)r.ObjectRange.Length))} bytes.");
- Console.WriteLine($"Total Committed Size: Size: {FormatMemorySize((ulong)gc_heap.Segments.Sum(r => (long)r.CommittedMemory.Length))} bytes.");
+ if (HeapWithFilters.HasFilters)
+ {
+ Console.WriteLine($"{TotalString} Allocated Size: Size: {FormatMemorySize((ulong)HeapWithFilters.EnumerateFilteredSegments(gc_heap).Sum(r => (long)r.ObjectRange.Length))} bytes.");
+ Console.WriteLine($"{TotalString} Committed Size: Size: {FormatMemorySize((ulong)HeapWithFilters.EnumerateFilteredSegments(gc_heap).Sum(r => (long)r.CommittedMemory.Length))} bytes.");
+ }
Console.WriteLine("------------------------------");
}
- ulong totalAllocated = (ulong)heap.SubHeaps.SelectMany(gc_heap => gc_heap.Segments).Sum(r => (long)r.ObjectRange.Length);
- ulong totalCommitted = (ulong)heap.SubHeaps.SelectMany(gc_heap => gc_heap.Segments).Sum(r => (long)r.CommittedMemory.Length);
+ string prefix = "";
+ if (HeapWithFilters.HasFilters)
+ {
+ prefix = "Partial ";
+ }
+
+ ulong totalAllocated = (ulong)HeapWithFilters.EnumerateFilteredSegments().Sum(r => (long)r.ObjectRange.Length);
+ ulong totalCommitted = (ulong)HeapWithFilters.EnumerateFilteredSegments().Sum(r => (long)r.CommittedMemory.Length);
- Console.WriteLine($"GC Allocated Heap Size: Size: {FormatMemorySize(totalAllocated)} bytes.");
- Console.WriteLine($"GC Committed Heap Size: Size: {FormatMemorySize(totalCommitted)} bytes.");
+ Console.WriteLine($"{prefix}GC Allocated Heap Size: Size: {FormatMemorySize(totalAllocated)} bytes.");
+ Console.WriteLine($"{prefix}GC Committed Heap Size: Size: {FormatMemorySize(totalCommitted)} bytes.");
return totalCommitted;
}
private static void WriteSegment(TableOutput gcOutput, ClrSegment segment)
{
- gcOutput.WriteRow(segment.Address,
+ gcOutput.WriteRow(new DmlDumpHeapSegment(segment),
segment.ObjectRange.Start, segment.ObjectRange.End, segment.CommittedMemory.End,
FormatMemorySize(segment.ObjectRange.Length), FormatMemorySize(segment.CommittedMemory.Length));
}
}
private void WriteDivider(char c = '-', int width = 40) => WriteLine(new string(c, width));
-
- private void WriteDivider(string header, int width = 120)
- {
- int lhs = (width - header.Length - 2) / 2;
- if (lhs < 0)
- {
- WriteLine(header);
- return;
- }
-
- int rhs = lhs;
- if ((header.Length % 2) == 1)
- {
- rhs++;
- }
-
- StringBuilder sb = new(width + 1);
- sb.Append('-', lhs);
- sb.Append(' ');
- sb.Append(header);
- sb.Append(' ');
- sb.Append('-', rhs);
-
- WriteLine(sb.ToString());
- }
}
}
}
}
+ /// <summary>
+ /// Whether or not to throw if there are now matching segments or subheaps.
+ /// </summary>
+ public bool ThrowIfNoMatchingGCRegions { get; set; } = true;
+
/// <summary>
/// The minimum size of an object to enumerate.
/// </summary>
/// </summary>
public Func<IEnumerable<ClrSegment>, IOrderedEnumerable<ClrSegment>> SortSegments { get; set; }
+ /// <summary>
+ /// The order in which to enumerate subheaps. This only applies to subheap enumeration.
+ /// </summary>
+ public Func<IEnumerable<ClrSubHeap>, IOrderedEnumerable<ClrSubHeap>> SortSubHeaps { get; set; }
+
public HeapWithFilters(ClrHeap heap)
{
_heap = heap;
SortSegments = (seg) => seg.OrderBy(s => s.SubHeap.Index).ThenBy(s => s.Address);
+ SortSubHeaps = (heap) => heap.OrderBy(heap => heap.Index);
}
public void FilterBySegmentHex(string segmentStr)
throw new ArgumentException($"Invalid segment address: {segmentStr}");
}
- if (!_heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)))
+ if (ThrowIfNoMatchingGCRegions && !_heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)))
{
throw new ArgumentException($"No segments match address: {segment:x}");
}
Segment = segment;
}
+ public void FilterByStringMemoryRange(string[] memoryRange, string commandName)
+ {
+ if (memoryRange.Length > 0)
+ {
+ if (memoryRange.Length > 2)
+ {
+ string badArgument = memoryRange.FirstOrDefault(f => f.StartsWith("-") || f.StartsWith("/"));
+ if (badArgument != null)
+ {
+ throw new ArgumentException($"Unknown argument: {badArgument}");
+ }
+
+ throw new ArgumentException($"Too many arguments to !{commandName}");
+ }
+
+ string start = memoryRange[0];
+ string end = memoryRange.Length > 1 ? memoryRange[1] : null;
+ FilterByHexMemoryRange(start, end);
+ }
+ }
+
public void FilterByHexMemoryRange(string startStr, string endStr)
{
if (!ulong.TryParse(startStr, NumberStyles.HexNumber, null, out ulong start))
MemoryRange = new(start, end);
}
- if (!_heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(MemoryRange.Value)))
+ if (ThrowIfNoMatchingGCRegions && !_heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(MemoryRange.Value)))
{
throw new ArgumentException($"No segments or objects in range {MemoryRange.Value}");
}
}
- public IEnumerable<ClrSegment> EnumerateFilteredSegments()
+ public IEnumerable<ClrSubHeap> EnumerateFilteredSubHeaps()
+ {
+ IEnumerable<ClrSubHeap> subheaps = _heap.SubHeaps;
+ if (GCHeap is int gcheap)
+ {
+ subheaps = subheaps.Where(heap => heap.Index == gcheap);
+ }
+
+ if (Segment is ulong segment)
+ {
+ subheaps = subheaps.Where(heap => heap.Segments.Any(seg => seg.Address == segment || seg.CommittedMemory.Contains(segment)));
+ }
+
+ if (MemoryRange is MemoryRange range)
+ {
+ subheaps = subheaps.Where(heap => heap.Segments.Any(seg => seg.CommittedMemory.Overlaps(range)));
+ }
+
+ if (SortSubHeaps is not null)
+ {
+ subheaps = SortSubHeaps(subheaps);
+ }
+
+ return subheaps;
+ }
+
+ public IEnumerable<ClrSegment> EnumerateFilteredSegments() => EnumerateFilteredSegments(null);
+
+ public IEnumerable<ClrSegment> EnumerateFilteredSegments(ClrSubHeap subheap)
{
- IEnumerable<ClrSegment> segments = _heap.Segments;
+ IEnumerable<ClrSegment> segments = subheap != null ? subheap.Segments : _heap.Segments;
if (GCHeap is int gcheap)
{
segments = segments.Where(seg => seg.SubHeap.Index == gcheap);