Home | History | Annotate | Download | only in sl4a_lib
      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 
     17 import logging
     18 import re
     19 import threading
     20 import time
     21 
     22 from acts import utils
     23 
     24 
     25 class 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 
     37 class 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