Skip to content

Commit

Permalink
Add -stat to gcInfo command to display a "dumpheap -stat" for each ge…
Browse files Browse the repository at this point in the history
…neration in each segment

cleanup: remove unused forgotten file
  • Loading branch information
Christophe Nasarre committed Mar 11, 2019
1 parent ae0de2b commit 3d73018
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 98 deletions.
4 changes: 2 additions & 2 deletions Documentation/gsose.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ System.Collections.Concurrent.ConcurrentDictionary<System.Int32,NetCoreConsoleAp
```
!GCInfo
!GCInfo lists generations per segments with pinned objects
0:000> !gci
!GCInfo lists generations per segments. Show pinned objects with -pinned and object instances count/size with -stat (by default)
0:000> !gci -pinned
13 - 7 generations
LOH | 9F06001000 - 9F0CDDB8C8 ( 115,189,960)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The few "debugging extensions" that have been created at Criteo to help post-mor
- 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.
[zip](./binaries/ClrMDStudio-1.5.1_x64.zip)
- 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.
[zip](./binaries/gsose-1.5.2_x64.zip)
[zip](./binaries/gsose-1.5.3_x64.zip)

More analyzers and commands will be added as needed.

Expand Down
Binary file not shown.
96 changes: 61 additions & 35 deletions src/ClrMDStudio/ClrMDHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ public FreeBlock(ulong address, ulong size)
public class GenerationInSegment
{
private List<(ClrHandle handle, string typeDescription)> _pinnedObjects;
private readonly IReadOnlyList<FreeBlock> _freeBlocks;

public GenerationInSegment(IReadOnlyList<FreeBlock> freeBlocks)

public GenerationInSegment(ulong[] instances, IReadOnlyList<FreeBlock> freeBlocks, int count, ulong size)
{
_freeBlocks = freeBlocks;
FreeBlocks = freeBlocks;
FreeBlocksCount = count;
FreeBlocksSize = size;
InstancesAddresses = instances;
_pinnedObjects = new List<(ClrHandle, string)>();
}

public int Generation { get; set; }
public ulong Start { get; set; }
public ulong End { get; set; }
public ulong Length { get; set; }
public ulong[] InstancesAddresses { get; }
public IReadOnlyList<(ClrHandle handle, string typeDescription)> PinnedObjects =>
_pinnedObjects.OrderBy(po => po.handle.Object).ToList();
public IReadOnlyList<FreeBlock> FreeBlocks => _freeBlocks;
public IReadOnlyList<FreeBlock> FreeBlocks { get; }
public int FreeBlocksCount { get; }
public ulong FreeBlocksSize { get; }

internal void AddPinnedObject(ClrHandle pinnedObject)
{
Expand Down Expand Up @@ -90,11 +95,12 @@ private bool IsAddressInGeneration(ulong address, GenerationInSegment generation
}
internal void AddGenerationInSegment(
int generation, ulong start, ulong end, ulong length,
ulong[] instances, int count, ulong size,
IReadOnlyList<FreeBlock> freeBlocks
)
{
_generations.Add(
new GenerationInSegment(freeBlocks)
new GenerationInSegment(instances, freeBlocks, count, size)
{
Generation = generation,
Start = start,
Expand Down Expand Up @@ -1243,7 +1249,7 @@ public IReadOnlyList<PinnedObjectsGeneration> ComputePinnedObjects()

return generations;
}
public IReadOnlyList<SegmentInfo> ComputeGCSegments()
public IReadOnlyList<SegmentInfo> ComputeGCSegments(bool needPinned)
{
// merge ClrSegments
List<SegmentInfo> segments = new List<SegmentInfo>();
Expand All @@ -1263,36 +1269,39 @@ public IReadOnlyList<SegmentInfo> ComputeGCSegments()
}

// dispatch pinned objects to the right segment/generation
var pinnedObjectsCount = 0;
foreach (var gcHandle in _clr.EnumerateHandles())
if (needPinned)
{
if (!gcHandle.IsPinned)
continue;

// address of the object pinned by the handle
var address = gcHandle.Object;
var segment = _heap.GetSegmentByAddress(address);
if (segment != null)
var pinnedObjectsCount = 0;
foreach (var gcHandle in _clr.EnumerateHandles())
{
var generation = segment.GetGeneration(address);
if (!gcHandle.IsPinned)
continue;

// take care of LOH case
if ((generation == 2) && (segment.IsLarge))
// address of the object pinned by the handle
var address = gcHandle.Object;
var segment = _heap.GetSegmentByAddress(address);
if (segment != null)
{
generation = 3;
}
var generation = segment.GetGeneration(address);

var genInSegment = GetGeneration(segments, address);
// take care of LOH case
if ((generation == 2) && (segment.IsLarge))
{
generation = 3;
}

Debug.Assert(genInSegment != null);
Debug.Assert(genInSegment.Generation == generation);
var genInSegment = GetGeneration(segments, address);

pinnedObjectsCount++;
genInSegment.AddPinnedObject(gcHandle);
}
else
{
// should never occur
Debug.Assert(genInSegment != null);
Debug.Assert(genInSegment.Generation == generation);

pinnedObjectsCount++;
genInSegment.AddPinnedObject(gcHandle);
}
else
{
// should never occur
}
}
}

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

// if LOH, just one generation in this segment
if (segment.IsLarge)
{
// add only an LOH generation in segment info
info.AddGenerationInSegment(
3, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length, segment.Gen2Length,
instances, freeBlocksCount, freeBlocksSize,
FilterFreeBlocks(freeObjects, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length)
);
return;
Expand All @@ -1337,34 +1348,49 @@ private void MergeSegment(ClrSegment segment, SegmentInfo info)
{
info.AddGenerationInSegment(
0, segment.Gen0Start, segment.Gen0Start + segment.Gen0Length, segment.Gen0Length,
instances, freeBlocksCount, freeBlocksSize,
FilterFreeBlocks(freeObjects, segment.Gen0Start, segment.Gen0Start + segment.Gen0Length)
);
info.AddGenerationInSegment(
1, segment.Gen1Start, segment.Gen1Start + segment.Gen1Length, segment.Gen1Length,
instances, freeBlocksCount, freeBlocksSize,
FilterFreeBlocks(freeObjects, segment.Gen1Start, segment.Gen1Start + segment.Gen1Length)
);
}

// always add gen2
info.AddGenerationInSegment(
2, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length, segment.Gen2Length,
instances, freeBlocksCount, freeBlocksSize,
FilterFreeBlocks(freeObjects, segment.Gen2Start, segment.Gen2Start + segment.Gen2Length)
);
}

private IReadOnlyList<FreeBlock> ComputeFreeBlocks(ClrSegment segment)
private IReadOnlyList<FreeBlock> DispatchInstances(ClrSegment segment,
out int freeBlocksCount, out ulong freeBlocksSize, out ulong[] instanceAddresses)
{
var freeBlocks = new List<FreeBlock>();

freeBlocksSize = 0;
freeBlocksCount = 0;
var freeBlocks = new List<FreeBlock>(128);
var instances = new List<ulong>(128);
for (ulong obj = segment.FirstObject; obj != 0; obj = segment.NextObject(obj))
{
var type = segment.Heap.GetObjectType(obj);
if (type.IsFree)
{
freeBlocks.Add(new FreeBlock(obj, type.GetSize(obj)));
var blockSize = type.GetSize(obj);
freeBlocksSize += blockSize;
freeBlocksCount++;

freeBlocks.Add(new FreeBlock(obj, blockSize));
}
else
{
instances.Add(obj);
}
}

instanceAddresses = instances.ToArray();
return freeBlocks;
}
private IReadOnlyList<FreeBlock> FilterFreeBlocks(
Expand Down
2 changes: 1 addition & 1 deletion src/ClrMDStudio/Panes/GCMemoryWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void ForceClose()
private void Initialize()
{
var helper = new ClrMDHelper(_session.Clr);
var segments = helper.ComputeGCSegments();
var segments = helper.ComputeGCSegments(needPinned:true);
SetResultAsync(segments);
}

Expand Down
42 changes: 0 additions & 42 deletions src/gsose/DumpHeap.cs

This file was deleted.

98 changes: 86 additions & 12 deletions src/gsose/GarbageCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,21 @@ public static void GCInfo(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string

private static void OnGCInfo(ClrRuntime runtime, string args)
{
if (!runtime.Heap.CanWalkHeap)
{
Console.WriteLine("Impossible to walk managed heap...");
return;
}

var showPinned = (args.Contains("-pinned"));
var showStats = (args.Contains("-stat")) || !showPinned; // show it by default if nothing else has been asked
var helper = new ClrMDHelper(runtime);
var segments = helper.ComputeGCSegments();
var segments = helper.ComputeGCSegments(showPinned);

ListGenerations(segments);
ListGenerations(runtime.Heap, segments, showStats, showPinned);
}

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

sb.Clear();
var pinnedObjects = generation.PinnedObjects;
for (int currentPinnedObject = 0; currentPinnedObject < pinnedObjects.Count; currentPinnedObject++)
if (showStats)
{
var pinnedObject = pinnedObjects[currentPinnedObject];
sb.AppendFormat(" {0,11} | <link cmd=\"!do {1:x}\">{1:x}</link> {2}\r\n",
pinnedObject.handle.HandleType,
pinnedObject.handle.Object,
pinnedObject.typeDescription
sb.Clear();

ShowStatsForGenerationInSegment(heap, generation, sb);
Console.WriteLine(sb.ToString());
}
if (showPinned)
{
sb.Clear();
var pinnedObjects = generation.PinnedObjects;
for (int currentPinnedObject = 0; currentPinnedObject < pinnedObjects.Count; currentPinnedObject++)
{
var pinnedObject = pinnedObjects[currentPinnedObject];
sb.AppendFormat(" {0,11} | <link cmd=\"!do {1:x}\">{1:x}</link> {2}\r\n",
pinnedObject.handle.HandleType,
pinnedObject.handle.Object,
pinnedObject.typeDescription
);
}
Console.WriteLine(sb.ToString());
}
Console.WriteLine(sb.ToString());
}
}
}


class TypeEntry
{
public string TypeName;
public int Count;
public ulong Size;
}

private static void ShowStatsForGenerationInSegment(ClrHeap heap, GenerationInSegment generation, StringBuilder sb)
{
var statistics = new Dictionary<string, TypeEntry>(128);
int objectCount = 0;
for (int i = 0; i < generation.InstancesAddresses.Length; i++)
{
var address = generation.InstancesAddresses[i];
var type = heap.GetObjectType(address);
var name = GetTypeName(type);

ulong size = type.GetSize(address);

if (!statistics.TryGetValue(name, out var entry))
{
entry = new TypeEntry()
{
TypeName = type.Name,
Size = 0
};
statistics[name] = entry;
}
entry.Count++;
entry.Size += size;
objectCount++;
}

var sortedStatistics =
from entry in statistics.Values
orderby entry.Size descending
select entry;
Console.WriteLine(" {0,12} {1,12} {2}", "Count", "TotalSize", "Class Name");
foreach (var entry in sortedStatistics)
Console.WriteLine($" {entry.Size,12:D} {entry.Count,12:D} {entry.TypeName}");
Console.WriteLine($" Total {objectCount} objects");
}

static readonly Dictionary<ClrType, string> TypeNames = new Dictionary<ClrType, string>();
private static string GetTypeName(ClrType type)
{
if (!TypeNames.TryGetValue(type, out var typeName))
{
typeName = type.Name;
TypeNames[type] = typeName;
}

return typeName;
}

private static string GetGenerationType(GenerationInSegment generation)
{
return GetGenerationType(generation.Generation);
Expand Down
4 changes: 2 additions & 2 deletions src/gsose/Help.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ public static void help(IntPtr client, [MarshalAs(UnmanagedType.LPStr)] string a
"-------------------------------------------------------------------------------\r\n" +
"!GCInfo\r\n" +
"\r\n" +
"!GCInfo lists generations per segments with pinned objects" +
"!GCInfo lists generations per segments. Show pinned objects with -pinned and object instances count/size with -stat (by default)" +
"\r\n" +
"0:000> !gci\r\n" +
"0:000> !gci [-stat] [-pinned]\r\n" +
"\r\n" +
"\r\n";
//
Expand Down
Loading

0 comments on commit 3d73018

Please sign in to comment.