James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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 |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 13 | import itertools |
| 14 | import logging |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 15 | import multiprocessing |
| 16 | import os |
| 17 | import re |
| 18 | import subprocess |
| 19 | import sys |
| 20 | import optparse |
| 21 | |
James Robinson | 0fae000 | 2015-05-05 16:31:51 -0700 | [diff] [blame] | 22 | from pylib.device import adb_wrapper |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 23 | from pylib.device import device_errors |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 24 | from pylib.device import device_utils |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 25 | from pylib.utils import run_tests_helper |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 26 | |
| 27 | |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 28 | _TZ_UTC = {'TZ': 'UTC'} |
| 29 | |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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 | """ |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 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.') |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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 | """ |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 62 | device_now_string = device.RunShellCommand( |
| 63 | ['date'], check_return=True, env=_TZ_UTC) |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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 | """ |
Elliot Glaysher | eae4929 | 2015-01-28 10:47:32 -0800 | [diff] [blame] | 78 | return device.ReadFile( |
| 79 | '/data/tombstones/' + tombstone_file, as_root=True).splitlines() |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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( |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 90 | ['rm', '/data/tombstones/' + tombstone_file], |
| 91 | as_root=True, check_return=True) |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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'])] |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 142 | logging.info('\n'.join(lines)) |
| 143 | logging.info('Resolving...') |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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: |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 157 | logging.warning('No tombstones to resolve.') |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 158 | return |
| 159 | if len(tombstones) == 1: |
Viet-Trung Luu | 235cf3d | 2015-06-11 10:01:25 -0700 | [diff] [blame] | 160 | data = [_ResolveTombstone(tombstones[0])] |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 161 | else: |
| 162 | pool = multiprocessing.Pool(processes=jobs) |
| 163 | data = pool.map(_ResolveTombstone, tombstones) |
Viet-Trung Luu | 235cf3d | 2015-06-11 10:01:25 -0700 | [diff] [blame] | 164 | for tombstone in data: |
| 165 | for line in tombstone: |
| 166 | logging.info(line) |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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: |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 179 | logging.warning('No tombstones.') |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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) |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 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): |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 202 | logging.info('%s: %s', str(device), line) |
Dave Moore | 0ae79f4 | 2015-03-17 12:56:46 -0700 | [diff] [blame] | 203 | raise |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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(): |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 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 | |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 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: |
James Robinson | 0fae000 | 2015-05-05 16:31:51 -0700 | [diff] [blame] | 237 | devices = [device_utils.DeviceUtils(options.device)] |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 238 | else: |
James Robinson | 0fae000 | 2015-05-05 16:31:51 -0700 | [diff] [blame] | 239 | devices = device_utils.DeviceUtils.HealthyDevices() |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 240 | |
Alhaad Gokhale | 4f51307 | 2015-03-24 10:49:34 -0700 | [diff] [blame] | 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 |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 244 | tombstones = [] |
James Robinson | 0fae000 | 2015-05-05 16:31:51 -0700 | [diff] [blame] | 245 | for device in devices: |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 246 | tombstones += _GetTombstonesForDevice(device, options) |
| 247 | |
| 248 | _ResolveTombstones(options.jobs, tombstones) |
| 249 | |
James Robinson | 0fae000 | 2015-05-05 16:31:51 -0700 | [diff] [blame] | 250 | |
James Robinson | 646469d | 2014-10-03 15:33:28 -0700 | [diff] [blame] | 251 | if __name__ == '__main__': |
| 252 | sys.exit(main()) |