OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import collections | 5 import collections |
6 import datetime | 6 import datetime |
7 import logging | 7 import logging |
8 import multiprocessing | 8 import multiprocessing |
9 import os | 9 import os |
10 import posixpath | 10 import posixpath |
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
115 min(multiprocessing.cpu_count(), 4)) | 115 min(multiprocessing.cpu_count(), 4)) |
116 self.max_queue_size = max_queue_size | 116 self.max_queue_size = max_queue_size |
117 self.addr2line_timeout = addr2line_timeout | 117 self.addr2line_timeout = addr2line_timeout |
118 self.requests_counter = 0 # For generating monotonic request IDs. | 118 self.requests_counter = 0 # For generating monotonic request IDs. |
119 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. | 119 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. |
120 | 120 |
121 # If necessary, create disambiguation lookup table | 121 # If necessary, create disambiguation lookup table |
122 self.disambiguate = source_root_path is not None | 122 self.disambiguate = source_root_path is not None |
123 self.disambiguation_table = {} | 123 self.disambiguation_table = {} |
124 self.strip_base_path = strip_base_path | 124 self.strip_base_path = strip_base_path |
125 if(self.disambiguate): | 125 if self.disambiguate: |
126 self.source_root_path = os.path.abspath(source_root_path) | 126 self.source_root_path = os.path.abspath(source_root_path) |
127 self._CreateDisambiguationTable() | 127 self._CreateDisambiguationTable() |
128 | 128 |
129 # Create one addr2line instance. More instances will be created on demand | 129 # Create one addr2line instance. More instances will be created on demand |
130 # (up to |max_concurrent_jobs|) depending on the rate of the requests. | 130 # (up to |max_concurrent_jobs|) depending on the rate of the requests. |
131 self._CreateNewA2LInstance() | 131 self._CreateNewA2LInstance() |
132 | 132 |
133 def SymbolizeAsync(self, addr, callback_arg=None): | 133 def SymbolizeAsync(self, addr, callback_arg=None): |
134 """Requests symbolization of a given address. | 134 """Requests symbolization of a given address. |
135 | 135 |
136 This method is not guaranteed to return immediately. It generally does, but | 136 This method is not guaranteed to return immediately. It generally does, but |
137 in some scenarios (e.g. all addr2line instances have full queues) it can | 137 in some scenarios (e.g. all addr2line instances have full queues) it can |
138 block to create back-pressure. | 138 block to create back-pressure. |
139 | 139 |
140 Args: | 140 Args: |
141 addr: address to symbolize. | 141 addr: address to symbolize. |
142 callback_arg: optional argument which will be passed to the |callback|.""" | 142 callback_arg: optional argument which will be passed to the |callback|.""" |
143 assert(isinstance(addr, int)) | 143 assert isinstance(addr, int) |
144 | 144 |
145 # Process all the symbols that have been resolved in the meanwhile. | 145 # Process all the symbols that have been resolved in the meanwhile. |
146 # Essentially, this drains all the addr2line(s) out queues. | 146 # Essentially, this drains all the addr2line(s) out queues. |
147 for a2l_to_purge in self._a2l_instances: | 147 for a2l_to_purge in self._a2l_instances: |
148 a2l_to_purge.ProcessAllResolvedSymbolsInQueue() | 148 a2l_to_purge.ProcessAllResolvedSymbolsInQueue() |
149 a2l_to_purge.RecycleIfNecessary() | 149 a2l_to_purge.RecycleIfNecessary() |
150 | 150 |
151 # Find the best instance according to this logic: | 151 # Find the best instance according to this logic: |
152 # 1. Find an existing instance with the shortest queue. | 152 # 1. Find an existing instance with the shortest queue. |
153 # 2. If all of instances' queues are full, but there is room in the pool, | 153 # 2. If all of instances' queues are full, but there is room in the pool, |
(...skipping 17 matching lines...) Expand all Loading... |
171 | 171 |
172 a2l.EnqueueRequest(addr, callback_arg) | 172 a2l.EnqueueRequest(addr, callback_arg) |
173 | 173 |
174 def Join(self): | 174 def Join(self): |
175 """Waits for all the outstanding requests to complete and terminates.""" | 175 """Waits for all the outstanding requests to complete and terminates.""" |
176 for a2l in self._a2l_instances: | 176 for a2l in self._a2l_instances: |
177 a2l.WaitForIdle() | 177 a2l.WaitForIdle() |
178 a2l.Terminate() | 178 a2l.Terminate() |
179 | 179 |
180 def _CreateNewA2LInstance(self): | 180 def _CreateNewA2LInstance(self): |
181 assert(len(self._a2l_instances) < self.max_concurrent_jobs) | 181 assert len(self._a2l_instances) < self.max_concurrent_jobs |
182 a2l = ELFSymbolizer.Addr2Line(self) | 182 a2l = ELFSymbolizer.Addr2Line(self) |
183 self._a2l_instances.append(a2l) | 183 self._a2l_instances.append(a2l) |
184 return a2l | 184 return a2l |
185 | 185 |
186 def _CreateDisambiguationTable(self): | 186 def _CreateDisambiguationTable(self): |
187 """ Non-unique file names will result in None entries""" | 187 """ Non-unique file names will result in None entries""" |
188 start_time = time.time() | 188 start_time = time.time() |
189 logging.info('Collecting information about available source files...') | 189 logging.info('Collecting information about available source files...') |
190 self.disambiguation_table = {} | 190 self.disambiguation_table = {} |
191 | 191 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
255 """Waits for the next pending request to be symbolized.""" | 255 """Waits for the next pending request to be symbolized.""" |
256 if not self.queue_size: | 256 if not self.queue_size: |
257 return | 257 return |
258 | 258 |
259 # This outer loop guards against a2l hanging (detecting stdout timeout). | 259 # This outer loop guards against a2l hanging (detecting stdout timeout). |
260 while True: | 260 while True: |
261 start_time = datetime.datetime.now() | 261 start_time = datetime.datetime.now() |
262 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) | 262 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) |
263 | 263 |
264 # The inner loop guards against a2l crashing (checking if it exited). | 264 # The inner loop guards against a2l crashing (checking if it exited). |
265 while (datetime.datetime.now() - start_time < timeout): | 265 while datetime.datetime.now() - start_time < timeout: |
266 # poll() returns !None if the process exited. a2l should never exit. | 266 # poll() returns !None if the process exited. a2l should never exit. |
267 if self._proc.poll(): | 267 if self._proc.poll(): |
268 logging.warning('addr2line crashed, respawning (lib: %s).' % | 268 logging.warning('addr2line crashed, respawning (lib: %s).', |
269 self._lib_file_name) | 269 self._lib_file_name) |
270 self._RestartAddr2LineProcess() | 270 self._RestartAddr2LineProcess() |
271 # TODO(primiano): the best thing to do in this case would be | 271 # TODO(primiano): the best thing to do in this case would be |
272 # shrinking the pool size as, very likely, addr2line is crashed | 272 # shrinking the pool size as, very likely, addr2line is crashed |
273 # due to low memory (and the respawned one will die again soon). | 273 # due to low memory (and the respawned one will die again soon). |
274 | 274 |
275 try: | 275 try: |
276 lines = self._out_queue.get(block=True, timeout=0.25) | 276 lines = self._out_queue.get(block=True, timeout=0.25) |
277 except Queue.Empty: | 277 except Queue.Empty: |
278 # On timeout (1/4 s.) repeat the inner loop and check if either the | 278 # On timeout (1/4 s.) repeat the inner loop and check if either the |
279 # addr2line process did crash or we waited its output for too long. | 279 # addr2line process did crash or we waited its output for too long. |
280 continue | 280 continue |
281 | 281 |
282 # In nominal conditions, we get straight to this point. | 282 # In nominal conditions, we get straight to this point. |
283 self._ProcessSymbolOutput(lines) | 283 self._ProcessSymbolOutput(lines) |
284 return | 284 return |
285 | 285 |
286 # If this point is reached, we waited more than |addr2line_timeout|. | 286 # If this point is reached, we waited more than |addr2line_timeout|. |
287 logging.warning('Hung addr2line process, respawning (lib: %s).' % | 287 logging.warning('Hung addr2line process, respawning (lib: %s).', |
288 self._lib_file_name) | 288 self._lib_file_name) |
289 self._RestartAddr2LineProcess() | 289 self._RestartAddr2LineProcess() |
290 | 290 |
291 def ProcessAllResolvedSymbolsInQueue(self): | 291 def ProcessAllResolvedSymbolsInQueue(self): |
292 """Consumes all the addr2line output lines produced (without blocking).""" | 292 """Consumes all the addr2line output lines produced (without blocking).""" |
293 if not self.queue_size: | 293 if not self.queue_size: |
294 return | 294 return |
295 while True: | 295 while True: |
296 try: | 296 try: |
297 lines = self._out_queue.get_nowait() | 297 lines = self._out_queue.get_nowait() |
(...skipping 10 matching lines...) Expand all Loading... |
308 self._RestartAddr2LineProcess() | 308 self._RestartAddr2LineProcess() |
309 | 309 |
310 | 310 |
311 def Terminate(self): | 311 def Terminate(self): |
312 """Kills the underlying addr2line process. | 312 """Kills the underlying addr2line process. |
313 | 313 |
314 The poller |_thread| will terminate as well due to the broken pipe.""" | 314 The poller |_thread| will terminate as well due to the broken pipe.""" |
315 try: | 315 try: |
316 self._proc.kill() | 316 self._proc.kill() |
317 self._proc.communicate() # Essentially wait() without risking deadlock. | 317 self._proc.communicate() # Essentially wait() without risking deadlock. |
318 except Exception: # An exception while terminating? How interesting. | 318 except Exception: # pylint: disable=broad-except |
| 319 # An exception while terminating? How interesting. |
319 pass | 320 pass |
320 self._proc = None | 321 self._proc = None |
321 | 322 |
322 def _WriteToA2lStdin(self, addr): | 323 def _WriteToA2lStdin(self, addr): |
323 self._proc.stdin.write('%s\n' % hex(addr)) | 324 self._proc.stdin.write('%s\n' % hex(addr)) |
324 if self._symbolizer.inlines: | 325 if self._symbolizer.inlines: |
325 # In the case of inlines we output an extra blank line, which causes | 326 # In the case of inlines we output an extra blank line, which causes |
326 # addr2line to emit a (??,??:0) tuple that we use as a boundary marker. | 327 # addr2line to emit a (??,??:0) tuple that we use as a boundary marker. |
327 self._proc.stdin.write('\n') | 328 self._proc.stdin.write('\n') |
328 self._proc.stdin.flush() | 329 self._proc.stdin.flush() |
(...skipping 10 matching lines...) Expand all Loading... |
339 name = line1 if not line1.startswith('?') else None | 340 name = line1 if not line1.startswith('?') else None |
340 source_path = None | 341 source_path = None |
341 source_line = None | 342 source_line = None |
342 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) | 343 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) |
343 if m: | 344 if m: |
344 if not m.group(1).startswith('?'): | 345 if not m.group(1).startswith('?'): |
345 source_path = m.group(1) | 346 source_path = m.group(1) |
346 if not m.group(2).startswith('?'): | 347 if not m.group(2).startswith('?'): |
347 source_line = int(m.group(2)) | 348 source_line = int(m.group(2)) |
348 else: | 349 else: |
349 logging.warning('Got invalid symbol path from addr2line: %s' % line2) | 350 logging.warning('Got invalid symbol path from addr2line: %s', line2) |
350 | 351 |
351 # In case disambiguation is on, and needed | 352 # In case disambiguation is on, and needed |
352 was_ambiguous = False | 353 was_ambiguous = False |
353 disambiguated = False | 354 disambiguated = False |
354 if self._symbolizer.disambiguate: | 355 if self._symbolizer.disambiguate: |
355 if source_path and not posixpath.isabs(source_path): | 356 if source_path and not posixpath.isabs(source_path): |
356 path = self._symbolizer.disambiguation_table.get(source_path) | 357 path = self._symbolizer.disambiguation_table.get(source_path) |
357 was_ambiguous = True | 358 was_ambiguous = True |
358 disambiguated = path is not None | 359 disambiguated = path is not None |
359 source_path = path if disambiguated else source_path | 360 source_path = path if disambiguated else source_path |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
458 self.source_line = source_line | 459 self.source_line = source_line |
459 # In the case of |inlines|=True, the |inlined_by| points to the outer | 460 # In the case of |inlines|=True, the |inlined_by| points to the outer |
460 # function inlining the current one (and so on, to form a chain). | 461 # function inlining the current one (and so on, to form a chain). |
461 self.inlined_by = None | 462 self.inlined_by = None |
462 self.disambiguated = disambiguated | 463 self.disambiguated = disambiguated |
463 self.was_ambiguous = was_ambiguous | 464 self.was_ambiguous = was_ambiguous |
464 | 465 |
465 def __str__(self): | 466 def __str__(self): |
466 return '%s [%s:%d]' % ( | 467 return '%s [%s:%d]' % ( |
467 self.name or '??', self.source_path or '??', self.source_line or 0) | 468 self.name or '??', self.source_path or '??', self.source_line or 0) |
OLD | NEW |