Index: build/android/tombstones.py |
diff --git a/build/android/tombstones.py b/build/android/tombstones.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..a279c63d3857bb0639e9fa1cfd1861d86f92ddeb |
--- /dev/null |
+++ b/build/android/tombstones.py |
@@ -0,0 +1,191 @@ |
+#!/usr/bin/env python |
+# |
+# Copyright (c) 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 datetime |
+import logging |
+import multiprocessing |
+import os |
+import subprocess |
+import sys |
+import optparse |
+ |
+from pylib import android_commands |
+ |
+ |
+def _ListTombstones(adb): |
+ """List the tombstone files on the device. |
+ |
+ Args: |
+ adb: An instance of AndroidCommands. |
+ |
+ Yields: |
+ Tuples of (tombstone filename, date time of file on device). |
+ """ |
+ lines = adb.RunShellCommand('TZ=UTC su -c ls -a -l /data/tombstones') |
+ for line in lines: |
+ if 'tombstone' in line and not 'No such file or directory' in line: |
+ details = line.split() |
+ t = datetime.datetime.strptime(details[-3] + ' ' + details[-2], |
+ '%Y-%m-%d %H:%M') |
+ yield details[-1], t |
+ |
+ |
+def _GetDeviceDateTime(adb): |
+ """Determine the date time on the device. |
+ |
+ Args: |
+ adb: An instance of AndroidCommands. |
+ |
+ Returns: |
+ A datetime instance. |
+ """ |
+ device_now_string = adb.RunShellCommand('TZ=UTC date') |
+ return datetime.datetime.strptime( |
+ device_now_string[0], '%a %b %d %H:%M:%S %Z %Y') |
+ |
+ |
+def _GetTombstoneData(adb, tombstone_file): |
+ """Retrieve the tombstone data from the device |
+ |
+ Args: |
+ tombstone_file: the tombstone to retrieve |
+ |
+ Returns: |
+ A list of lines |
+ """ |
+ return adb.GetProtectedFileContents('/data/tombstones/' + tombstone_file) |
+ |
+ |
+def _EraseTombstone(adb, tombstone_file): |
+ """Deletes a tombstone from the device. |
+ |
+ Args: |
+ tombstone_file: the tombstone to delete. |
+ """ |
+ return adb.RunShellCommand('su -c rm /data/tombstones/' + tombstone_file) |
+ |
+ |
+def _ResolveSymbols(tombstone_data, include_stack): |
+ """Run the stack tool for given tombstone input. |
+ |
+ Args: |
+ tombstone_data: a list of strings of tombstone data. |
+ include_stack: boolean whether to include stack data in output. |
+ |
+ Yields: |
+ A string for each line of resolved stack output. |
+ """ |
+ stack_tool = os.path.join(os.path.dirname(__file__), '..', '..', |
+ 'third_party', 'android_platform', 'development', |
+ 'scripts', 'stack') |
+ proc = subprocess.Popen(stack_tool, stdin=subprocess.PIPE, |
+ stdout=subprocess.PIPE) |
+ output = proc.communicate(input='\n'.join(tombstone_data))[0] |
+ for line in output.split('\n'): |
+ if not include_stack and 'Stack Data:' in line: |
+ break |
+ yield line |
+ |
+ |
+def _ResolveTombstone(tombstone): |
+ lines = [] |
+ lines += [tombstone['file'] + ' created on ' + str(tombstone['time']) + |
+ ', about this long ago: ' + |
+ (str(tombstone['device_now'] - tombstone['time']) + |
+ ' Device: ' + tombstone['serial'])] |
+ print '\n'.join(lines) |
+ print 'Resolving...' |
+ lines += _ResolveSymbols(tombstone['data'], tombstone['stack']) |
+ return lines |
+ |
+ |
+def _ResolveTombstones(jobs, tombstones): |
+ """Resolve a list of tombstones. |
+ |
+ Args: |
+ jobs: the number of jobs to use with multiprocess. |
+ tombstones: a list of tombstones. |
+ """ |
+ if not tombstones: |
+ print 'No device attached? Or no tombstones?' |
+ return |
+ if len(tombstones) == 1: |
+ data = _ResolveTombstone(tombstones[0]) |
+ else: |
+ pool = multiprocessing.Pool(processes=jobs) |
+ data = pool.map(_ResolveTombstone, tombstones) |
+ data = ['\n'.join(d) for d in data] |
+ print '\n'.join(data) |
+ |
+ |
+def _GetTombstonesForDevice(adb, options): |
+ """Returns a list of tombstones on a given adb connection. |
+ |
+ Args: |
+ adb: An instance of Androidcommands. |
+ options: command line arguments from OptParse |
+ """ |
+ ret = [] |
+ all_tombstones = list(_ListTombstones(adb)) |
+ if not all_tombstones: |
+ print 'No device attached? Or 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 options.all_tombstones else [all_tombstones[0]] |
+ |
+ device_now = _GetDeviceDateTime(adb) |
+ for tombstone_file, tombstone_time in tombstones: |
+ ret += [{'serial': adb.Adb().GetSerialNumber(), |
+ 'device_now': device_now, |
+ 'time': tombstone_time, |
+ 'file': tombstone_file, |
+ 'stack': options.stack, |
+ 'data': _GetTombstoneData(adb, tombstone_file)}] |
+ |
+ # Erase all the tombstones if desired. |
+ if options.wipe_tombstones: |
+ for tombstone_file, _ in all_tombstones: |
+ _EraseTombstone(adb, tombstone_file) |
+ |
+ return ret |
+ |
+def main(): |
+ parser = optparse.OptionParser() |
+ parser.add_option('-a', '--all-tombstones', action='store_true', |
+ dest='all_tombstones', default=False, |
+ help="""Resolve symbols for all tombstones, rather than just |
+ the most recent""") |
+ parser.add_option('-s', '--stack', action='store_true', |
+ dest='stack', default=False, |
+ help='Also include symbols for stack data') |
+ parser.add_option('-w', '--wipe-tombstones', action='store_true', |
+ dest='wipe_tombstones', default=False, |
+ help='Erase all tombstones from device after processing') |
+ parser.add_option('-j', '--jobs', type='int', |
+ default=4, |
+ help='Number of jobs to use when processing multiple ' |
+ 'crash stacks.') |
+ options, args = parser.parse_args() |
+ |
+ devices = android_commands.GetAttachedDevices() |
+ tombstones = [] |
+ for device in devices: |
+ adb = android_commands.AndroidCommands(device) |
+ tombstones += _GetTombstonesForDevice(adb, options) |
+ |
+ _ResolveTombstones(options.jobs, tombstones) |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |