| OLD | NEW | 
| (Empty) |  | 
 |    1 #!/usr/bin/env python | 
 |    2 # | 
 |    3 # Copyright 2013 The Chromium Authors. All rights reserved. | 
 |    4 # Use of this source code is governed by a BSD-style license that can be | 
 |    5 # found in the LICENSE file. | 
 |    6 # | 
 |    7 # Find the most recent tombstone file(s) on all connected devices | 
 |    8 # and prints their stacks. | 
 |    9 # | 
 |   10 # Assumes tombstone file was created with current symbols. | 
 |   11  | 
 |   12 import datetime | 
 |   13 import itertools | 
 |   14 import logging | 
 |   15 import multiprocessing | 
 |   16 import os | 
 |   17 import re | 
 |   18 import subprocess | 
 |   19 import sys | 
 |   20 import optparse | 
 |   21  | 
 |   22 from pylib.device import adb_wrapper | 
 |   23 from pylib.device import device_errors | 
 |   24 from pylib.device import device_utils | 
 |   25 from pylib.utils import run_tests_helper | 
 |   26  | 
 |   27  | 
 |   28 _TZ_UTC = {'TZ': 'UTC'} | 
 |   29  | 
 |   30 def _ListTombstones(device): | 
 |   31   """List the tombstone files on the device. | 
 |   32  | 
 |   33   Args: | 
 |   34     device: An instance of DeviceUtils. | 
 |   35  | 
 |   36   Yields: | 
 |   37     Tuples of (tombstone filename, date time of file on device). | 
 |   38   """ | 
 |   39   try: | 
 |   40     lines = device.RunShellCommand( | 
 |   41         ['ls', '-a', '-l', '/data/tombstones'], | 
 |   42         as_root=True, check_return=True, env=_TZ_UTC, timeout=60) | 
 |   43     for line in lines: | 
 |   44       if 'tombstone' in line and not 'No such file or directory' in line: | 
 |   45         details = line.split() | 
 |   46         t = datetime.datetime.strptime(details[-3] + ' ' + details[-2], | 
 |   47                                        '%Y-%m-%d %H:%M') | 
 |   48         yield details[-1], t | 
 |   49   except device_errors.CommandFailedError: | 
 |   50     logging.exception('Could not retrieve tombstones.') | 
 |   51  | 
 |   52  | 
 |   53 def _GetDeviceDateTime(device): | 
 |   54   """Determine the date time on the device. | 
 |   55  | 
 |   56   Args: | 
 |   57     device: An instance of DeviceUtils. | 
 |   58  | 
 |   59   Returns: | 
 |   60     A datetime instance. | 
 |   61   """ | 
 |   62   device_now_string = device.RunShellCommand( | 
 |   63       ['date'], check_return=True, env=_TZ_UTC) | 
 |   64   return datetime.datetime.strptime( | 
 |   65       device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') | 
 |   66  | 
 |   67  | 
 |   68 def _GetTombstoneData(device, tombstone_file): | 
 |   69   """Retrieve the tombstone data from the device | 
 |   70  | 
 |   71   Args: | 
 |   72     device: An instance of DeviceUtils. | 
 |   73     tombstone_file: the tombstone to retrieve | 
 |   74  | 
 |   75   Returns: | 
 |   76     A list of lines | 
 |   77   """ | 
 |   78   return device.ReadFile( | 
 |   79       '/data/tombstones/' + tombstone_file, as_root=True).splitlines() | 
 |   80  | 
 |   81  | 
 |   82 def _EraseTombstone(device, tombstone_file): | 
 |   83   """Deletes a tombstone from the device. | 
 |   84  | 
 |   85   Args: | 
 |   86     device: An instance of DeviceUtils. | 
 |   87     tombstone_file: the tombstone to delete. | 
 |   88   """ | 
 |   89   return device.RunShellCommand( | 
 |   90       ['rm', '/data/tombstones/' + tombstone_file], | 
 |   91       as_root=True, check_return=True) | 
 |   92  | 
 |   93  | 
 |   94 def _DeviceAbiToArch(device_abi): | 
 |   95   # The order of this list is significant to find the more specific match (e.g., | 
 |   96   # arm64) before the less specific (e.g., arm). | 
 |   97   arches = ['arm64', 'arm', 'x86_64', 'x86_64', 'x86', 'mips'] | 
 |   98   for arch in arches: | 
 |   99     if arch in device_abi: | 
 |  100       return arch | 
 |  101   raise RuntimeError('Unknown device ABI: %s' % device_abi) | 
 |  102  | 
 |  103 def _ResolveSymbols(tombstone_data, include_stack, device_abi): | 
 |  104   """Run the stack tool for given tombstone input. | 
 |  105  | 
 |  106   Args: | 
 |  107     tombstone_data: a list of strings of tombstone data. | 
 |  108     include_stack: boolean whether to include stack data in output. | 
 |  109     device_abi: the default ABI of the device which generated the tombstone. | 
 |  110  | 
 |  111   Yields: | 
 |  112     A string for each line of resolved stack output. | 
 |  113   """ | 
 |  114   # Check if the tombstone data has an ABI listed, if so use this in preference | 
 |  115   # to the device's default ABI. | 
 |  116   for line in tombstone_data: | 
 |  117     found_abi = re.search('ABI: \'(.+?)\'', line) | 
 |  118     if found_abi: | 
 |  119       device_abi = found_abi.group(1) | 
 |  120   arch = _DeviceAbiToArch(device_abi) | 
 |  121   if not arch: | 
 |  122     return | 
 |  123  | 
 |  124   stack_tool = os.path.join(os.path.dirname(__file__), '..', '..', | 
 |  125                             'third_party', 'android_platform', 'development', | 
 |  126                             'scripts', 'stack') | 
 |  127   proc = subprocess.Popen([stack_tool, '--arch', arch], stdin=subprocess.PIPE, | 
 |  128                           stdout=subprocess.PIPE) | 
 |  129   output = proc.communicate(input='\n'.join(tombstone_data))[0] | 
 |  130   for line in output.split('\n'): | 
 |  131     if not include_stack and 'Stack Data:' in line: | 
 |  132       break | 
 |  133     yield line | 
 |  134  | 
 |  135  | 
 |  136 def _ResolveTombstone(tombstone): | 
 |  137   lines = [] | 
 |  138   lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + | 
 |  139             ', about this long ago: ' + | 
 |  140             (str(tombstone['device_now'] - tombstone['time']) + | 
 |  141             ' Device: ' + tombstone['serial'])] | 
 |  142   logging.info('\n'.join(lines)) | 
 |  143   logging.info('Resolving...') | 
 |  144   lines += _ResolveSymbols(tombstone['data'], tombstone['stack'], | 
 |  145                            tombstone['device_abi']) | 
 |  146   return lines | 
 |  147  | 
 |  148  | 
 |  149 def _ResolveTombstones(jobs, tombstones): | 
 |  150   """Resolve a list of tombstones. | 
 |  151  | 
 |  152   Args: | 
 |  153     jobs: the number of jobs to use with multiprocess. | 
 |  154     tombstones: a list of tombstones. | 
 |  155   """ | 
 |  156   if not tombstones: | 
 |  157     logging.warning('No tombstones to resolve.') | 
 |  158     return | 
 |  159   if len(tombstones) == 1: | 
 |  160     data = [_ResolveTombstone(tombstones[0])] | 
 |  161   else: | 
 |  162     pool = multiprocessing.Pool(processes=jobs) | 
 |  163     data = pool.map(_ResolveTombstone, tombstones) | 
 |  164   for tombstone in data: | 
 |  165     for line in tombstone: | 
 |  166       logging.info(line) | 
 |  167  | 
 |  168  | 
 |  169 def _GetTombstonesForDevice(device, options): | 
 |  170   """Returns a list of tombstones on a given device. | 
 |  171  | 
 |  172   Args: | 
 |  173     device: An instance of DeviceUtils. | 
 |  174     options: command line arguments from OptParse | 
 |  175   """ | 
 |  176   ret = [] | 
 |  177   all_tombstones = list(_ListTombstones(device)) | 
 |  178   if not all_tombstones: | 
 |  179     logging.warning('No tombstones.') | 
 |  180     return ret | 
 |  181  | 
 |  182   # Sort the tombstones in date order, descending | 
 |  183   all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1])) | 
 |  184  | 
 |  185   # Only resolve the most recent unless --all-tombstones given. | 
 |  186   tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]] | 
 |  187  | 
 |  188   device_now = _GetDeviceDateTime(device) | 
 |  189   try: | 
 |  190     for tombstone_file, tombstone_time in tombstones: | 
 |  191       ret += [{'serial': str(device), | 
 |  192                'device_abi': device.product_cpu_abi, | 
 |  193                'device_now': device_now, | 
 |  194                'time': tombstone_time, | 
 |  195                'file': tombstone_file, | 
 |  196                'stack': options.stack, | 
 |  197                'data': _GetTombstoneData(device, tombstone_file)}] | 
 |  198   except device_errors.CommandFailedError: | 
 |  199     for line in device.RunShellCommand( | 
 |  200         ['ls', '-a', '-l', '/data/tombstones'], | 
 |  201         as_root=True, check_return=True, env=_TZ_UTC, timeout=60): | 
 |  202       logging.info('%s: %s', str(device), line) | 
 |  203     raise | 
 |  204  | 
 |  205   # Erase all the tombstones if desired. | 
 |  206   if options.wipe_tombstones: | 
 |  207     for tombstone_file, _ in all_tombstones: | 
 |  208       _EraseTombstone(device, tombstone_file) | 
 |  209  | 
 |  210   return ret | 
 |  211  | 
 |  212  | 
 |  213 def main(): | 
 |  214   custom_handler = logging.StreamHandler(sys.stdout) | 
 |  215   custom_handler.setFormatter(run_tests_helper.CustomFormatter()) | 
 |  216   logging.getLogger().addHandler(custom_handler) | 
 |  217   logging.getLogger().setLevel(logging.INFO) | 
 |  218  | 
 |  219   parser = optparse.OptionParser() | 
 |  220   parser.add_option('--device', | 
 |  221                     help='The serial number of the device. If not specified ' | 
 |  222                          'will use all devices.') | 
 |  223   parser.add_option('-a', '--all-tombstones', action='store_true', | 
 |  224                     help="""Resolve symbols for all tombstones, rather than just | 
 |  225                          the most recent""") | 
 |  226   parser.add_option('-s', '--stack', action='store_true', | 
 |  227                     help='Also include symbols for stack data') | 
 |  228   parser.add_option('-w', '--wipe-tombstones', action='store_true', | 
 |  229                     help='Erase all tombstones from device after processing') | 
 |  230   parser.add_option('-j', '--jobs', type='int', | 
 |  231                     default=4, | 
 |  232                     help='Number of jobs to use when processing multiple ' | 
 |  233                          'crash stacks.') | 
 |  234   options, _ = parser.parse_args() | 
 |  235  | 
 |  236   if options.device: | 
 |  237     devices = [device_utils.DeviceUtils(options.device)] | 
 |  238   else: | 
 |  239     devices = device_utils.DeviceUtils.HealthyDevices() | 
 |  240  | 
 |  241   # This must be done serially because strptime can hit a race condition if | 
 |  242   # used for the first time in a multithreaded environment. | 
 |  243   # http://bugs.python.org/issue7980 | 
 |  244   tombstones = [] | 
 |  245   for device in devices: | 
 |  246     tombstones += _GetTombstonesForDevice(device, options) | 
 |  247  | 
 |  248   _ResolveTombstones(options.jobs, tombstones) | 
 |  249  | 
 |  250  | 
 |  251 if __name__ == '__main__': | 
 |  252   sys.exit(main()) | 
| OLD | NEW |