Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / build / android / tombstones.py
blobdbfe3f76c2887270eeeb90fd02ea31fead5b574c
1 #!/usr/bin/env python
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.
7 # Find the most recent tombstone file(s) on all connected devices
8 # and prints their stacks.
10 # Assumes tombstone file was created with current symbols.
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
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
28 _TZ_UTC = {'TZ': 'UTC'}
30 def _ListTombstones(device):
31 """List the tombstone files on the device.
33 Args:
34 device: An instance of DeviceUtils.
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.')
53 def _GetDeviceDateTime(device):
54 """Determine the date time on the device.
56 Args:
57 device: An instance of DeviceUtils.
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')
68 def _GetTombstoneData(device, tombstone_file):
69 """Retrieve the tombstone data from the device
71 Args:
72 device: An instance of DeviceUtils.
73 tombstone_file: the tombstone to retrieve
75 Returns:
76 A list of lines
77 """
78 return device.ReadFile(
79 '/data/tombstones/' + tombstone_file, as_root=True).splitlines()
82 def _EraseTombstone(device, tombstone_file):
83 """Deletes a tombstone from the device.
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)
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)
103 def _ResolveSymbols(tombstone_data, include_stack, device_abi):
104 """Run the stack tool for given tombstone input.
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.
111 Yields:
112 A string for each line of resolved stack output.
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
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
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
149 def _ResolveTombstones(jobs, tombstones):
150 """Resolve a list of tombstones.
152 Args:
153 jobs: the number of jobs to use with multiprocess.
154 tombstones: a list of tombstones.
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)
169 def _GetTombstonesForDevice(device, options):
170 """Returns a list of tombstones on a given device.
172 Args:
173 device: An instance of DeviceUtils.
174 options: command line arguments from OptParse
176 ret = []
177 all_tombstones = list(_ListTombstones(device))
178 if not all_tombstones:
179 logging.warning('No tombstones.')
180 return ret
182 # Sort the tombstones in date order, descending
183 all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1]))
185 # Only resolve the most recent unless --all-tombstones given.
186 tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]]
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
205 # Erase all the tombstones if desired.
206 if options.wipe_tombstones:
207 for tombstone_file, _ in all_tombstones:
208 _EraseTombstone(device, tombstone_file)
210 return ret
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)
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()
236 if options.device:
237 devices = [device_utils.DeviceUtils(options.device)]
238 else:
239 devices = device_utils.DeviceUtils.HealthyDevices()
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)
248 _ResolveTombstones(options.jobs, tombstones)
251 if __name__ == '__main__':
252 sys.exit(main())