Skip to content

Commit 1a7cbc2

Browse files
authored
Add !dumpheap fragmentation statistics (#3799)
In my previous !dumpheap change I overlooked the fragmentation output. This adds fragmentation output in the exact same was as the previous C++ code. The new code now validates the Free region is actually followed by the next object, and that those objects do not live on the Pinned, Frozen, or Large object heaps.
1 parent b7e7737 commit 1a7cbc2

File tree

5 files changed

+66
-8
lines changed

5 files changed

+66
-8
lines changed

src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public override void Invoke()
128128
});
129129
}
130130

131+
bool printFragmentation = false;
131132
DumpHeapService.DisplayKind displayKind = DumpHeapService.DisplayKind.Normal;
132133
if (ThinLock)
133134
{
@@ -141,8 +142,12 @@ public override void Invoke()
141142
{
142143
displayKind = DumpHeapService.DisplayKind.Short;
143144
}
145+
else
146+
{
147+
printFragmentation = true;
148+
}
144149

145-
DumpHeap.PrintHeap(objectsToPrint, displayKind, StatOnly);
150+
DumpHeap.PrintHeap(objectsToPrint, displayKind, StatOnly, printFragmentation);
146151
}
147152

148153
private void ParseArguments()

src/Microsoft.Diagnostics.ExtensionCommands/DumpHeapService.cs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands
1818
[ServiceExport(Scope = ServiceScope.Runtime)]
1919
public class DumpHeapService
2020
{
21+
private const ulong FragmentationBlockMinSize = 512 * 1024;
2122
private const char StringReplacementCharacter = '.';
2223

2324
[ServiceImport]
@@ -34,18 +35,20 @@ public enum DisplayKind
3435
Strings
3536
}
3637

37-
public void PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, bool statsOnly)
38+
public void PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, bool statsOnly, bool printFragmentation)
3839
{
40+
List<(ClrObject Free, ClrObject Next)> fragmentation = null;
41+
Dictionary<(string String, ulong Size), uint> stringTable = null;
42+
Dictionary<ulong, (int Count, ulong Size, string TypeName)> stats = new();
43+
3944
TableOutput thinLockOutput = null;
4045
TableOutput objectTable = new(Console, (12, "x12"), (12, "x12"), (12, ""), (0, ""));
4146
if (!statsOnly && (displayKind is DisplayKind.Normal or DisplayKind.Strings))
4247
{
4348
objectTable.WriteRow("Address", "MT", "Size");
4449
}
4550

46-
Dictionary<ulong, (int Count, ulong Size, string TypeName)> stats = new();
47-
Dictionary<(string String, ulong Size), uint> stringTable = null;
48-
51+
ClrObject lastFreeObject = default;
4952
foreach (ClrObject obj in objects)
5053
{
5154
if (displayKind == DisplayKind.ThinLock)
@@ -77,6 +80,35 @@ public void PrintHeap(IEnumerable<ClrObject> objects, DisplayKind displayKind, b
7780
objectTable.WriteRow(new DmlDumpObj(obj), new DmlDumpHeap(obj.Type?.MethodTable ?? 0), size, obj.IsFree ? "Free" : "");
7881
}
7982

83+
if (printFragmentation)
84+
{
85+
if (lastFreeObject.IsFree && obj.IsValid && !obj.IsFree)
86+
{
87+
// Check to see if the previous object lands directly before this one. We don't want
88+
// to print fragmentation after changing segments, or after an allocation context.
89+
if (lastFreeObject.Address + lastFreeObject.Size == obj.Address)
90+
{
91+
// Also, don't report fragmentation for Large/Pinned/Frozen segments. This check
92+
// is a little slow, so we do this last.
93+
ClrSegment seg = obj.Type.Heap.GetSegmentByAddress(obj);
94+
if (seg is not null && seg.Kind is not GCSegmentKind.Large or GCSegmentKind.Pinned or GCSegmentKind.Frozen)
95+
{
96+
fragmentation ??= new();
97+
fragmentation.Add((lastFreeObject, obj));
98+
}
99+
}
100+
}
101+
102+
if (obj.IsFree && size >= FragmentationBlockMinSize)
103+
{
104+
lastFreeObject = obj;
105+
}
106+
else
107+
{
108+
lastFreeObject = default;
109+
}
110+
}
111+
80112
if (displayKind == DisplayKind.Strings)
81113
{
82114
// We only read a maximum of 1024 characters for each string. This may lead to some collisions if strings are unique
@@ -198,6 +230,28 @@ orderby Size
198230
Console.WriteLine($"Total {stats.Values.Sum(r => r.Count):n0} objects, {stats.Values.Sum(r => (long)r.Size):n0} bytes");
199231
}
200232
}
233+
234+
// Print fragmentation if we calculated it
235+
PrintFragmentation(fragmentation);
236+
}
237+
238+
private void PrintFragmentation(List<(ClrObject Free, ClrObject Next)> fragmentation)
239+
{
240+
if (fragmentation is null || fragmentation.Count == 0)
241+
{
242+
return;
243+
}
244+
245+
TableOutput output = new(Console, (16, "x12"), (12, "n0"), (16, "x12"));
246+
247+
Console.WriteLine();
248+
Console.WriteLine("Fragmented blocks larger than 0.5 MB:");
249+
output.WriteRow("Address", "Size", "Followed By");
250+
251+
foreach ((ClrObject free, ClrObject next) in fragmentation)
252+
{
253+
output.WriteRow(free.Address, free.Size, new DmlDumpObj(next.Address), next.Type?.Name ?? "<unknown_type>");
254+
}
201255
}
202256

203257
private string Sanitize(string str, int maxLen)

src/Microsoft.Diagnostics.ExtensionCommands/NotReachableInRangeCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public override void Invoke()
6262
ulong curr = start;
6363

6464
IEnumerable<ClrObject> liveObjs = EnumerateLiveObjectsInRange(end, curr);
65-
DumpHeap.PrintHeap(liveObjs, Short ? DumpHeapService.DisplayKind.Short : DumpHeapService.DisplayKind.Normal, statsOnly: false);
65+
DumpHeap.PrintHeap(liveObjs, Short ? DumpHeapService.DisplayKind.Short : DumpHeapService.DisplayKind.Normal, statsOnly: false, printFragmentation: false);
6666
}
6767

6868
private IEnumerable<ClrObject> EnumerateLiveObjectsInRange(ulong end, ulong curr)

src/Microsoft.Diagnostics.ExtensionCommands/ObjSizeCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override void Invoke()
4949
Console.WriteLine();
5050

5151
DumpHeapService.DisplayKind displayKind = Strings ? DumpHeapService.DisplayKind.Strings : DumpHeapService.DisplayKind.Normal;
52-
DumpHeap.PrintHeap(GetTransitiveClosure(obj), displayKind, Stat);
52+
DumpHeap.PrintHeap(GetTransitiveClosure(obj), displayKind, Stat, printFragmentation: false);
5353
}
5454

5555
private static IEnumerable<ClrObject> GetTransitiveClosure(ClrObject obj)

src/SOS/Strike/strike.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3833,7 +3833,6 @@ DECLARE_API(DumpRuntimeTypes)
38333833
return Status;
38343834
}
38353835

3836-
#define MIN_FRAGMENTATIONBLOCK_BYTES (1024*512)
38373836
namespace sos
38383837
{
38393838
class FragmentationBlock

0 commit comments

Comments
 (0)