1#!/usr/bin/env python3.4 2# 3# Copyright 2018 - 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 17import logging 18import re 19import threading 20import time 21 22from acts import utils 23 24 25class ErrorLogger(logging.LoggerAdapter): 26 """A logger for a given error report.""" 27 28 def __init__(self, label): 29 self.label = label 30 super(ErrorLogger, self).__init__(logging.getLogger(), {}) 31 32 def process(self, msg, kwargs): 33 """Transforms a log message to be in a given format.""" 34 return '[Error Report|%s] %s' % (self.label, msg), kwargs 35 36 37class ErrorReporter(object): 38 """A class that reports errors and diagnoses possible points of failure. 39 40 Attributes: 41 max_reports: The maximum number of reports that should be reported. 42 Defaulted to 1 to prevent multiple reports from reporting at the 43 same time over one another. 44 name: The name of the report to be used in the error logs. 45 """ 46 47 def __init__(self, name, max_reports=1): 48 """Creates an error report. 49 50 Args: 51 name: The name of the error report. 52 max_reports: Sets the maximum number of reports to this value. 53 """ 54 self.name = name 55 self.max_reports = max_reports 56 self._ticket_number = 0 57 self._ticket_lock = threading.Lock() 58 self._current_request_count = 0 59 self._accept_requests = True 60 61 def create_error_report(self, sl4a_manager, sl4a_session, rpc_connection): 62 """Creates an error report, if possible. 63 64 Returns: 65 False iff a report cannot be created. 66 """ 67 if not self._accept_requests: 68 return False 69 70 self._current_request_count += 1 71 72 try: 73 ticket = self._get_report_ticket() 74 if not ticket: 75 return False 76 77 report = ErrorLogger('%s|%s' % (self.name, ticket)) 78 79 (self.report_on_adb(sl4a_manager.adb, report) 80 and self.report_device_processes(sl4a_manager.adb, report) and 81 self.report_sl4a_state(rpc_connection, sl4a_manager.adb, report) 82 and self.report_sl4a_session(sl4a_manager, sl4a_session, report)) 83 84 return True 85 finally: 86 self._current_request_count -= 1 87 88 def report_on_adb(self, adb, report): 89 """Creates an error report for ADB. Returns false if ADB has failed.""" 90 adb_uptime = utils.get_process_uptime('adb') 91 if adb_uptime: 92 report.info('The adb daemon has an uptime of %s ' 93 '([[dd-]hh:]mm:ss).' % adb_uptime) 94 else: 95 report.warning('The adb daemon (on the host machine) is not ' 96 'running. All forwarded ports have been removed.') 97 return False 98 99 devices_output = adb.devices() 100 if adb.serial not in devices_output: 101 report.warning( 102 'This device cannot be found by ADB. The device may have shut ' 103 'down or disconnected.') 104 return False 105 elif re.findall(r'%s\s+offline' % adb.serial, devices_output): 106 report.warning( 107 'The device is marked as offline in ADB. We are no longer able ' 108 'to access the device.') 109 return False 110 else: 111 report.info( 112 'The device is online and accessible through ADB calls.') 113 return True 114 115 def report_device_processes(self, adb, report): 116 """Creates an error report for the device's required processes. 117 118 Returns: 119 False iff user-apks cannot be communicated with over tcp. 120 """ 121 zygote_uptime = utils.get_device_process_uptime(adb, 'zygote') 122 if zygote_uptime: 123 report.info( 124 'Zygote has been running for %s ([[dd-]hh:]mm:ss). If this ' 125 'value is low, the phone may have recently crashed.' % 126 zygote_uptime) 127 else: 128 report.warning( 129 'Zygote has been killed. It is likely the Android Runtime has ' 130 'crashed. Check the bugreport/logcat for more information.') 131 return False 132 133 netd_uptime = utils.get_device_process_uptime(adb, 'netd') 134 if netd_uptime: 135 report.info( 136 'Netd has been running for %s ([[dd-]hh:]mm:ss). If this ' 137 'value is low, the phone may have recently crashed.' % 138 zygote_uptime) 139 else: 140 report.warning( 141 'Netd has been killed. The Android Runtime may have crashed. ' 142 'Check the bugreport/logcat for more information.') 143 return False 144 145 adbd_uptime = utils.get_device_process_uptime(adb, 'adbd') 146 if netd_uptime: 147 report.info( 148 'Adbd has been running for %s ([[dd-]hh:]mm:ss). If this ' 149 'value is low, the phone may have recently crashed.' % 150 adbd_uptime) 151 else: 152 report.warning('Adbd is not running.') 153 return False 154 return True 155 156 def report_sl4a_state(self, rpc_connection, adb, report): 157 """Creates an error report for the state of SL4A.""" 158 report.info( 159 'Diagnosing Failure over connection %s.' % rpc_connection.ports) 160 161 ports = rpc_connection.ports 162 forwarded_ports_output = adb.forward('--list') 163 164 expected_output = '%s tcp:%s tcp:%s' % (adb.serial, 165 ports.forwarded_port, 166 ports.server_port) 167 if expected_output not in forwarded_ports_output: 168 formatted_output = re.sub( 169 '^', ' ', forwarded_ports_output, flags=re.MULTILINE) 170 report.warning( 171 'The forwarded port for the failed RpcConnection is missing.\n' 172 'Expected:\n %s\nBut found:\n%s' % (expected_output, 173 formatted_output)) 174 return False 175 else: 176 report.info('The connection port has been properly forwarded to ' 177 'the device.') 178 179 sl4a_uptime = utils.get_device_process_uptime( 180 adb, 'com.googlecode.android_scripting') 181 if sl4a_uptime: 182 report.info( 183 'SL4A has been running for %s ([[dd-]hh:]mm:ss). If this ' 184 'value is lower than the test case, it must have been ' 185 'restarted during the test.' % sl4a_uptime) 186 else: 187 report.warning( 188 'The SL4A scripting service is not running. SL4A may have ' 189 'crashed, or have been terminated by the Android Runtime.') 190 return False 191 return True 192 193 def report_sl4a_session(self, sl4a_manager, session, report): 194 """Reports the state of an SL4A session.""" 195 if session.server_port not in sl4a_manager.sl4a_ports_in_use: 196 report.warning('SL4A server port %s not found in set of open ' 197 'ports %s' % (session.server_port, 198 sl4a_manager.sl4a_ports_in_use)) 199 return False 200 201 if session not in sl4a_manager.sessions.values(): 202 report.warning('SL4A session %s over port %s is not managed by ' 203 'the SL4A Manager. This session is already dead.' % 204 (session.uid, session.server_port)) 205 return False 206 return True 207 208 def finalize_reports(self): 209 self._accept_requests = False 210 while self._current_request_count > 0: 211 # Wait for other threads to finish. 212 time.sleep(.1) 213 214 def _get_report_ticket(self): 215 """Returns the next ticket, or none if all tickets have been used.""" 216 with self._ticket_lock: 217 self._ticket_number += 1 218 ticket_number = self._ticket_number 219 220 if ticket_number <= self.max_reports: 221 return ticket_number 222 else: 223 return None 224