| #!/usr/bin/env vpython | |
| # | |
| # Copyright 2013 The Chromium Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| # | |
| # Find the most recent tombstone file(s) on all connected devices | |
| # and prints their stacks. | |
| # | |
| # Assumes tombstone file was created with current symbols. | |
| import argparse | |
| import datetime | |
| import logging | |
| import os | |
| import sys | |
| from multiprocessing.pool import ThreadPool | |
| import devil_chromium | |
| from devil.android import device_denylist | |
| from devil.android import device_errors | |
| from devil.android import device_utils | |
| from devil.utils import run_tests_helper | |
| from pylib import constants | |
| from pylib.symbols import stack_symbolizer | |
| _TZ_UTC = {'TZ': 'UTC'} | |
| def _ListTombstones(device): | |
| """List the tombstone files on the device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| Yields: | |
| Tuples of (tombstone filename, date time of file on device). | |
| """ | |
| try: | |
| if not device.PathExists('/data/tombstones', as_root=True): | |
| return | |
| entries = device.StatDirectory('/data/tombstones', as_root=True) | |
| for entry in entries: | |
| if 'tombstone' in entry['filename']: | |
| yield (entry['filename'], | |
| datetime.datetime.fromtimestamp(entry['st_mtime'])) | |
| except device_errors.CommandFailedError: | |
| logging.exception('Could not retrieve tombstones.') | |
| except device_errors.DeviceUnreachableError: | |
| logging.exception('Device unreachable retrieving tombstones.') | |
| except device_errors.CommandTimeoutError: | |
| logging.exception('Timed out retrieving tombstones.') | |
| def _GetDeviceDateTime(device): | |
| """Determine the date time on the device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| Returns: | |
| A datetime instance. | |
| """ | |
| device_now_string = device.RunShellCommand( | |
| ['date'], check_return=True, env=_TZ_UTC) | |
| return datetime.datetime.strptime( | |
| device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') | |
| def _GetTombstoneData(device, tombstone_file): | |
| """Retrieve the tombstone data from the device | |
| Args: | |
| device: An instance of DeviceUtils. | |
| tombstone_file: the tombstone to retrieve | |
| Returns: | |
| A list of lines | |
| """ | |
| return device.ReadFile( | |
| '/data/tombstones/' + tombstone_file, as_root=True).splitlines() | |
| def _EraseTombstone(device, tombstone_file): | |
| """Deletes a tombstone from the device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| tombstone_file: the tombstone to delete. | |
| """ | |
| return device.RunShellCommand( | |
| ['rm', '/data/tombstones/' + tombstone_file], | |
| as_root=True, check_return=True) | |
| def _ResolveTombstone(args): | |
| tombstone = args[0] | |
| tombstone_symbolizer = args[1] | |
| lines = [] | |
| lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + | |
| ', about this long ago: ' + | |
| (str(tombstone['device_now'] - tombstone['time']) + | |
| ' Device: ' + tombstone['serial'])] | |
| logging.info('\n'.join(lines)) | |
| logging.info('Resolving...') | |
| lines += tombstone_symbolizer.ExtractAndResolveNativeStackTraces( | |
| tombstone['data'], | |
| tombstone['device_abi'], | |
| tombstone['stack']) | |
| return lines | |
| def _ResolveTombstones(jobs, tombstones, tombstone_symbolizer): | |
| """Resolve a list of tombstones. | |
| Args: | |
| jobs: the number of jobs to use with multithread. | |
| tombstones: a list of tombstones. | |
| """ | |
| if not tombstones: | |
| logging.warning('No tombstones to resolve.') | |
| return [] | |
| if len(tombstones) == 1: | |
| data = [_ResolveTombstone([tombstones[0], tombstone_symbolizer])] | |
| else: | |
| pool = ThreadPool(jobs) | |
| data = pool.map( | |
| _ResolveTombstone, | |
| [[tombstone, tombstone_symbolizer] for tombstone in tombstones]) | |
| pool.close() | |
| pool.join() | |
| resolved_tombstones = [] | |
| for tombstone in data: | |
| resolved_tombstones.extend(tombstone) | |
| return resolved_tombstones | |
| def _GetTombstonesForDevice(device, resolve_all_tombstones, | |
| include_stack_symbols, | |
| wipe_tombstones): | |
| """Returns a list of tombstones on a given device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| resolve_all_tombstone: Whether to resolve every tombstone. | |
| include_stack_symbols: Whether to include symbols for stack data. | |
| wipe_tombstones: Whether to wipe tombstones. | |
| """ | |
| ret = [] | |
| all_tombstones = list(_ListTombstones(device)) | |
| if not all_tombstones: | |
| logging.warning('No tombstones.') | |
| return ret | |
| # Sort the tombstones in date order, descending | |
| all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1])) | |
| # Only resolve the most recent unless --all-tombstones given. | |
| tombstones = all_tombstones if resolve_all_tombstones else [all_tombstones[0]] | |
| device_now = _GetDeviceDateTime(device) | |
| try: | |
| for tombstone_file, tombstone_time in tombstones: | |
| ret += [{'serial': str(device), | |
| 'device_abi': device.product_cpu_abi, | |
| 'device_now': device_now, | |
| 'time': tombstone_time, | |
| 'file': tombstone_file, | |
| 'stack': include_stack_symbols, | |
| 'data': _GetTombstoneData(device, tombstone_file)}] | |
| except device_errors.CommandFailedError: | |
| for entry in device.StatDirectory( | |
| '/data/tombstones', as_root=True, timeout=60): | |
| logging.info('%s: %s', str(device), entry) | |
| raise | |
| # Erase all the tombstones if desired. | |
| if wipe_tombstones: | |
| for tombstone_file, _ in all_tombstones: | |
| _EraseTombstone(device, tombstone_file) | |
| return ret | |
| def ClearAllTombstones(device): | |
| """Clear all tombstones in the device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| """ | |
| all_tombstones = list(_ListTombstones(device)) | |
| if not all_tombstones: | |
| logging.warning('No tombstones to clear.') | |
| for tombstone_file, _ in all_tombstones: | |
| _EraseTombstone(device, tombstone_file) | |
| def ResolveTombstones(device, resolve_all_tombstones, include_stack_symbols, | |
| wipe_tombstones, jobs=4, apk_under_test=None, | |
| tombstone_symbolizer=None): | |
| """Resolve tombstones in the device. | |
| Args: | |
| device: An instance of DeviceUtils. | |
| resolve_all_tombstone: Whether to resolve every tombstone. | |
| include_stack_symbols: Whether to include symbols for stack data. | |
| wipe_tombstones: Whether to wipe tombstones. | |
| jobs: Number of jobs to use when processing multiple crash stacks. | |
| Returns: | |
| A list of resolved tombstones. | |
| """ | |
| return _ResolveTombstones(jobs, | |
| _GetTombstonesForDevice(device, | |
| resolve_all_tombstones, | |
| include_stack_symbols, | |
| wipe_tombstones), | |
| (tombstone_symbolizer | |
| or stack_symbolizer.Symbolizer(apk_under_test))) | |
| def main(): | |
| custom_handler = logging.StreamHandler(sys.stdout) | |
| custom_handler.setFormatter(run_tests_helper.CustomFormatter()) | |
| logging.getLogger().addHandler(custom_handler) | |
| logging.getLogger().setLevel(logging.INFO) | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--device', | |
| help='The serial number of the device. If not specified ' | |
| 'will use all devices.') | |
| parser.add_argument('--denylist-file', help='Device denylist JSON file.') | |
| parser.add_argument('-a', '--all-tombstones', action='store_true', | |
| help='Resolve symbols for all tombstones, rather than ' | |
| 'just the most recent.') | |
| parser.add_argument('-s', '--stack', action='store_true', | |
| help='Also include symbols for stack data') | |
| parser.add_argument('-w', '--wipe-tombstones', action='store_true', | |
| help='Erase all tombstones from device after processing') | |
| parser.add_argument('-j', '--jobs', type=int, | |
| default=4, | |
| help='Number of jobs to use when processing multiple ' | |
| 'crash stacks.') | |
| parser.add_argument('--output-directory', | |
| help='Path to the root build directory.') | |
| parser.add_argument('--adb-path', type=os.path.abspath, | |
| help='Path to the adb binary.') | |
| args = parser.parse_args() | |
| if args.output_directory: | |
| constants.SetOutputDirectory(args.output_directory) | |
| devil_chromium.Initialize(output_directory=constants.GetOutDirectory(), | |
| adb_path=args.adb_path) | |
| denylist = (device_denylist.Denylist(args.denylist_file) | |
| if args.denylist_file else None) | |
| if args.device: | |
| devices = [device_utils.DeviceUtils(args.device)] | |
| else: | |
| devices = device_utils.DeviceUtils.HealthyDevices(denylist) | |
| # This must be done serially because strptime can hit a race condition if | |
| # used for the first time in a multithreaded environment. | |
| # http://bugs.python.org/issue7980 | |
| for device in devices: | |
| resolved_tombstones = ResolveTombstones( | |
| device, args.all_tombstones, | |
| args.stack, args.wipe_tombstones, args.jobs) | |
| for line in resolved_tombstones: | |
| logging.info(line) | |
| if __name__ == '__main__': | |
| sys.exit(main()) |