| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import os |
| import re |
| import tempfile |
| import time |
| |
| from devil.utils import cmd_helper |
| from pylib import constants |
| from pylib.constants import host_paths |
| from .expensive_line_transformer import ExpensiveLineTransformer |
| from .expensive_line_transformer import ExpensiveLineTransformerPool |
| |
| _STACK_TOOL = os.path.join(host_paths.ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH, |
| 'stack') |
| _MINIMUM_TIMEOUT = 10.0 |
| _PER_LINE_TIMEOUT = .005 # Should be able to process 200 lines per second. |
| _PROCESS_START_TIMEOUT = 20.0 |
| _MAX_RESTARTS = 4 # Should be plenty unless tool is crashing on start-up. |
| _POOL_SIZE = 1 |
| _PASSTHROUH_ON_FAILURE = True |
| ABI_REG = re.compile('ABI: \'(.+?)\'') |
| |
| |
| def _DeviceAbiToArch(device_abi): |
| # The order of this list is significant to find the more specific match |
| # (e.g., arm64) before the less specific (e.g., arm). |
| arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips'] |
| for arch in arches: |
| if arch in device_abi: |
| return arch |
| raise RuntimeError('Unknown device ABI: %s' % device_abi) |
| |
| |
| class Symbolizer: |
| """A helper class to symbolize stack.""" |
| |
| def __init__(self, apk_under_test=None): |
| self._apk_under_test = apk_under_test |
| self._time_spent_symbolizing = 0 |
| |
| |
| def __del__(self): |
| self.CleanUp() |
| |
| |
| def CleanUp(self): |
| """Clean up the temporary directory of apk libs.""" |
| if self._time_spent_symbolizing > 0: |
| logging.info( |
| 'Total time spent symbolizing: %.2fs', self._time_spent_symbolizing) |
| |
| |
| def ExtractAndResolveNativeStackTraces(self, data_to_symbolize, |
| device_abi, include_stack=True): |
| """Run the stack tool for given input. |
| |
| Args: |
| data_to_symbolize: a list of strings to symbolize. |
| include_stack: boolean whether to include stack data in output. |
| device_abi: the default ABI of the device which generated the tombstone. |
| |
| Yields: |
| A string for each line of resolved stack output. |
| """ |
| if not os.path.exists(_STACK_TOOL): |
| logging.warning('%s missing. Unable to resolve native stack traces.', |
| _STACK_TOOL) |
| return |
| |
| arch = _DeviceAbiToArch(device_abi) |
| if not arch: |
| logging.warning('No device_abi can be found.') |
| return |
| |
| cmd = [_STACK_TOOL, '--arch', arch, '--output-directory', |
| constants.GetOutDirectory(), '--more-info'] |
| env = dict(os.environ) |
| env['PYTHONDONTWRITEBYTECODE'] = '1' |
| with tempfile.NamedTemporaryFile(mode='w') as f: |
| f.write('\n'.join(data_to_symbolize)) |
| f.flush() |
| start = time.time() |
| try: |
| _, output = cmd_helper.GetCmdStatusAndOutput(cmd + [f.name], env=env) |
| finally: |
| self._time_spent_symbolizing += time.time() - start |
| for line in output.splitlines(): |
| if not include_stack and 'Stack Data:' in line: |
| break |
| yield line |
| |
| |
| class PassThroughSymbolizer(ExpensiveLineTransformer): |
| def __init__(self, device_abi): |
| self._command = None |
| super().__init__(_PROCESS_START_TIMEOUT, _MINIMUM_TIMEOUT, |
| _PER_LINE_TIMEOUT) |
| if not os.path.exists(_STACK_TOOL): |
| logging.warning('%s: %s missing. Unable to resolve native stack traces.', |
| PassThroughSymbolizer.name, _STACK_TOOL) |
| return |
| arch = _DeviceAbiToArch(device_abi) |
| if not arch: |
| logging.warning('%s: No device_abi can be found.', |
| PassThroughSymbolizer.name) |
| return |
| self._command = [ |
| _STACK_TOOL, '--arch', arch, '--output-directory', |
| constants.GetOutDirectory(), '--more-info', '--pass-through', '--flush', |
| '--quiet', '-' |
| ] |
| self.start() |
| |
| @property |
| def name(self): |
| return "symbolizer" |
| |
| @property |
| def command(self): |
| return self._command |
| |
| |
| class PassThroughSymbolizerPool(ExpensiveLineTransformerPool): |
| def __init__(self, device_abi): |
| self._device_abi = device_abi |
| super().__init__(_MAX_RESTARTS, _POOL_SIZE, _PASSTHROUH_ON_FAILURE) |
| |
| def CreateTransformer(self): |
| return PassThroughSymbolizer(self._device_abi) |
| |
| @property |
| def name(self): |
| return "symbolizer-pool" |