@@ -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 )
0 commit comments