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