public void Execute(CommandExecutionContext context) { if (!String.IsNullOrEmpty(TypeRegex)) { try { new Regex(TypeRegex); } catch (ArgumentException) { context.WriteErrorLine("The regular expression specified for --type is not valid; did you forget to escape regex characters?"); return; } } _heap = context.Runtime.GetHeap(); if (!_heap.CanWalkHeap) { context.WriteErrorLine("The heap is not in a walkable state."); return; } var typeInfos = new Dictionary<ulong, TypeInfo>(); // MT to TypeInfo long totalObjectCount = 0; if (!StatisticsOnly) { context.WriteLine("{0,-20} {1,-20} {2}", "MT", "Address", "Size"); } foreach (var obj in _heap.EnumerateObjectAddresses()) { ulong mt = 0; context.Runtime.ReadPointer(obj, out mt); if (!FilterObject(obj, mt)) continue; var type = _heap.GetObjectType(obj); if (type == null || String.IsNullOrEmpty(type.Name)) continue; var size = type.GetSize(obj); if (!StatisticsOnly) { context.WriteLine("{0,-20:x16} {1,-20:x16} {2,-10}", mt, obj, size); } if (typeInfos.ContainsKey(mt)) { var current = typeInfos[mt]; current.Count += 1; current.Size += size; typeInfos[mt] = current; } else { var objType = _heap.GetObjectType(obj); var objTypeName = objType != null ? objType.Name : "<no name>"; typeInfos.Add(mt, new TypeInfo { Size = size, Count = 1, TypeName = objTypeName }); } ++totalObjectCount; } context.WriteLine("Statistics:"); context.WriteLine("{0,-20} {1,-10} {2,-10} {3}", "MT", "Count", "TotalSize", "Class Name"); foreach (var kvp in (from e in typeInfos orderby e.Value.Size ascending select e)) { context.WriteLine("{0,-20:x16} {1,-10} {2,-10} {3}", kvp.Key, kvp.Value.Count, kvp.Value.Size, kvp.Value.TypeName); } context.WriteLine("Total {0} objects", totalObjectCount); }
private static void ExamineProcessHeap(ClrRuntime runtime, ClrHeap heap, bool showGcHeapInfo) { if (!heap.CanWalkHeap) { Console.WriteLine("Cannot walk the heap!"); return; } ulong totalStringObjectSize = 0, stringObjectCounter = 0, byteArraySize = 0; ulong asciiStringSize = 0, unicodeStringSize = 0, isoStringSize = 0; //, utf8StringSize = 0; ulong asciiStringCount = 0, unicodeStringCount = 0, isoStringCount = 0; //, utf8StringCount = 0; ulong compressedStringSize = 0, uncompressedStringSize = 0; foreach (var obj in heap.EnumerateObjectAddresses()) { ClrType type = heap.GetObjectType(obj); // If heap corruption, continue past this object. Or if it's NOT a String we also ignore it if (type == null || type.IsString == false) continue; stringObjectCounter++; var text = (string)type.GetValue(obj); var rawBytes = Encoding.Unicode.GetBytes(text); totalStringObjectSize += type.GetSize(obj); byteArraySize += (ulong)rawBytes.Length; VerifyStringObjectSize(runtime, type, obj, text); // Try each encoding in order, so we find the most-compact encoding that the text would fit in byte[] textAsBytes = null; if (IsASCII(text, out textAsBytes)) { asciiStringSize += (ulong)rawBytes.Length; asciiStringCount++; // ASCII is compressed as ISO-8859-1 (Latin-1) NOT ASCII if (IsIsoLatin1(text, out textAsBytes)) compressedStringSize += (ulong)textAsBytes.Length; else Console.WriteLine("ERROR: \"{0}\" is ASCII but can't be encoded as ISO-8859-1 (Latin-1)", text); } // From http://stackoverflow.com/questions/7048745/what-is-the-difference-between-utf-8-and-iso-8859-1 // "ISO 8859-1 is a single-byte encoding that can represent the first 256 Unicode characters" else if (IsIsoLatin1(text, out textAsBytes)) { isoStringSize += (ulong)rawBytes.Length; isoStringCount++; compressedStringSize += (ulong)textAsBytes.Length; } // UTF-8 and UTF-16 can both support the same range of text/character values ("Code Points"), they just store it in different ways // From http://stackoverflow.com/questions/4655250/difference-between-utf-8-and-utf-16/4655335#4655335 // "Both UTF-8 and UTF-16 are variable length (multi-byte) encodings. // However, in UTF-8 a character may occupy a minimum of 8 bits, while in UTF-16 character length starts with 16 bits." //else if (IsUTF8(text, out textAsBytes)) //{ // utf8StringSize += (ulong)rawBytes.Length; // utf8StringCount++; // compressedStringSize += (ulong)textAsBytes.Length; //} else { unicodeStringSize += (ulong)rawBytes.Length; unicodeStringCount++; uncompressedStringSize += (ulong)rawBytes.Length; } } Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("\n\"System.String\" memory usage info"); Console.ResetColor(); Console.WriteLine("Overall {0:N0} \"System.String\" objects take up {1:N0} bytes ({2:N2} MB)", stringObjectCounter, totalStringObjectSize, totalStringObjectSize / 1024.0 / 1024.0); Console.WriteLine("Of this underlying byte arrays (as Unicode) take up {0:N0} bytes ({1:N2} MB)", byteArraySize, byteArraySize / 1024.0 / 1024.0); Console.WriteLine("Remaining data (object headers, other fields, etc) are {0:N0} bytes ({1:N2} MB), at {2:0.##} bytes per object\n", totalStringObjectSize - byteArraySize, (totalStringObjectSize - byteArraySize) / 1024.0 / 1024.0, (totalStringObjectSize - byteArraySize) / (double)stringObjectCounter); Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("Actual Encoding that the \"System.String\" could be stored as (with corresponding data size)"); Console.ResetColor(); Console.WriteLine(" {0,15:N0} bytes ({1,8:N0} strings) as ASCII", asciiStringSize, asciiStringCount); Console.WriteLine(" {0,15:N0} bytes ({1,8:N0} strings) as ISO-8859-1 (Latin-1)", isoStringSize, isoStringCount); //Console.WriteLine(" {0,15:N0} bytes ({1,8:N0} strings) are UTF-8", utf8StringSize, utf8StringCount); Console.WriteLine(" {0,15:N0} bytes ({1,8:N0} strings) as Unicode", unicodeStringSize, unicodeStringCount); Console.WriteLine("Total: {0:N0} bytes (expected: {1:N0}{2})\n", asciiStringSize + isoStringSize + unicodeStringSize, byteArraySize, (asciiStringSize + isoStringSize + unicodeStringSize != byteArraySize) ? " - ERROR" : ""); Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine("Compression Summary:"); Console.ResetColor(); Console.WriteLine(" {0,15:N0} bytes Compressed (to ISO-8859-1 (Latin-1))", compressedStringSize); Console.WriteLine(" {0,15:N0} bytes Uncompressed (as Unicode)", uncompressedStringSize); Console.WriteLine(" {0,15:N0} bytes EXTRA to enable compression (1-byte field, per \"System.String\" object)", stringObjectCounter); var totalBytesUsed = compressedStringSize + uncompressedStringSize + stringObjectCounter; var totalBytesSaved = byteArraySize - totalBytesUsed; Console.WriteLine("\nTotal Usage: {0:N0} bytes ({1:N2} MB), compared to {2:N0} ({3:N2} MB) before compression", totalBytesUsed, totalBytesUsed / 1024.0 / 1024.0, byteArraySize, byteArraySize / 1024.0 / 1024.0); Console.WriteLine("Total Saving: {0:N0} bytes ({1:N2} MB)\n", totalBytesSaved, totalBytesSaved / 1024.0 / 1024.0); }