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