Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
|
Primiano Tucci (use gerrit)
2016/04/01 15:56:28
not sure whether this is the right folder for 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 |