| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """A class to keep track of devices across builds and report state.""" | 7 """A class to keep track of devices across builds and report state.""" |
| 8 import argparse |
| 8 import json | 9 import json |
| 9 import logging | 10 import logging |
| 10 import optparse | |
| 11 import os | 11 import os |
| 12 import psutil | 12 import psutil |
| 13 import re | 13 import re |
| 14 import signal | 14 import signal |
| 15 import smtplib | 15 import smtplib |
| 16 import subprocess | 16 import subprocess |
| 17 import sys | 17 import sys |
| 18 import time | 18 import time |
| 19 import urllib | 19 import urllib |
| 20 | 20 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 34 from pylib.device import battery_utils | 34 from pylib.device import battery_utils |
| 35 from pylib.device import device_blacklist | 35 from pylib.device import device_blacklist |
| 36 from pylib.device import device_errors | 36 from pylib.device import device_errors |
| 37 from pylib.device import device_list | 37 from pylib.device import device_list |
| 38 from pylib.device import device_utils | 38 from pylib.device import device_utils |
| 39 from pylib.utils import run_tests_helper | 39 from pylib.utils import run_tests_helper |
| 40 from pylib.utils import timeout_retry | 40 from pylib.utils import timeout_retry |
| 41 | 41 |
| 42 _RE_DEVICE_ID = re.compile('Device ID = (\d+)') | 42 _RE_DEVICE_ID = re.compile('Device ID = (\d+)') |
| 43 | 43 |
| 44 def DeviceInfo(device, options): | 44 def DeviceInfo(device, args): |
| 45 """Gathers info on a device via various adb calls. | 45 """Gathers info on a device via various adb calls. |
| 46 | 46 |
| 47 Args: | 47 Args: |
| 48 device: A DeviceUtils instance for the device to construct info about. | 48 device: A DeviceUtils instance for the device to construct info about. |
| 49 | 49 |
| 50 Returns: | 50 Returns: |
| 51 Tuple of device type, build id, report as a string, error messages, and | 51 Tuple of device type, build id, report as a string, error messages, and |
| 52 boolean indicating whether or not device can be used for testing. | 52 boolean indicating whether or not device can be used for testing. |
| 53 """ | 53 """ |
| 54 battery = battery_utils.BatteryUtils(device) | 54 battery = battery_utils.BatteryUtils(device) |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 if m: | 89 if m: |
| 90 json_data['imei_slice'] = m.group(1)[-6:] | 90 json_data['imei_slice'] = m.group(1)[-6:] |
| 91 except device_errors.CommandFailedError: | 91 except device_errors.CommandFailedError: |
| 92 logging.exception('Failed to get IMEI slice for %s', str(device)) | 92 logging.exception('Failed to get IMEI slice for %s', str(device)) |
| 93 | 93 |
| 94 if battery_level < 15: | 94 if battery_level < 15: |
| 95 errors += ['Device critically low in battery.'] | 95 errors += ['Device critically low in battery.'] |
| 96 dev_good = False | 96 dev_good = False |
| 97 if not battery.GetCharging(): | 97 if not battery.GetCharging(): |
| 98 battery.SetCharging(True) | 98 battery.SetCharging(True) |
| 99 if not options.no_provisioning_check: | 99 if not args.no_provisioning_check: |
| 100 setup_wizard_disabled = ( | 100 setup_wizard_disabled = ( |
| 101 device.GetProp('ro.setupwizard.mode') == 'DISABLED') | 101 device.GetProp('ro.setupwizard.mode') == 'DISABLED') |
| 102 if not setup_wizard_disabled and device.build_type != 'user': | 102 if not setup_wizard_disabled and device.build_type != 'user': |
| 103 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] | 103 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] |
| 104 if (device.product_name == 'mantaray' and | 104 if (device.product_name == 'mantaray' and |
| 105 battery_info.get('AC powered', None) != 'true'): | 105 battery_info.get('AC powered', None) != 'true'): |
| 106 errors += ['Mantaray device not connected to AC power.'] | 106 errors += ['Mantaray device not connected to AC power.'] |
| 107 except device_errors.CommandFailedError: | 107 except device_errors.CommandFailedError: |
| 108 logging.exception('Failure while getting device status.') | 108 logging.exception('Failure while getting device status.') |
| 109 dev_good = False | 109 dev_good = False |
| 110 except device_errors.CommandTimeoutError: | 110 except device_errors.CommandTimeoutError: |
| 111 logging.exception('Timeout while getting device status.') | 111 logging.exception('Timeout while getting device status.') |
| 112 dev_good = False | 112 dev_good = False |
| 113 | 113 |
| 114 return (build_product, build_id, battery_level, errors, dev_good, json_data) | 114 return (build_product, build_id, battery_level, errors, dev_good, json_data) |
| 115 | 115 |
| 116 | 116 |
| 117 def CheckForMissingDevices(options, devices): | 117 def CheckForMissingDevices(args, devices): |
| 118 """Uses file of previous online devices to detect broken phones. | 118 """Uses file of previous online devices to detect broken phones. |
| 119 | 119 |
| 120 Args: | 120 Args: |
| 121 options: out_dir parameter of options argument is used as the base | 121 args: out_dir parameter of args argument is used as the base |
| 122 directory to load and update the cache file. | 122 directory to load and update the cache file. |
| 123 devices: A list of DeviceUtils instance for the currently visible and | 123 devices: A list of DeviceUtils instance for the currently visible and |
| 124 online attached devices. | 124 online attached devices. |
| 125 """ | 125 """ |
| 126 out_dir = os.path.abspath(options.out_dir) | 126 out_dir = os.path.abspath(args.out_dir) |
| 127 device_serials = set(d.adb.GetDeviceSerial() for d in devices) | 127 device_serials = set(d.adb.GetDeviceSerial() for d in devices) |
| 128 | 128 |
| 129 # last_devices denotes all known devices prior to this run | 129 # last_devices denotes all known devices prior to this run |
| 130 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME) | 130 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME) |
| 131 last_missing_devices_path = os.path.join(out_dir, | 131 last_missing_devices_path = os.path.join(out_dir, |
| 132 device_list.LAST_MISSING_DEVICES_FILENAME) | 132 device_list.LAST_MISSING_DEVICES_FILENAME) |
| 133 try: | 133 try: |
| 134 last_devices = device_list.GetPersistentDeviceList(last_devices_path) | 134 last_devices = device_list.GetPersistentDeviceList(last_devices_path) |
| 135 except IOError: | 135 except IOError: |
| 136 # Ignore error, file might not exist | 136 # Ignore error, file might not exist |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 except (psutil.NoSuchProcess, psutil.AccessDenied): | 217 except (psutil.NoSuchProcess, psutil.AccessDenied): |
| 218 pass | 218 pass |
| 219 for p in GetAllAdb(): | 219 for p in GetAllAdb(): |
| 220 try: | 220 try: |
| 221 logging.error('Unable to kill %d (%s [%s])', p.pid, p.name, | 221 logging.error('Unable to kill %d (%s [%s])', p.pid, p.name, |
| 222 ' '.join(p.cmdline)) | 222 ' '.join(p.cmdline)) |
| 223 except (psutil.NoSuchProcess, psutil.AccessDenied): | 223 except (psutil.NoSuchProcess, psutil.AccessDenied): |
| 224 pass | 224 pass |
| 225 | 225 |
| 226 | 226 |
| 227 def RecoverDevices(args): | 227 def RecoverDevices(blacklist, output_directory): |
| 228 # Remove the last build's "bad devices" before checking device statuses. | 228 # Remove the last build's "bad devices" before checking device statuses. |
| 229 device_blacklist.ResetBlacklist() | 229 blacklist.Reset() |
| 230 | 230 |
| 231 previous_devices = set(a.GetDeviceSerial() | 231 previous_devices = set(a.GetDeviceSerial() |
| 232 for a in adb_wrapper.AdbWrapper.Devices()) | 232 for a in adb_wrapper.AdbWrapper.Devices()) |
| 233 | 233 |
| 234 KillAllAdb() | 234 KillAllAdb() |
| 235 reset_usb.reset_all_android_devices() | 235 reset_usb.reset_all_android_devices() |
| 236 | 236 |
| 237 try: | 237 try: |
| 238 expected_devices = set(device_list.GetPersistentDeviceList( | 238 expected_devices = set(device_list.GetPersistentDeviceList( |
| 239 os.path.join(args.out_dir, device_list.LAST_DEVICES_FILENAME))) | 239 os.path.join(output_directory, device_list.LAST_DEVICES_FILENAME))) |
| 240 except IOError: | 240 except IOError: |
| 241 expected_devices = set() | 241 expected_devices = set() |
| 242 | 242 |
| 243 all_devices = [device_utils.DeviceUtils(d) | 243 all_devices = [device_utils.DeviceUtils(d) |
| 244 for d in previous_devices.union(expected_devices)] | 244 for d in previous_devices.union(expected_devices)] |
| 245 | 245 |
| 246 def blacklisting_recovery(device): | 246 def blacklisting_recovery(device): |
| 247 try: | 247 try: |
| 248 device.WaitUntilFullyBooted() | 248 device.WaitUntilFullyBooted() |
| 249 except device_errors.CommandFailedError: | 249 except device_errors.CommandFailedError: |
| 250 logging.exception('Failure while waiting for %s. Adding to blacklist.', | 250 logging.exception('Failure while waiting for %s. Adding to blacklist.', |
| 251 str(device)) | 251 str(device)) |
| 252 device_blacklist.ExtendBlacklist([str(device)]) | 252 blacklist.Extend([str(device)]) |
| 253 except device_errors.CommandTimeoutError: | 253 except device_errors.CommandTimeoutError: |
| 254 logging.exception('Timed out while waiting for %s. Adding to blacklist.', | 254 logging.exception('Timed out while waiting for %s. Adding to blacklist.', |
| 255 str(device)) | 255 str(device)) |
| 256 device_blacklist.ExtendBlacklist([str(device)]) | 256 blacklist.Extend([str(device)]) |
| 257 | 257 |
| 258 device_utils.DeviceUtils.parallel(all_devices).pMap(blacklisting_recovery) | 258 device_utils.DeviceUtils.parallel(all_devices).pMap(blacklisting_recovery) |
| 259 | 259 |
| 260 devices = device_utils.DeviceUtils.HealthyDevices() | 260 devices = device_utils.DeviceUtils.HealthyDevices(blacklist) |
| 261 device_serials = set(d.adb.GetDeviceSerial() for d in devices) | 261 device_serials = set(d.adb.GetDeviceSerial() for d in devices) |
| 262 | 262 |
| 263 missing_devices = expected_devices.difference(device_serials) | 263 missing_devices = expected_devices.difference(device_serials) |
| 264 new_devices = device_serials.difference(expected_devices) | 264 new_devices = device_serials.difference(expected_devices) |
| 265 | 265 |
| 266 if missing_devices or new_devices: | 266 if missing_devices or new_devices: |
| 267 logging.warning('expected_devices:') | 267 logging.warning('expected_devices:') |
| 268 for d in sorted(expected_devices): | 268 for d in sorted(expected_devices): |
| 269 logging.warning(' %s', d) | 269 logging.warning(' %s', d) |
| 270 logging.warning('devices:') | 270 logging.warning('devices:') |
| 271 for d in sorted(device_serials): | 271 for d in sorted(device_serials): |
| 272 logging.warning(' %s', d) | 272 logging.warning(' %s', d) |
| 273 | 273 |
| 274 return devices | 274 return devices |
| 275 | 275 |
| 276 | 276 |
| 277 def main(): | 277 def main(): |
| 278 parser = optparse.OptionParser() | 278 parser = argparse.ArgumentParser() |
| 279 parser.add_option('', '--out-dir', | 279 parser.add_argument('--out-dir', |
| 280 help='Directory where the device path is stored', | 280 help='Directory where the device path is stored', |
| 281 default=os.path.join(constants.DIR_SOURCE_ROOT, 'out')) | 281 default=os.path.join(constants.DIR_SOURCE_ROOT, 'out')) |
| 282 parser.add_option('--no-provisioning-check', action='store_true', | 282 parser.add_argument('--no-provisioning-check', action='store_true', |
| 283 help='Will not check if devices are provisioned properly.') | 283 help='Will not check if devices are provisioned ' |
| 284 parser.add_option('--device-status-dashboard', action='store_true', | 284 'properly.') |
| 285 help='Output device status data for dashboard.') | 285 parser.add_argument('--device-status-dashboard', action='store_true', |
| 286 parser.add_option('--restart-usb', action='store_true', | 286 help='Output device status data for dashboard.') |
| 287 help='DEPRECATED. ' | 287 parser.add_argument('--restart-usb', action='store_true', |
| 288 'This script now always tries to reset USB.') | 288 help='DEPRECATED. ' |
| 289 parser.add_option('--json-output', | 289 'This script now always tries to reset USB.') |
| 290 help='Output JSON information into a specified file.') | 290 parser.add_argument('--json-output', |
| 291 parser.add_option('-v', '--verbose', action='count', default=1, | 291 help='Output JSON information into a specified file.') |
| 292 help='Log more information.') | 292 parser.add_argument('--blacklist-file', help='Device blacklist JSON file.') |
| 293 parser.add_argument('-v', '--verbose', action='count', default=1, |
| 294 help='Log more information.') |
| 293 | 295 |
| 294 options, args = parser.parse_args() | 296 args = parser.parse_args() |
| 295 if args: | |
| 296 parser.error('Unknown options %s' % args) | |
| 297 | 297 |
| 298 run_tests_helper.SetLogLevel(options.verbose) | 298 run_tests_helper.SetLogLevel(args.verbose) |
| 299 | 299 |
| 300 devices = RecoverDevices(options) | 300 if args.blacklist_file: |
| 301 blacklist = device_blacklist.Blacklist(args.blacklist_file) |
| 302 else: |
| 303 # TODO(jbudorick): Remove this once bots pass the blacklist file. |
| 304 blacklist = device_blacklist.Blacklist(device_blacklist.BLACKLIST_JSON) |
| 305 |
| 306 devices = RecoverDevices(blacklist, args.out_dir) |
| 301 | 307 |
| 302 types, builds, batteries, errors, devices_ok, json_data = ( | 308 types, builds, batteries, errors, devices_ok, json_data = ( |
| 303 [], [], [], [], [], []) | 309 [], [], [], [], [], []) |
| 304 if devices: | 310 if devices: |
| 305 types, builds, batteries, errors, devices_ok, json_data = ( | 311 types, builds, batteries, errors, devices_ok, json_data = ( |
| 306 zip(*[DeviceInfo(dev, options) for dev in devices])) | 312 zip(*[DeviceInfo(dev, args) for dev in devices])) |
| 307 | 313 |
| 308 # Write device info to file for buildbot info display. | 314 # Write device info to file for buildbot info display. |
| 309 if os.path.exists('/home/chrome-bot'): | 315 if os.path.exists('/home/chrome-bot'): |
| 310 with open('/home/chrome-bot/.adb_device_info', 'w') as f: | 316 with open('/home/chrome-bot/.adb_device_info', 'w') as f: |
| 311 for device in json_data: | 317 for device in json_data: |
| 312 try: | 318 try: |
| 313 f.write('%s %s %s %.1fC %s%%\n' % (device['serial'], device['type'], | 319 f.write('%s %s %s %.1fC %s%%\n' % (device['serial'], device['type'], |
| 314 device['build'], float(device['battery']['temperature']) / 10, | 320 device['build'], float(device['battery']['temperature']) / 10, |
| 315 device['battery']['level'])) | 321 device['battery']['level'])) |
| 316 except Exception: | 322 except Exception: |
| 317 pass | 323 pass |
| 318 | 324 |
| 319 err_msg = CheckForMissingDevices(options, devices) or [] | 325 err_msg = CheckForMissingDevices(args, devices) or [] |
| 320 | 326 |
| 321 unique_types = list(set(types)) | 327 unique_types = list(set(types)) |
| 322 unique_builds = list(set(builds)) | 328 unique_builds = list(set(builds)) |
| 323 | 329 |
| 324 bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s' | 330 bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s' |
| 325 % (len(devices), unique_types, unique_builds)) | 331 % (len(devices), unique_types, unique_builds)) |
| 326 | 332 |
| 327 for j in json_data: | 333 for j in json_data: |
| 328 logging.info('Device %s (%s)', j.get('serial'), j.get('type')) | 334 logging.info('Device %s (%s)', j.get('serial'), j.get('type')) |
| 329 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail')) | 335 logging.info(' Build: %s (%s)', j.get('build'), j.get('build_detail')) |
| (...skipping 13 matching lines...) Expand all Loading... |
| 343 bb_annotations.PrintWarning() | 349 bb_annotations.PrintWarning() |
| 344 for e in err_msg: | 350 for e in err_msg: |
| 345 logging.error(e) | 351 logging.error(e) |
| 346 from_address = 'buildbot@chromium.org' | 352 from_address = 'buildbot@chromium.org' |
| 347 to_addresses = ['chromium-android-device-alerts@google.com'] | 353 to_addresses = ['chromium-android-device-alerts@google.com'] |
| 348 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') | 354 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') |
| 349 slave_name = os.environ.get('BUILDBOT_SLAVENAME') | 355 slave_name = os.environ.get('BUILDBOT_SLAVENAME') |
| 350 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) | 356 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) |
| 351 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg)) | 357 SendEmail(from_address, to_addresses, [], subject, '\n'.join(err_msg)) |
| 352 | 358 |
| 353 if options.device_status_dashboard: | 359 if args.device_status_dashboard: |
| 354 offline_devices = [ | 360 offline_devices = [ |
| 355 device_utils.DeviceUtils(a) | 361 device_utils.DeviceUtils(a) |
| 356 for a in adb_wrapper.AdbWrapper.Devices(is_ready=False) | 362 for a in adb_wrapper.AdbWrapper.Devices(is_ready=False) |
| 357 if a.GetState() == 'offline'] | 363 if a.GetState() == 'offline'] |
| 358 | 364 |
| 359 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices', | 365 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices', |
| 360 [len(devices)], 'devices') | 366 [len(devices)], 'devices') |
| 361 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices', | 367 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices', |
| 362 [len(offline_devices)], 'devices', | 368 [len(offline_devices)], 'devices', |
| 363 'unimportant') | 369 'unimportant') |
| 364 for dev, battery in zip(devices, batteries): | 370 for dev, battery in zip(devices, batteries): |
| 365 perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev), | 371 perf_tests_results_helper.PrintPerfResult('DeviceBattery', str(dev), |
| 366 [battery], '%', | 372 [battery], '%', |
| 367 'unimportant') | 373 'unimportant') |
| 368 | 374 |
| 369 if options.json_output: | 375 if args.json_output: |
| 370 with open(options.json_output, 'wb') as f: | 376 with open(args.json_output, 'wb') as f: |
| 371 f.write(json.dumps(json_data, indent=4)) | 377 f.write(json.dumps(json_data, indent=4)) |
| 372 | 378 |
| 373 num_failed_devs = 0 | 379 num_failed_devs = 0 |
| 374 for device_ok, device in zip(devices_ok, devices): | 380 for device_ok, device in zip(devices_ok, devices): |
| 375 if not device_ok: | 381 if not device_ok: |
| 376 logging.warning('Blacklisting %s', str(device)) | 382 logging.warning('Blacklisting %s', str(device)) |
| 377 device_blacklist.ExtendBlacklist([str(device)]) | 383 blacklist.Extend([str(device)]) |
| 378 num_failed_devs += 1 | 384 num_failed_devs += 1 |
| 379 | 385 |
| 380 if num_failed_devs == len(devices): | 386 if num_failed_devs == len(devices): |
| 381 return 2 | 387 return 2 |
| 382 | 388 |
| 383 if not devices: | 389 if not devices: |
| 384 return 1 | 390 return 1 |
| 385 | 391 |
| 386 | 392 |
| 387 if __name__ == '__main__': | 393 if __name__ == '__main__': |
| 388 sys.exit(main()) | 394 sys.exit(main()) |
| OLD | NEW |