michael@0: #!/usr/bin/env python michael@0: # michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: """A class to keep track of devices across builds and report state.""" michael@0: import logging michael@0: import optparse michael@0: import os michael@0: import smtplib michael@0: import sys michael@0: michael@0: from pylib import buildbot_report michael@0: from pylib.android_commands import GetAttachedDevices michael@0: from pylib.cmd_helper import GetCmdOutput michael@0: michael@0: michael@0: def DeviceInfo(serial): michael@0: """Gathers info on a device via various adb calls. michael@0: michael@0: Args: michael@0: serial: The serial of the attached device to construct info about. michael@0: michael@0: Returns: michael@0: Tuple of device type, build id and report as a string. michael@0: """ michael@0: michael@0: def AdbShellCmd(cmd): michael@0: return GetCmdOutput('adb -s %s shell %s' % (serial, cmd), michael@0: shell=True).strip() michael@0: michael@0: device_type = AdbShellCmd('getprop ro.build.product') michael@0: device_build = AdbShellCmd('getprop ro.build.id') michael@0: michael@0: report = ['Device %s (%s)' % (serial, device_type), michael@0: ' Build: %s (%s)' % (device_build, michael@0: AdbShellCmd('getprop ro.build.fingerprint')), michael@0: ' Battery: %s%%' % AdbShellCmd('dumpsys battery | grep level ' michael@0: "| awk '{print $2}'"), michael@0: ' Battery temp: %s' % AdbShellCmd('dumpsys battery' michael@0: '| grep temp ' michael@0: "| awk '{print $2}'"), michael@0: ' IMEI slice: %s' % AdbShellCmd('dumpsys iphonesubinfo ' michael@0: '| grep Device' michael@0: "| awk '{print $4}'")[-6:], michael@0: ' Wifi IP: %s' % AdbShellCmd('getprop dhcp.wlan0.ipaddress'), michael@0: ''] michael@0: michael@0: return device_type, device_build, '\n'.join(report) michael@0: michael@0: michael@0: def CheckForMissingDevices(options, adb_online_devs): michael@0: """Uses file of previous online devices to detect broken phones. michael@0: michael@0: Args: michael@0: options: out_dir parameter of options argument is used as the base michael@0: directory to load and update the cache file. michael@0: adb_online_devs: A list of serial numbers of the currently visible michael@0: and online attached devices. michael@0: """ michael@0: # TODO(navabi): remove this once the bug that causes different number michael@0: # of devices to be detected between calls is fixed. michael@0: logger = logging.getLogger() michael@0: logger.setLevel(logging.INFO) michael@0: michael@0: out_dir = os.path.abspath(options.out_dir) michael@0: michael@0: def ReadDeviceList(file_name): michael@0: devices_path = os.path.join(out_dir, file_name) michael@0: devices = [] michael@0: try: michael@0: with open(devices_path) as f: michael@0: devices = f.read().splitlines() michael@0: except IOError: michael@0: # Ignore error, file might not exist michael@0: pass michael@0: return devices michael@0: michael@0: def WriteDeviceList(file_name, device_list): michael@0: path = os.path.join(out_dir, file_name) michael@0: if not os.path.exists(out_dir): michael@0: os.makedirs(out_dir) michael@0: with open(path, 'w') as f: michael@0: # Write devices currently visible plus devices previously seen. michael@0: f.write('\n'.join(set(device_list))) michael@0: michael@0: last_devices_path = os.path.join(out_dir, '.last_devices') michael@0: last_devices = ReadDeviceList('.last_devices') michael@0: michael@0: missing_devs = list(set(last_devices) - set(adb_online_devs)) michael@0: if missing_devs: michael@0: from_address = 'buildbot@chromium.org' michael@0: to_address = 'chromium-android-device-alerts@google.com' michael@0: bot_name = os.environ['BUILDBOT_BUILDERNAME'] michael@0: slave_name = os.environ['BUILDBOT_SLAVENAME'] michael@0: num_online_devs = len(adb_online_devs) michael@0: subject = 'Devices offline on %s, %s (%d remaining).' % (slave_name, michael@0: bot_name, michael@0: num_online_devs) michael@0: buildbot_report.PrintWarning() michael@0: devices_missing_msg = '%d devices not detected.' % len(missing_devs) michael@0: buildbot_report.PrintSummaryText(devices_missing_msg) michael@0: michael@0: # TODO(navabi): Debug by printing both output from GetCmdOutput and michael@0: # GetAttachedDevices to compare results. michael@0: body = '\n'.join( michael@0: ['Current online devices: %s' % adb_online_devs, michael@0: '%s are no longer visible. Were they removed?\n' % missing_devs, michael@0: 'SHERIFF: See go/chrome_device_monitor', michael@0: 'Cache file: %s\n\n' % last_devices_path, michael@0: 'adb devices: %s' % GetCmdOutput(['adb', 'devices']), michael@0: 'adb devices(GetAttachedDevices): %s' % GetAttachedDevices()]) michael@0: michael@0: print body michael@0: michael@0: # Only send email if the first time a particular device goes offline michael@0: last_missing = ReadDeviceList('.last_missing') michael@0: new_missing_devs = set(missing_devs) - set(last_missing) michael@0: michael@0: if new_missing_devs: michael@0: msg_body = '\r\n'.join( michael@0: ['From: %s' % from_address, michael@0: 'To: %s' % to_address, michael@0: 'Subject: %s' % subject, michael@0: '', body]) michael@0: try: michael@0: server = smtplib.SMTP('localhost') michael@0: server.sendmail(from_address, [to_address], msg_body) michael@0: server.quit() michael@0: except Exception as e: michael@0: print 'Failed to send alert email. Error: %s' % e michael@0: else: michael@0: new_devs = set(adb_online_devs) - set(last_devices) michael@0: if new_devs and os.path.exists(last_devices_path): michael@0: buildbot_report.PrintWarning() michael@0: buildbot_report.PrintSummaryText( michael@0: '%d new devices detected' % len(new_devs)) michael@0: print ('New devices detected %s. And now back to your ' michael@0: 'regularly scheduled program.' % list(new_devs)) michael@0: WriteDeviceList('.last_devices', (adb_online_devs + last_devices)) michael@0: WriteDeviceList('.last_missing', missing_devs) michael@0: michael@0: michael@0: def main(): michael@0: parser = optparse.OptionParser() michael@0: parser.add_option('', '--out-dir', michael@0: help='Directory where the device path is stored', michael@0: default=os.path.join(os.path.dirname(__file__), '..', michael@0: '..', 'out')) michael@0: michael@0: options, args = parser.parse_args() michael@0: if args: michael@0: parser.error('Unknown options %s' % args) michael@0: buildbot_report.PrintNamedStep('Device Status Check') michael@0: devices = GetAttachedDevices() michael@0: types, builds, reports = [], [], [] michael@0: if devices: michael@0: types, builds, reports = zip(*[DeviceInfo(dev) for dev in devices]) michael@0: michael@0: unique_types = list(set(types)) michael@0: unique_builds = list(set(builds)) michael@0: michael@0: buildbot_report.PrintMsg('Online devices: %d. Device types %s, builds %s' michael@0: % (len(devices), unique_types, unique_builds)) michael@0: print '\n'.join(reports) michael@0: CheckForMissingDevices(options, devices) michael@0: michael@0: if __name__ == '__main__': michael@0: sys.exit(main())