forked from maartenba/memory-demos
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
601 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<configuration> | ||
<startup> | ||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | ||
</startup> | ||
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
namespace ClrMd.Explorer | ||
{ | ||
public class ClrHeapObject | ||
{ | ||
public int Generation { get; } | ||
public ulong Ptr { get; } | ||
|
||
public ClrHeapObject(int generation, ulong ptr) | ||
{ | ||
Generation = generation; | ||
Ptr = ptr; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{A370D1FA-C986-41F2-9AE3-5E9FD4A06DDF}</ProjectGuid> | ||
<OutputType>Exe</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>ClrMd.Explorer</RootNamespace> | ||
<AssemblyName>ClrMd.Explorer</AssemblyName> | ||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> | ||
<FileAlignment>512</FileAlignment> | ||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
<OutputPath>bin\Debug\</OutputPath> | ||
<DefineConstants>DEBUG;TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<DebugType>pdbonly</DebugType> | ||
<Optimize>true</Optimize> | ||
<OutputPath>bin\Release\</OutputPath> | ||
<DefineConstants>TRACE</DefineConstants> | ||
<ErrorReport>prompt</ErrorReport> | ||
<WarningLevel>4</WarningLevel> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="Microsoft.Diagnostics.Runtime, Version=0.8.31.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | ||
<HintPath>..\packages\Microsoft.Diagnostics.Runtime.0.8.31-beta\lib\net40\Microsoft.Diagnostics.Runtime.dll</HintPath> | ||
<Private>True</Private> | ||
</Reference> | ||
<Reference Include="System" /> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Xml.Linq" /> | ||
<Reference Include="System.Data.DataSetExtensions" /> | ||
<Reference Include="Microsoft.CSharp" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Net.Http" /> | ||
<Reference Include="System.Xml" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="ClrHeapObject.cs" /> | ||
<Compile Include="Program.cs" /> | ||
<Compile Include="Properties\AssemblyInfo.cs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<None Include="App.config" /> | ||
<None Include="packages.config" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | ||
Other similar extension points exist, see Microsoft.Common.targets. | ||
<Target Name="BeforeBuild"> | ||
</Target> | ||
<Target Name="AfterBuild"> | ||
</Target> | ||
--> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading; | ||
using Microsoft.Diagnostics.Runtime; | ||
|
||
namespace ClrMd.Explorer | ||
{ | ||
class Program | ||
{ | ||
static void Main(string[] args) | ||
{ | ||
// Start the ClrMd.Target process | ||
var demoProcess = StartDemoProcess(); | ||
|
||
// Give it a few seconds to run | ||
Thread.Sleep(TimeSpan.FromSeconds(2)); | ||
|
||
// Attach ClrMd to our process | ||
using (var dataTarget = DataTarget.AttachToProcess(demoProcess.Id, 10000, AttachFlag.Invasive)) // invasive, pausing our target process | ||
{ | ||
// Dump CLR info | ||
var clrVersion = dataTarget.ClrVersions.First(); | ||
var dacInfo = clrVersion.DacInfo; | ||
|
||
Console.WriteLine("# CLR Info"); | ||
Console.WriteLine("Version: {0}", clrVersion.Version); | ||
Console.WriteLine("Filesize: {0:X}", dacInfo.FileSize); | ||
Console.WriteLine("Timestamp: {0:X}", dacInfo.TimeStamp); | ||
Console.WriteLine("Dac file: {0}", dacInfo.FileName); | ||
Console.WriteLine(""); | ||
|
||
// Dump runtime info | ||
var runtime = clrVersion.CreateRuntime(); | ||
var appDomain = runtime.AppDomains.First(); | ||
|
||
Console.WriteLine("# Runtime Info"); | ||
Console.WriteLine("AppDomain: {0}", appDomain.Name); | ||
Console.WriteLine("Address: {0}", appDomain.Address); | ||
Console.WriteLine("Configuration: {0}", appDomain.ConfigurationFile); | ||
Console.WriteLine("Directory: {0}", appDomain.ApplicationBase); | ||
Console.WriteLine(""); | ||
|
||
// Dump thread info | ||
Console.WriteLine("## Threads"); | ||
Console.WriteLine("Thread count: {0}", runtime.Threads.Count); | ||
Console.WriteLine(""); | ||
foreach (var thread in runtime.Threads) | ||
{ | ||
Console.WriteLine("### Thread {0}", thread.OSThreadId); | ||
Console.WriteLine("Thread type: {0}", thread.IsBackground ? "Background" | ||
: thread.IsGC ? "GC" | ||
: "Foreground"); | ||
Console.WriteLine(""); | ||
Console.WriteLine("Stack trace:"); | ||
foreach (var stackFrame in thread.EnumerateStackTrace()) | ||
{ | ||
Console.WriteLine("* {0}", stackFrame.DisplayString); | ||
} | ||
Console.WriteLine(""); | ||
} | ||
|
||
// Dump heap info | ||
var heap = runtime.GetHeap(); | ||
|
||
if (heap.CanWalkHeap) | ||
{ | ||
Console.WriteLine("## What's the retention path of the Clock object?"); | ||
Console.WriteLine(""); | ||
foreach (var ptr in heap.EnumerateObjectAddresses()) | ||
{ | ||
var type = heap.GetObjectType(ptr); | ||
if (type == null || type.Name != "ClrMd.Target.Clock") | ||
{ | ||
continue; | ||
} | ||
|
||
|
||
// Enumerate roots and try to find the current object | ||
var stack = new Stack<ulong>(); | ||
foreach (var root in heap.EnumerateRoots()) | ||
{ | ||
stack.Clear(); | ||
stack.Push(root.Object); | ||
if (GetPathToObject(heap, ptr, stack, new HashSet<ulong>())) | ||
{ | ||
// Print retention path | ||
var depth = 0; | ||
foreach (var address in stack) | ||
{ | ||
var t = heap.GetObjectType(address); | ||
if (t == null) | ||
{ | ||
continue; | ||
} | ||
|
||
Console.WriteLine("{0} {1} - {2} - {3} bytes", new string('+', depth++), address, t.Name, t.GetSize(address)); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
} | ||
|
||
Console.WriteLine("## Heap info"); | ||
Console.WriteLine("Heap count: {0}", runtime.HeapCount); | ||
Console.WriteLine(""); | ||
|
||
foreach (var generation in heap.EnumerateObjectAddresses() | ||
.Select(ptr => new ClrHeapObject(heap.GetGeneration(ptr), ptr)) | ||
.GroupBy(item => item.Generation) | ||
.OrderBy(item => item.Key)) | ||
{ | ||
Console.WriteLine("### Generation {0}", generation.Key); | ||
foreach (var objectAddress in generation) | ||
{ | ||
var type = heap.GetObjectType(objectAddress.Ptr); | ||
if (type == null) | ||
{ | ||
continue; | ||
} | ||
|
||
Console.WriteLine("* {0} - {1} - {2} bytes", objectAddress.Ptr, type.Name, type.GetSize(objectAddress.Ptr)); | ||
if (type.HasSimpleValue) | ||
{ | ||
Console.WriteLine("** Value: {0}", type.GetValue(objectAddress.Ptr)); | ||
} | ||
} | ||
Console.WriteLine(""); | ||
} | ||
} | ||
|
||
|
||
Console.WriteLine("## Heap info"); | ||
Console.WriteLine("Heap count: {0}", runtime.HeapCount); | ||
Console.WriteLine(""); | ||
if (heap.CanWalkHeap) | ||
{ | ||
foreach (var generation in heap.EnumerateObjectAddresses() | ||
.Select(ptr => new ClrHeapObject(heap.GetGeneration(ptr), ptr)) | ||
.GroupBy(item => item.Generation) | ||
.OrderBy(item => item.Key)) | ||
{ | ||
Console.WriteLine("### Generation {0}", generation.Key); | ||
foreach (var objectAddress in generation) | ||
{ | ||
var type = heap.GetObjectType(objectAddress.Ptr); | ||
if (type == null) | ||
{ | ||
continue; | ||
} | ||
|
||
Console.WriteLine("* {0} - {1} - {2} bytes", objectAddress.Ptr, type.Name, type.GetSize(objectAddress.Ptr)); | ||
if (type.HasSimpleValue) | ||
{ | ||
Console.WriteLine("** Value: {0}", type.GetValue(objectAddress.Ptr)); | ||
} | ||
|
||
if (type.Name == "ClrMd.Target.Clock") | ||
{ | ||
// Enumerate roots and try to find the current object | ||
var stack = new Stack<ulong>(); | ||
foreach (var root in heap.EnumerateRoots()) | ||
{ | ||
stack.Clear(); | ||
stack.Push(root.Object); | ||
if (GetPathToObject(heap, objectAddress.Ptr, stack, new HashSet<ulong>())) | ||
{ | ||
// Print retention path | ||
var depth = 0; | ||
foreach (var address in stack) | ||
{ | ||
var t = heap.GetObjectType(address); | ||
if (t == null) | ||
{ | ||
continue; | ||
} | ||
|
||
Console.WriteLine("{0} {1} - {2} - {3} bytes", new string('+', depth++), address, t.Name, t.GetSize(address)); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
} | ||
} | ||
Console.WriteLine(""); | ||
} | ||
} | ||
|
||
// Dump strings | ||
if (heap.CanWalkHeap) | ||
{ | ||
var numberOfStrings = 0; | ||
var uniqueStrings = new Dictionary<string, int>(); | ||
|
||
foreach (var ptr in heap.EnumerateObjectAddresses()) | ||
{ | ||
var type = heap.GetObjectType(ptr); | ||
|
||
// Skip if not a string | ||
if (type == null || type.IsString == false) | ||
{ | ||
continue; | ||
} | ||
|
||
// Count total | ||
numberOfStrings++; | ||
|
||
// Get value | ||
var text = (string)type.GetValue(ptr); | ||
if (uniqueStrings.ContainsKey(text)) | ||
{ | ||
uniqueStrings[text]++; | ||
} | ||
else | ||
{ | ||
uniqueStrings[text] = 1; | ||
} | ||
} | ||
|
||
Console.WriteLine("## String info"); | ||
Console.WriteLine("String count: {0}", numberOfStrings); | ||
Console.WriteLine(""); | ||
|
||
Console.WriteLine("Most duplicated strings: (top 5)"); | ||
foreach (var keyValuePair in uniqueStrings.OrderByDescending(kvp => kvp.Value).Take(5)) | ||
{ | ||
Console.WriteLine("* {0} usages: {1}", keyValuePair.Value, keyValuePair.Key); | ||
} | ||
Console.WriteLine(""); | ||
} | ||
} | ||
|
||
// Wait | ||
Console.WriteLine("Press <enter> to quit"); | ||
Console.ReadLine(); | ||
|
||
// Kill demo process | ||
try | ||
{ | ||
demoProcess.Kill(); | ||
} | ||
catch | ||
{ | ||
} | ||
} | ||
|
||
private static Process StartDemoProcess() | ||
{ | ||
// Start the ClrMd.Target process | ||
var processStartInfo = new ProcessStartInfo(Assembly.GetExecutingAssembly().Location | ||
.Replace("ClrMd.Explorer", "ClrMd.Target")); | ||
|
||
return Process.Start(processStartInfo); | ||
} | ||
|
||
private static bool GetPathToObject(ClrHeap heap, ulong objectPointer, Stack<ulong> stack, HashSet<ulong> touchedObjects) | ||
{ | ||
// Start of the journey - get address of the first objetc on our reference chain | ||
var currentObject = stack.Peek(); | ||
|
||
// Have we checked this object before? | ||
if (!touchedObjects.Add(currentObject)) | ||
{ | ||
return false; | ||
} | ||
|
||
// Did we find our object? Then we have the path! | ||
if (currentObject == objectPointer) | ||
{ | ||
return true; | ||
} | ||
|
||
|
||
// Enumerate internal references of the object | ||
var found = false; | ||
var type = heap.GetObjectType(currentObject); | ||
if (type != null) | ||
{ | ||
type.EnumerateRefsOfObject(currentObject, (innerObject, fieldOffset) => | ||
{ | ||
if (innerObject == 0 || touchedObjects.Contains(innerObject)) | ||
{ | ||
return; | ||
} | ||
// Push the object onto our stack | ||
stack.Push(innerObject); | ||
if (GetPathToObject(heap, objectPointer, stack, touchedObjects)) | ||
{ | ||
found = true; | ||
return; | ||
} | ||
// If not found, pop the object from our stack as this is not the tree we're looking for | ||
stack.Pop(); | ||
}); | ||
} | ||
|
||
return found; | ||
} | ||
} | ||
} |
Oops, something went wrong.