OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
Primiano Tucci (use gerrit)
2016/04/07 15:51:57
Did you figure out with perezju where to move this
| |
2 # Copyright (c) 2016 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 import argparse | |
7 import bisect | |
8 import collections | |
9 import gzip | |
10 import json | |
11 import os | |
12 import posixpath | |
13 import sys | |
14 | |
15 # This is how tools/binary_size/run_binary_size_analysis.py does it. | |
16 # See http://crbug.com/375725 | |
17 elf_symbolizer_path = os.path.abspath(os.path.join( | |
18 os.path.dirname(__file__), | |
19 '..', | |
20 '..', | |
21 'build', | |
22 'android', | |
23 'pylib')) | |
24 sys.path.append(elf_symbolizer_path) | |
25 import symbols.elf_symbolizer as elf_symbolizer # pylint: disable=F0401 | |
26 | |
27 | |
28 class ProcessMemoryMaps(object): | |
29 Region = collections.namedtuple( | |
30 'Region', | |
31 ['start_address', 'end_address', 'file_name']) | |
32 | |
33 def __init__(self, value): | |
34 self._regions = [] | |
35 for region_value in value['vm_regions']: | |
36 start_address = long(region_value['sa'], 16) | |
37 size = long(region_value['sz'], 16) | |
38 self._regions.append(self.Region( | |
39 start_address, | |
40 start_address + size, | |
41 region_value['mf'])) | |
42 | |
43 self._regions.sort() | |
44 | |
45 # Remove duplicates; check for overlaps | |
46 region_index = 0 | |
47 while region_index < len(self._regions): | |
48 region = self._regions[region_index] | |
49 if region_index != 0: | |
50 last_region = self._regions[region_index - 1] | |
51 if last_region == region: | |
52 del self._regions[region_index] | |
53 continue | |
54 | |
55 assert region.start_address >= last_region.end_address, \ | |
56 'Regions {} and {} overlap.'.format(last_region, region) | |
57 region_index += 1 | |
58 | |
59 @property | |
60 def regions(self): | |
61 return self._regions | |
62 | |
63 def FindRegion(self, address): | |
64 if not self._regions: | |
65 return None | |
66 region_index = bisect.bisect_left( | |
67 self._regions, self.Region(address, None, None)) | |
68 region = self._regions[region_index] | |
69 if region.start_address > address and region_index != 0: | |
70 region_index -= 1 | |
71 region = self._regions[region_index] | |
72 if address >= region.start_address and address < region.end_address: | |
73 return region | |
74 return None | |
75 | |
76 | |
77 class StackFrames(object): | |
78 def __init__(self, value): | |
79 self._frames_value = value | |
80 | |
81 def CollectPCs(self): | |
82 return [pc for _, pc in self._IteratePCs()] | |
83 | |
84 def SymbolizePCs(self, pc_symbol_map): | |
85 symbolized = False | |
86 for frame, pc in self._IteratePCs(): | |
87 if pc in pc_symbol_map: | |
88 frame['name'] = pc_symbol_map[pc] | |
89 symbolized = True | |
90 return symbolized | |
91 | |
92 # Details | |
93 | |
94 _PC_TAG = 'pc:' | |
95 | |
96 def _ParsePC(self, name): | |
97 if not name.startswith(self._PC_TAG): | |
98 return None | |
99 return long(name[len(self._PC_TAG):], 16) | |
100 | |
101 def _IteratePCs(self): | |
102 for frame in self._frames_value.itervalues(): | |
103 pc = self._ParsePC(frame['name']) | |
104 if pc is not None: | |
105 yield (frame, pc) | |
106 | |
107 | |
108 class Symbolizer(object): | |
109 def __init__(self, binary_path, addr2line_path): | |
110 self._elf_symbolizer = None | |
111 self._pc_symbol_map = {} | |
112 self._failed_pcs = set() | |
113 self._queued_pcs = set() | |
114 | |
115 if os.path.isfile(binary_path): | |
116 self._elf_symbolizer = elf_symbolizer.ELFSymbolizer( | |
117 binary_path, | |
118 addr2line_path, | |
119 self._SymbolizerCallback) | |
120 | |
121 @property | |
122 def pc_symbol_map(self): | |
123 return self._pc_symbol_map | |
124 | |
125 @property | |
126 def failed_pcs(self): | |
127 return self._failed_pcs | |
128 | |
129 def SymbolizeAsync(self, pc, region): | |
130 if self._elf_symbolizer is None: | |
131 self._failed_pcs.add(pc) | |
132 return | |
133 | |
134 if pc not in self._queued_pcs: | |
135 self._elf_symbolizer.SymbolizeAsync(int(pc - region.start_address), pc) | |
136 self._queued_pcs.add(pc) | |
137 | |
138 def Join(self): | |
139 if self._elf_symbolizer is None: | |
140 return | |
141 | |
142 self._elf_symbolizer.Join() | |
143 | |
144 # Details | |
145 | |
146 def _SymbolizerCallback(self, sym_info, pc): | |
147 self._queued_pcs.remove(pc) | |
148 if sym_info.name: | |
149 self._pc_symbol_map[pc] = sym_info.name | |
150 else: | |
151 self._failed_pcs.add(pc) | |
152 | |
153 | |
154 class Process(object): | |
155 def __init__(self, pid): | |
156 self.pid = pid | |
157 self.name = None | |
158 self.mmaps = None | |
159 self.stack_frames = None | |
160 | |
161 | |
162 def CollectProcesses(trace): | |
163 process_map = {} | |
164 | |
165 for event in trace['traceEvents']: | |
166 name = event.get('name') | |
167 if not name: | |
168 continue | |
169 | |
170 pid = event['pid'] | |
171 process = process_map.get(pid) | |
172 if process is None: | |
173 process = Process(pid) | |
174 process_map[pid] = process | |
175 | |
176 if name == 'process_name': | |
177 process.name = event['args']['name'] | |
178 elif name == 'stackFrames': | |
179 value = event['args']['stackFrames'] | |
180 process.stack_frames = StackFrames(value) | |
181 elif name == 'periodic_interval': | |
182 value = event['args']['dumps'].get('process_mmaps') | |
183 if value: | |
184 process.mmaps = ProcessMemoryMaps(value) | |
185 | |
186 processes = [] | |
187 for process in process_map.itervalues(): | |
188 if process.mmaps is not None and process.stack_frames is not None: | |
189 processes.append(process) | |
190 | |
191 return processes | |
192 | |
193 | |
194 def SymbolizeProcess(process, addr2line_path): | |
195 pcs = process.stack_frames.CollectPCs() | |
196 if not pcs: | |
197 return False | |
198 | |
199 print 'Symbolizing {} ({})...'.format(process.name, process.pid) | |
200 | |
201 def _SubPrintf(message, *args): | |
202 print (' ' + message).format(*args) | |
203 | |
204 symbolizer_map = {} | |
205 unresolved_pcs = set() | |
206 for pc in pcs: | |
207 region = process.mmaps.FindRegion(pc) | |
208 if region is None: | |
209 unresolved_pcs.add(pc) | |
210 continue | |
211 symbolizer = symbolizer_map.get(region.file_name) | |
212 if symbolizer is None: | |
213 symbolizer = Symbolizer(region.file_name, addr2line_path) | |
214 symbolizer_map[region.file_name] = symbolizer | |
215 symbolizer.SymbolizeAsync(pc, region) | |
216 | |
217 if unresolved_pcs: | |
218 _SubPrintf('{} PCs were not resolved.', len(unresolved_pcs)) | |
219 | |
220 pc_symbol_map = {} | |
221 for file_path, symbolizer in symbolizer_map.iteritems(): | |
222 symbolizer.Join() | |
223 _SubPrintf('{}: {} PCs symbolized ({} failed)', | |
224 file_path, | |
225 len(symbolizer.pc_symbol_map), | |
226 len(symbolizer.failed_pcs)) | |
227 pc_symbol_map.update(symbolizer.pc_symbol_map) | |
228 | |
229 return process.stack_frames.SymbolizePCs(pc_symbol_map) | |
230 | |
231 | |
232 def FindInSystemPath(binary_name): | |
233 paths = os.environ["PATH"].split(os.pathsep) | |
234 for path in paths: | |
235 binary_path = os.path.join(path, binary_name) | |
236 if os.path.isfile(binary_path): | |
237 return binary_path | |
238 return None | |
239 | |
240 | |
241 def main(): | |
242 BACKUP_FILE_TAG = '.BACKUP' | |
243 | |
244 parser = argparse.ArgumentParser() | |
245 parser.add_argument('file', nargs=1, | |
246 help='Trace file to symbolize (.json or .json.gz)') | |
247 parser.add_argument('--no-backup', action='store_true', | |
248 help="Don't create {} files".format(BACKUP_FILE_TAG)) | |
249 options = parser.parse_args() | |
250 | |
251 trace_file_path = options.file[0] | |
252 def _OpenTraceFile(mode): | |
253 if trace_file_path.endswith('.gz'): | |
254 return gzip.open(trace_file_path, mode) | |
255 else: | |
256 return open(trace_file_path, mode) | |
257 | |
258 with _OpenTraceFile('rb') as trace_file: | |
259 trace = json.load(trace_file) | |
260 | |
261 processes = CollectProcesses(trace) | |
262 | |
263 addr2line_path = FindInSystemPath('addr2line') | |
264 | |
265 update_trace = False | |
266 for process in processes: | |
267 if SymbolizeProcess(process, addr2line_path): | |
268 update_trace = True | |
269 | |
270 if update_trace: | |
271 if not options.no_backup: | |
272 print 'Backing up trace file...' | |
273 os.rename(trace_file_path, trace_file_path + BACKUP_FILE_TAG) | |
274 | |
275 print 'Updating trace file...' | |
276 with _OpenTraceFile('wb') as trace_file: | |
277 json.dump(trace, trace_file) | |
278 | |
279 | |
280 if __name__ == '__main__': | |
281 main() | |
OLD | NEW |