blob: ae0bb4a7b18b1aed4be9c850c78651166b296bee [file] [log] [blame]
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001#!/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."""
8import logging
9import optparse
10import os
11import smtplib
12import sys
13import re
Ben Murdochbb1529c2013-08-08 10:24:53 +010014import urllib
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010015
16import bb_annotations
17
18sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
19from pylib import android_commands
20from pylib import constants
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010021from pylib import perf_tests_helper
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010022from pylib.cmd_helper import GetCmdOutput
23
24
25def DeviceInfo(serial, options):
26 """Gathers info on a device via various adb calls.
27
28 Args:
29 serial: The serial of the attached device to construct info about.
30
31 Returns:
32 Tuple of device type, build id, report as a string, error messages, and
33 boolean indicating whether or not device can be used for testing.
34 """
35
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010036 device_adb = android_commands.AndroidCommands(serial)
37
38 # TODO(navabi): Replace AdbShellCmd with device_adb.
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010039 device_type = device_adb.GetBuildProduct()
40 device_build = device_adb.GetBuildId()
41 device_build_type = device_adb.GetBuildType()
42 device_product_name = device_adb.GetProductName()
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010043
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010044 setup_wizard_disabled = device_adb.GetSetupWizardStatus() == 'DISABLED'
45 battery = device_adb.GetBatteryInfo()
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010046 install_output = GetCmdOutput(
47 ['%s/build/android/adb_install_apk.py' % constants.DIR_SOURCE_ROOT, '--apk',
48 '%s/build/android/CheckInstallApk-debug.apk' % constants.DIR_SOURCE_ROOT])
Ben Murdoch58e6fbe2013-07-26 10:20:38 +010049
50 def _GetData(re_expression, line, lambda_function=lambda x:x):
51 if not line:
52 return 'Unknown'
53 found = re.findall(re_expression, line)
54 if found and len(found):
55 return lambda_function(found[0])
56 return 'Unknown'
57
58 install_speed = _GetData('(\d+) KB/s', install_output)
59 ac_power = _GetData('AC powered: (\w+)', battery)
60 battery_level = _GetData('level: (\d+)', battery)
61 battery_temp = _GetData('temperature: (\d+)', battery,
62 lambda x: float(x) / 10.0)
63 imei_slice = _GetData('Device ID = (\d+)',
64 device_adb.GetSubscriberInfo(),
65 lambda x: x[-6:])
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010066 report = ['Device %s (%s)' % (serial, device_type),
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010067 ' Build: %s (%s)' %
68 (device_build, device_adb.GetBuildFingerprint()),
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010069 ' Battery: %s%%' % battery_level,
70 ' Battery temp: %s' % battery_temp,
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010071 ' IMEI slice: %s' % imei_slice,
72 ' Wifi IP: %s' % device_adb.GetWifiIP(),
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010073 ' Install Speed: %s KB/s' % install_speed,
74 '']
75
76 errors = []
77 if battery_level < 15:
78 errors += ['Device critically low in battery. Turning off device.']
79 if (not setup_wizard_disabled and device_build_type != 'user' and
80 not options.no_provisioning_check):
81 errors += ['Setup wizard not disabled. Was it provisioned correctly?']
82 if device_product_name == 'mantaray' and ac_power != 'true':
83 errors += ['Mantaray device not connected to AC power.']
84 # TODO(navabi): Insert warning once we have a better handle of what install
85 # speeds to expect. The following lines were causing too many alerts.
86 # if install_speed < 500:
87 # errors += ['Device install speed too low. Do not use for testing.']
88
89 # Causing the device status check step fail for slow install speed or low
90 # battery currently is too disruptive to the bots (especially try bots).
91 # Turn off devices with low battery and the step does not fail.
92 if battery_level < 15:
93 device_adb.EnableAdbRoot()
Ben Murdocha3f7b4e2013-07-24 10:36:34 +010094 device_adb.Shutdown()
95 full_report = '\n'.join(report)
96 return device_type, device_build, battery_level, full_report, errors, True
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010097
98
99def CheckForMissingDevices(options, adb_online_devs):
100 """Uses file of previous online devices to detect broken phones.
101
102 Args:
103 options: out_dir parameter of options argument is used as the base
104 directory to load and update the cache file.
105 adb_online_devs: A list of serial numbers of the currently visible
106 and online attached devices.
107 """
108 # TODO(navabi): remove this once the bug that causes different number
109 # of devices to be detected between calls is fixed.
110 logger = logging.getLogger()
111 logger.setLevel(logging.INFO)
112
113 out_dir = os.path.abspath(options.out_dir)
114
115 def ReadDeviceList(file_name):
116 devices_path = os.path.join(out_dir, file_name)
117 devices = []
118 try:
119 with open(devices_path) as f:
120 devices = f.read().splitlines()
121 except IOError:
122 # Ignore error, file might not exist
123 pass
124 return devices
125
126 def WriteDeviceList(file_name, device_list):
127 path = os.path.join(out_dir, file_name)
128 if not os.path.exists(out_dir):
129 os.makedirs(out_dir)
130 with open(path, 'w') as f:
131 # Write devices currently visible plus devices previously seen.
132 f.write('\n'.join(set(device_list)))
133
134 last_devices_path = os.path.join(out_dir, '.last_devices')
135 last_devices = ReadDeviceList('.last_devices')
136 missing_devs = list(set(last_devices) - set(adb_online_devs))
137
138 all_known_devices = list(set(adb_online_devs) | set(last_devices))
139 WriteDeviceList('.last_devices', all_known_devices)
140 WriteDeviceList('.last_missing', missing_devs)
141
142 if not all_known_devices:
143 # This can happen if for some reason the .last_devices file is not
144 # present or if it was empty.
145 return ['No online devices. Have any devices been plugged in?']
146 if missing_devs:
147 devices_missing_msg = '%d devices not detected.' % len(missing_devs)
148 bb_annotations.PrintSummaryText(devices_missing_msg)
149
150 # TODO(navabi): Debug by printing both output from GetCmdOutput and
151 # GetAttachedDevices to compare results.
Ben Murdochbb1529c2013-08-08 10:24:53 +0100152 crbug_link = ('https://code.google.com/p/chromium/issues/entry?summary='
153 '%s&comment=%s&labels=Restrict-View-Google,OS-Android,Infra' %
154 (urllib.quote('Device Offline'),
155 urllib.quote('Buildbot: %s %s\n'
156 'Build: %s\n'
157 '(please don\'t change any labels)' %
158 (os.environ.get('BUILDBOT_BUILDERNAME'),
159 os.environ.get('BUILDBOT_SLAVENAME'),
160 os.environ.get('BUILDBOT_BUILDNUMBER')))))
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100161 return ['Current online devices: %s' % adb_online_devs,
162 '%s are no longer visible. Were they removed?\n' % missing_devs,
Ben Murdochbb1529c2013-08-08 10:24:53 +0100163 'SHERIFF:\n',
164 '@@@STEP_LINK@Click here to file a bug@%s@@@\n' % crbug_link,
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100165 'Cache file: %s\n\n' % last_devices_path,
166 'adb devices: %s' % GetCmdOutput(['adb', 'devices']),
167 'adb devices(GetAttachedDevices): %s' %
168 android_commands.GetAttachedDevices()]
169 else:
170 new_devs = set(adb_online_devs) - set(last_devices)
171 if new_devs and os.path.exists(last_devices_path):
172 bb_annotations.PrintWarning()
173 bb_annotations.PrintSummaryText(
174 '%d new devices detected' % len(new_devs))
175 print ('New devices detected %s. And now back to your '
176 'regularly scheduled program.' % list(new_devs))
177
178
179def SendDeviceStatusAlert(msg):
180 from_address = 'buildbot@chromium.org'
181 to_address = 'chromium-android-device-alerts@google.com'
182 bot_name = os.environ.get('BUILDBOT_BUILDERNAME')
183 slave_name = os.environ.get('BUILDBOT_SLAVENAME')
184 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name)
185 msg_body = '\r\n'.join(['From: %s' % from_address, 'To: %s' % to_address,
186 'Subject: %s' % subject, '', msg])
187 try:
188 server = smtplib.SMTP('localhost')
189 server.sendmail(from_address, [to_address], msg_body)
190 server.quit()
191 except Exception as e:
192 print 'Failed to send alert email. Error: %s' % e
193
194
195def main():
196 parser = optparse.OptionParser()
197 parser.add_option('', '--out-dir',
198 help='Directory where the device path is stored',
Ben Murdochca12bfa2013-07-23 11:17:05 +0100199 default=os.path.join(constants.DIR_SOURCE_ROOT, 'out'))
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100200 parser.add_option('--no-provisioning-check',
201 help='Will not check if devices are provisioned properly.')
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100202 parser.add_option('--device-status-dashboard',
203 help='Output device status data for dashboard.')
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100204 options, args = parser.parse_args()
205 if args:
206 parser.error('Unknown options %s' % args)
207 devices = android_commands.GetAttachedDevices()
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100208 # TODO(navabi): Test to make sure this fails and then fix call
209 offline_devices = android_commands.GetAttachedDevices(hardware=False,
210 emulator=False,
211 offline=True)
212
213 types, builds, batteries, reports, errors = [], [], [], [], []
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100214 fail_step_lst = []
215 if devices:
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100216 types, builds, batteries, reports, errors, fail_step_lst = (
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100217 zip(*[DeviceInfo(dev, options) for dev in devices]))
218
219 err_msg = CheckForMissingDevices(options, devices) or []
220
221 unique_types = list(set(types))
222 unique_builds = list(set(builds))
223
224 bb_annotations.PrintMsg('Online devices: %d. Device types %s, builds %s'
225 % (len(devices), unique_types, unique_builds))
226 print '\n'.join(reports)
227
228 for serial, dev_errors in zip(devices, errors):
229 if dev_errors:
230 err_msg += ['%s errors:' % serial]
231 err_msg += [' %s' % error for error in dev_errors]
232
233 if err_msg:
234 bb_annotations.PrintWarning()
235 msg = '\n'.join(err_msg)
236 print msg
237 SendDeviceStatusAlert(msg)
238
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100239 if options.device_status_dashboard:
240 perf_tests_helper.PrintPerfResult('BotDevices', 'OnlineDevices',
241 [len(devices)], 'devices')
242 perf_tests_helper.PrintPerfResult('BotDevices', 'OfflineDevices',
243 [len(offline_devices)], 'devices',
244 'unimportant')
245 for serial, battery in zip(devices, batteries):
246 perf_tests_helper.PrintPerfResult('DeviceBattery', serial, [battery], '%',
247 'unimportant')
248
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100249 if False in fail_step_lst:
250 # TODO(navabi): Build fails on device status check step if there exists any
251 # devices with critically low battery or install speed. Remove those devices
252 # from testing, allowing build to continue with good devices.
253 return 1
254
255 if not devices:
256 return 1
257
258
259if __name__ == '__main__':
260 sys.exit(main())