Roll src/third_party/WebKit f36d5e0:68b67cd (svn 193299:193303)
[chromium-blink-merge.git] / build / android / tombstones.py
blob1a958ac28673c9d9a7ac83adc8c64fa6f75b9f5f
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 import android_commands
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 d in data:
165 logging.info(d)
168 def _GetTombstonesForDevice(device, options):
169 """Returns a list of tombstones on a given device.
171 Args:
172 device: An instance of DeviceUtils.
173 options: command line arguments from OptParse
175 ret = []
176 all_tombstones = list(_ListTombstones(device))
177 if not all_tombstones:
178 logging.warning('No tombstones.')
179 return ret
181 # Sort the tombstones in date order, descending
182 all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1]))
184 # Only resolve the most recent unless --all-tombstones given.
185 tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]]
187 device_now = _GetDeviceDateTime(device)
188 try:
189 for tombstone_file, tombstone_time in tombstones:
190 ret += [{'serial': str(device),
191 'device_abi': device.product_cpu_abi,
192 'device_now': device_now,
193 'time': tombstone_time,
194 'file': tombstone_file,
195 'stack': options.stack,
196 'data': _GetTombstoneData(device, tombstone_file)}]
197 except device_errors.CommandFailedError:
198 for line in device.RunShellCommand(
199 ['ls', '-a', '-l', '/data/tombstones'],
200 as_root=True, check_return=True, env=_TZ_UTC, timeout=60):
201 logging.info('%s: %s', str(device), line)
202 raise
204 # Erase all the tombstones if desired.
205 if options.wipe_tombstones:
206 for tombstone_file, _ in all_tombstones:
207 _EraseTombstone(device, tombstone_file)
209 return ret
212 def main():
213 custom_handler = logging.StreamHandler(sys.stdout)
214 custom_handler.setFormatter(run_tests_helper.CustomFormatter())
215 logging.getLogger().addHandler(custom_handler)
216 logging.getLogger().setLevel(logging.INFO)
218 parser = optparse.OptionParser()
219 parser.add_option('--device',
220 help='The serial number of the device. If not specified '
221 'will use all devices.')
222 parser.add_option('-a', '--all-tombstones', action='store_true',
223 help="""Resolve symbols for all tombstones, rather than just
224 the most recent""")
225 parser.add_option('-s', '--stack', action='store_true',
226 help='Also include symbols for stack data')
227 parser.add_option('-w', '--wipe-tombstones', action='store_true',
228 help='Erase all tombstones from device after processing')
229 parser.add_option('-j', '--jobs', type='int',
230 default=4,
231 help='Number of jobs to use when processing multiple '
232 'crash stacks.')
233 options, _ = parser.parse_args()
235 if options.device:
236 devices = [options.device]
237 else:
238 devices = android_commands.GetAttachedDevices()
240 # This must be done serially because strptime can hit a race condition if
241 # used for the first time in a multithreaded environment.
242 # http://bugs.python.org/issue7980
243 tombstones = []
244 for device_serial in devices:
245 device = device_utils.DeviceUtils(device_serial)
246 tombstones += _GetTombstonesForDevice(device, options)
248 _ResolveTombstones(options.jobs, tombstones)
250 if __name__ == '__main__':
251 sys.exit(main())