1#!/usr/bin/env python 2# 3# Copyright (C) 2014 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Analyzes the dump of initialization failures and creates a Graphviz dot file 18 representing dependencies.""" 19 20import codecs 21import os 22import re 23import string 24import sys 25 26 27_CLASS_RE = re.compile(r'^L(.*);$') 28_ERROR_LINE_RE = re.compile(r'^dalvik.system.TransactionAbortError: (.*)') 29_STACK_LINE_RE = re.compile(r'^\s*at\s[^\s]*\s([^\s]*)') 30 31def Confused(filename, line_number, line): 32 sys.stderr.write('%s:%d: confused by:\n%s\n' % (filename, line_number, line)) 33 raise Exception("giving up!") 34 sys.exit(1) 35 36 37def ProcessFile(filename): 38 lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') 39 it = iter(lines) 40 41 class_fail_class = {} 42 class_fail_method = {} 43 class_fail_load_library = {} 44 class_fail_get_property = {} 45 root_failures = set() 46 root_errors = {} 47 48 while True: 49 try: 50 # We start with a class descriptor. 51 raw_line = it.next() 52 m = _CLASS_RE.search(raw_line) 53 # print(raw_line) 54 if m is None: 55 continue 56 # Found a class. 57 failed_clazz = m.group(1).replace('/','.') 58 # print('Is a class %s' % failed_clazz) 59 # The error line should be next. 60 raw_line = it.next() 61 m = _ERROR_LINE_RE.search(raw_line) 62 # print(raw_line) 63 if m is None: 64 Confused(filename, -1, raw_line) 65 continue 66 # Found an error line. 67 error = m.group(1) 68 # print('Is an error %s' % error) 69 # Get the top of the stack 70 raw_line = it.next() 71 m = _STACK_LINE_RE.search(raw_line) 72 if m is None: 73 continue 74 # Found a stack line. Get the method. 75 method = m.group(1) 76 # print('Is a stack element %s' % method) 77 (left_of_paren,paren,right_of_paren) = method.partition('(') 78 (root_err_class,dot,root_method_name) = left_of_paren.rpartition('.') 79 # print('Error class %s' % err_class) 80 # print('Error method %s' % method_name) 81 # Record the root error. 82 root_failures.add(root_err_class) 83 # Parse all the trace elements to find the "immediate" cause. 84 immediate_class = root_err_class 85 immediate_method = root_method_name 86 root_errors[root_err_class] = error 87 was_load_library = False 88 was_get_property = False 89 # Now go "up" the stack. 90 while True: 91 raw_line = it.next() 92 m = _STACK_LINE_RE.search(raw_line) 93 if m is None: 94 break # Nothing more to see here. 95 method = m.group(1) 96 (left_of_paren,paren,right_of_paren) = method.partition('(') 97 (err_class,dot,err_method_name) = left_of_paren.rpartition('.') 98 if err_method_name == "<clinit>": 99 # A class initializer is on the stack... 100 class_fail_class[err_class] = immediate_class 101 class_fail_method[err_class] = immediate_method 102 class_fail_load_library[err_class] = was_load_library 103 immediate_class = err_class 104 immediate_method = err_method_name 105 class_fail_get_property[err_class] = was_get_property 106 was_get_property = False 107 was_load_library = err_method_name == "loadLibrary" 108 was_get_property = was_get_property or err_method_name == "getProperty" 109 failed_clazz_norm = re.sub(r"^L", "", failed_clazz) 110 failed_clazz_norm = re.sub(r";$", "", failed_clazz_norm) 111 failed_clazz_norm = re.sub(r"/", "", failed_clazz_norm) 112 if immediate_class != failed_clazz_norm: 113 class_fail_class[failed_clazz_norm] = immediate_class 114 class_fail_method[failed_clazz_norm] = immediate_method 115 except StopIteration: 116 # print('Done') 117 break # Done 118 119 # Assign IDs. 120 fail_sources = set(class_fail_class.values()); 121 all_classes = fail_sources | set(class_fail_class.keys()) 122 i = 0 123 class_index = {} 124 for clazz in all_classes: 125 class_index[clazz] = i 126 i = i + 1 127 128 # Now create the nodes. 129 for (r_class, r_id) in class_index.items(): 130 error_string = '' 131 if r_class in root_failures: 132 error_string = ',style=filled,fillcolor=Red,tooltip="' + root_errors[r_class] + '",URL="' + root_errors[r_class] + '"' 133 elif r_class in class_fail_load_library and class_fail_load_library[r_class] == True: 134 error_string = error_string + ',style=filled,fillcolor=Bisque' 135 elif r_class in class_fail_get_property and class_fail_get_property[r_class] == True: 136 error_string = error_string + ',style=filled,fillcolor=Darkseagreen' 137 print(' n%d [shape=box,label="%s"%s];' % (r_id, r_class, error_string)) 138 139 # Some space. 140 print('') 141 142 # Connections. 143 for (failed_class,error_class) in class_fail_class.items(): 144 print(' n%d -> n%d;' % (class_index[failed_class], class_index[error_class])) 145 146 147def main(): 148 print('digraph {') 149 print(' overlap=false;') 150 print(' splines=true;') 151 ProcessFile(sys.argv[1]) 152 print('}') 153 sys.exit(0) 154 155 156if __name__ == '__main__': 157 main() 158