Skip to content

Commit 3d73018

Browse files
author
Christophe Nasarre
committed
Add -stat to gcInfo command to display a "dumpheap -stat" for each generation in each segment
cleanup: remove unused forgotten file
1 parent ae0de2b commit 3d73018

File tree

10 files changed

+161
-98
lines changed

10 files changed

+161
-98
lines changed

Documentation/gsose.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ System.Collections.Concurrent.ConcurrentDictionary<System.Int32,NetCoreConsoleAp
210210
```
211211
!GCInfo
212212
213-
!GCInfo lists generations per segments with pinned objects
214-
0:000> !gci
213+
!GCInfo lists generations per segments. Show pinned objects with -pinned and object instances count/size with -stat (by default)
214+
0:000> !gci -pinned
215215
13 - 7 generations
216216
LOH | 9F06001000 - 9F0CDDB8C8 ( 115,189,960)
217217

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The few "debugging extensions" that have been created at Criteo to help post-mor
44
- as a [stand alone tool](./Documentation/ClrMDStudio.md) to load a .NET application memory dump and start automatic thread, thread pool, tasks and timer analysis.
55
[zip](./binaries/ClrMDStudio-1.5.1_x64.zip)
66
- as a [WinDBG extension](./Documentation/gsose.md) to get the same level of details plus more commands such as getting a method signature based on its address.
7-
[zip](./binaries/gsose-1.5.2_x64.zip)
7+
[zip](./binaries/gsose-1.5.3_x64.zip)
88

99
More analyzers and commands will be added as needed.
1010

Binary file not shown.

src/ClrMDStudio/ClrMDHelper.cs

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,26 @@ public FreeBlock(ulong address, ulong size)
2121
public class GenerationInSegment
2222
{
2323
private List<(ClrHandle handle, string typeDescription)> _pinnedObjects;
24-
private readonly IReadOnlyList<FreeBlock> _freeBlocks;
25-
26-
public GenerationInSegment(IReadOnlyList<FreeBlock> freeBlocks)
24+
25+
public GenerationInSegment(ulong[] instances, IReadOnlyList<FreeBlock> freeBlocks, int count, ulong size)
2726
{
28-
_freeBlocks = freeBlocks;
27+
FreeBlocks = freeBlocks;
28+
FreeBlocksCount = count;
29+
FreeBlocksSize = size;
30+
InstancesAddresses = instances;
2931
_pinnedObjects = new List<(ClrHandle, string)>();
3032
}
3133

3234
public int Generation { get; set; }
3335
public ulong Start { get; set; }
3436
public ulong End { get; set; }
3537
public ulong Length { get; set; }
38+
public ulong[] InstancesAddresses { get; }
3639
public IReadOnlyList<(ClrHandle handle, string typeDescription)> PinnedObjects =>
3740
_pinnedObjects.OrderBy(po => po.handle.Object).ToList();
38-
public IReadOnlyList<FreeBlock> FreeBlocks => _freeBlocks;
41+
public IReadOnlyList<FreeBlock> FreeBlocks { get; }
42+
public int FreeBlocksCount { get; }
43+
public ulong FreeBlocksSize { get; }
3944

4045
internal void AddPinnedObject(ClrHandle pinnedObject)
4146
{
@@ -90,11 +95,12 @@ private bool IsAddressInGeneration(ulong address, GenerationInSegment generation
9095
}
9196
internal void AddGenerationInSegment(
9297
int generation, ulong start, ulong end, ulong length,
98+
ulong[] instances, int count, ulong size,
9399
IReadOnlyList<FreeBlock> freeBlocks
94100
)
95101
{
96102
_generations.Add(
97-
new GenerationInSegment(freeBlocks)
103+
new GenerationInSegment(instances, freeBlocks, count, size)
98104
{
99105
Generation = generation,
100106
Start = start,
@@ -1243,7 +1249,7 @@ public IReadOnlyList<PinnedObjectsGeneration> ComputePinnedObjects()
12431249

12441250
return generations;
12451251
}
1246-
public IReadOnlyList<SegmentInfo> ComputeGCSegments()
1252+
public IReadOnlyList<SegmentInfo> ComputeGCSegments(bool needPinned)
12471253
{
12481254
// merge ClrSegments
12491255
List<SegmentInfo> segments = new List<SegmentInfo>();
@@ -1263,36 +1269,39 @@ public IReadOnlyList<SegmentInfo> ComputeGCSegments()
12631269
}
12641270

12651271
// dispatch pinned objects to the right segment/generation
1266-
var pinnedObjectsCount = 0;
1267-
foreach (var gcHandle in _clr.EnumerateHandles())
1272+
if (needPinned)
12681273
{
1269-
if (!gcHandle.IsPinned)
1270-
continue;
1271-
1272-
// address of the object pinned by the handle
1273-
var address = gcHandle.Object;
1274-
var segment = _heap.GetSegmentByAddress(address);
1275-
if (segment != null)
1274+
var pinnedObjectsCount = 0;
1275+
foreach (var gcHandle in _clr.EnumerateHandles())
12761276
{
1277-
var generation = segment.GetGeneration(address);
1277+
if (!gcHandle.IsPinned)
1278+
continue;
12781279

1279-
// take care of LOH case
1280-
if ((generation == 2) && (segment.IsLarge))
1280+
// address of the object pinned by the handle
1281+
var address = gcHandle.Object;
1282+
var segment = _heap.GetSegmentByAddress(address);
1283+
if (segment != null)
12811284
{
1282-
generation = 3;
1283-
}
1285+
var generation = segment.GetGeneration(address);
12841286

1285-
var genInSegment = GetGeneration(segments, address);
1287+
// take care of LOH case
1288+
if ((generation == 2) && (segment.IsLarge))
1289+
{
1290+
generation = 3;
1291+
}
12861292

1287-
Debug.Assert(genInSegment != null);
1288-
Debug.Assert(genInSegment.Generation == generation);
1293+
var genInSegment = GetGeneration(segments, address);
12891294

1290-
pinnedObjectsCount++;
1291-
genInSegment.AddPinnedObject(gcHandle);
1292-
}
1293-
else
1294-
{
1295-
// should never occur
1295+
Debug.Assert(genInSegment != null);
1296+
Debug.Assert(genInSegment.Generation == generation);
1297+
1298+
pinnedObjectsCount++;
1299+
genInSegment.AddPinnedObject(gcHandle);
1300+
}
1301+
else
1302+
{
1303+
// should never occur
1304+
}
12961305
}
12971306
}
12981307

@@ -1319,14 +1328,16 @@ private GenerationInSegment GetGeneration(List<SegmentInfo> segments, ulong addr
13191328
}
13201329
private void MergeSegment(ClrSegment segment, SegmentInfo info)
13211330
{
1322-
var freeObjects = ComputeFreeBlocks(segment);
1331+
var freeObjects =
1332+
DispatchInstances(segment, out var freeBlocksCount, out var freeBlocksSize, out var instances);
13231333

13241334
// if LOH, just one generation in this segment
13251335
if (segment.IsLarge)
13261336
{
13271337
// add only an LOH generation in segment info
13281338
info.AddGenerationInSegment(
13291339
3, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length, segment.Gen2Length,
1340+
instances, freeBlocksCount, freeBlocksSize,
13301341
FilterFreeBlocks(freeObjects, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length)
13311342
);
13321343
return;
@@ -1337,34 +1348,49 @@ private void MergeSegment(ClrSegment segment, SegmentInfo info)
13371348
{
13381349
info.AddGenerationInSegment(
13391350
0, segment.Gen0Start, segment.Gen0Start + segment.Gen0Length, segment.Gen0Length,
1351+
instances, freeBlocksCount, freeBlocksSize,
13401352
FilterFreeBlocks(freeObjects, segment.Gen0Start, segment.Gen0Start + segment.Gen0Length)
13411353
);
13421354
info.AddGenerationInSegment(
13431355
1, segment.Gen1Start, segment.Gen1Start + segment.Gen1Length, segment.Gen1Length,
1356+
instances, freeBlocksCount, freeBlocksSize,
13441357
FilterFreeBlocks(freeObjects, segment.Gen1Start, segment.Gen1Start + segment.Gen1Length)
13451358
);
13461359
}
13471360

13481361
// always add gen2
13491362
info.AddGenerationInSegment(
13501363
2, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length, segment.Gen2Length,
1364+
instances, freeBlocksCount, freeBlocksSize,
13511365
FilterFreeBlocks(freeObjects, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length)
13521366
);
13531367
}
13541368

1355-
private IReadOnlyList<FreeBlock> ComputeFreeBlocks(ClrSegment segment)
1369+
private IReadOnlyList<FreeBlock> DispatchInstances(ClrSegment segment,
1370+
out int freeBlocksCount, out ulong freeBlocksSize, out ulong[] instanceAddresses)
13561371
{
1357-
var freeBlocks = new List<FreeBlock>();
1358-
1372+
freeBlocksSize = 0;
1373+
freeBlocksCount = 0;
1374+
var freeBlocks = new List<FreeBlock>(128);
1375+
var instances = new List<ulong>(128);
13591376
for (ulong obj = segment.FirstObject; obj != 0; obj = segment.NextObject(obj))
13601377
{
13611378
var type = segment.Heap.GetObjectType(obj);
13621379
if (type.IsFree)
13631380
{
1364-
freeBlocks.Add(new FreeBlock(obj, type.GetSize(obj)));
1381+
var blockSize = type.GetSize(obj);
1382+
freeBlocksSize += blockSize;
1383+
freeBlocksCount++;
1384+
1385+
freeBlocks.Add(new FreeBlock(obj, blockSize));
1386+
}
1387+
else
1388+
{
1389+
instances.Add(obj);
13651390
}
13661391
}
13671392

1393+
instanceAddresses = instances.ToArray();
13681394
return freeBlocks;
13691395
}
13701396
private IReadOnlyList<FreeBlock> FilterFreeBlocks(

src/ClrMDStudio/Panes/GCMemoryWindow.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void ForceClose()
5252
private void Initialize()
5353
{
5454
var helper = new ClrMDHelper(_session.Clr);
55-
var segments = helper.ComputeGCSegments();
55+
var segments = helper.ComputeGCSegments(needPinned:true);
5656
SetResultAsync(segments);
5757
}
5858

src/gsose/DumpHeap.cs

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/gsose/GarbageCollector.cs

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,21 @@ public static void GCInfo(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string
3030

3131
private static void OnGCInfo(ClrRuntime runtime, string args)
3232
{
33+
if (!runtime.Heap.CanWalkHeap)
34+
{
35+
Console.WriteLine("Impossible to walk managed heap...");
36+
return;
37+
}
38+
39+
var showPinned = (args.Contains("-pinned"));
40+
var showStats = (args.Contains("-stat")) || !showPinned; // show it by default if nothing else has been asked
3341
var helper = new ClrMDHelper(runtime);
34-
var segments = helper.ComputeGCSegments();
42+
var segments = helper.ComputeGCSegments(showPinned);
3543

36-
ListGenerations(segments);
44+
ListGenerations(runtime.Heap, segments, showStats, showPinned);
3745
}
3846

39-
private static void ListGenerations(IReadOnlyList<SegmentInfo> segments)
47+
private static void ListGenerations(ClrHeap heap, IReadOnlyList<SegmentInfo> segments, bool showStats, bool showPinned)
4048
{
4149
var sb = new StringBuilder(8 * 1024 * 1024);
4250
for (int currentSegment = 0; currentSegment < segments.Count; currentSegment++)
@@ -50,22 +58,88 @@ private static void ListGenerations(IReadOnlyList<SegmentInfo> segments)
5058
var generation = generations[currentGeneration]; // V---- up to 99 GB
5159
Console.WriteLine($" {GetGenerationType(generation)} | {generation.Start.ToString("X")} - {generation.End.ToString("X")} ({(generation.End - generation.Start).ToString("N0").PadLeft(14)})");
5260

53-
sb.Clear();
54-
var pinnedObjects = generation.PinnedObjects;
55-
for (int currentPinnedObject = 0; currentPinnedObject < pinnedObjects.Count; currentPinnedObject++)
61+
if (showStats)
5662
{
57-
var pinnedObject = pinnedObjects[currentPinnedObject];
58-
sb.AppendFormat(" {0,11} | <link cmd=\"!do {1:x}\">{1:x}</link> {2}\r\n",
59-
pinnedObject.handle.HandleType,
60-
pinnedObject.handle.Object,
61-
pinnedObject.typeDescription
63+
sb.Clear();
64+
65+
ShowStatsForGenerationInSegment(heap, generation, sb);
66+
Console.WriteLine(sb.ToString());
67+
}
68+
if (showPinned)
69+
{
70+
sb.Clear();
71+
var pinnedObjects = generation.PinnedObjects;
72+
for (int currentPinnedObject = 0; currentPinnedObject < pinnedObjects.Count; currentPinnedObject++)
73+
{
74+
var pinnedObject = pinnedObjects[currentPinnedObject];
75+
sb.AppendFormat(" {0,11} | <link cmd=\"!do {1:x}\">{1:x}</link> {2}\r\n",
76+
pinnedObject.handle.HandleType,
77+
pinnedObject.handle.Object,
78+
pinnedObject.typeDescription
6279
);
80+
}
81+
Console.WriteLine(sb.ToString());
6382
}
64-
Console.WriteLine(sb.ToString());
6583
}
6684
}
6785
}
6886

87+
88+
class TypeEntry
89+
{
90+
public string TypeName;
91+
public int Count;
92+
public ulong Size;
93+
}
94+
95+
private static void ShowStatsForGenerationInSegment(ClrHeap heap, GenerationInSegment generation, StringBuilder sb)
96+
{
97+
var statistics = new Dictionary<string, TypeEntry>(128);
98+
int objectCount = 0;
99+
for (int i = 0; i < generation.InstancesAddresses.Length; i++)
100+
{
101+
var address = generation.InstancesAddresses[i];
102+
var type = heap.GetObjectType(address);
103+
var name = GetTypeName(type);
104+
105+
ulong size = type.GetSize(address);
106+
107+
if (!statistics.TryGetValue(name, out var entry))
108+
{
109+
entry = new TypeEntry()
110+
{
111+
TypeName = type.Name,
112+
Size = 0
113+
};
114+
statistics[name] = entry;
115+
}
116+
entry.Count++;
117+
entry.Size += size;
118+
objectCount++;
119+
}
120+
121+
var sortedStatistics =
122+
from entry in statistics.Values
123+
orderby entry.Size descending
124+
select entry;
125+
Console.WriteLine(" {0,12} {1,12} {2}", "Count", "TotalSize", "Class Name");
126+
foreach (var entry in sortedStatistics)
127+
Console.WriteLine($" {entry.Size,12:D} {entry.Count,12:D} {entry.TypeName}");
128+
Console.WriteLine($" Total {objectCount} objects");
129+
}
130+
131+
static readonly Dictionary<ClrType, string> TypeNames = new Dictionary<ClrType, string>();
132+
private static string GetTypeName(ClrType type)
133+
{
134+
if (!TypeNames.TryGetValue(type, out var typeName))
135+
{
136+
typeName = type.Name;
137+
TypeNames[type] = typeName;
138+
}
139+
140+
return typeName;
141+
}
142+
69143
private static string GetGenerationType(GenerationInSegment generation)
70144
{
71145
return GetGenerationType(generation.Generation);

src/gsose/Help.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,9 @@ public static void help(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string a
216216
"-------------------------------------------------------------------------------\r\n" +
217217
"!GCInfo\r\n" +
218218
"\r\n" +
219-
"!GCInfo lists generations per segments with pinned objects" +
219+
"!GCInfo lists generations per segments. Show pinned objects with -pinned and object instances count/size with -stat (by default)" +
220220
"\r\n" +
221-
"0:000> !gci\r\n" +
221+
"0:000> !gci [-stat] [-pinned]\r\n" +
222222
"\r\n" +
223223
"\r\n";
224224
//

0 commit comments

Comments
 (0)