1#!/usr/bin/env python3 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 report.info('Creating error report.') 79 80 (self.report_on_adb(sl4a_manager.adb, report) 81 and self.report_device_processes(sl4a_manager.adb, report) and 82 self.report_sl4a_state(rpc_connection, sl4a_manager.adb, report) 83 and self.report_sl4a_session(sl4a_manager, sl4a_session, report)) 84 85 return True 86 finally: 87 self._current_request_count -= 1 88 89 def report_on_adb(self, adb, report): 90 """Creates an error report for ADB. Returns false if ADB has failed.""" 91 adb_uptime = utils.get_command_uptime('"adb .* server"') 92 if adb_uptime: 93 report.info('The adb daemon has an uptime of %s ' 94 '([[dd-]hh:]mm:ss).' % adb_uptime) 95 else: 96 report.warning('The adb daemon (on the host machine) is not ' 97 'running. All forwarded ports have been removed.') 98 return False 99 100 devices_output = adb.devices() 101 if adb.serial not in devices_output: 102 report.warning( 103 'This device cannot be found by ADB. The device may have shut ' 104 'down or disconnected.') 105 return False 106 elif re.findall(r'%s\s+offline' % adb.serial, devices_output): 107 report.warning( 108 'The device is marked as offline in ADB. We are no longer able ' 109 'to access the device.') 110 return False 111 else: 112 report.info( 113 'The device is online and accessible through ADB calls.') 114 return True 115 116 def report_device_processes(self, adb, report): 117 """Creates an error report for the device's required processes. 118 119 Returns: 120 False iff user-apks cannot be communicated with over tcp. 121 """ 122 zygote_uptime = utils.get_device_process_uptime(adb, 'zygote') 123 if zygote_uptime: 124 report.info( 125 'Zygote has been running for %s ([[dd-]hh:]mm:ss). If this ' 126 'value is low, the phone may have recently crashed.' % 127 zygote_uptime) 128 else: 129 report.warning( 130 'Zygote has been killed. It is likely the Android Runtime has ' 131 'crashed. Check the bugreport/logcat for more information.') 132 return False 133 134 netd_uptime = utils.get_device_process_uptime(adb, 'netd') 135 if netd_uptime: 136 report.info( 137 'Netd has been running for %s ([[dd-]hh:]mm:ss). If this ' 138 'value is low, the phone may have recently crashed.' % 139 zygote_uptime) 140 else: 141 report.warning( 142 'Netd has been killed. The Android Runtime may have crashed. ' 143 'Check the bugreport/logcat for more information.') 144 return False 145 146 adbd_uptime = utils.get_device_process_uptime(adb, 'adbd') 147 if netd_uptime: 148 report.info( 149 'Adbd has been running for %s ([[dd-]hh:]mm:ss). If this ' 150 'value is low, the phone may have recently crashed.' % 151 adbd_uptime) 152 else: 153 report.warning('Adbd is not running.') 154 return False 155 return True 156 157 def report_sl4a_state(self, rpc_connection, adb, report): 158 """Creates an error report for the state of SL4A.""" 159 report.info( 160 'Diagnosing Failure over connection %s.' % rpc_connection.ports) 161 162 ports = rpc_connection.ports 163 forwarded_ports_output = adb.forward('--list') 164 165 expected_output = '%s tcp:%s tcp:%s' % ( 166 adb.serial, ports.forwarded_port, 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 logging.debug('Getting ticket for SL4A error report.') 217 with self._ticket_lock: 218 self._ticket_number += 1 219 ticket_number = self._ticket_number 220 221 if ticket_number <= self.max_reports: 222 return ticket_number 223 else: 224 return None 225