• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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