// Process a trace and create a summary. static SnapshotSummary GetAllocSummary(ITraceProcessor trace) { var pendingSnapshotData = trace.UseHeapSnapshots(); var pendingSymbols = trace.UseSymbols(); trace.Process(); var snapshotData = pendingSnapshotData.Result; var symbols = pendingSymbols.Result; symbols.LoadSymbolsAsync(new SymCachePath(@"c:\symcache")).GetAwaiter().GetResult(); if (snapshotData.Snapshots.Count != 1) { Console.Error.WriteLine("Trace must contain exactly one heap snapshot - actually contained {0}.", snapshotData.Snapshots.Count); return(new SnapshotSummary(null, 0)); } // Scan through all of the allocations and collect them by // SnapshotUniqueStackId (which corresponds to Stack Ref#), // accumulating the bytes allocated, allocation count, and the // stack. var allocsByStackId = new Dictionary <ulong, AllocDetails>(); foreach (IHeapAllocation row in snapshotData.Snapshots[0].Allocations) { allocsByStackId.TryGetValue(row.SnapshotUniqueStackId, out AllocDetails value); value.Stack = row.Stack; value.Size += row.Size; value.Count += 1; allocsByStackId[row.SnapshotUniqueStackId] = value; } // Count how many allocations each stack frame is part of. // RtlThreadStart will presumably be near the top, along with // RtlpAllocateHeapInternal, but some clues may be found. var hotStackFrames = new Dictionary <string, long>(); foreach (var data in allocsByStackId.Values) { foreach (var entry in data.Stack) { var analyzerString = entry.GetAnalyzerString(); hotStackFrames.TryGetValue(analyzerString, out long count); count += data.Count; hotStackFrames[analyzerString] = count; } } var result = new SnapshotSummary(allocsByStackId, snapshotData.Snapshots[0].ProcessId); // Create a summary of the alloc counts and byte counts. var totalAllocBytes = DataSize.Zero; long totalAllocCount = 0; foreach (var data in allocsByStackId.Values) { totalAllocBytes += data.Size; totalAllocCount += data.Count; } result.hotStackFrames_ = hotStackFrames; result.totalBytes_ = totalAllocBytes; result.allocCount_ = totalAllocCount; return(result); }
static void Main(string[] args) { if (args.Length == 0) { Console.WriteLine("Use this to summarize a heap snapshot or compare multiple heap snapshots"); Console.WriteLine("from one run of a program."); return; } SnapshotSummary lastAllocs = null; string lastTracename = ""; foreach (var arg in args) { if (!File.Exists(arg)) { Console.Error.WriteLine("File '{0}' does not exist.", arg); continue; } using (ITraceProcessor trace = TraceProcessor.Create(arg)) { Console.WriteLine("Summarizing '{0}'", Path.GetFileName(arg)); var allocs = GetAllocSummary(trace); if (allocs.allocsByStackId_ == null) { Console.WriteLine("Ignoring trace {0}.", arg); continue; } Console.WriteLine("{0,7:F2} MB from {1,9:#,#} allocations on {2,7:#,#} stacks", allocs.totalBytes_.TotalMegabytes, allocs.allocCount_, allocs.allocsByStackId_.Count); const int maxPrinted = 40; Console.WriteLine("Hottest stack frames:"); // Display a summary of the first (possibly only) heap snapshot trace. var sortedHotStackEntries = new List <KeyValuePair <string, long> >(allocs.hotStackFrames_); sortedHotStackEntries.Sort((x, y) => y.Value.CompareTo(x.Value)); for (int i = 0; i < sortedHotStackEntries.Count && i < maxPrinted; ++i) { var data = sortedHotStackEntries[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } if (lastAllocs != null) { Console.WriteLine("Comparing old ({0}) to new ({1}) snapshots.", Path.GetFileName(lastTracename), Path.GetFileName(arg)); if (allocs.pid_ != lastAllocs.pid_) { Console.WriteLine("WARNING: process IDs are different ({0} and {1}) so stack IDs may not be comparable.", lastAllocs.pid_, allocs.pid_); } var hotStackFramesDelta = new Dictionary <string, long>(allocs.hotStackFrames_); // Subtract the lastAllocs stack frame counts fomr the current stack frame counts. foreach (var entry in lastAllocs.hotStackFrames_) { hotStackFramesDelta.TryGetValue(entry.Key, out long count); count -= entry.Value; hotStackFramesDelta[entry.Key] = count; } Console.WriteLine("Hottest stack frame deltas:"); // Print the biggest deltas, positive then negative. var sortedHotStackFramesDelta = new List <KeyValuePair <string, long> >(hotStackFramesDelta); sortedHotStackFramesDelta.Sort((x, y) => y.Value.CompareTo(x.Value)); // Print the first half... for (int i = 0; i < sortedHotStackFramesDelta.Count && i < maxPrinted / 2; ++i) { var data = sortedHotStackFramesDelta[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } Console.WriteLine("..."); int start = sortedHotStackFramesDelta.Count - maxPrinted / 2; if (start < 0) { start = 0; } for (int i = start; i < sortedHotStackFramesDelta.Count - 1; ++i) { var data = sortedHotStackFramesDelta[i]; Console.WriteLine("{0,5} allocs cross {1}", data.Value, data.Key); } ulong newOnlyStacks = 0; ulong oldOnlyStacks = 0; foreach (var tag in allocs.allocsByStackId_.Keys) { if (!lastAllocs.allocsByStackId_.ContainsKey(tag)) { newOnlyStacks++; } } foreach (var tag in lastAllocs.allocsByStackId_.Keys) { if (!allocs.allocsByStackId_.ContainsKey(tag)) { oldOnlyStacks++; } } Console.WriteLine(" Old snapshot had {0} unique-to-it stacks, new trace had {1} unique-to-it stacks.", oldOnlyStacks, newOnlyStacks); } lastAllocs = allocs; lastTracename = arg; } } }