blob: 082e7c1c783d7ee1468e0dca9ee18320c39d47da [file] [log] [blame]
#!/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())