Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / memory_inspector / memory_inspector / core / native_heap.py
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
5 from memory_inspector.core import memory_map
6 from memory_inspector.core import stacktrace
7 from memory_inspector.core import symbol
8
9 from memory_inspector.core.memory_map import PAGE_SIZE
10
11
12 class 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
99 class 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)