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 |