/// <summary> /// Write one line of type statistics metric. This method adds extra columns for CSV output to see which process, cmd line and time /// the data was generated to make it possible to record data in append mode. /// </summary> /// <param name="allocated">Allocated bytes</param> /// <param name="instances">Allocates Intances</param> /// <param name="type">Type name</param> void WriteTypeStatisticsLine(long allocated, long instances, string type) { // Default format string for console output. // fmt is ignored for CSV output where all columns are simply \t separated. string fmt = "{0,-17:N0}\t{1,-17:N0}\t{2}"; if (IsFirstLine) { string[] header = new string[] { AllocatedColumnBase + DisplayUnit, InstancesColumn, TypeColumn }; if (OutputStringWriter.CsvOutput) { header = header.Concat(new string[] { ProcessIdColumn, TimeColumn, CommandeLineColumn, AgeColumn, NameColumn, ContextColumn }).ToArray(); } // write header OutputStringWriter.FormatAndWriteHeader(fmt, header); IsFirstLine = false; } if (OutputStringWriter.CsvOutput) { OutputStringWriter.FormatAndWrite(fmt, allocated, instances, type, TargetInfo.IsLiveProcess ? TargetInfo.Pid1.ToString() : "", TimeAndOrDate, CmdLine, TargetInfo.ProcessAgeInSeconds, TargetInfo.ProcessName, Context); } else { OutputStringWriter.FormatAndWrite(fmt, allocated, instances == 0 ? "" : (object)instances, type); } }
public void Execute(int topN, bool bShowAddress) { Task <StringAnalysisResult> res = Task.Factory.StartNew <StringAnalysisResult>(() => Analyze(Heap, LiveOnly)); if (Heap2 != null) { StringAnalysisResult res2 = Analyze(Heap2, LiveOnly); PrintDiff(topN, res.Result, res2); } else { if (topN > 0) { var sorted = res.Result.StringCounts.OrderByDescending(kvp => kvp.Value.InstanceCount).Take(topN); OutputStringWriter.FormatAndWrite("{0}\t{1}\t{2}", "Strings(Count)", $"Waste({DisplayUnit})", "String"); string fmt = "{0,-12}\t{1,-11:N0}\t{2}"; foreach (var kvp in sorted) { string addressString = bShowAddress ? " 0x" + kvp.Value.SampleAddress.ToString("X") : ""; OutputStringWriter.FormatAndWrite(fmt, kvp.Value.InstanceCount, ((kvp.Value.InstanceCount - 1L) * kvp.Value.SizePerInstance) / (long)DisplayUnit, GetShortString(kvp.Key) + addressString); } } if (!OutputStringWriter.CsvOutput) { Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine("=========================================="); Console.WriteLine($"Strings {res.Result.StringObjectCount,12:N0} count"); Console.WriteLine($"Allocated Size {res.Result.StringsAllocatedInBytes/(long)DisplayUnit,12:N0} {DisplayUnit}"); Console.WriteLine($"Waste Duplicate Strings {res.Result.StringWasteInBytes/(long)DisplayUnit,12:N0} {DisplayUnit}"); } } }
/// <summary> /// Format a type diff format string where only diff, size, size2 and type name are printed to the output. /// </summary> /// <param name="fmt">Type diff format string.</param> /// <param name="unit">Unit in which bytes are printed.</param> /// <returns>Delegate which accepts sizeDiff, size1, size2 as long values.</returns> Action <string, long, long, long> GetSimpleDiffFormatter(string fmt, DisplayUnit unit) { return((typeName, sizeInBytesDiff, sizeInBytes1, sizeInBytes2) => { long unitDivisor = (long)unit; OutputStringWriter.FormatAndWrite(fmt, (long)(sizeInBytesDiff / unitDivisor), "", "", "", (long)(sizeInBytes1 / unitDivisor), (long)(sizeInBytes2 / unitDivisor), "", "", typeName + unit); }); }
/// <summary> /// Print type statistics diff. /// </summary> /// <param name="typeInfos"></param> /// <param name="typeInfos2"></param> /// <param name="vmmap"></param> /// <param name="vmmap2"></param> /// <param name="topN"></param> /// <param name="orderBySize"></param> /// <param name="minCount"></param> /// <returns>Allocated memory diff in KB. If VMmap data is present the total allocated memory diff for allocated managed heap, heap, private, file mappings and sharable memory is returned.</returns> private int PrintTypeStatisticsDiff(List <TypeInfo> typeInfos, List <TypeInfo> typeInfos2, VMMapData vmmap, VMMapData vmmap2, int topN, int minCount, bool orderBySize) { int allocatedMemoryInKB = 0; TypeDiffStatistics delta = GetDiffStatistics(typeInfos, typeInfos2, orderBySize); if (minCount > 0) { delta.TypeDiffs = delta.TypeDiffs.Where(x => Math.Abs(orderBySize ? x.InstanceCountDiff : x.AllocatedBytesDiff) > minCount).ToList(); } string fmt = "{0,-12:N0}\t{1,-17:N0}\t{2,-11:N0}\t{3,-11:N0}\t{4,-17:N0}\t{5,-18:N0}" + "\t{6,-14}\t{7,-15}\t{8}"; OutputStringWriter.FormatAndWriteHeader(fmt, $"Delta({DisplayUnit})", "Delta(Instances)", "Instances", "Instances2", $"Allocated({DisplayUnit})", $"Allocated2({DisplayUnit})", "AvgSize(Bytes)", "AvgSize2(Bytes)", "Type"); long unitDivisor = (long)DisplayUnit; if (topN > 0) { foreach (var diff in delta.TypeDiffs.Take(topN)) { if (diff.Name == FreeTypeName) { continue; } OutputStringWriter.FormatAndWrite(fmt, diff.AllocatedBytesDiff / unitDivisor, diff.InstanceCountDiff, diff?.Info.SafeCount(), diff?.Info2.SafeCount(), diff?.Info.TotalSize() / unitDivisor, diff?.Info2.TotalSize() / unitDivisor, diff?.Info?.AverageSizePerInstance, diff?.Info2?.AverageSizePerInstance, diff.Name); } } allocatedMemoryInKB = (int)(delta.DeltaBytes / (long)DisplayUnit.KB); OutputStringWriter.FormatAndWrite(fmt, delta.DeltaBytes / unitDivisor, delta.DeltaInstances, delta.Count, delta.Count2, delta.SizeInBytes / unitDivisor, delta.SizeInBytes2 / unitDivisor, "", "", ManagedHeapAllocated); long heap1Size = Heap.GetTotalHeapSize(); long heap2Size = Heap2.GetTotalHeapSize(); OutputStringWriter.FormatAndWrite(fmt, (heap2Size - heap1Size) / unitDivisor, 0, 0, 0, heap1Size / unitDivisor, heap2Size / unitDivisor, "", "", ManagedHeapSize); if (vmmap != null && vmmap2 != null && vmmap.HasValues && vmmap2.HasValues) { var diff = vmmap2 - vmmap; WriteVMMapDataDiff(GetSimpleDiffFormatter(fmt, DisplayUnit), vmmap, vmmap2, diff); OutputStringWriter.FormatAndWrite(fmt, (diff.AllocatedBytesWithoutManagedHeap + delta.DeltaBytes) / unitDivisor, "", "", "", (delta.SizeInBytes + vmmap.AllocatedBytesWithoutManagedHeap) / unitDivisor, (vmmap2.AllocatedBytesWithoutManagedHeap + delta.SizeInBytes2) / unitDivisor, "" , "", AllocatedTotal); // When VMMap data is present add the other memory types which usually leak as well also to the allocation number. allocatedMemoryInKB += (int)(diff.AllocatedBytesWithoutManagedHeap / (long)DisplayUnit.KB); } return(allocatedMemoryInKB); }
private void PrintDiff(int topN, StringAnalysisResult res, StringAnalysisResult res2) { var top = res.StringCounts.OrderByDescending(kvp => kvp.Value.InstanceCount).Take(topN).ToDictionary(x => x.Key, x => x.Value); var top2 = res2.StringCounts.OrderByDescending(kvp => kvp.Value.InstanceCount).Take(topN).ToDictionary(x => x.Key, x => x.Value); HashSet <string> uniqueStringValues = new HashSet <string>(top.Select(x => x.Key).Concat(top2.Select(x => x.Key)).ToArray()); List <StringDiff> diffs = new List <StringDiff>(); foreach (var stringValue in uniqueStringValues) { ObjectStatistics stat = null; ObjectStatistics stat2 = null; top.TryGetValue(stringValue, out stat); top2.TryGetValue(stringValue, out stat2); diffs.Add(new StringDiff { DiffInBytes = (stat2 != null ? stat2.AllocatedInBytes : 0) - (stat != null ? stat.AllocatedInBytes : 0), InstanceDiffCount = (stat2 != null ? stat2.InstanceCount : 0) - (stat != null ? stat.InstanceCount : 0), Stat = stat, Stat2 = stat2, Value = stringValue }); } var sortedDiffs = diffs.OrderByDescending(x => Math.Abs(x.DiffInBytes)).ToArray(); Console.WriteLine("String Allocation Diff Statistics"); string fmtString = "{0,-12:N0}\t{1,17:N0}\t{2,-11:N0}\t{3,-11:N0}\t{4,-17:N0}\t{5,-18:N0}\t{6}"; OutputStringWriter.FormatAndWrite(fmtString, $"Delta({DisplayUnit})", "Delta(Instances)", "Instances", "Instances2", $"Allocated({DisplayUnit})", $"Allocated2({DisplayUnit})", "Value"); long displayUnitDiv = (long)DisplayUnit; foreach (var diff in sortedDiffs) { OutputStringWriter.FormatAndWrite(fmtString, diff.DiffInBytes / displayUnitDiv, diff.InstanceDiffCount, diff?.Stat?.InstanceCount, diff?.Stat2?.InstanceCount, diff?.Stat?.AllocatedInBytes / displayUnitDiv, diff?.Stat2?.AllocatedInBytes / displayUnitDiv, GetShortString(diff.Value)); } var deltaCount = res2.StringObjectCount - res.StringObjectCount; var deltaWaste = res2.StringWasteInBytes - res.StringWasteInBytes; var deltaBytes = res2.StringsAllocatedInBytes - res.StringsAllocatedInBytes; OutputStringWriter.FormatAndWrite(fmtString, deltaBytes / displayUnitDiv, deltaCount, res.StringObjectCount, res2.StringObjectCount, res.StringsAllocatedInBytes / displayUnitDiv, res2.StringsAllocatedInBytes / displayUnitDiv, "Strings(Total)"); }
private void Run() { AddProcessStartDirectoryToPath(); MemAnalyzerBase analyzer = null; ShowHelpMessage = false; // When we now throw exceptions it is not due to wrong command line arguments. try { TargetInformation.LoadProcessRenameFile(ProcessRenameFile); if (!String.IsNullOrEmpty(OutFile)) { OutputStringWriter.CsvOutput = true; OutputStringWriter.DisableExcelCSVSep = DisableExcelCSVSep; var info = new FileInfo(OutFile); // skip header if file has already content if (info.Exists && info.Length > 0) { OutputStringWriter.SuppressHeader = true; } // when child process is spawned do not print the same message twice if (!IsChild && !IsSilent) { Console.WriteLine($"Writing output to csv file {OutFile}. {(OverWriteOutFile || !info.Exists ? "" : "File contents are appended.")}"); } FileStream file = new FileStream(OutFile, (OverWriteOutFile || TargetInformation.IsProcessCompare) ? FileMode.Create : FileMode.Append); OutputStringWriter.Output = new StreamWriter(file); } analyzer = CreateAnalyzer(Action); switch (Action) { case Actions.None: Help("No command specified."); break; case Actions.DumpTypesByCount: int?allocatedKB = (analyzer as MemAnalyzer)?.DumpTypes(TopN, false, MinCount); if (allocatedKB != null) { ReturnCode = allocatedKB.Value; } break; case Actions.DumpTypesBySize: allocatedKB = (analyzer as MemAnalyzer)?.DumpTypes(TopN, true, MinCount); if (allocatedKB != null) { ReturnCode = allocatedKB.Value; } break; case Actions.DumpStrings: (analyzer as StringStatisticsCommand)?.Execute(TopN, ShowAddress); break; case Actions.ProcDump: DumpCreator dumper = new DumpCreator(IsDebug, VerifyDump); dumper.Dump(ProcDumpArgs); break; default: throw new NotSupportedException(String.Format("Command {0} is not recognized as a valid command", this.Action)); } } finally { OutputStringWriter.Flush(); if (analyzer != null) { analyzer.Dispose(); } if (Target != null) { Target.Dispose(); } if (Target2 != null) { Target2.Dispose(); } } }