OLD | NEW |
| (Empty) |
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 | |
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 | |
21 | |
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 | |
26 | |
27 | |
28 _TZ_UTC = {'TZ': 'UTC'} | |
29 | |
30 def _ListTombstones(device): | |
31 """List the tombstone files on the device. | |
32 | |
33 Args: | |
34 device: An instance of DeviceUtils. | |
35 | |
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.') | |
51 | |
52 | |
53 def _GetDeviceDateTime(device): | |
54 """Determine the date time on the device. | |
55 | |
56 Args: | |
57 device: An instance of DeviceUtils. | |
58 | |
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') | |
66 | |
67 | |
68 def _GetTombstoneData(device, tombstone_file): | |
69 """Retrieve the tombstone data from the device | |
70 | |
71 Args: | |
72 device: An instance of DeviceUtils. | |
73 tombstone_file: the tombstone to retrieve | |
74 | |
75 Returns: | |
76 A list of lines | |
77 """ | |
78 return device.ReadFile( | |
79 '/data/tombstones/' + tombstone_file, as_root=True).splitlines() | |
80 | |
81 | |
82 def _EraseTombstone(device, tombstone_file): | |
83 """Deletes a tombstone from the device. | |
84 | |
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) | |
92 | |
93 | |
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) | |
102 | |
103 def _ResolveSymbols(tombstone_data, include_stack, device_abi): | |
104 """Run the stack tool for given tombstone input. | |
105 | |
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. | |
110 | |
111 Yields: | |
112 A string for each line of resolved stack output. | |
113 """ | |
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 | |
123 | |
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 | |
134 | |
135 | |
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 | |
147 | |
148 | |
149 def _ResolveTombstones(jobs, tombstones): | |
150 """Resolve a list of tombstones. | |
151 | |
152 Args: | |
153 jobs: the number of jobs to use with multiprocess. | |
154 tombstones: a list of tombstones. | |
155 """ | |
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) | |
167 | |
168 | |
169 def _GetTombstonesForDevice(device, options): | |
170 """Returns a list of tombstones on a given device. | |
171 | |
172 Args: | |
173 device: An instance of DeviceUtils. | |
174 options: command line arguments from OptParse | |
175 """ | |
176 ret = [] | |
177 all_tombstones = list(_ListTombstones(device)) | |
178 if not all_tombstones: | |
179 logging.warning('No tombstones.') | |
180 return ret | |
181 | |
182 # Sort the tombstones in date order, descending | |
183 all_tombstones.sort(cmp=lambda a, b: cmp(b[1], a[1])) | |
184 | |
185 # Only resolve the most recent unless --all-tombstones given. | |
186 tombstones = all_tombstones if options.all_tombstones else [all_tombstones[0]] | |
187 | |
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 | |
204 | |
205 # Erase all the tombstones if desired. | |
206 if options.wipe_tombstones: | |
207 for tombstone_file, _ in all_tombstones: | |
208 _EraseTombstone(device, tombstone_file) | |
209 | |
210 return ret | |
211 | |
212 | |
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) | |
218 | |
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() | |
235 | |
236 if options.device: | |
237 devices = [device_utils.DeviceUtils(options.device)] | |
238 else: | |
239 devices = device_utils.DeviceUtils.HealthyDevices() | |
240 | |
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) | |
247 | |
248 _ResolveTombstones(options.jobs, tombstones) | |
249 | |
250 | |
251 if __name__ == '__main__': | |
252 sys.exit(main()) | |
OLD | NEW |