/// <summary> /// Dump types by size or by count. /// </summary> /// <param name="topN">Only print the first N types ordered by size or count.</param> /// <param name="orderBySize">If true types are sorted by size. Otherwise by count.</param> /// <returns>Allocated memory in KB if VMMap data is available. Otherwise only the allocated managed heap size in KB is returned.</returns> /// <remarks>The returned value is returned, if no other error has occurred from the Main method. That allows test automation to trigger e.g. a dump of /// a leak was detected or to automatically enable memory profiling if the allocated memory reached a threshold.</remarks> public int DumpTypes(int topN, bool orderBySize, int minCount) { var typeInfosTask = Task.Factory.StartNew(() => GetTypeStatistics(Heap, LiveOnly)); int allocatedMemoryInKB = 0; if (Heap2 != null) { var typeInfos2 = GetTypeStatistics(Heap2, LiveOnly); VMMapData vmmap = null; VMMapData vmmap2 = null; if (this.GetVMMapData) { vmmap2 = GetVMMapDataFromProcess(false, TargetInfo, Heap2); typeInfosTask.Wait(); vmmap = GetVMMapDataFromProcess(true, TargetInfo, Heap); } // Get allocated diff allocatedMemoryInKB = PrintTypeStatisticsDiff(typeInfosTask.Result, typeInfos2, vmmap, vmmap2, topN, minCount, orderBySize); } else { // get allocated memory allocatedMemoryInKB = PrintTypeStatistics(topN, minCount, orderBySize, typeInfosTask); } return(allocatedMemoryInKB); }
/// <summary> /// For diffs subtraction is useful. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public static VMMapData operator -(VMMapData x, VMMapData y) { var lret = new VMMapData { Committed_DllBytes = x.Committed_DllBytes - y.Committed_DllBytes, Committed_HeapBytes = x.Committed_HeapBytes - y.Committed_HeapBytes, Committed_ManagedHeapBytes = x.Committed_ManagedHeapBytes - y.Committed_ManagedHeapBytes, Committed_MappedFileBytes = x.Committed_MappedFileBytes - y.Committed_MappedFileBytes, Committed_PageTable = x.Committed_PageTable - y.Committed_PageTable, Committed_PrivateBytes = x.Committed_PrivateBytes - y.Committed_PrivateBytes, Committed_ShareableBytes = x.Committed_ShareableBytes - y.Committed_ShareableBytes, Committed_Stack = x.Committed_Stack - y.Committed_Stack, LargestFreeBlockBytes = x.LargestFreeBlockBytes - y.LargestFreeBlockBytes, Reserved_DllBytes = x.Reserved_DllBytes - y.Reserved_DllBytes, Reserved_HeapBytes = x.Reserved_HeapBytes - y.Reserved_HeapBytes, Reserved_ManagedHeapBytes = x.Reserved_ManagedHeapBytes - y.Reserved_ManagedHeapBytes, Reserved_MappedFileBytes = x.Reserved_MappedFileBytes - y.Reserved_MappedFileBytes, Reserved_PageTable = x.Reserved_PageTable - y.Reserved_PageTable, Reserved_PrivateBytes = x.Reserved_PrivateBytes - y.Reserved_PrivateBytes, Reserved_ShareableBytes = x.Reserved_ShareableBytes - y.Reserved_ShareableBytes, Reserved_Stack = x.Reserved_Stack - y.Reserved_Stack }; return(lret); }
/// <summary> /// Write VMMap data with given formatter function. /// </summary> /// <param name="formatter"></param> /// <param name="vm"></param> void WriteVMMapData(Action <string, long> formatter, VMMapData vm) { // free blocks only play a role in x86 if (vm.LargestFreeBlockBytes < 4 * 1024 * 1024 * 1024L) { formatter(VMMapData.Col_Reserved_LargestFreeBlock, vm.LargestFreeBlockBytes); } formatter(VMMapData.Col_Reserved_Stack, vm.Reserved_Stack); formatter(VMMapData.Col_Committed_Dll, vm.Committed_DllBytes); formatter(VMMapData.Col_Committed_Heap, vm.Committed_HeapBytes); formatter(VMMapData.Col_Committed_MappedFile, vm.Committed_MappedFileBytes); formatter(VMMapData.Col_Committed_Private, vm.Committed_PrivateBytes); formatter(VMMapData.Col_Committed_Shareable, vm.Committed_ShareableBytes); formatter(VMMapData.Col_Committed_Total, vm.TotalCommittedBytes); }
/// <summary> /// Print type statistics. If output is a CSV file time and process information are appended to allow subsequent writes into one big CSV file for long data series. /// </summary> /// <param name="topN"></param> /// <param name="orderBySize"></param> /// <param name="typeInfosTask"></param> /// <returns>Allocated memory 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 PrintTypeStatistics(int topN, int minCount, bool orderBySize, Task <List <TypeInfo> > typeInfosTask) { int allocatedMemoryInKB = 0; var typeInfos = typeInfosTask.Result; if (minCount > 0) { typeInfos = typeInfos.Where(x => x.Count > minCount).ToList(); } typeInfos.Sort((x, y) => orderBySize ? y.AllocatedSizeInBytes.CompareTo(x.AllocatedSizeInBytes) : y.Count.CompareTo(x.Count)); // can be null if only live objects are considered. var free = typeInfos.FirstOrDefault(x => x.Name == FreeTypeName); if (topN > 0) { foreach (var type in typeInfos.Take(topN)) { if (free != null && type == free) { continue; } WriteTypeStatisticsLine(type.AllocatedSizeInBytes / (long)DisplayUnit, type.Count, type.Name); } } // Total heap size is only possible to calculate if the free objects are included if (free != null) { WriteTypeStatisticsLine(free.AllocatedSizeInBytes / (long)DisplayUnit, free.Count, ManagedHeapFree); } float managedAllocatedBytes = typeInfos.Where(x => free != null ? x != free : true).Sum(x => (float)x.AllocatedSizeInBytes); WriteTypeStatisticsLine((long)(managedAllocatedBytes / (long)DisplayUnit), typeInfos.Where(x => x != free).Sum(x => (long)x.Count), ManagedHeapAllocated); WriteTypeStatisticsLine(Heap.GetTotalHeapSize(), 0, ManagedHeapSize); allocatedMemoryInKB = (int)(managedAllocatedBytes / (long)DisplayUnit.KB); if (GetVMMapData) { VMMapData data = GetVMMapDataFromProcess(true, TargetInfo, Heap); WriteVMMapData(GetSimpleTypeFormatter(DisplayUnit), data); WriteTypeStatisticsLine((long)((managedAllocatedBytes + data.AllocatedBytesWithoutManagedHeap) / (long)DisplayUnit), 0, AllocatedTotal); allocatedMemoryInKB += (int)(data.AllocatedBytesWithoutManagedHeap / (long)DisplayUnit.KB); } return(allocatedMemoryInKB); }
/// <summary> /// Parse VMMap output from an existing process or a previously saved csv file. /// </summary> /// <returns>Parsed VMMap data. If an error occurs a empty VMMapData instance is returned.</returns> public VMMapData GetMappingData() { if (ExistingVMMapFile != null) { return(ParseVMMapFile(ExistingVMMapFile, bDelete: false)); } if (NoVMMap) { return(new VMMapData()); } SaveVMmapDataToFile(Pid, TempFileName); VMMapData lret = ParseVMMapFile(TempFileName, bDelete: true); return(lret); }
internal void MapDataFromLine(VMMapData lret, string line) { string[] parts = SplitLine(line); if (parts.Length >= 11) { string name = parts[0]; if (RowMapper.TryGetValue(parts[0], out Action <long, long, long, VMMapData> mapper)) { if (parts[1] == "") // Page table data can sometimes be empty { return; } long reserved = long.Parse(parts[1]); long.TryParse(parts[2], out long committed); long.TryParse(parts[10], out long largestBlock); mapper(reserved, committed, largestBlock, lret); } } }
internal VMMapData ParseVMMapFile(string fileName, bool bDelete) { VMMapData lret = new VMMapData(); if (!File.Exists(fileName)) { return(lret); } using (var reader = new StreamReader(fileName)) { string line = null; bool bShouldParse = false; while ((line = reader.ReadLine()) != null) { if (line.StartsWith(SummaryDataStart)) { bShouldParse = true; continue; } if (line.StartsWith(SummaryDataStart)) { bShouldParse = false; break; } if (!bShouldParse) { continue; } MapDataFromLine(lret, line); } } if (bDelete) { RemoveTempVMMapFile(fileName); } return(lret); }
/// <summary> /// Get VMMap data from process /// </summary> /// <param name="bFirstProcess"></param> /// <param name="targetInfo"></param> /// <param name="heap"></param> /// <returns></returns> private static VMMapData GetVMMapDataFromProcess(bool bFirstProcess, TargetInformation targetInfo, ClrHeap heap) { int pid = bFirstProcess ? targetInfo.Pid1 : targetInfo.Pid2; VMMapData data = new VMMapData(); if (pid != 0) { // we must first detach CLRMD or VMMAp will block at least in x64 in the target process to get heap information. // Play safe and do not try this asynchronously. heap?.Runtime?.DataTarget?.Dispose(); data = StartVMMap(pid, null); } else { string existingVMMapFile = bFirstProcess ? targetInfo.DumpVMMapFile1 : targetInfo.DumpVMMapFile2; if (existingVMMapFile != null) { data = StartVMMap(0, existingVMMapFile); } } return(data); }
/// <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); }
VMMapData ReturnNullIfNoDataPresent(VMMapData vmmap) { return(vmmap.IsEmpty ? null : vmmap); }