-
-
Save mattwarren/3dce1aea76c50da850af53a2d453e3c0 to your computer and use it in GitHub Desktop.
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
using Microsoft.Diagnostics.Runtime; | |
using System; | |
using System.IO; | |
using System.Linq; | |
namespace MemoryDumpGcInfo | |
{ | |
// See https://github.com/Microsoft/clrmd/blob/master/Documentation/GettingStarted.md | |
// and https://github.com/Microsoft/clrmd/blob/master/Documentation/ClrRuntime.md | |
// and https://github.com/Microsoft/clrmd/blob/master/Documentation/WalkingTheHeap.md | |
// A useful list of instructions for working with CLRMD, | |
// see http://blogs.msdn.com/b/kirillosenkov/archive/2014/07/05/get-most-duplicated-strings-from-a-heap-dump-using-clrmd.aspx | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Console.ForegroundColor = ConsoleColor.DarkCyan; | |
Console.WriteLine(".NET Memory Dump Heap Analyser - created by Matt Warren - github.com/mattwarren\n"); | |
Console.ResetColor(); | |
if (args.Length < 1) | |
{ | |
Console.WriteLine("Usage:\n MemoryDumpGcInfo.exe <Dump File>\n"); | |
return; | |
} | |
if (File.Exists(args[0]) == false) | |
{ | |
Console.WriteLine("{0} - does not exist!", args[0]); | |
return; | |
} | |
using (DataTarget target = DataTarget.LoadCrashDump(args[0])) | |
{ | |
string dacLocation = null; | |
foreach (ClrInfo version in target.ClrVersions) | |
{ | |
Console.WriteLine("Found CLR Version: " + version.Version.ToString()); | |
dacLocation = LoadCorrectDacForMemoryDump(version); | |
} | |
var runtimeInfo = target.ClrVersions[0]; // just using the first runtime | |
ClrRuntime runtime = null; | |
try | |
{ | |
if (string.IsNullOrEmpty(dacLocation)) | |
runtime = runtimeInfo.CreateRuntime(); | |
else | |
runtime = runtimeInfo.CreateRuntime(dacLocation); | |
} | |
catch (Exception ex) | |
{ | |
Console.WriteLine("\n" + ex); | |
Console.WriteLine("\nEnsure that this program is compliled for the same architecture as the memory dump (i.e. 32-bit or 64-bit)"); | |
return; | |
} | |
var heap = runtime.GetHeap(); | |
//PrintMemoryRegionInfo(runtime); | |
PrintGCHeapInfo(heap); | |
} | |
} | |
private static string LoadCorrectDacForMemoryDump(ClrInfo version) | |
{ | |
// First try the main location, i.e.: | |
// C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll | |
if (version.LocalMatchingDac != null && File.Exists(version.LocalMatchingDac)) | |
{ | |
Console.WriteLine("\nDac already exists on the local machine at:\n{0}", version.LocalMatchingDac); | |
return version.LocalMatchingDac; | |
} | |
// Location: <TEMP>\symbols\mscordacwks_amd64_amd64_4.0.30319.18444.dll\52717f9a96b000\mscordacwks_amd64_amd64_4.0.30319.18444.dll | |
ModuleInfo dacInfo = version.DacInfo; | |
var dacLocation = string.Format(@"{0}\symbols\{1}\{2:x}{3:x}\{4}", | |
Path.GetTempPath(), | |
dacInfo.FileName, | |
dacInfo.TimeStamp, | |
dacInfo.FileSize, | |
dacInfo.FileName); | |
if (File.Exists(dacLocation)) | |
{ | |
Console.WriteLine("\nDac {0} already exists in the local cache at:\n{1}", dacInfo.FileName, dacLocation); | |
return dacLocation; | |
} | |
else | |
{ | |
Console.WriteLine("\nUnable to find copy of the dac on the local machine."); | |
Console.WriteLine("It will now be downloaded from the Microsoft Symbol Server."); | |
Console.WriteLine("Press <ENTER> if you are okay with this, if not you can just type Ctrl-C to exit"); | |
Console.ReadLine(); | |
string downloadLocation = version.TryDownloadDac(new SymbolNotification()); | |
Console.WriteLine("Downloaded a copy of the dac to:\n" + downloadLocation); | |
return downloadLocation; | |
} | |
} | |
private static void PrintMemoryRegionInfo(ClrRuntime runtime) | |
{ | |
Console.ForegroundColor = ConsoleColor.DarkYellow; | |
Console.WriteLine("\nMemory Region Information"); | |
Console.ResetColor(); | |
Console.WriteLine("--------------------------------------------"); | |
Console.WriteLine("{0,6} {1,15} {2}", "Count", "Total Size", "Type"); | |
Console.WriteLine("--------------------------------------------"); | |
foreach (var region in (from r in runtime.EnumerateMemoryRegions() | |
where r.Type != ClrMemoryRegionType.ReservedGCSegment | |
group r by r.Type into g | |
let total = g.Sum(p => (uint)p.Size) | |
orderby total descending | |
select new | |
{ | |
TotalSize = total, | |
Count = g.Count(), | |
Type = g.Key | |
})) | |
{ | |
Console.WriteLine("{0,6:n0} {1,15:n0} {2}", region.Count, region.TotalSize, region.Type.ToString()); | |
} | |
Console.WriteLine("--------------------------------------------"); | |
} | |
private static void PrintGCHeapInfo(ClrHeap heap) | |
{ | |
Console.ForegroundColor = ConsoleColor.DarkYellow; | |
Console.WriteLine("\nGC Heap Information (Segments)"); | |
Console.ResetColor(); | |
Console.WriteLine("----------------------------------------------------------------------------"); | |
Console.WriteLine("{0,12} {1,12} {2,8} {3,9} {4,8} {5,4} {6}", "Start", "End", "Length", "Committed", "Reserved", "Heap", "Type"); | |
Console.WriteLine("----------------------------------------------------------------------------"); | |
foreach (ClrSegment segment in heap.Segments) | |
{ | |
string type; | |
if (segment.IsEphemeral) | |
type = "Ephemeral"; | |
else if (segment.IsLarge) | |
type = "Large"; | |
else | |
type = "Gen2"; | |
//Console.WriteLine("{0,12:X} {1,12:X} {2,8:N2} {3,9:N2} {4,8:N2} {5,4} {6}", | |
Console.WriteLine("{0,12:X} {1,12:X} {2,8:N0} {3,9:N0} {4,8:N0} {5,4} {6}", | |
segment.Start, | |
segment.End, | |
segment.Length / 1024.0, // / 1024.0, | |
(segment.CommittedEnd - segment.Start) / 1024.0, // / 1024.0, | |
(segment.ReservedEnd - segment.Start) / 1024.0, // / 1024.0, | |
segment.ProcessorAffinity, | |
type); | |
} | |
Console.WriteLine("----------------------------------------------------------------------------"); | |
Console.ForegroundColor = ConsoleColor.DarkYellow; | |
Console.WriteLine("\nGC Heap Information (Per CPU)"); | |
Console.ResetColor(); | |
Console.WriteLine("-----------------------------------------------------------"); | |
foreach (var item in (from seg in heap.Segments | |
group seg by seg.ProcessorAffinity into g | |
orderby g.Key | |
select new | |
{ | |
Heap = g.Key, | |
Size = g.Sum(p => (uint)p.Length) | |
})) | |
{ | |
Console.WriteLine("Heap {0,2}: {1,12:n0} bytes ({2:N2} MB)", item.Heap, item.Size, item.Size / 1024.0 / 1024.0); | |
} | |
Console.WriteLine("-----------------------------------------------------------"); | |
Console.WriteLine("Total : {0,12:N0} bytes ({1:N2} MB)", | |
heap.Segments.Sum(s => (long)s.Length), heap.Segments.Sum(s => (long)s.Length) / 1024.0 / 1024.0); | |
Console.WriteLine("-----------------------------------------------------------"); | |
Console.WriteLine(); | |
} | |
} | |
class SymbolNotification : ISymbolNotification | |
{ | |
public void DecompressionComplete(string localPath) | |
{ | |
Console.WriteLine("DecompressionComplete: " + (localPath ?? "<NULL>")); | |
} | |
public void DownloadComplete(string localPath, bool requiresDecompression) | |
{ | |
Console.WriteLine("DecompressionComplete: " + (localPath ?? "<NULL>")); | |
} | |
public void DownloadProgress(int bytesDownloaded) | |
{ | |
Console.WriteLine("DownloadProgress: bytesDownloaded = " + bytesDownloaded); | |
} | |
public void FoundSymbolInCache(string localPath) | |
{ | |
Console.WriteLine("FoundSymbolInCache: " + (localPath ?? "<NULL>")); | |
} | |
public void FoundSymbolOnPath(string url) | |
{ | |
Console.WriteLine("FoundSymbolOnPath: " + (url ?? "<NULL>")); | |
} | |
public void ProbeFailed(string url) | |
{ | |
Console.WriteLine("ProbeFailed: " + (url ?? "<NULL>")); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment