Skip to content

Instantly share code, notes, and snippets.

@jagt
Last active January 11, 2021 11:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jagt/fb1d66e54fd6cf226fa34be78552c05c to your computer and use it in GitHub Desktop.
Save jagt/fb1d66e54fd6cf226fa34be78552c05c to your computer and use it in GitHub Desktop.
Dump Unity 5.3x memory profiler snapshot directly to file so it wont freeze the window and can be diffed on.
using UnityEditor;
using UnityEditor.MemoryProfiler;
using System;
using System.IO;
using System.Collections.Generic;
// Standalone menu item to dump memory to a text format that's
// more easier to diff on.
namespace MemoryProfilerWindow
{
internal static class AltDumpSnapshot
{
private static string previousDirectory = null;
private enum SnapshotAction
{
SaveRaw,
SaveDumpedText,
}
private static SnapshotAction _snapshotAction;
[MenuItem("Temp/Snapshot Raw")]
private static void SnapshotRaw()
{
_snapshotAction = SnapshotAction.SaveRaw;
MemorySnapshot.OnSnapshotReceived -= IncomingSnapshot;
MemorySnapshot.OnSnapshotReceived += IncomingSnapshot;
MemorySnapshot.RequestNewSnapshot();
return;
}
[MenuItem("Temp/SnapshotAndDump")]
private static void SnapshotAndDump()
{
_snapshotAction = SnapshotAction.SaveDumpedText;
MemorySnapshot.OnSnapshotReceived -= IncomingSnapshot;
MemorySnapshot.OnSnapshotReceived += IncomingSnapshot;
MemorySnapshot.RequestNewSnapshot();
return;
}
[MenuItem("Temp/Load Snapshot And Dump")]
private static void LoadSnapshotAndDump()
{
var snapshot = PackedMemorySnapshotUtility.LoadFromFile();
if (snapshot == null)
return;
DumpShapshot(snapshot);
return;
}
private static void DumpShapshot(PackedMemorySnapshot snapshot)
{
var packedCrawl = new Crawler().Crawl(snapshot);
var unpacked = CrawlDataUnpacker.Unpack(packedCrawl);
var filePath = EditorUtility.SaveFilePanel("Save Dump", previousDirectory, string.Format("Dump{0}", DateTime.Now.ToString("u")), "txt");
if (!string.IsNullOrEmpty(filePath))
{
previousDirectory = Path.GetDirectoryName(filePath);
using (TextWriter writer = File.CreateText(filePath))
{
var printer = new SnapshotDumpPrinter(unpacked, writer);
printer.WriteToFile();
}
}
return;
}
private static void IncomingSnapshot(PackedMemorySnapshot snapshot)
{
try
{
if (_snapshotAction == SnapshotAction.SaveRaw)
{
PackedMemorySnapshotUtility.SaveToFile(snapshot);
}
else if (_snapshotAction == SnapshotAction.SaveDumpedText)
{
DumpShapshot(snapshot);
}
}
finally
{
// remove snapshot
MemorySnapshot.OnSnapshotReceived -= IncomingSnapshot;
}
return;
}
}
internal class SnapshotDumpPrinter
{
private CrawledMemorySnapshot _unpacked;
private TextWriter _writer;
private class Group
{
private class ReverseComparer : IComparer<int>
{
public int Compare(int x, int y)
{
if (x == y)
return 1; // allow duplicated key
return y - x;
}
}
private static ReverseComparer _COMPARER = new ReverseComparer();
public string name;
public SortedList<int, ThingInMemory> things;
public float totalSize;
public Group(string name)
{
this.name = name;
things = new SortedList<int, ThingInMemory>(_COMPARER);
return;
}
public void Add(ThingInMemory thing)
{
totalSize += thing.size;
things.Add(thing.size, thing);
return;
}
}
private SortedList<string, Group> _groups;
public SnapshotDumpPrinter(CrawledMemorySnapshot unpacked, TextWriter writer)
{
_unpacked = unpacked;
_writer = writer;
_groups = new SortedList<string, Group>();
return;
}
/* Write To File */
public void WriteToFile()
{
Organize();
Dump();
_writer.Flush();
return;
}
/* Indent */
private string _indentStr = "";
/* Inc/Dec indent size */
private void _Indent(int n, bool print = false)
{
_indentStr = new String('\t', n + _indentStr.Length);
if (print)
_writer.Write(_indentStr);
return;
}
private void _P(string str, bool newLine = true)
{
if (newLine)
_N();
_writer.Write(str);
return;
}
private void _N()
{
_writer.WriteLine();
_writer.Write(_indentStr);
return;
}
private void Organize()
{
var allObjects = _unpacked.allObjects;
foreach (var obj in allObjects)
{
var groupname = GetGroupName(obj);
Group group;
if (!_groups.TryGetValue(groupname, out group))
{
group = new Group(groupname);
_groups[groupname] = group;
}
group.Add(obj);
}
return;
}
public string GetGroupName(ThingInMemory thing)
{
if (thing is NativeUnityEngineObject)
return (thing as NativeUnityEngineObject).className ?? "MissingName";
if (thing is ManagedObject)
return (thing as ManagedObject).typeDescription.name;
return thing.GetType().Name;
}
private void Dump()
{
_P(string.Format("total size: {0:0.00}kb", _unpacked.totalSize / 1024), newLine:false);
foreach (var group in _groups.Values)
DumpGroup(group);
return;
}
private void DumpGroup(Group group)
{
_P(string.Format("##~##{0} size: {1:0.00}kb\tcount:{2}", group.name, group.totalSize / 1024, group.things.Count));
_Indent(2);
foreach (var thing in group.things.Values)
DumpThingInMemory(thing);
_Indent(-2);
return;
}
private void DumpThingInMemory(ThingInMemory thing)
{
// print head and indent
_P(string.Format("{0:0.00}kb\t\t", ((float)thing.size / 1024)));
_P(FormatThing(thing), newLine:false);
if (thing is ManagedObject)
{
var managed = (ManagedObject)thing;
_P(string.Format("{0}, @{1}", managed.typeDescription.name, managed.address.ToString("X")), newLine: false);
if (managed.typeDescription.name == "System.String")
{
_Indent(2);
_P(ReadStringAtMost(_unpacked.managedHeap.Find(managed.address, _unpacked.virtualMachineInformation), _unpacked.virtualMachineInformation, 64));
if (thing.referencedBy != null)
{
foreach (var refThing in thing.referencedBy)
_P(string.Format("refed by: {0}", FormatThing(refThing)));
}
_Indent(-2);
}
}
return;
}
private string FormatThing(ThingInMemory thing)
{
if (thing is NativeUnityEngineObject)
{
var nativeObj = (NativeUnityEngineObject)thing;
return string.Format("{0}, id:{1}", string.IsNullOrEmpty(nativeObj.name) ? "<missing>" : nativeObj.name, nativeObj.instanceID);
}
else if (thing is ManagedObject)
{
var managed = (ManagedObject)thing;
return string.Format("{0}, @{1}", managed.typeDescription.name, managed.address.ToString("X"));
}
else if (thing is GCHandle)
{
return string.Format("<GCHandle>");
}
else if (thing is StaticFields)
{
var fields = (StaticFields)thing;
return string.Format("<Static> {0}", fields.typeDescription.name);
}
else
{
return "<UNKNOWN>";
}
}
private static string ReadStringAtMost(BytesAndOffset bo, VirtualMachineInformation virtualMachineInformation, int atMost)
{
var lengthPointer = bo.Add(virtualMachineInformation.objectHeaderSize);
var length = lengthPointer.ReadInt32();
var firstChar = lengthPointer.Add(4);
return System.Text.Encoding.Unicode.GetString(firstChar.bytes, firstChar.offset, Math.Min(length * 2, atMost * 2));
}
}
}
#!python
import sys, argparse, re
from collections import namedtuple, OrderedDict
GROUP_LINE_PAT = re.compile(r'^##~##(.+)\s+size:\s*(.+)kb\s*count:(.+)$')
DumpEntry = namedtuple('DumpEntry', ['name', 'size', 'count'])
def parse_dump_file(filename):
dump = OrderedDict()
with open(filename, 'r') as f:
for line in f:
match = GROUP_LINE_PAT.match(line)
if not match: continue
groupname, size, count = match.groups()
dump[groupname] = DumpEntry(groupname, float(size), int(count))
return dump
def print_diff(pre, post):
pre_dump = parse_dump_file(args.pre)
post_dump = parse_dump_file(args.post)
diff_entries = []
for groupname, entry in pre_dump.iteritems():
size_diff = 0
count_diff = 0
if groupname in post_dump:
post_entry = post_dump[groupname]
diff_entries.append(DumpEntry(groupname, post_entry.size - entry.size, post_entry.count - entry.count))
post_dump.pop(groupname)
else:
# missing entries are considered as removal
diff_entries.append(DumpEntry(groupname, -entry.size, -entry.count))
for groupname, entry in post_dump.iteritems():
# new entries are considered as added
diff_entries.append(DumpEntry(groupname, entry.size, entry.count))
# sort in descending
diff_entries.sort(key=lambda x: -x.size)
total_size_diff = 0
total_count_diff = 0
for entry in diff_entries:
if entry.size != 0 or entry.count != 0:
print '%s\t%+.2f\t%+d' % entry
total_size_diff += entry.size
total_count_diff += entry.count
print ''
print 'total size diff: %+.2f' % total_size_diff
print 'total count diff: %+d' % total_count_diff
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Compare Memory Dumps')
parser.add_argument('pre', type=str, help='path to pre dump txt')
parser.add_argument('post', type=str, help='path to post dump txt')
args = parser.parse_args()
print_diff(args.pre, args.post)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment