1#!/usr/bin/env python 2# Copyright 2016 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""A script to keep track of devices across builds and report state.""" 7 8import argparse 9import json 10import logging 11import os 12import re 13import sys 14 15if __name__ == '__main__': 16 sys.path.append( 17 os.path.abspath(os.path.join(os.path.dirname(__file__), 18 '..', '..', '..'))) 19from devil.android import battery_utils 20from devil.android import device_blacklist 21from devil.android import device_errors 22from devil.android import device_list 23from devil.android import device_utils 24from devil.android.sdk import adb_wrapper 25from devil.android.tools import script_common 26from devil.constants import exit_codes 27from devil.utils import logging_common 28from devil.utils import lsusb 29 30logger = logging.getLogger(__name__) 31 32_RE_DEVICE_ID = re.compile(r'Device ID = (\d+)') 33 34 35def IsBlacklisted(serial, blacklist): 36 return blacklist and serial in blacklist.Read() 37 38 39def _BatteryStatus(device, blacklist): 40 battery_info = {} 41 try: 42 battery = battery_utils.BatteryUtils(device) 43 battery_info = battery.GetBatteryInfo(timeout=5) 44 battery_level = int(battery_info.get('level', 100)) 45 46 if battery_level < 15: 47 logger.error('Critically low battery level (%d)', battery_level) 48 battery = battery_utils.BatteryUtils(device) 49 if not battery.GetCharging(): 50 battery.SetCharging(True) 51 if blacklist: 52 blacklist.Extend([device.adb.GetDeviceSerial()], reason='low_battery') 53 54 except (device_errors.CommandFailedError, 55 device_errors.DeviceUnreachableError): 56 logger.exception('Failed to get battery information for %s', 57 str(device)) 58 59 return battery_info 60 61 62def DeviceStatus(devices, blacklist): 63 """Generates status information for the given devices. 64 65 Args: 66 devices: The devices to generate status for. 67 blacklist: The current device blacklist. 68 Returns: 69 A dict of the following form: 70 { 71 '<serial>': { 72 'serial': '<serial>', 73 'adb_status': str, 74 'usb_status': bool, 75 'blacklisted': bool, 76 # only if the device is connected and not blacklisted 77 'type': ro.build.product, 78 'build': ro.build.id, 79 'build_detail': ro.build.fingerprint, 80 'battery': { 81 ... 82 }, 83 'imei_slice': str, 84 'wifi_ip': str, 85 }, 86 ... 87 } 88 """ 89 adb_devices = { 90 a[0].GetDeviceSerial(): a 91 for a in adb_wrapper.AdbWrapper.Devices(desired_state=None, long_list=True) 92 } 93 usb_devices = set(lsusb.get_android_devices()) 94 95 def blacklisting_device_status(device): 96 serial = device.adb.GetDeviceSerial() 97 adb_status = ( 98 adb_devices[serial][1] if serial in adb_devices 99 else 'missing') 100 usb_status = bool(serial in usb_devices) 101 102 device_status = { 103 'serial': serial, 104 'adb_status': adb_status, 105 'usb_status': usb_status, 106 } 107 108 if not IsBlacklisted(serial, blacklist): 109 if adb_status == 'device': 110 try: 111 build_product = device.build_product 112 build_id = device.build_id 113 build_fingerprint = device.build_fingerprint 114 build_description = device.build_description 115 wifi_ip = device.GetProp('dhcp.wlan0.ipaddress') 116 battery_info = _BatteryStatus(device, blacklist) 117 try: 118 imei_slice = device.GetIMEI() 119 except device_errors.CommandFailedError: 120 logging.exception('Unable to fetch IMEI for %s.', str(device)) 121 imei_slice = 'unknown' 122 123 if (device.product_name == 'mantaray' and 124 battery_info.get('AC powered', None) != 'true'): 125 logger.error('Mantaray device not connected to AC power.') 126 127 device_status.update({ 128 'ro.build.product': build_product, 129 'ro.build.id': build_id, 130 'ro.build.fingerprint': build_fingerprint, 131 'ro.build.description': build_description, 132 'battery': battery_info, 133 'imei_slice': imei_slice, 134 'wifi_ip': wifi_ip, 135 }) 136 137 except (device_errors.CommandFailedError, 138 device_errors.DeviceUnreachableError): 139 logger.exception('Failure while getting device status for %s.', 140 str(device)) 141 if blacklist: 142 blacklist.Extend([serial], reason='status_check_failure') 143 144 except device_errors.CommandTimeoutError: 145 logger.exception('Timeout while getting device status for %s.', 146 str(device)) 147 if blacklist: 148 blacklist.Extend([serial], reason='status_check_timeout') 149 150 elif blacklist: 151 blacklist.Extend([serial], 152 reason=adb_status if usb_status else 'offline') 153 154 device_status['blacklisted'] = IsBlacklisted(serial, blacklist) 155 156 return device_status 157 158 parallel_devices = device_utils.DeviceUtils.parallel(devices) 159 statuses = parallel_devices.pMap(blacklisting_device_status).pGet(None) 160 return statuses 161 162 163def _LogStatuses(statuses): 164 # Log the state of all devices. 165 for status in statuses: 166 logger.info(status['serial']) 167 adb_status = status.get('adb_status') 168 blacklisted = status.get('blacklisted') 169 logger.info(' USB status: %s', 170 'online' if status.get('usb_status') else 'offline') 171 logger.info(' ADB status: %s', adb_status) 172 logger.info(' Blacklisted: %s', str(blacklisted)) 173 if adb_status == 'device' and not blacklisted: 174 logger.info(' Device type: %s', status.get('ro.build.product')) 175 logger.info(' OS build: %s', status.get('ro.build.id')) 176 logger.info(' OS build fingerprint: %s', 177 status.get('ro.build.fingerprint')) 178 logger.info(' Battery state:') 179 for k, v in status.get('battery', {}).iteritems(): 180 logger.info(' %s: %s', k, v) 181 logger.info(' IMEI slice: %s', status.get('imei_slice')) 182 logger.info(' WiFi IP: %s', status.get('wifi_ip')) 183 184 185def _WriteBuildbotFile(file_path, statuses): 186 buildbot_path, _ = os.path.split(file_path) 187 if os.path.exists(buildbot_path): 188 with open(file_path, 'w') as f: 189 for status in statuses: 190 try: 191 if status['adb_status'] == 'device': 192 f.write('{serial} {adb_status} {build_product} {build_id} ' 193 '{temperature:.1f}C {level}%\n'.format( 194 serial=status['serial'], 195 adb_status=status['adb_status'], 196 build_product=status['type'], 197 build_id=status['build'], 198 temperature=float(status['battery']['temperature']) / 10, 199 level=status['battery']['level'] 200 )) 201 elif status.get('usb_status', False): 202 f.write('{serial} {adb_status}\n'.format( 203 serial=status['serial'], 204 adb_status=status['adb_status'] 205 )) 206 else: 207 f.write('{serial} offline\n'.format( 208 serial=status['serial'] 209 )) 210 except Exception: # pylint: disable=broad-except 211 pass 212 213 214def GetExpectedDevices(known_devices_files): 215 expected_devices = set() 216 try: 217 for path in known_devices_files: 218 if os.path.exists(path): 219 expected_devices.update(device_list.GetPersistentDeviceList(path)) 220 else: 221 logger.warning('Could not find known devices file: %s', path) 222 except IOError: 223 logger.warning('Problem reading %s, skipping.', path) 224 225 logger.info('Expected devices:') 226 for device in expected_devices: 227 logger.info(' %s', device) 228 return expected_devices 229 230 231def AddArguments(parser): 232 parser.add_argument('--json-output', 233 help='Output JSON information into a specified file.') 234 parser.add_argument('--blacklist-file', help='Device blacklist JSON file.') 235 parser.add_argument('--known-devices-file', action='append', default=[], 236 dest='known_devices_files', 237 help='Path to known device lists.') 238 parser.add_argument('--buildbot-path', '-b', 239 default='/home/chrome-bot/.adb_device_info', 240 help='Absolute path to buildbot file location') 241 parser.add_argument('-w', '--overwrite-known-devices-files', 242 action='store_true', 243 help='If set, overwrites known devices files wiht new ' 244 'values.') 245 246def main(): 247 parser = argparse.ArgumentParser() 248 logging_common.AddLoggingArguments(parser) 249 script_common.AddEnvironmentArguments(parser) 250 AddArguments(parser) 251 args = parser.parse_args() 252 253 logging_common.InitializeLogging(args) 254 script_common.InitializeEnvironment(args) 255 256 blacklist = (device_blacklist.Blacklist(args.blacklist_file) 257 if args.blacklist_file 258 else None) 259 260 expected_devices = GetExpectedDevices(args.known_devices_files) 261 usb_devices = set(lsusb.get_android_devices()) 262 devices = [device_utils.DeviceUtils(s) 263 for s in expected_devices.union(usb_devices)] 264 265 statuses = DeviceStatus(devices, blacklist) 266 267 # Log the state of all devices. 268 _LogStatuses(statuses) 269 270 # Update the last devices file(s). 271 if args.overwrite_known_devices_files: 272 for path in args.known_devices_files: 273 device_list.WritePersistentDeviceList( 274 path, [status['serial'] for status in statuses]) 275 276 # Write device info to file for buildbot info display. 277 _WriteBuildbotFile(args.buildbot_path, statuses) 278 279 # Dump the device statuses to JSON. 280 if args.json_output: 281 with open(args.json_output, 'wb') as f: 282 f.write(json.dumps( 283 statuses, indent=4, sort_keys=True, separators=(',', ': '))) 284 285 live_devices = [status['serial'] for status in statuses 286 if (status['adb_status'] == 'device' 287 and not IsBlacklisted(status['serial'], blacklist))] 288 289 # If all devices failed, or if there are no devices, it's an infra error. 290 if not live_devices: 291 logger.error('No available devices.') 292 return 0 if live_devices else exit_codes.INFRA 293 294 295if __name__ == '__main__': 296 sys.exit(main()) 297