| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # | |
| 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 4 # Use of this source code is governed by a BSD-style license that can be | |
| 5 # found in the LICENSE file. | |
| 6 | |
| 7 """A class to keep track of devices across builds and report state.""" | |
| 8 import logging | |
| 9 import optparse | |
| 10 import os | |
| 11 import smtplib | |
| 12 import sys | |
| 13 import re | |
| 14 | |
| 15 from pylib import android_commands | |
| 16 from pylib import buildbot_report | |
| 17 from pylib import constants | |
| 18 from pylib.cmd_helper import GetCmdOutput | |
| 19 | |
| 20 | |
| 21 def DeviceInfo(serial, options): | |
| 22 """Gathers info on a device via various adb calls. | |
| 23 | |
| 24 Args: | |
| 25 serial: The serial of the attached device to construct info about. | |
| 26 | |
| 27 Returns: | |
| 28 Tuple of device type, build id, report as a string, error messages, and | |
| 29 boolean indicating whether or not device can be used for testing. | |
| 30 """ | |
| 31 | |
| 32 def AdbShellCmd(cmd): | |
| 33 return GetCmdOutput('adb -s %s shell %s' % (serial, cmd), | |
| 34 shell=True).strip() | |
| 35 | |
| 36 device_adb = android_commands.AndroidCommands(serial) | |
| 37 | |
| 38 # TODO(navabi): Replace AdbShellCmd with device_adb. | |
| 39 device_type = AdbShellCmd('getprop ro.build.product') | |
| 40 device_build = AdbShellCmd('getprop ro.build.id') | |
| 41 device_build_type = AdbShellCmd('getprop ro.build.type') | |
| 42 device_product_name = AdbShellCmd('getprop ro.product.name') | |
| 43 | |
| 44 setup_wizard_disabled = AdbShellCmd( | |
| 45 'getprop ro.setupwizard.mode') == 'DISABLED' | |
| 46 battery = AdbShellCmd('dumpsys battery') | |
| 47 install_output = GetCmdOutput( | |
| 48 ['%s/build/android/adb_install_apk.py' % constants.DIR_SOURCE_ROOT, '--apk', | |
| 49 '%s/build/android/CheckInstallApk-debug.apk' % constants.DIR_SOURCE_ROOT]) | |
| 50 install_speed_found = re.findall('(\d+) KB/s', install_output) | |
| 51 if install_speed_found: | |
| 52 install_speed = int(install_speed_found[0]) | |
| 53 else: | |
| 54 install_speed = 'Unknown' | |
| 55 if 'Error' in battery: | |
| 56 ac_power = 'Unknown' | |
| 57 battery_level = 'Unknown' | |
| 58 battery_temp = 'Unknown' | |
| 59 else: | |
| 60 ac_power = re.findall('AC powered: (\w+)', battery)[0] | |
| 61 battery_level = int(re.findall('level: (\d+)', battery)[0]) | |
| 62 battery_temp = float(re.findall('temperature: (\d+)', battery)[0]) / 10 | |
| 63 report = ['Device %s (%s)' % (serial, device_type), | |
| 64 ' Build: %s (%s)' % (device_build, | |
| 65 AdbShellCmd('getprop ro.build.fingerprint')), | |
| 66 ' Battery: %s%%' % battery_level, | |
| 67 ' Battery temp: %s' % battery_temp, | |
| 68 ' IMEI slice: %s' % AdbShellCmd('dumpsys iphonesubinfo ' | |
| 69 '| grep Device' | |
| 70 "| awk '{print $4}'")[-6:], | |
| 71 ' Wifi IP: %s' % AdbShellCmd('getprop dhcp.wlan0.ipaddress'), | |
| 72 ' Install Speed: %s KB/s' % install_speed, | |
| 73 ''] | |
| 74 | |
| 75 errors = [] | |
| 76 if battery_level < 15: | |
| 77 errors += ['Device critically low in battery. Turning off device.'] | |
| 78 if (not setup_wizard_disabled and device_build_type != 'user' and | |
| 79 not options.no_provisioning_check): | |
| 80 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] | |
| 81 if device_product_name == 'mantaray' and ac_power != 'true': | |
| 82 errors += ['Mantaray device not connected to AC power.'] | |
| 83 # TODO(navabi): Insert warning once we have a better handle of what install | |
| 84 # speeds to expect. The following lines were causing too many alerts. | |
| 85 # if install_speed < 500: | |
| 86 # errors += ['Device install speed too low. Do not use for testing.'] | |
| 87 | |
| 88 # Causing the device status check step fail for slow install speed or low | |
| 89 # battery currently is too disruptive to the bots (especially try bots). | |
| 90 # Turn off devices with low battery and the step does not fail. | |
| 91 if battery_level < 15: | |
| 92 device_adb.EnableAdbRoot() | |
| 93 AdbShellCmd('reboot -p') | |
| 94 return device_type, device_build, '\n'.join(report), errors, True | |
| 95 | |
| 96 | |
| 97 def CheckForMissingDevices(options, adb_online_devs): | |
| 98 """Uses file of previous online devices to detect broken phones. | |
| 99 | |
| 100 Args: | |
| 101 options: out_dir parameter of options argument is used as the base | |
| 102 directory to load and update the cache file. | |
| 103 adb_online_devs: A list of serial numbers of the currently visible | |
| 104 and online attached devices. | |
| 105 """ | |
| 106 # TODO(navabi): remove this once the bug that causes different number | |
| 107 # of devices to be detected between calls is fixed. | |
| 108 logger = logging.getLogger() | |
| 109 logger.setLevel(logging.INFO) | |
| 110 | |
| 111 out_dir = os.path.abspath(options.out_dir) | |
| 112 | |
| 113 def ReadDeviceList(file_name): | |
| 114 devices_path = os.path.join(out_dir, file_name) | |
| 115 devices = [] | |
| 116 try: | |
| 117 with open(devices_path) as f: | |
| 118 devices = f.read().splitlines() | |
| 119 except IOError: | |
| 120 # Ignore error, file might not exist | |
| 121 pass | |
| 122 return devices | |
| 123 | |
| 124 def WriteDeviceList(file_name, device_list): | |
| 125 path = os.path.join(out_dir, file_name) | |
| 126 if not os.path.exists(out_dir): | |
| 127 os.makedirs(out_dir) | |
| 128 with open(path, 'w') as f: | |
| 129 # Write devices currently visible plus devices previously seen. | |
| 130 f.write('\n'.join(set(device_list))) | |
| 131 | |
| 132 last_devices_path = os.path.join(out_dir, '.last_devices') | |
| 133 last_devices = ReadDeviceList('.last_devices') | |
| 134 missing_devs = list(set(last_devices) - set(adb_online_devs)) | |
| 135 | |
| 136 all_known_devices = list(set(adb_online_devs) | set(last_devices)) | |
| 137 WriteDeviceList('.last_devices', all_known_devices) | |
| 138 WriteDeviceList('.last_missing', missing_devs) | |
| 139 | |
| 140 if not all_known_devices: | |
| 141 # This can happen if for some reason the .last_devices file is not | |
| 142 # present or if it was empty. | |
| 143 return ['No online devices. Have any devices been plugged in?'] | |
| 144 if missing_devs: | |
| 145 devices_missing_msg = '%d devices not detected.' % len(missing_devs) | |
| 146 buildbot_report.PrintSummaryText(devices_missing_msg) | |
| 147 | |
| 148 # TODO(navabi): Debug by printing both output from GetCmdOutput and | |
| 149 # GetAttachedDevices to compare results. | |
| 150 return ['Current online devices: %s' % adb_online_devs, | |
| 151 '%s are no longer visible. Were they removed?\n' % missing_devs, | |
| 152 'SHERIFF: See go/chrome_device_monitor', | |
| 153 'Cache file: %s\n\n' % last_devices_path, | |
| 154 'adb devices: %s' % GetCmdOutput(['adb', 'devices']), | |
| 155 'adb devices(GetAttachedDevices): %s' % | |
| 156 android_commands.GetAttachedDevices()] | |
| 157 else: | |
| 158 new_devs = set(adb_online_devs) - set(last_devices) | |
| 159 if new_devs and os.path.exists(last_devices_path): | |
| 160 buildbot_report.PrintWarning() | |
| 161 buildbot_report.PrintSummaryText( | |
| 162 '%d new devices detected' % len(new_devs)) | |
| 163 print ('New devices detected %s. And now back to your ' | |
| 164 'regularly scheduled program.' % list(new_devs)) | |
| 165 | |
| 166 | |
| 167 def SendDeviceStatusAlert(msg): | |
| 168 from_address = 'buildbot@chromium.org' | |
| 169 to_address = 'chromium-android-device-alerts@google.com' | |
| 170 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') | |
| 171 slave_name = os.environ.get('BUILDBOT_SLAVENAME') | |
| 172 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) | |
| 173 msg_body = '\r\n'.join(['From: %s' % from_address, 'To: %s' % to_address, | |
| 174 'Subject: %s' % subject, '', msg]) | |
| 175 try: | |
| 176 server = smtplib.SMTP('localhost') | |
| 177 server.sendmail(from_address, [to_address], msg_body) | |
| 178 server.quit() | |
| 179 except Exception as e: | |
| 180 print 'Failed to send alert email. Error: %s' % e | |
| 181 | |
| 182 | |
| 183 def main(): | |
| 184 parser = optparse.OptionParser() | |
| 185 parser.add_option('', '--out-dir', | |
| 186 help='Directory where the device path is stored', | |
| 187 default=os.path.join(os.path.dirname(__file__), '..', | |
| 188 '..', 'out')) | |
| 189 parser.add_option('--no-provisioning-check', | |
| 190 help='Will not check if devices are provisioned properly.') | |
| 191 | |
| 192 options, args = parser.parse_args() | |
| 193 if args: | |
| 194 parser.error('Unknown options %s' % args) | |
| 195 devices = android_commands.GetAttachedDevices() | |
| 196 types, builds, reports, errors = [], [], [], [] | |
| 197 fail_step_lst = [] | |
| 198 if devices: | |
| 199 types, builds, reports, errors, fail_step_lst = ( | |
| 200 zip(*[DeviceInfo(dev, options) for dev in devices])) | |
| 201 | |
| 202 err_msg = CheckForMissingDevices(options, devices) or [] | |
| 203 | |
| 204 unique_types = list(set(types)) | |
| 205 unique_builds = list(set(builds)) | |
| 206 | |
| 207 buildbot_report.PrintMsg('Online devices: %d. Device types %s, builds %s' | |
| 208 % (len(devices), unique_types, unique_builds)) | |
| 209 print '\n'.join(reports) | |
| 210 | |
| 211 for serial, dev_errors in zip(devices, errors): | |
| 212 if dev_errors: | |
| 213 err_msg += ['%s errors:' % serial] | |
| 214 err_msg += [' %s' % error for error in dev_errors] | |
| 215 | |
| 216 if err_msg: | |
| 217 buildbot_report.PrintWarning() | |
| 218 msg = '\n'.join(err_msg) | |
| 219 print msg | |
| 220 SendDeviceStatusAlert(msg) | |
| 221 | |
| 222 if False in fail_step_lst: | |
| 223 # TODO(navabi): Build fails on device status check step if there exists any | |
| 224 # devices with critically low battery or install speed. Remove those devices | |
| 225 # from testing, allowing build to continue with good devices. | |
| 226 return 1 | |
| 227 | |
| 228 if not devices: | |
| 229 return 1 | |
| 230 | |
| 231 | |
| 232 if __name__ == '__main__': | |
| 233 sys.exit(main()) | |
| OLD | NEW |