[email protected] | e804425 | 2013-07-11 16:17:56 | [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 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 12 | import argparse |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 13 | import datetime |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 14 | import logging |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 15 | import os |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 16 | import sys |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 17 | |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 18 | from multiprocessing.pool import ThreadPool |
| 19 | |
jbudorick | 0c2a94a | 2015-12-04 14:27:43 | [diff] [blame] | 20 | import devil_chromium |
| 21 | |
jbudorick | 06162944 | 2015-09-03 18:00:57 | [diff] [blame] | 22 | from devil.android import device_blacklist |
| 23 | from devil.android import device_errors |
| 24 | from devil.android import device_utils |
jbudorick | 06162944 | 2015-09-03 18:00:57 | [diff] [blame] | 25 | from devil.utils import run_tests_helper |
agrieve | 32dbc200 | 2016-02-06 02:37:54 | [diff] [blame] | 26 | from pylib import constants |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 27 | from pylib.symbols import stack_symbolizer |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 28 | |
perezju | 5ad3722 | 2016-06-09 16:35:04 | [diff] [blame] | 29 | |
jbudorick | 2b4779f3 | 2015-03-13 15:50:41 | [diff] [blame] | 30 | _TZ_UTC = {'TZ': 'UTC'} |
| 31 | |
perezju | 5ad3722 | 2016-06-09 16:35:04 | [diff] [blame] | 32 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 33 | def _ListTombstones(device): |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 34 | """List the tombstone files on the device. |
| 35 | |
| 36 | Args: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 37 | device: An instance of DeviceUtils. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 38 | |
| 39 | Yields: |
| 40 | Tuples of (tombstone filename, date time of file on device). |
| 41 | """ |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 42 | try: |
jbudorick | c693a8d | 2016-01-15 02:14:26 | [diff] [blame] | 43 | if not device.PathExists('/data/tombstones', as_root=True): |
primiano | b5bcd43a | 2015-11-06 16:48:45 | [diff] [blame] | 44 | return |
perezju | 5ad3722 | 2016-06-09 16:35:04 | [diff] [blame] | 45 | entries = device.StatDirectory('/data/tombstones', as_root=True) |
| 46 | for entry in entries: |
| 47 | if 'tombstone' in entry['filename']: |
| 48 | yield (entry['filename'], |
| 49 | datetime.datetime.fromtimestamp(entry['st_mtime'])) |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 50 | except device_errors.CommandFailedError: |
| 51 | logging.exception('Could not retrieve tombstones.') |
perezju | 46865bb3 | 2017-04-13 12:19:51 | [diff] [blame] | 52 | except device_errors.DeviceUnreachableError: |
| 53 | logging.exception('Device unreachable retrieving tombstones.') |
jbudorick | 6d9a867e | 2015-08-09 18:21:54 | [diff] [blame] | 54 | except device_errors.CommandTimeoutError: |
| 55 | logging.exception('Timed out retrieving tombstones.') |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 56 | |
| 57 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 58 | def _GetDeviceDateTime(device): |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 59 | """Determine the date time on the device. |
| 60 | |
| 61 | Args: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 62 | device: An instance of DeviceUtils. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 63 | |
| 64 | Returns: |
| 65 | A datetime instance. |
| 66 | """ |
jbudorick | 2b4779f3 | 2015-03-13 15:50:41 | [diff] [blame] | 67 | device_now_string = device.RunShellCommand( |
| 68 | ['date'], check_return=True, env=_TZ_UTC) |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 69 | return datetime.datetime.strptime( |
| 70 | device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') |
| 71 | |
| 72 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 73 | def _GetTombstoneData(device, tombstone_file): |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 74 | """Retrieve the tombstone data from the device |
| 75 | |
| 76 | Args: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 77 | device: An instance of DeviceUtils. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 78 | tombstone_file: the tombstone to retrieve |
| 79 | |
| 80 | Returns: |
| 81 | A list of lines |
| 82 | """ |
perezju | 7138148b | 2015-01-26 10:10:00 | [diff] [blame] | 83 | return device.ReadFile( |
| 84 | '/data/tombstones/' + tombstone_file, as_root=True).splitlines() |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 85 | |
| 86 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 87 | def _EraseTombstone(device, tombstone_file): |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 88 | """Deletes a tombstone from the device. |
| 89 | |
| 90 | Args: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 91 | device: An instance of DeviceUtils. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 92 | tombstone_file: the tombstone to delete. |
| 93 | """ |
[email protected] | f0f4b973 | 2014-06-16 18:59:30 | [diff] [blame] | 94 | return device.RunShellCommand( |
jbudorick | 2b4779f3 | 2015-03-13 15:50:41 | [diff] [blame] | 95 | ['rm', '/data/tombstones/' + tombstone_file], |
| 96 | as_root=True, check_return=True) |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 97 | |
| 98 | |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 99 | def _ResolveTombstone(args): |
| 100 | tombstone = args[0] |
| 101 | tombstone_symbolizer = args[1] |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 102 | lines = [] |
| 103 | lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + |
| 104 | ', about this long ago: ' + |
| 105 | (str(tombstone['device_now'] - tombstone['time']) + |
| 106 | ' Device: ' + tombstone['serial'])] |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 107 | logging.info('\n'.join(lines)) |
| 108 | logging.info('Resolving...') |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 109 | lines += tombstone_symbolizer.ExtractAndResolveNativeStackTraces( |
| 110 | tombstone['data'], |
| 111 | tombstone['device_abi'], |
| 112 | tombstone['stack']) |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 113 | return lines |
| 114 | |
| 115 | |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 116 | def _ResolveTombstones(jobs, tombstones, tombstone_symbolizer): |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 117 | """Resolve a list of tombstones. |
| 118 | |
| 119 | Args: |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 120 | jobs: the number of jobs to use with multithread. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 121 | tombstones: a list of tombstones. |
| 122 | """ |
| 123 | if not tombstones: |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 124 | logging.warning('No tombstones to resolve.') |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 125 | return [] |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 126 | tombstone_symbolizer.UnzipAPKIfNecessary() |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 127 | if len(tombstones) == 1: |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 128 | data = [_ResolveTombstone([tombstones[0], tombstone_symbolizer])] |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 129 | else: |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 130 | pool = ThreadPool(jobs) |
| 131 | data = pool.map( |
| 132 | _ResolveTombstone, |
| 133 | [[tombstone, tombstone_symbolizer] for tombstone in tombstones]) |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 134 | resolved_tombstones = [] |
jbudorick | 8c1087b | 2015-05-18 21:06:57 | [diff] [blame] | 135 | for tombstone in data: |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 136 | resolved_tombstones.extend(tombstone) |
| 137 | return resolved_tombstones |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 138 | |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 139 | |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 140 | def _GetTombstonesForDevice(device, resolve_all_tombstones, |
| 141 | include_stack_symbols, |
| 142 | wipe_tombstones): |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 143 | """Returns a list of tombstones on a given device. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 144 | |
| 145 | Args: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 146 | device: An instance of DeviceUtils. |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 147 | resolve_all_tombstone: Whether to resolve every tombstone. |
| 148 | include_stack_symbols: Whether to include symbols for stack data. |
| 149 | wipe_tombstones: Whether to wipe tombstones. |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 150 | """ |
| 151 | ret = [] |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 152 | all_tombstones = list(_ListTombstones(device)) |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 153 | if not all_tombstones: |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 154 | logging.warning('No tombstones.') |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 155 | return ret |
| 156 | |
| 157 | # Sort the tombstones in date order, descending |
| 158 | all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1])) |
| 159 | |
| 160 | # Only resolve the most recent unless --all-tombstones given. |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 161 | tombstones = all_tombstones if resolve_all_tombstones else [all_tombstones[0]] |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 162 | |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 163 | device_now = _GetDeviceDateTime(device) |
jbudorick | b937823 | 2015-03-12 22:50:18 | [diff] [blame] | 164 | try: |
| 165 | for tombstone_file, tombstone_time in tombstones: |
| 166 | ret += [{'serial': str(device), |
| 167 | 'device_abi': device.product_cpu_abi, |
| 168 | 'device_now': device_now, |
| 169 | 'time': tombstone_time, |
| 170 | 'file': tombstone_file, |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 171 | 'stack': include_stack_symbols, |
jbudorick | b937823 | 2015-03-12 22:50:18 | [diff] [blame] | 172 | 'data': _GetTombstoneData(device, tombstone_file)}] |
| 173 | except device_errors.CommandFailedError: |
perezju | 5ad3722 | 2016-06-09 16:35:04 | [diff] [blame] | 174 | for entry in device.StatDirectory( |
| 175 | '/data/tombstones', as_root=True, timeout=60): |
| 176 | logging.info('%s: %s', str(device), entry) |
jbudorick | b937823 | 2015-03-12 22:50:18 | [diff] [blame] | 177 | raise |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 178 | |
| 179 | # Erase all the tombstones if desired. |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 180 | if wipe_tombstones: |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 181 | for tombstone_file, _ in all_tombstones: |
[email protected] | 044d79b | 2014-04-10 19:37:30 | [diff] [blame] | 182 | _EraseTombstone(device, tombstone_file) |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 183 | |
| 184 | return ret |
| 185 | |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 186 | |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 187 | def ClearAllTombstones(device): |
| 188 | """Clear all tombstones in the device. |
| 189 | |
| 190 | Args: |
| 191 | device: An instance of DeviceUtils. |
| 192 | """ |
| 193 | all_tombstones = list(_ListTombstones(device)) |
| 194 | if not all_tombstones: |
| 195 | logging.warning('No tombstones to clear.') |
| 196 | |
| 197 | for tombstone_file, _ in all_tombstones: |
| 198 | _EraseTombstone(device, tombstone_file) |
| 199 | |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 200 | |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 201 | def ResolveTombstones(device, resolve_all_tombstones, include_stack_symbols, |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 202 | wipe_tombstones, jobs=4, |
| 203 | apk_under_test=None, enable_relocation_packing=None, |
| 204 | tombstone_symbolizer=None): |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 205 | """Resolve tombstones in the device. |
| 206 | |
| 207 | Args: |
| 208 | device: An instance of DeviceUtils. |
| 209 | resolve_all_tombstone: Whether to resolve every tombstone. |
| 210 | include_stack_symbols: Whether to include symbols for stack data. |
| 211 | wipe_tombstones: Whether to wipe tombstones. |
| 212 | jobs: Number of jobs to use when processing multiple crash stacks. |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 213 | |
| 214 | Returns: |
| 215 | A list of resolved tombstones. |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 216 | """ |
| 217 | return _ResolveTombstones(jobs, |
| 218 | _GetTombstonesForDevice(device, |
| 219 | resolve_all_tombstones, |
| 220 | include_stack_symbols, |
hzl | 55fc2af | 2017-07-27 08:24:50 | [diff] [blame] | 221 | wipe_tombstones), |
| 222 | (tombstone_symbolizer |
| 223 | or stack_symbolizer.Symbolizer( |
| 224 | apk_under_test, |
| 225 | enable_relocation_packing))) |
[email protected] | 1d10a306 | 2014-07-28 15:35:18 | [diff] [blame] | 226 | |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 227 | |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 228 | def main(): |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 229 | custom_handler = logging.StreamHandler(sys.stdout) |
| 230 | custom_handler.setFormatter(run_tests_helper.CustomFormatter()) |
| 231 | logging.getLogger().addHandler(custom_handler) |
| 232 | logging.getLogger().setLevel(logging.INFO) |
| 233 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 234 | parser = argparse.ArgumentParser() |
| 235 | parser.add_argument('--device', |
| 236 | help='The serial number of the device. If not specified ' |
| 237 | 'will use all devices.') |
| 238 | parser.add_argument('--blacklist-file', help='Device blacklist JSON file.') |
| 239 | parser.add_argument('-a', '--all-tombstones', action='store_true', |
| 240 | help='Resolve symbols for all tombstones, rather than ' |
| 241 | 'just the most recent.') |
| 242 | parser.add_argument('-s', '--stack', action='store_true', |
| 243 | help='Also include symbols for stack data') |
| 244 | parser.add_argument('-w', '--wipe-tombstones', action='store_true', |
| 245 | help='Erase all tombstones from device after processing') |
| 246 | parser.add_argument('-j', '--jobs', type=int, |
| 247 | default=4, |
| 248 | help='Number of jobs to use when processing multiple ' |
| 249 | 'crash stacks.') |
| 250 | parser.add_argument('--output-directory', |
| 251 | help='Path to the root build directory.') |
| 252 | parser.add_argument('--adb-path', type=os.path.abspath, |
| 253 | help='Path to the adb binary.') |
| 254 | args = parser.parse_args() |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 255 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 256 | devil_chromium.Initialize(adb_path=args.adb_path) |
jbudorick | d28554a | 2016-01-11 16:22:59 | [diff] [blame] | 257 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 258 | blacklist = (device_blacklist.Blacklist(args.blacklist_file) |
| 259 | if args.blacklist_file |
jbudorick | a583ba3 | 2015-09-11 17:23:19 | [diff] [blame] | 260 | else None) |
jbudorick | dde688fb | 2015-08-27 03:00:17 | [diff] [blame] | 261 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 262 | if args.output_directory: |
| 263 | constants.SetOutputDirectory(args.output_directory) |
agrieve | 243a025a | 2016-02-12 19:11:02 | [diff] [blame] | 264 | # Do an up-front test that the output directory is known. |
| 265 | constants.CheckOutputDirectory() |
agrieve | 32dbc200 | 2016-02-06 02:37:54 | [diff] [blame] | 266 | |
jbudorick | 3e0f4b2 | 2016-05-28 04:49:47 | [diff] [blame] | 267 | if args.device: |
| 268 | devices = [device_utils.DeviceUtils(args.device)] |
[email protected] | 366198e0 | 2013-07-12 21:11:18 | [diff] [blame] | 269 | else: |
jbudorick | dde688fb | 2015-08-27 03:00:17 | [diff] [blame] | 270 | devices = device_utils.DeviceUtils.HealthyDevices(blacklist) |
[email protected] | 366198e0 | 2013-07-12 21:11:18 | [diff] [blame] | 271 | |
jbudorick | 8e79ceb | 2015-03-19 16:28:11 | [diff] [blame] | 272 | # This must be done serially because strptime can hit a race condition if |
| 273 | # used for the first time in a multithreaded environment. |
| 274 | # http://bugs.python.org/issue7980 |
jbudorick | 119e457 | 2015-04-24 17:20:03 | [diff] [blame] | 275 | for device in devices: |
hzl | 02ff9ee | 2016-08-15 23:22:57 | [diff] [blame] | 276 | resolved_tombstones = ResolveTombstones( |
| 277 | device, args.all_tombstones, |
| 278 | args.stack, args.wipe_tombstones, args.jobs) |
| 279 | for line in resolved_tombstones: |
| 280 | logging.info(line) |
jbudorick | 119e457 | 2015-04-24 17:20:03 | [diff] [blame] | 281 | |
hzl | 15465f2 | 2016-12-14 23:14:46 | [diff] [blame] | 282 | |
[email protected] | e804425 | 2013-07-11 16:17:56 | [diff] [blame] | 283 | if __name__ == '__main__': |
| 284 | sys.exit(main()) |