summaryrefslogtreecommitdiffstats
path: root/tools/memory_inspector
diff options
context:
space:
mode:
authorprimiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-03 09:36:05 +0000
committerprimiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-03 09:36:05 +0000
commit247160c191f0f059ded8ccfb4087eb1db36c3a2f (patch)
tree9f8bc53062a4d4c8a215bed280baa53c94f09668 /tools/memory_inspector
parenta2507854f005ad7c5e36a9f0bda1117b38b54f6c (diff)
downloadchromium_src-247160c191f0f059ded8ccfb4087eb1db36c3a2f.zip
chromium_src-247160c191f0f059ded8ccfb4087eb1db36c3a2f.tar.gz
chromium_src-247160c191f0f059ded8ccfb4087eb1db36c3a2f.tar.bz2
Add JSON serializers and file storage to memory_inspector.
This CL introduces the data serializers that allow: 1) To exchange JSON objects with the (upcoming) HTML UI. 2) To store/retrieve from disk memory dumps. BUG=340294 NOTRY=true Review URL: https://codereview.chromium.org/178693002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@254451 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/memory_inspector')
-rw-r--r--tools/memory_inspector/memory_inspector/backends/android/dumpheap_native_parser.py8
-rw-r--r--tools/memory_inspector/memory_inspector/core/memory_map.py26
-rw-r--r--tools/memory_inspector/memory_inspector/core/native_heap.py9
-rw-r--r--tools/memory_inspector/memory_inspector/core/stacktrace.py10
-rw-r--r--tools/memory_inspector/memory_inspector/core/symbol.py18
-rw-r--r--tools/memory_inspector/memory_inspector/data/__init__.py0
-rw-r--r--tools/memory_inspector/memory_inspector/data/file_storage.py168
-rw-r--r--tools/memory_inspector/memory_inspector/data/file_storage_unittest.py138
-rw-r--r--tools/memory_inspector/memory_inspector/data/serialization.py101
9 files changed, 446 insertions, 32 deletions
diff --git a/tools/memory_inspector/memory_inspector/backends/android/dumpheap_native_parser.py b/tools/memory_inspector/memory_inspector/backends/android/dumpheap_native_parser.py
index a55d41f..1b3dfce 100644
--- a/tools/memory_inspector/memory_inspector/backends/android/dumpheap_native_parser.py
+++ b/tools/memory_inspector/memory_inspector/backends/android/dumpheap_native_parser.py
@@ -49,7 +49,6 @@ def Parse(lines):
state = STATE_PARSING_BACKTRACES
skip_first_n_lines = 5
mmap = memory_map.Map()
- stack_frames = {} # absolute_address (int) -> |stacktrace.Frame|.
nativeheap = native_heap.NativeHeap()
for line in lines:
@@ -74,10 +73,7 @@ def Parse(lines):
# to ease the complexity of the final de-offset pass.
for absolute_addr in alloc_bt_str.split():
absolute_addr = int(absolute_addr, 16)
- stack_frame = stack_frames.get(absolute_addr)
- if not stack_frame:
- stack_frame = stacktrace.Frame(absolute_addr)
- stack_frames[absolute_addr] = stack_frame
+ stack_frame = nativeheap.GetStackFrame(absolute_addr)
strace.Add(stack_frame)
nativeheap.Add(native_heap.Allocation(alloc_size, alloc_count, strace))
@@ -109,7 +105,7 @@ def Parse(lines):
# Final pass: translate all the stack frames' absolute addresses into
# relative offsets (exec_file + offset) using the memory maps just processed.
- for abs_addr, stack_frame in stack_frames.iteritems():
+ for abs_addr, stack_frame in nativeheap.stack_frames.iteritems():
assert(abs_addr == stack_frame.address)
map_entry = mmap.Lookup(abs_addr)
if not map_entry:
diff --git a/tools/memory_inspector/memory_inspector/core/memory_map.py b/tools/memory_inspector/memory_inspector/core/memory_map.py
index ec1973d..f987ab9 100644
--- a/tools/memory_inspector/memory_inspector/core/memory_map.py
+++ b/tools/memory_inspector/memory_inspector/core/memory_map.py
@@ -11,18 +11,18 @@ class Map(object):
This is typically obtained by calling backends.Process.DumpMemoryMaps()."""
def __init__(self):
- self._entries = []
+ self.entries = []
def Add(self, entry):
assert(isinstance(entry, MapEntry))
- bisect.insort_right(self._entries, entry)
+ bisect.insort_right(self.entries, entry)
def Lookup(self, addr):
"""Returns the MapEntry containing the given address, if any."""
- idx = bisect.bisect_right(self._entries, addr) - 1
+ idx = bisect.bisect_right(self.entries, addr) - 1
if idx < 0:
return None
- entry = self._entries[idx]
+ entry = self.entries[idx]
assert(addr >= entry.start)
# bisect_right returns the latest element <= addr, but addr might fall after
# its end (in which case we want to return None here).
@@ -31,17 +31,19 @@ class Map(object):
return entry
def __getitem__(self, index):
- return self._entries[index]
+ return self.entries[index]
def __len__(self):
- return len(self._entries)
+ return len(self.entries)
class MapEntry(object):
"""An entry (address range + stats) in a memory |Map|."""
PAGE_SIZE = 4096
- def __init__(self, start, end, prot_flags, mapped_file, mapped_offset):
+ def __init__(self, start, end, prot_flags, mapped_file, mapped_offset,
+ priv_dirty_bytes=0, priv_clean_bytes=0, shared_dirty_bytes=0,
+ shared_clean_bytes=0, resident_pages=None):
assert(end > start)
assert(start >= 0)
self.start = start
@@ -49,13 +51,13 @@ class MapEntry(object):
self.prot_flags = prot_flags
self.mapped_file = mapped_file
self.mapped_offset = mapped_offset
- self.priv_dirty_bytes = 0
- self.priv_clean_bytes = 0
- self.shared_dirty_bytes = 0
- self.shared_clean_bytes = 0
+ self.priv_dirty_bytes = priv_dirty_bytes
+ self.priv_clean_bytes = priv_clean_bytes
+ self.shared_dirty_bytes = shared_dirty_bytes
+ self.shared_clean_bytes = shared_clean_bytes
# resident_pages is a bitmap (array of bytes) in which each bit represents
# the presence of its corresponding page.
- self.resident_pages = []
+ self.resident_pages = resident_pages or []
def GetRelativeOffset(self, abs_addr):
"""Converts abs_addr to the corresponding offset in the mapped file."""
diff --git a/tools/memory_inspector/memory_inspector/core/native_heap.py b/tools/memory_inspector/memory_inspector/core/native_heap.py
index ea7c45f..a186f16 100644
--- a/tools/memory_inspector/memory_inspector/core/native_heap.py
+++ b/tools/memory_inspector/memory_inspector/core/native_heap.py
@@ -13,11 +13,20 @@ class NativeHeap(object):
def __init__(self):
self.allocations = []
+ self.stack_frames = {} # absolute_address (int) -> |stacktrace.Frame|.
def Add(self, allocation):
assert(isinstance(allocation, Allocation))
self.allocations += [allocation]
+ def GetStackFrame(self, absolute_addr):
+ assert(isinstance(absolute_addr, int))
+ stack_frame = self.stack_frames.get(absolute_addr)
+ if not stack_frame:
+ stack_frame = stacktrace.Frame(absolute_addr)
+ self.stack_frames[absolute_addr] = stack_frame
+ return stack_frame
+
class Allocation(object):
"""A Native allocation, modeled in a size*count fashion.
diff --git a/tools/memory_inspector/memory_inspector/core/stacktrace.py b/tools/memory_inspector/memory_inspector/core/stacktrace.py
index 2da04f2..0441bd4 100644
--- a/tools/memory_inspector/memory_inspector/core/stacktrace.py
+++ b/tools/memory_inspector/memory_inspector/core/stacktrace.py
@@ -11,21 +11,21 @@ class Stacktrace(object):
"""Models a stack-trace, which is a sequence of stack |Frame|s."""
def __init__(self):
- self._frames = []
+ self.frames = []
def Add(self, frame):
assert(isinstance(frame, Frame))
- self._frames += [frame]
+ self.frames += [frame]
@property
def depth(self):
- return len(self._frames)
+ return len(self.frames)
def __getitem__(self, index):
- return self._frames[index]
+ return self.frames[index]
def __str__(self):
- return ', '.join([str(x) for x in self._frames])
+ return ', '.join([str(x) for x in self.frames])
class Frame(object):
diff --git a/tools/memory_inspector/memory_inspector/core/symbol.py b/tools/memory_inspector/memory_inspector/core/symbol.py
index e6aab77..07c27184 100644
--- a/tools/memory_inspector/memory_inspector/core/symbol.py
+++ b/tools/memory_inspector/memory_inspector/core/symbol.py
@@ -7,22 +7,21 @@ class Symbols(object):
"""A dictionary of symbols indexed by the key 'exec_path+0xoffset'."""
def __init__(self):
- self.dict = {}
+ self.symbols = {} # 'foo.so+0x1234' -> |Symbol|
def Add(self, exec_file_rel_path, offset, symbol):
assert(isinstance(symbol, Symbol))
- self.dict[Symbols._GetKey(exec_file_rel_path, offset)] = symbol
+ self.symbols[Symbols._GetKey(exec_file_rel_path, offset)] = symbol
def Lookup(self, exec_file_rel_path, offset):
- return self.dict.get(Symbols._GetKey(exec_file_rel_path, offset))
+ return self.symbols.get(Symbols._GetKey(exec_file_rel_path, offset))
def Merge(self, other):
assert(isinstance(other, Symbols))
- self.dict.update(other.dict) # pylint: disable=W0212
+ self.symbols.update(other.symbols) # pylint: disable=W0212
- @property
- def length(self):
- return len(self.dict)
+ def __len__(self):
+ return len(self.symbols)
@staticmethod
def _GetKey(exec_file_rel_path, offset):
@@ -35,10 +34,11 @@ class Symbol(object):
Note: a symbol can have more than one source line associated to it.
"""
- def __init__(self, name, source_file_path, line_number):
+ def __init__(self, name, source_file_path=None, line_number=None):
self.name = name
self.source_info = []
- self.AddSourceLineInfo(source_file_path, line_number)
+ if source_file_path and line_number:
+ self.AddSourceLineInfo(source_file_path, line_number)
def AddSourceLineInfo(self, source_file_path, line_number):
self.source_info += [SourceInfo(source_file_path, line_number)]
diff --git a/tools/memory_inspector/memory_inspector/data/__init__.py b/tools/memory_inspector/memory_inspector/data/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/data/__init__.py
diff --git a/tools/memory_inspector/memory_inspector/data/file_storage.py b/tools/memory_inspector/memory_inspector/data/file_storage.py
new file mode 100644
index 0000000..407076f
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/data/file_storage.py
@@ -0,0 +1,168 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This module handles file-backed storage of the core classes.
+
+The storage is logically organized as follows:
+Storage -> N Archives -> 1 Symbol index
+ N Snapshots -> 1 Mmaps dump.
+ -> 0/1 Native heap dump.
+
+Where an "archive" is essentially a collection of snapshots taken for a given
+app at a given point in time.
+"""
+
+import datetime
+import json
+import os
+
+from memory_inspector.core import memory_map
+from memory_inspector.core import native_heap
+from memory_inspector.core import symbol
+from memory_inspector.data import serialization
+
+
+class Storage(object):
+
+ _SETTINGS_FILE = 'settings-%s.json'
+
+ def __init__(self, root_path):
+ """Creates a file-backed storage. Files will be placed in |root_path|."""
+ self._root = root_path
+ if not os.path.exists(self._root):
+ os.makedirs(self._root)
+
+ def LoadSettings(self, name):
+ """Loads a key-value dict from the /settings-name.json file.
+
+ This is for backend and device settings (e.g., symbols path, adb path)."""
+ file_path = os.path.join(self._root, Storage._SETTINGS_FILE % name)
+ if not os.path.exists(file_path):
+ return {}
+ with open(file_path) as f:
+ return json.load(f)
+
+ def StoreSettings(self, name, settings):
+ """Stores a key-value dict into /settings-name.json file."""
+ assert(isinstance(settings, dict))
+ file_path = os.path.join(self._root, Storage._SETTINGS_FILE % name)
+ if not settings:
+ os.unlink(file_path)
+ return
+ with open(file_path, 'w') as f:
+ return json.dump(settings, f)
+
+ def ListArchives(self):
+ """Lists archives. Each of them is a sub-folder inside the |root_path|."""
+ return sorted(
+ [name for name in os.listdir(self._root)
+ if os.path.isdir(os.path.join(self._root, name))])
+
+ def OpenArchive(self, archive_name, create=False):
+ """Returns an instance of |Archive|."""
+ archive_path = os.path.join(self._root, archive_name)
+ if not os.path.exists(archive_path) and create:
+ os.makedirs(archive_path)
+ return Archive(archive_name, archive_path)
+
+ def DeleteArchive(self, archive_name):
+ """Deletes the archive (removing its folder)."""
+ archive_path = os.path.join(self._root, archive_name)
+ for f in os.listdir(archive_path):
+ os.unlink(os.path.join(archive_path, f))
+ os.rmdir(archive_path)
+
+
+class Archive(object):
+ """A collection of snapshots, each one holding one memory dump (per kind)."""
+
+ _MMAP_EXT = '-mmap.json'
+ _NHEAP_EXT = '-nheap.json'
+ _SNAP_EXT = '.snapshot'
+ _SYM_FILE = 'syms.json'
+ _TIME_FMT = '%Y-%m-%d_%H:%M:%S:%f'
+
+ def __init__(self, name, path):
+ assert(os.path.isdir(path))
+ self._name = name
+ self._path = path
+ self._cur_snapshot = None
+
+ def StoreSymbols(self, symbols):
+ """Stores the symbol db (one per the overall archive)."""
+ assert(isinstance(symbols, symbol.Symbols))
+ file_path = os.path.join(self._path, Archive._SYM_FILE)
+ with open(file_path, 'w') as f:
+ json.dump(symbols, f, cls=serialization.Encoder)
+
+ def HasSymbols(self):
+ return os.path.exists(os.path.join(self._path, Archive._SYM_FILE))
+
+ def LoadSymbols(self):
+ assert(self.HasSymbols())
+ file_path = os.path.join(self._path, Archive._SYM_FILE)
+ with open(file_path) as f:
+ return json.load(f, cls=serialization.SymbolsDecoder)
+
+ def StartNewSnapshot(self):
+ """Creates a 2014-01-01_02:03:04.snapshot marker (an empty file)."""
+ self._cur_snapshot = Archive._TimestampToStr(datetime.datetime.now())
+ file_path = os.path.join(self._path,
+ self._cur_snapshot + Archive._SNAP_EXT)
+ assert(not os.path.exists(file_path))
+ open(file_path, 'w').close()
+ return datetime.datetime.strptime(self._cur_snapshot, Archive._TIME_FMT)
+
+ def ListSnapshots(self):
+ """Returns a list of timestamps (datetime.datetime instances)."""
+ file_names = sorted(
+ [name[:-(len(Archive._SNAP_EXT))] for name in os.listdir(self._path)
+ if name.endswith(Archive._SNAP_EXT)],
+ reverse=True)
+ timestamps = [datetime.datetime.strptime(x, Archive._TIME_FMT)
+ for x in file_names]
+ return timestamps
+
+ def StoreMemMaps(self, mmaps):
+ assert(isinstance(mmaps, memory_map.Map))
+ assert(self._cur_snapshot), 'Must call StartNewSnapshot first'
+ file_path = os.path.join(self._path, self._cur_snapshot + Archive._MMAP_EXT)
+ with open(file_path, 'w') as f:
+ json.dump(mmaps, f, cls=serialization.Encoder)
+
+ def HasMemMaps(self, timestamp):
+ return self._HasSnapshotFile(timestamp, Archive._MMAP_EXT)
+
+ def LoadMemMaps(self, timestamp):
+ assert(self.HasMemMaps(timestamp))
+ snapshot_name = Archive._TimestampToStr(timestamp)
+ file_path = os.path.join(self._path, snapshot_name + Archive._MMAP_EXT)
+ with open(file_path) as f:
+ return json.load(f, cls=serialization.MmapDecoder)
+
+ def StoreNativeHeap(self, nheap):
+ assert(isinstance(nheap, native_heap.NativeHeap))
+ assert(self._cur_snapshot), 'Must call StartNewSnapshot first'
+ file_path = os.path.join(self._path,
+ self._cur_snapshot + Archive._NHEAP_EXT)
+ with open(file_path, 'w') as f:
+ json.dump(nheap, f, cls=serialization.Encoder)
+
+ def HasNativeHeap(self, timestamp):
+ return self._HasSnapshotFile(timestamp, Archive._NHEAP_EXT)
+
+ def LoadNativeHeap(self, timestamp):
+ assert(self.HasNativeHeap(timestamp))
+ snapshot_name = Archive._TimestampToStr(timestamp)
+ file_path = os.path.join(self._path, snapshot_name + Archive._NHEAP_EXT)
+ with open(file_path) as f:
+ return json.load(f, cls=serialization.NativeHeapDecoder)
+
+ def _HasSnapshotFile(self, timestamp, ext):
+ name = Archive._TimestampToStr(timestamp)
+ return os.path.exists(os.path.join(self._path, name + ext))
+
+ @staticmethod
+ def _TimestampToStr(timestamp):
+ return timestamp.strftime(Archive._TIME_FMT) \ No newline at end of file
diff --git a/tools/memory_inspector/memory_inspector/data/file_storage_unittest.py b/tools/memory_inspector/memory_inspector/data/file_storage_unittest.py
new file mode 100644
index 0000000..07458bc
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/data/file_storage_unittest.py
@@ -0,0 +1,138 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This unittest covers both file_storage and serialization modules."""
+
+import os
+import tempfile
+import time
+import unittest
+
+from memory_inspector.core import memory_map
+from memory_inspector.core import native_heap
+from memory_inspector.core import stacktrace
+from memory_inspector.core import symbol
+from memory_inspector.data import file_storage
+
+
+class FileStorageTest(unittest.TestCase):
+ def setUp(self):
+ self._storage_path = tempfile.mkdtemp()
+ self._storage = file_storage.Storage(self._storage_path)
+
+ def tearDown(self):
+ os.removedirs(self._storage_path)
+
+ def testSettings(self):
+ settings_1 = { 'foo' : 1, 'bar' : 2 }
+ settings_2 = { 'foo' : 1, 'bar' : 2 }
+ self._storage.StoreSettings('one', settings_1)
+ self._storage.StoreSettings('two', settings_2)
+ self._DeepCompare(settings_1, self._storage.LoadSettings('one'))
+ self._DeepCompare(settings_2, self._storage.LoadSettings('two'))
+ self._storage.StoreSettings('one', {})
+ self._storage.StoreSettings('two', {})
+
+ def testArchives(self):
+ self._storage.OpenArchive('foo', create=True)
+ self._storage.OpenArchive('bar', create=True)
+ self._storage.OpenArchive('baz', create=True)
+ self._storage.DeleteArchive('bar')
+ self.assertTrue('foo' in self._storage.ListArchives())
+ self.assertFalse('bar' in self._storage.ListArchives())
+ self.assertTrue('baz' in self._storage.ListArchives())
+ self._storage.DeleteArchive('foo')
+ self._storage.DeleteArchive('baz')
+
+ def testSnapshots(self):
+ archive = self._storage.OpenArchive('snapshots', create=True)
+ t1 = archive.StartNewSnapshot()
+ archive.StoreMemMaps(memory_map.Map())
+ time.sleep(0.01) # Max snapshot resolution is in the order of usecs.
+ t2 = archive.StartNewSnapshot()
+ archive.StoreMemMaps(memory_map.Map())
+ archive.StoreNativeHeap(native_heap.NativeHeap())
+ self.assertIn(t1, archive.ListSnapshots())
+ self.assertIn(t2, archive.ListSnapshots())
+ self.assertTrue(archive.HasMemMaps(t1))
+ self.assertFalse(archive.HasNativeHeap(t1))
+ self.assertTrue(archive.HasMemMaps(t2))
+ self.assertTrue(archive.HasNativeHeap(t2))
+ self._storage.DeleteArchive('snapshots')
+
+ def testMmap(self):
+ archive = self._storage.OpenArchive('mmap', create=True)
+ timestamp = archive.StartNewSnapshot()
+ mmap = memory_map.Map()
+ map_entry1 = memory_map.MapEntry(4096, 8191, 'rw--', '/foo', 0)
+ map_entry2 = memory_map.MapEntry(65536, 81919, 'rw--', '/bar', 4096)
+ map_entry2.resident_pages = [5]
+ mmap.Add(map_entry1)
+ mmap.Add(map_entry2)
+ archive.StoreMemMaps(mmap)
+ mmap_deser = archive.LoadMemMaps(timestamp)
+ self._DeepCompare(mmap, mmap_deser)
+ self._storage.DeleteArchive('mmap')
+
+ def testNativeHeap(self):
+ archive = self._storage.OpenArchive('nheap', create=True)
+ timestamp = archive.StartNewSnapshot()
+ nh = native_heap.NativeHeap()
+ for i in xrange(1, 4):
+ stack_trace = stacktrace.Stacktrace()
+ frame = nh.GetStackFrame(i * 10 + 1)
+ frame.SetExecFileInfo('foo.so', 1)
+ stack_trace.Add(frame)
+ frame = nh.GetStackFrame(i * 10 + 2)
+ frame.SetExecFileInfo('bar.so', 2)
+ stack_trace.Add(frame)
+ nh.Add(native_heap.Allocation(i * 2, i * 3, stack_trace))
+ archive.StoreNativeHeap(nh)
+ nh_deser = archive.LoadNativeHeap(timestamp)
+ self._DeepCompare(nh, nh_deser)
+ self._storage.DeleteArchive('nheap')
+
+ def testSymbols(self):
+ archive = self._storage.OpenArchive('symbols', create=True)
+ symbols = symbol.Symbols()
+ # Symbol db is global per archive, no need to StartNewSnapshot.
+ symbols.Add('foo.so', 1, symbol.Symbol('sym1', 'file1.c', 11))
+ symbols.Add('bar.so', 2, symbol.Symbol('sym2', 'file2.c', 12))
+ sym3 = symbol.Symbol('sym3', 'file2.c', 13)
+ sym3.AddSourceLineInfo('outer_file.c', 23)
+ symbols.Add('baz.so', 3, sym3)
+ archive.StoreSymbols(symbols)
+ symbols_deser = archive.LoadSymbols()
+ self._DeepCompare(symbols, symbols_deser)
+ self._storage.DeleteArchive('symbols')
+
+ def _DeepCompare(self, a, b, prefix=''):
+ """Recursively compares two objects (original and deserialized)."""
+
+ self.assertEqual(a is None, b is None)
+ if a is None:
+ return
+
+ _BASICTYPES = (int, basestring, float)
+ if isinstance(a, _BASICTYPES) and isinstance(b, _BASICTYPES):
+ return self.assertEqual(a, b, prefix)
+
+ self.assertEqual(type(a), type(b), prefix + ' type (%s vs %s' % (
+ type(a), type(b)))
+
+ if isinstance(a, list):
+ self.assertEqual(len(a), len(b), prefix + ' len (%d vs %d)' % (
+ len(a), len(b)))
+ for i in range(len(a)):
+ self._DeepCompare(a[i], b[i], prefix + '[%d]' % i)
+ return
+
+ if isinstance(a, dict):
+ self.assertEqual(a.keys(), b.keys(), prefix + ' keys (%s vs %s)' % (
+ str(a.keys()), str(b.keys())))
+ for k in a.iterkeys():
+ self._DeepCompare(a[k], b[k], prefix + '.' + str(k))
+ return
+
+ return self._DeepCompare(a.__dict__, b.__dict__, prefix) \ No newline at end of file
diff --git a/tools/memory_inspector/memory_inspector/data/serialization.py b/tools/memory_inspector/memory_inspector/data/serialization.py
new file mode 100644
index 0000000..82325e7
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/data/serialization.py
@@ -0,0 +1,101 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This module handles the JSON de/serialization of the core classes.
+
+This is needed for both long term storage (e.g., loading/storing traces to local
+files) and for short term data exchange (AJAX with the HTML UI).
+
+The rationale of these serializers is to store data in an efficient (i.e. avoid
+to store redundant information) and intelligible (i.e. flatten the classes
+hierarchy keeping only the meaningful bits) format.
+"""
+
+import json
+
+from memory_inspector.core import memory_map
+from memory_inspector.core import native_heap
+from memory_inspector.core import stacktrace
+from memory_inspector.core import symbol
+
+
+class Encoder(json.JSONEncoder):
+ def default(self, obj): # pylint: disable=E0202
+ if isinstance(obj, memory_map.Map):
+ return [entry.__dict__ for entry in obj.entries]
+
+ if isinstance(obj, symbol.Symbols):
+ return obj.symbols
+
+ if isinstance(obj, (symbol.Symbol, symbol.SourceInfo)):
+ return obj.__dict__
+
+ if isinstance(obj, native_heap.NativeHeap):
+ # Just keep the list of (distinct) stack frames from the index. Encoding
+ # it as a JSON dictionary would be redundant.
+ return {'stack_frames': obj.stack_frames.values(),
+ 'allocations': obj.allocations}
+
+ if isinstance(obj, native_heap.Allocation):
+ return obj.__dict__
+
+ if isinstance(obj, stacktrace.Stacktrace):
+ # Keep just absolute addrs of stack frames. The full frame details will be
+ # kept in (and rebuilt from) |native_heap.NativeHeap.stack_frames|. See
+ # NativeHeapDecoder below.
+ return [frame.address for frame in obj.frames]
+
+ if isinstance(obj, stacktrace.Frame):
+ # Strip out the symbol information from stack frames. Symbols are stored
+ # (and will be loaded) separately. Rationale: different heap snapshots can
+ # share the same symbol db. Serializing the symbol information for each
+ # stack frame for each heap snapshot is a waste.
+ return {'address': obj.address,
+ 'exec_file_rel_path': obj.exec_file_rel_path,
+ 'offset': obj.offset}
+
+ return json.JSONEncoder.default(self, obj)
+
+
+class MmapDecoder(json.JSONDecoder):
+ def decode(self, json_str): # pylint: disable=W0221
+ d = super(MmapDecoder, self).decode(json_str)
+ mmap = memory_map.Map()
+ for entry_dict in d:
+ entry = memory_map.MapEntry(**entry_dict)
+ mmap.Add(entry)
+ return mmap
+
+
+class SymbolsDecoder(json.JSONDecoder):
+ def decode(self, json_str): # pylint: disable=W0221
+ d = super(SymbolsDecoder, self).decode(json_str)
+ symbols = symbol.Symbols()
+ for sym_key, sym_dict in d.iteritems():
+ sym = symbol.Symbol(sym_dict['name'])
+ for source_info in sym_dict['source_info']:
+ sym.AddSourceLineInfo(**source_info)
+ symbols.symbols[sym_key] = sym
+ return symbols
+
+
+class NativeHeapDecoder(json.JSONDecoder):
+ def decode(self, json_str): # pylint: disable=W0221
+ d = super(NativeHeapDecoder, self).decode(json_str)
+ nh = native_heap.NativeHeap()
+ # First load and rebuild the stack_frame index.
+ for frame_dict in d['stack_frames']:
+ frame = nh.GetStackFrame(frame_dict['address'])
+ frame.SetExecFileInfo(frame_dict['exec_file_rel_path'],
+ frame_dict['offset'])
+ # Then load backtraces (reusing stack frames from the index above).
+ for alloc_dict in d['allocations']:
+ stack_trace = stacktrace.Stacktrace()
+ for absolute_addr in alloc_dict['stack_trace']:
+ stack_trace.Add(nh.GetStackFrame(absolute_addr))
+ allocation = native_heap.Allocation(alloc_dict['size'],
+ alloc_dict['count'],
+ stack_trace)
+ nh.Add(allocation)
+ return nh \ No newline at end of file