| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # | |
| 3 # Copyright 2013 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 | |
| 9 import argparse | |
| 10 import json | |
| 11 import logging | |
| 12 import os | |
| 13 import psutil | |
| 14 import re | |
| 15 import signal | |
| 16 import sys | |
| 17 | |
| 18 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) | |
| 19 import devil_chromium | |
| 20 from devil.android import battery_utils | |
| 21 from devil.android import device_blacklist | |
| 22 from devil.android import device_errors | |
| 23 from devil.android import device_list | |
| 24 from devil.android import device_utils | |
| 25 from devil.android.sdk import adb_wrapper | |
| 26 from devil.constants import exit_codes | |
| 27 from devil.utils import lsusb | |
| 28 from devil.utils import reset_usb | |
| 29 from devil.utils import run_tests_helper | |
| 30 from pylib.constants import host_paths | |
| 31 | |
| 32 _RE_DEVICE_ID = re.compile(r'Device ID = (\d+)') | |
| 33 | |
| 34 | |
| 35 def KillAllAdb(): | |
| 36 def GetAllAdb(): | |
| 37 for p in psutil.process_iter(): | |
| 38 try: | |
| 39 if 'adb' in p.name: | |
| 40 yield p | |
| 41 except (psutil.NoSuchProcess, psutil.AccessDenied): | |
| 42 pass | |
| 43 | |
| 44 for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]: | |
| 45 for p in GetAllAdb(): | |
| 46 try: | |
| 47 logging.info('kill %d %d (%s [%s])', sig, p.pid, p.name, | |
| 48 ' '.join(p.cmdline)) | |
| 49 p.send_signal(sig) | |
| 50 except (psutil.NoSuchProcess, psutil.AccessDenied): | |
| 51 pass | |
| 52 for p in GetAllAdb(): | |
| 53 try: | |
| 54 logging.error('Unable to kill %d (%s [%s])', p.pid, p.name, | |
| 55 ' '.join(p.cmdline)) | |
| 56 except (psutil.NoSuchProcess, psutil.AccessDenied): | |
| 57 pass | |
| 58 | |
| 59 | |
| 60 def _IsBlacklisted(serial, blacklist): | |
| 61 return blacklist and serial in blacklist.Read() | |
| 62 | |
| 63 | |
| 64 def _BatteryStatus(device, blacklist): | |
| 65 battery_info = {} | |
| 66 try: | |
| 67 battery = battery_utils.BatteryUtils(device) | |
| 68 battery_info = battery.GetBatteryInfo(timeout=5) | |
| 69 battery_level = int(battery_info.get('level', 100)) | |
| 70 | |
| 71 if battery_level < 15: | |
| 72 logging.error('Critically low battery level (%d)', battery_level) | |
| 73 battery = battery_utils.BatteryUtils(device) | |
| 74 if not battery.GetCharging(): | |
| 75 battery.SetCharging(True) | |
| 76 if blacklist: | |
| 77 blacklist.Extend([device.adb.GetDeviceSerial()], reason='low_battery') | |
| 78 | |
| 79 except device_errors.CommandFailedError: | |
| 80 logging.exception('Failed to get battery information for %s', | |
| 81 str(device)) | |
| 82 | |
| 83 return battery_info | |
| 84 | |
| 85 | |
| 86 def _IMEISlice(device): | |
| 87 imei_slice = '' | |
| 88 try: | |
| 89 for l in device.RunShellCommand(['dumpsys', 'iphonesubinfo'], | |
| 90 check_return=True, timeout=5): | |
| 91 m = _RE_DEVICE_ID.match(l) | |
| 92 if m: | |
| 93 imei_slice = m.group(1)[-6:] | |
| 94 except device_errors.CommandFailedError: | |
| 95 logging.exception('Failed to get IMEI slice for %s', str(device)) | |
| 96 | |
| 97 return imei_slice | |
| 98 | |
| 99 | |
| 100 def DeviceStatus(devices, blacklist): | |
| 101 """Generates status information for the given devices. | |
| 102 | |
| 103 Args: | |
| 104 devices: The devices to generate status for. | |
| 105 blacklist: The current device blacklist. | |
| 106 Returns: | |
| 107 A dict of the following form: | |
| 108 { | |
| 109 '<serial>': { | |
| 110 'serial': '<serial>', | |
| 111 'adb_status': str, | |
| 112 'usb_status': bool, | |
| 113 'blacklisted': bool, | |
| 114 # only if the device is connected and not blacklisted | |
| 115 'type': ro.build.product, | |
| 116 'build': ro.build.id, | |
| 117 'build_detail': ro.build.fingerprint, | |
| 118 'battery': { | |
| 119 ... | |
| 120 }, | |
| 121 'imei_slice': str, | |
| 122 'wifi_ip': str, | |
| 123 }, | |
| 124 ... | |
| 125 } | |
| 126 """ | |
| 127 adb_devices = { | |
| 128 a[0].GetDeviceSerial(): a | |
| 129 for a in adb_wrapper.AdbWrapper.Devices(desired_state=None, long_list=True) | |
| 130 } | |
| 131 usb_devices = set(lsusb.get_android_devices()) | |
| 132 | |
| 133 def blacklisting_device_status(device): | |
| 134 serial = device.adb.GetDeviceSerial() | |
| 135 adb_status = ( | |
| 136 adb_devices[serial][1] if serial in adb_devices | |
| 137 else 'missing') | |
| 138 usb_status = bool(serial in usb_devices) | |
| 139 | |
| 140 device_status = { | |
| 141 'serial': serial, | |
| 142 'adb_status': adb_status, | |
| 143 'usb_status': usb_status, | |
| 144 } | |
| 145 | |
| 146 if not _IsBlacklisted(serial, blacklist): | |
| 147 if adb_status == 'device': | |
| 148 try: | |
| 149 build_product = device.build_product | |
| 150 build_id = device.build_id | |
| 151 build_fingerprint = device.GetProp('ro.build.fingerprint', cache=True) | |
| 152 wifi_ip = device.GetProp('dhcp.wlan0.ipaddress') | |
| 153 battery_info = _BatteryStatus(device, blacklist) | |
| 154 imei_slice = _IMEISlice(device) | |
| 155 | |
| 156 if (device.product_name == 'mantaray' and | |
| 157 battery_info.get('AC powered', None) != 'true'): | |
| 158 logging.error('Mantaray device not connected to AC power.') | |
| 159 | |
| 160 device_status.update({ | |
| 161 'ro.build.product': build_product, | |
| 162 'ro.build.id': build_id, | |
| 163 'ro.build.fingerprint': build_fingerprint, | |
| 164 'battery': battery_info, | |
| 165 'imei_slice': imei_slice, | |
| 166 'wifi_ip': wifi_ip, | |
| 167 | |
| 168 # TODO(jbudorick): Remove these once no clients depend on them. | |
| 169 'type': build_product, | |
| 170 'build': build_id, | |
| 171 'build_detail': build_fingerprint, | |
| 172 }) | |
| 173 | |
| 174 except device_errors.CommandFailedError: | |
| 175 logging.exception('Failure while getting device status for %s.', | |
| 176 str(device)) | |
| 177 if blacklist: | |
| 178 blacklist.Extend([serial], reason='status_check_failure') | |
| 179 | |
| 180 except device_errors.CommandTimeoutError: | |
| 181 logging.exception('Timeout while getting device status for %s.', | |
| 182 str(device)) | |
| 183 if blacklist: | |
| 184 blacklist.Extend([serial], reason='status_check_timeout') | |
| 185 | |
| 186 elif blacklist: | |
| 187 blacklist.Extend([serial], | |
| 188 reason=adb_status if usb_status else 'offline') | |
| 189 | |
| 190 device_status['blacklisted'] = _IsBlacklisted(serial, blacklist) | |
| 191 | |
| 192 return device_status | |
| 193 | |
| 194 parallel_devices = device_utils.DeviceUtils.parallel(devices) | |
| 195 statuses = parallel_devices.pMap(blacklisting_device_status).pGet(None) | |
| 196 return statuses | |
| 197 | |
| 198 | |
| 199 def RecoverDevices(devices, blacklist): | |
| 200 """Attempts to recover any inoperable devices in the provided list. | |
| 201 | |
| 202 Args: | |
| 203 devices: The list of devices to attempt to recover. | |
| 204 blacklist: The current device blacklist, which will be used then | |
| 205 reset. | |
| 206 Returns: | |
| 207 Nothing. | |
| 208 """ | |
| 209 | |
| 210 statuses = DeviceStatus(devices, blacklist) | |
| 211 | |
| 212 should_restart_usb = set( | |
| 213 status['serial'] for status in statuses | |
| 214 if (not status['usb_status'] | |
| 215 or status['adb_status'] in ('offline', 'missing'))) | |
| 216 should_restart_adb = should_restart_usb.union(set( | |
| 217 status['serial'] for status in statuses | |
| 218 if status['adb_status'] == 'unauthorized')) | |
| 219 should_reboot_device = should_restart_adb.union(set( | |
| 220 status['serial'] for status in statuses | |
| 221 if status['blacklisted'])) | |
| 222 | |
| 223 logging.debug('Should restart USB for:') | |
| 224 for d in should_restart_usb: | |
| 225 logging.debug(' %s', d) | |
| 226 logging.debug('Should restart ADB for:') | |
| 227 for d in should_restart_adb: | |
| 228 logging.debug(' %s', d) | |
| 229 logging.debug('Should reboot:') | |
| 230 for d in should_reboot_device: | |
| 231 logging.debug(' %s', d) | |
| 232 | |
| 233 if blacklist: | |
| 234 blacklist.Reset() | |
| 235 | |
| 236 if should_restart_adb: | |
| 237 KillAllAdb() | |
| 238 for serial in should_restart_usb: | |
| 239 try: | |
| 240 reset_usb.reset_android_usb(serial) | |
| 241 except IOError: | |
| 242 logging.exception('Unable to reset USB for %s.', serial) | |
| 243 if blacklist: | |
| 244 blacklist.Extend([serial], reason='usb_failure') | |
| 245 except device_errors.DeviceUnreachableError: | |
| 246 logging.exception('Unable to reset USB for %s.', serial) | |
| 247 if blacklist: | |
| 248 blacklist.Extend([serial], reason='offline') | |
| 249 | |
| 250 def blacklisting_recovery(device): | |
| 251 if _IsBlacklisted(device.adb.GetDeviceSerial(), blacklist): | |
| 252 logging.debug('%s is blacklisted, skipping recovery.', str(device)) | |
| 253 return | |
| 254 | |
| 255 if str(device) in should_reboot_device: | |
| 256 try: | |
| 257 device.WaitUntilFullyBooted(retries=0) | |
| 258 return | |
| 259 except (device_errors.CommandTimeoutError, | |
| 260 device_errors.CommandFailedError): | |
| 261 logging.exception('Failure while waiting for %s. ' | |
| 262 'Attempting to recover.', str(device)) | |
| 263 | |
| 264 try: | |
| 265 try: | |
| 266 device.Reboot(block=False, timeout=5, retries=0) | |
| 267 except device_errors.CommandTimeoutError: | |
| 268 logging.warning('Timed out while attempting to reboot %s normally.' | |
| 269 'Attempting alternative reboot.', str(device)) | |
| 270 # The device drops offline before we can grab the exit code, so | |
| 271 # we don't check for status. | |
| 272 device.adb.Root() | |
| 273 device.adb.Shell('echo b > /proc/sysrq-trigger', expect_status=None, | |
| 274 timeout=5, retries=0) | |
| 275 except device_errors.CommandFailedError: | |
| 276 logging.exception('Failed to reboot %s.', str(device)) | |
| 277 if blacklist: | |
| 278 blacklist.Extend([device.adb.GetDeviceSerial()], | |
| 279 reason='reboot_failure') | |
| 280 except device_errors.CommandTimeoutError: | |
| 281 logging.exception('Timed out while rebooting %s.', str(device)) | |
| 282 if blacklist: | |
| 283 blacklist.Extend([device.adb.GetDeviceSerial()], | |
| 284 reason='reboot_timeout') | |
| 285 | |
| 286 try: | |
| 287 device.WaitUntilFullyBooted(retries=0) | |
| 288 except device_errors.CommandFailedError: | |
| 289 logging.exception('Failure while waiting for %s.', str(device)) | |
| 290 if blacklist: | |
| 291 blacklist.Extend([device.adb.GetDeviceSerial()], | |
| 292 reason='reboot_failure') | |
| 293 except device_errors.CommandTimeoutError: | |
| 294 logging.exception('Timed out while waiting for %s.', str(device)) | |
| 295 if blacklist: | |
| 296 blacklist.Extend([device.adb.GetDeviceSerial()], | |
| 297 reason='reboot_timeout') | |
| 298 | |
| 299 device_utils.DeviceUtils.parallel(devices).pMap(blacklisting_recovery) | |
| 300 | |
| 301 | |
| 302 def main(): | |
| 303 parser = argparse.ArgumentParser() | |
| 304 parser.add_argument('--out-dir', | |
| 305 help='Directory where the device path is stored', | |
| 306 default=os.path.join(host_paths.DIR_SOURCE_ROOT, 'out')) | |
| 307 parser.add_argument('--restart-usb', action='store_true', | |
| 308 help='DEPRECATED. ' | |
| 309 'This script now always tries to reset USB.') | |
| 310 parser.add_argument('--json-output', | |
| 311 help='Output JSON information into a specified file.') | |
| 312 parser.add_argument('--adb-path', type=os.path.abspath, | |
| 313 help='Absolute path to the adb binary to use.') | |
| 314 parser.add_argument('--blacklist-file', help='Device blacklist JSON file.') | |
| 315 parser.add_argument('--known-devices-file', action='append', default=[], | |
| 316 dest='known_devices_files', | |
| 317 help='Path to known device lists.') | |
| 318 parser.add_argument('-v', '--verbose', action='count', default=1, | |
| 319 help='Log more information.') | |
| 320 | |
| 321 args = parser.parse_args() | |
| 322 | |
| 323 run_tests_helper.SetLogLevel(args.verbose) | |
| 324 | |
| 325 devil_chromium.Initialize(adb_path=args.adb_path) | |
| 326 | |
| 327 blacklist = (device_blacklist.Blacklist(args.blacklist_file) | |
| 328 if args.blacklist_file | |
| 329 else None) | |
| 330 | |
| 331 expected_devices = set() | |
| 332 try: | |
| 333 for path in args.known_devices_files: | |
| 334 if os.path.exists(path): | |
| 335 expected_devices.update(device_list.GetPersistentDeviceList(path)) | |
| 336 except IOError: | |
| 337 logging.warning('Problem reading %s, skipping.', path) | |
| 338 | |
| 339 logging.info('Expected devices:') | |
| 340 for device in expected_devices: | |
| 341 logging.info(' %s', device) | |
| 342 | |
| 343 usb_devices = set(lsusb.get_android_devices()) | |
| 344 devices = [device_utils.DeviceUtils(s) | |
| 345 for s in expected_devices.union(usb_devices)] | |
| 346 | |
| 347 RecoverDevices(devices, blacklist) | |
| 348 statuses = DeviceStatus(devices, blacklist) | |
| 349 | |
| 350 # Log the state of all devices. | |
| 351 for status in statuses: | |
| 352 logging.info(status['serial']) | |
| 353 adb_status = status.get('adb_status') | |
| 354 blacklisted = status.get('blacklisted') | |
| 355 logging.info(' USB status: %s', | |
| 356 'online' if status.get('usb_status') else 'offline') | |
| 357 logging.info(' ADB status: %s', adb_status) | |
| 358 logging.info(' Blacklisted: %s', str(blacklisted)) | |
| 359 if adb_status == 'device' and not blacklisted: | |
| 360 logging.info(' Device type: %s', status.get('ro.build.product')) | |
| 361 logging.info(' OS build: %s', status.get('ro.build.id')) | |
| 362 logging.info(' OS build fingerprint: %s', | |
| 363 status.get('ro.build.fingerprint')) | |
| 364 logging.info(' Battery state:') | |
| 365 for k, v in status.get('battery', {}).iteritems(): | |
| 366 logging.info(' %s: %s', k, v) | |
| 367 logging.info(' IMEI slice: %s', status.get('imei_slice')) | |
| 368 logging.info(' WiFi IP: %s', status.get('wifi_ip')) | |
| 369 | |
| 370 # Update the last devices file(s). | |
| 371 for path in args.known_devices_files: | |
| 372 device_list.WritePersistentDeviceList( | |
| 373 path, [status['serial'] for status in statuses]) | |
| 374 | |
| 375 # Write device info to file for buildbot info display. | |
| 376 if os.path.exists('/home/chrome-bot'): | |
| 377 with open('/home/chrome-bot/.adb_device_info', 'w') as f: | |
| 378 for status in statuses: | |
| 379 try: | |
| 380 if status['adb_status'] == 'device': | |
| 381 f.write('{serial} {adb_status} {build_product} {build_id} ' | |
| 382 '{temperature:.1f}C {level}%\n'.format( | |
| 383 serial=status['serial'], | |
| 384 adb_status=status['adb_status'], | |
| 385 build_product=status['type'], | |
| 386 build_id=status['build'], | |
| 387 temperature=float(status['battery']['temperature']) / 10, | |
| 388 level=status['battery']['level'] | |
| 389 )) | |
| 390 elif status.get('usb_status', False): | |
| 391 f.write('{serial} {adb_status}\n'.format( | |
| 392 serial=status['serial'], | |
| 393 adb_status=status['adb_status'] | |
| 394 )) | |
| 395 else: | |
| 396 f.write('{serial} offline\n'.format( | |
| 397 serial=status['serial'] | |
| 398 )) | |
| 399 except Exception: # pylint: disable=broad-except | |
| 400 pass | |
| 401 | |
| 402 # Dump the device statuses to JSON. | |
| 403 if args.json_output: | |
| 404 with open(args.json_output, 'wb') as f: | |
| 405 f.write(json.dumps(statuses, indent=4)) | |
| 406 | |
| 407 live_devices = [status['serial'] for status in statuses | |
| 408 if (status['adb_status'] == 'device' | |
| 409 and not _IsBlacklisted(status['serial'], blacklist))] | |
| 410 | |
| 411 # If all devices failed, or if there are no devices, it's an infra error. | |
| 412 if not live_devices: | |
| 413 logging.error('No available devices.') | |
| 414 return 0 if live_devices else exit_codes.INFRA | |
| 415 | |
| 416 | |
| 417 if __name__ == '__main__': | |
| 418 sys.exit(main()) | |
| OLD | NEW |