native_heap.py revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from memory_inspector.core import memory_map 6from memory_inspector.core import stacktrace 7from memory_inspector.core import symbol 8 9from memory_inspector.core.memory_map import PAGE_SIZE 10 11 12class NativeHeap(object): 13 """A snapshot of outstanding (i.e. not freed) native allocations. 14 15 This is typically obtained by calling |backends.Process|.DumpNativeHeap() 16 """ 17 18 def __init__(self): 19 self.allocations = [] # list of individual |Allocation|s. 20 self.stack_frames = {} # absolute_address (int) -> |stacktrace.Frame|. 21 22 def Add(self, allocation): 23 assert(isinstance(allocation, Allocation)) 24 self.allocations += [allocation] 25 26 def GetStackFrame(self, absolute_addr): 27 """Guarantees that multiple calls with the same addr return the same obj.""" 28 assert(isinstance(absolute_addr, (long, int))) 29 stack_frame = self.stack_frames.get(absolute_addr) 30 if not stack_frame: 31 stack_frame = stacktrace.Frame(absolute_addr) 32 self.stack_frames[absolute_addr] = stack_frame 33 return stack_frame 34 35 def SymbolizeUsingSymbolDB(self, symbols): 36 assert(isinstance(symbols, symbol.Symbols)) 37 for stack_frame in self.stack_frames.itervalues(): 38 if not stack_frame.exec_file_rel_path: 39 continue 40 sym = symbols.Lookup(stack_frame.exec_file_rel_path, stack_frame.offset) 41 if sym: 42 stack_frame.SetSymbolInfo(sym) 43 44 def RelativizeStackFrames(self, mmap): 45 """Turns stack frames' absolute addresses into mmap relative addresses. 46 47 For each absolute address, the containing mmap is looked up and the frame 48 is decorated with the mapped file + relative address in the file.""" 49 assert(isinstance(mmap, memory_map.Map)) 50 for abs_addr, stack_frame in self.stack_frames.iteritems(): 51 assert(abs_addr == stack_frame.address) 52 map_entry = mmap.Lookup(abs_addr) 53 if not map_entry: 54 continue 55 stack_frame.SetExecFileInfo(map_entry.mapped_file, 56 map_entry.GetRelativeFileOffset(abs_addr)) 57 58 def CalculateResidentSize(self, mmap): 59 """Updates the |Allocation|.|resident_size|s by looking at mmap stats. 60 61 Not all the allocated memory is always used (read: resident). This function 62 estimates the resident size of an allocation intersecting the mmaps dump. 63 """ 64 assert(isinstance(mmap, memory_map.Map)) 65 for alloc in self.allocations: 66 # This function loops over all the memory pages that intersect, partially 67 # or fully, with each allocation. For each of them, the allocation is 68 # attributed a resident size equal to the size of intersecting range iff 69 # the page is resident. 70 # The tricky part is that, in the general case, an allocation can span 71 # over multiple (contiguous) mmaps. See the chart below for a reference: 72 # 73 # VA space: |0 |4k |8k |12k |16k |20k |24k |28k |32k | 74 # Mmaps: [ mm 1 ][ mm2 ] [ map 3 ] 75 # Allocs: <a1> < a2 > < a3 > 76 # 77 # Note: this accounting technique is not fully correct but is generally a 78 # good tradeoff between accuracy and speed of profiling. The OS provides 79 # resident information with the page granularity (typ. 4k). Finer values 80 # would require more fancy techniques based, for instance, on run-time 81 # instrumentation tools like Valgrind or *sanitizer. 82 cur_start = alloc.start 83 mm = None 84 while cur_start < alloc.end: 85 if not mm or not mm.Contains(cur_start): 86 mm = mmap.Lookup(cur_start) 87 if mm: 88 page, page_off = mm.GetRelativeMMOffset(cur_start) 89 if mm.IsPageResident(page): 90 page_end = mm.start + page * PAGE_SIZE + PAGE_SIZE - 1 91 alloc_memory_in_current_page = PAGE_SIZE - page_off 92 if alloc.end < page_end: 93 alloc_memory_in_current_page -= page_end - alloc.end 94 alloc.resident_size += alloc_memory_in_current_page 95 # Move to the next page boundary. 96 cur_start = (cur_start + PAGE_SIZE) & ~(PAGE_SIZE - 1) 97 98 99class Allocation(object): 100 """Records profiling information about a native heap allocation. 101 102 Args: 103 size: size of the allocation, in bytes. 104 stack_trace: the allocation call-site. See |stacktrace.Stacktrace|. 105 start: (Optional) Absolute start address in the process VMA. It is 106 required only for |CalculateResidentSize|. 107 flags: (Optional) More details about the call site (e.g., mmap vs malloc). 108 resident_size: this is normally obtained through |CalculateResidentSize| 109 and is part of the initializer just for deserialization purposes. 110 """ 111 112 def __init__(self, size, stack_trace, start=0, flags=0, resident_size=0): 113 assert(size > 0) 114 assert(isinstance(stack_trace, stacktrace.Stacktrace)) 115 self.size = size # in bytes. 116 self.stack_trace = stack_trace 117 self.start = start # Optional, for using the resident size logic. 118 self.flags = flags 119 self.resident_size = resident_size # see |CalculateResidentSize|. 120 121 @property 122 def end(self): 123 return self.start + self.size - 1 124 125 def __str__(self): 126 return '%d : %s' % (self.size, self.stack_trace) 127