| Index: tools/memory_inspector/memory_inspector/core/native_heap.py
|
| diff --git a/tools/memory_inspector/memory_inspector/core/native_heap.py b/tools/memory_inspector/memory_inspector/core/native_heap.py
|
| index ab52a3927a0e86d721f919c9a76d3b898c799498..4a5537b7a629a425fecd8ea7ae0d9d8c7bbac9f9 100644
|
| --- a/tools/memory_inspector/memory_inspector/core/native_heap.py
|
| +++ b/tools/memory_inspector/memory_inspector/core/native_heap.py
|
| @@ -2,9 +2,12 @@
|
| # Use of this source code is governed by a BSD-style license that can be
|
| # found in the LICENSE file.
|
|
|
| +from memory_inspector.core import memory_map
|
| from memory_inspector.core import stacktrace
|
| from memory_inspector.core import symbol
|
|
|
| +from memory_inspector.core.memory_map import PAGE_SIZE
|
| +
|
|
|
| class NativeHeap(object):
|
| """A snapshot of outstanding (i.e. not freed) native allocations.
|
| @@ -32,31 +35,88 @@ class NativeHeap(object):
|
| def SymbolizeUsingSymbolDB(self, symbols):
|
| assert(isinstance(symbols, symbol.Symbols))
|
| for stack_frame in self.stack_frames.itervalues():
|
| - if stack_frame.exec_file_rel_path is None:
|
| + if not stack_frame.exec_file_rel_path:
|
| continue
|
| sym = symbols.Lookup(stack_frame.exec_file_rel_path, stack_frame.offset)
|
| if sym:
|
| stack_frame.SetSymbolInfo(sym)
|
|
|
| + def RelativizeStackFrames(self, mmap):
|
| + """Turns stack frames' absolute addresses into mmap relative addresses.
|
| +
|
| + For each absolute address, the containing mmap is looked up and the frame
|
| + is decorated with the mapped file + relative address in the file."""
|
| + assert(isinstance(mmap, memory_map.Map))
|
| + for abs_addr, stack_frame in self.stack_frames.iteritems():
|
| + assert(abs_addr == stack_frame.address)
|
| + map_entry = mmap.Lookup(abs_addr)
|
| + if not map_entry:
|
| + continue
|
| + stack_frame.SetExecFileInfo(map_entry.mapped_file,
|
| + map_entry.GetRelativeFileOffset(abs_addr))
|
| +
|
| + def CalculateResidentSize(self, mmap):
|
| + """Updates the |Allocation|.|resident_size|s by looking at mmap stats.
|
| +
|
| + Not all the allocated memory is always used (read: resident). This function
|
| + estimates the resident size of an allocation intersecting the mmaps dump.
|
| + """
|
| + assert(isinstance(mmap, memory_map.Map))
|
| + for alloc in self.allocations:
|
| + # This function loops over all the memory pages that intersect, partially
|
| + # or fully, with each allocation. For each of them, the allocation is
|
| + # attributed a resident size equal to the size of intersecting range iff
|
| + # the page is resident.
|
| + # The tricky part is that, in the general case, an allocation can span
|
| + # over multiple (contiguous) mmaps. See the chart below for a reference:
|
| + #
|
| + # VA space: |0 |4k |8k |12k |16k |20k |24k |28k |32k |
|
| + # Mmaps: [ mm 1 ][ mm2 ] [ map 3 ]
|
| + # Allocs: <a1> < a2 > < a3 >
|
| + #
|
| + # Note: this accounting technique is not fully correct but is generally a
|
| + # good tradeoff between accuracy and speed of profiling. The OS provides
|
| + # resident information with the page granularity (typ. 4k). Finer values
|
| + # would require more fancy techniques based, for instance, on run-time
|
| + # instrumentation tools like Valgrind or *sanitizer.
|
| + cur_start = alloc.start
|
| + mm = None
|
| + while cur_start < alloc.end:
|
| + if not mm or not mm.Contains(cur_start):
|
| + mm = mmap.Lookup(cur_start)
|
| + if mm:
|
| + page, page_off = mm.GetRelativeMMOffset(cur_start)
|
| + if mm.IsPageResident(page):
|
| + page_end = mm.start + page * PAGE_SIZE + PAGE_SIZE - 1
|
| + alloc_memory_in_current_page = PAGE_SIZE - page_off
|
| + if alloc.end < page_end:
|
| + alloc_memory_in_current_page -= page_end - alloc.end
|
| + alloc.resident_size += alloc_memory_in_current_page
|
| + # Move to the next page boundary.
|
| + cur_start = (cur_start + PAGE_SIZE) & ~(PAGE_SIZE - 1)
|
| +
|
|
|
| class Allocation(object):
|
| - """Records profiling information aobut a native heap allocation.
|
| + """Records profiling information about a native heap allocation.
|
|
|
| Args:
|
| size: size of the allocation, in bytes.
|
| stack_trace: the allocation call-site. See |stacktrace.Stacktrace|.
|
| - start: (Optional) Absolute start address in the process VMA. Optional.
|
| - It is required only for |CalculateResidentSize|.
|
| + start: (Optional) Absolute start address in the process VMA. It is
|
| + required only for |CalculateResidentSize|.
|
| flags: (Optional) More details about the call site (e.g., mmap vs malloc).
|
| + resident_size: this is normally obtained through |CalculateResidentSize|
|
| + and is part of the initializer just for deserialization purposes.
|
| """
|
|
|
| - def __init__(self, size, stack_trace, start=0, flags=0):
|
| + def __init__(self, size, stack_trace, start=0, flags=0, resident_size=0):
|
| assert(size > 0)
|
| assert(isinstance(stack_trace, stacktrace.Stacktrace))
|
| self.size = size # in bytes.
|
| self.stack_trace = stack_trace
|
| self.start = start # Optional, for using the resident size logic.
|
| self.flags = flags
|
| + self.resident_size = resident_size # see |CalculateResidentSize|.
|
|
|
| @property
|
| def end(self):
|
|
|