diff options
author | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-03 09:36:05 +0000 |
---|---|---|
committer | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-03 09:36:05 +0000 |
commit | 247160c191f0f059ded8ccfb4087eb1db36c3a2f (patch) | |
tree | 9f8bc53062a4d4c8a215bed280baa53c94f09668 /tools/memory_inspector | |
parent | a2507854f005ad7c5e36a9f0bda1117b38b54f6c (diff) | |
download | chromium_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')
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 |