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.
7 """A class to keep track of devices across builds and report state."""
24 sys.path.append(os.path.join(os.path.dirname(__file__),
25 os.pardir, os.pardir, 'util', 'lib',
27 import perf_tests_results_helper # pylint: disable=F0401
29 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
30 from pylib import android_commands
31 from pylib import constants
32 from pylib.cmd_helper import GetCmdOutput
33 from pylib.device import device_blacklist
34 from pylib.device import device_list
35 from pylib.device import device_utils
37 def DeviceInfo(serial, options):
38 """Gathers info on a device via various adb calls.
41 serial: The serial of the attached device to construct info about.
44 Tuple of device type, build id, report as a string, error messages, and
45 boolean indicating whether or not device can be used for testing.
48 device_adb = device_utils.DeviceUtils(serial)
49 device_type = device_adb.GetProp('ro.build.product')
50 device_build = device_adb.GetProp('ro.build.id')
51 device_build_type = device_adb.GetProp('ro.build.type')
52 device_product_name = device_adb.GetProp('ro.product.name')
55 battery_info = device_adb.old_interface.GetBatteryInfo()
56 except Exception as e:
58 logging.error('Unable to obtain battery info for %s, %s', serial, e)
60 def _GetData(re_expression, line, lambda_function=lambda x:x):
63 found = re.findall(re_expression, line)
64 if found and len(found):
65 return lambda_function(found[0])
68 battery_level = int(battery_info.get('level', 100))
69 imei_slice = _GetData('Device ID = (\d+)',
70 device_adb.old_interface.GetSubscriberInfo(),
72 report = ['Device %s (%s)' % (serial, device_type),
74 (device_build, device_adb.GetProp('ro.build.fingerprint')),
75 ' Current Battery Service state: ',
76 '\n'.join([' %s: %s' % (k, v)
77 for k, v in battery_info.iteritems()]),
78 ' IMEI slice: %s' % imei_slice,
79 ' Wifi IP: %s' % device_adb.GetProp('dhcp.wlan0.ipaddress'),
84 if battery_level < 15:
85 errors += ['Device critically low in battery. Will add to blacklist.']
87 if not device_adb.old_interface.IsDeviceCharging():
88 if device_adb.old_interface.CanControlUsbCharging():
89 device_adb.old_interface.EnableUsbCharging()
91 logging.error('Device %s is not charging' % serial)
92 if not options.no_provisioning_check:
93 setup_wizard_disabled = (
94 device_adb.GetProp('ro.setupwizard.mode') == 'DISABLED')
95 if not setup_wizard_disabled and device_build_type != 'user':
96 errors += ['Setup wizard not disabled. Was it provisioned correctly?']
97 if (device_product_name == 'mantaray' and
98 battery_info.get('AC powered', None) != 'true'):
99 errors += ['Mantaray device not connected to AC power.']
101 full_report = '\n'.join(report)
102 return device_type, device_build, battery_level, full_report, errors, dev_good
105 def CheckForMissingDevices(options, adb_online_devs):
106 """Uses file of previous online devices to detect broken phones.
109 options: out_dir parameter of options argument is used as the base
110 directory to load and update the cache file.
111 adb_online_devs: A list of serial numbers of the currently visible
112 and online attached devices.
114 # TODO(navabi): remove this once the bug that causes different number
115 # of devices to be detected between calls is fixed.
116 logger = logging.getLogger()
117 logger.setLevel(logging.INFO)
119 out_dir = os.path.abspath(options.out_dir)
121 # last_devices denotes all known devices prior to this run
122 last_devices_path = os.path.join(out_dir, device_list.LAST_DEVICES_FILENAME)
123 last_missing_devices_path = os.path.join(out_dir,
124 device_list.LAST_MISSING_DEVICES_FILENAME)
126 last_devices = device_list.GetPersistentDeviceList(last_devices_path)
128 # Ignore error, file might not exist
132 last_missing_devices = device_list.GetPersistentDeviceList(
133 last_missing_devices_path)
135 last_missing_devices = []
137 missing_devs = list(set(last_devices) - set(adb_online_devs))
138 new_missing_devs = list(set(missing_devs) - set(last_missing_devices))
140 if new_missing_devs and os.environ.get('BUILDBOT_SLAVENAME'):
141 logging.info('new_missing_devs %s' % new_missing_devs)
142 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
143 bb_annotations.PrintSummaryText(devices_missing_msg)
145 from_address = 'chrome-bot@chromium.org'
146 to_addresses = ['chrome-labs-tech-ticket@google.com',
147 'chrome-android-device-alert@google.com']
148 cc_addresses = ['chrome-android-device-alert@google.com']
149 subject = 'Devices offline on %s, %s, %s' % (
150 os.environ.get('BUILDBOT_SLAVENAME'),
151 os.environ.get('BUILDBOT_BUILDERNAME'),
152 os.environ.get('BUILDBOT_BUILDNUMBER'))
153 msg = ('Please reboot the following devices:\n%s' %
154 '\n'.join(map(str,new_missing_devs)))
155 SendEmail(from_address, to_addresses, cc_addresses, subject, msg)
157 all_known_devices = list(set(adb_online_devs) | set(last_devices))
158 device_list.WritePersistentDeviceList(last_devices_path, all_known_devices)
159 device_list.WritePersistentDeviceList(last_missing_devices_path, missing_devs)
161 if not all_known_devices:
162 # This can happen if for some reason the .last_devices file is not
163 # present or if it was empty.
164 return ['No online devices. Have any devices been plugged in?']
166 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
167 bb_annotations.PrintSummaryText(devices_missing_msg)
169 # TODO(navabi): Debug by printing both output from GetCmdOutput and
170 # GetAttachedDevices to compare results.
171 crbug_link = ('https://code.google.com/p/chromium/issues/entry?summary='
172 '%s&comment=%s&labels=Restrict-View-Google,OS-Android,Infra' %
173 (urllib.quote('Device Offline'),
174 urllib.quote('Buildbot: %s %s\n'
176 '(please don\'t change any labels)' %
177 (os.environ.get('BUILDBOT_BUILDERNAME'),
178 os.environ.get('BUILDBOT_SLAVENAME'),
179 os.environ.get('BUILDBOT_BUILDNUMBER')))))
180 return ['Current online devices: %s' % adb_online_devs,
181 '%s are no longer visible. Were they removed?\n' % missing_devs,
183 '@@@STEP_LINK@Click here to file a bug@%s@@@\n' % crbug_link,
184 'Cache file: %s\n\n' % last_devices_path,
185 'adb devices: %s' % GetCmdOutput(['adb', 'devices']),
186 'adb devices(GetAttachedDevices): %s' % adb_online_devs]
188 new_devs = set(adb_online_devs) - set(last_devices)
189 if new_devs and os.path.exists(last_devices_path):
190 bb_annotations.PrintWarning()
191 bb_annotations.PrintSummaryText(
192 '%d new devices detected' % len(new_devs))
193 print ('New devices detected %s. And now back to your '
194 'regularly scheduled program.' % list(new_devs))
197 def SendEmail(from_address, to_addresses, cc_addresses, subject, msg):
198 msg_body = '\r\n'.join(['From: %s' % from_address,
199 'To: %s' % ', '.join(to_addresses),
200 'CC: %s' % ', '.join(cc_addresses),
201 'Subject: %s' % subject, '', msg])
203 server = smtplib.SMTP('localhost')
204 server.sendmail(from_address, to_addresses, msg_body)
206 except Exception as e:
207 print 'Failed to send alert email. Error: %s' % e
211 if not os.path.isfile('/usr/bin/restart_usb'):
212 print ('ERROR: Could not restart usb. /usr/bin/restart_usb not installed '
213 'on host (see BUG=305769).')
216 lsusb_proc = bb_utils.SpawnCmd(['lsusb'], stdout=subprocess.PIPE)
217 lsusb_output, _ = lsusb_proc.communicate()
218 if lsusb_proc.returncode:
219 print ('Error: Could not get list of USB ports (i.e. lsusb).')
220 return lsusb_proc.returncode
222 usb_devices = [re.findall('Bus (\d\d\d) Device (\d\d\d)', lsusb_line)[0]
223 for lsusb_line in lsusb_output.strip().split('\n')]
226 # Walk USB devices from leaves up (i.e reverse sorted) restarting the
227 # connection. If a parent node (e.g. usb hub) is restarted before the
228 # devices connected to it, the (bus, dev) for the hub can change, making the
229 # output we have wrong. This way we restart the devices before the hub.
230 for (bus, dev) in reversed(sorted(usb_devices)):
231 # Can not restart root usb connections
233 return_code = bb_utils.RunCmd(['/usr/bin/restart_usb', bus, dev])
235 print 'Error restarting USB device /dev/bus/usb/%s/%s' % (bus, dev)
236 all_restarted = False
238 print 'Restarted USB device /dev/bus/usb/%s/%s' % (bus, dev)
245 for p in psutil.process_iter():
249 except (psutil.NoSuchProcess, psutil.AccessDenied):
252 for sig in [signal.SIGTERM, signal.SIGQUIT, signal.SIGKILL]:
253 for p in GetAllAdb():
255 print 'kill %d %d (%s [%s])' % (sig, p.pid, p.name,
258 except (psutil.NoSuchProcess, psutil.AccessDenied):
260 for p in GetAllAdb():
262 print 'Unable to kill %d (%s [%s])' % (p.pid, p.name, ' '.join(p.cmdline))
263 except (psutil.NoSuchProcess, psutil.AccessDenied):
268 parser = optparse.OptionParser()
269 parser.add_option('', '--out-dir',
270 help='Directory where the device path is stored',
271 default=os.path.join(constants.DIR_SOURCE_ROOT, 'out'))
272 parser.add_option('--no-provisioning-check', action='store_true',
273 help='Will not check if devices are provisioned properly.')
274 parser.add_option('--device-status-dashboard', action='store_true',
275 help='Output device status data for dashboard.')
276 parser.add_option('--restart-usb', action='store_true',
277 help='Restart USB ports before running device check.')
278 parser.add_option('--json-output',
279 help='Output JSON information into a specified file.')
281 options, args = parser.parse_args()
283 parser.error('Unknown options %s' % args)
285 # Remove the last build's "bad devices" before checking device statuses.
286 device_blacklist.ResetBlacklist()
289 expected_devices = device_list.GetPersistentDeviceList(
290 os.path.join(options.out_dir, device_list.LAST_DEVICES_FILENAME))
292 expected_devices = []
293 devices = android_commands.GetAttachedDevices()
294 # Only restart usb if devices are missing.
295 if set(expected_devices) != set(devices):
296 print 'expected_devices: %s, devices: %s' % (expected_devices, devices)
300 if options.restart_usb:
302 usb_restarted = False
303 bb_annotations.PrintWarning()
304 print 'USB reset stage failed, wait for any device to come back.'
306 print 'retry adb devices...'
308 devices = android_commands.GetAttachedDevices()
309 if set(expected_devices) == set(devices):
310 # All devices are online, keep going.
312 if not usb_restarted and devices:
313 # The USB wasn't restarted, but there's at least one device online.
314 # No point in trying to wait for all devices.
318 # TODO(navabi): Test to make sure this fails and then fix call
319 offline_devices = android_commands.GetAttachedDevices(
320 hardware=False, emulator=False, offline=True)
322 types, builds, batteries, reports, errors = [], [], [], [], []
325 types, builds, batteries, reports, errors, fail_step_lst = (
326 zip(*[DeviceInfo(dev, options) for dev in devices]))
328 err_msg = CheckForMissingDevices(options, devices) or []
330 unique_types = list(set(types))
331 unique_builds = list(set(builds))
333 bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s'
334 % (len(devices), unique_types, unique_builds))
335 print '\n'.join(reports)
337 for serial, dev_errors in zip(devices, errors):
339 err_msg += ['%s errors:' % serial]
340 err_msg += [' %s' % error for error in dev_errors]
343 bb_annotations.PrintWarning()
344 msg = '\n'.join(err_msg)
346 from_address = 'buildbot@chromium.org'
347 to_addresses = ['chromium-android-device-alerts@google.com']
348 bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
349 slave_name = os.environ.get('BUILDBOT_SLAVENAME')
350 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
351 SendEmail(from_address, to_addresses, [], subject, msg)
353 if options.device_status_dashboard:
354 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
355 [len(devices)], 'devices')
356 perf_tests_results_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
357 [len(offline_devices)], 'devices',
359 for serial, battery in zip(devices, batteries):
360 perf_tests_results_helper.PrintPerfResult('DeviceBattery', serial,
364 if options.json_output:
365 with open(options.json_output, 'wb') as f:
367 'online_devices': devices,
368 'offline_devices': offline_devices,
369 'expected_devices': expected_devices,
370 'unique_types': unique_types,
371 'unique_builds': unique_builds,
375 for fail_status, device in zip(fail_step_lst, devices):
377 device_blacklist.ExtendBlacklist([str(device)])
380 if num_failed_devs == len(devices):
387 if __name__ == '__main__':