OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright (c) 2012 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.""" | |
8 import logging | |
9 import optparse | |
10 import os | |
11 import smtplib | |
12 import sys | |
13 import re | |
14 | |
15 from pylib import android_commands | |
16 from pylib import buildbot_report | |
17 from pylib import constants | |
18 from pylib.cmd_helper import GetCmdOutput | |
19 | |
20 | |
21 def DeviceInfo(serial, options): | |
22 """Gathers info on a device via various adb calls. | |
23 | |
24 Args: | |
25 serial: The serial of the attached device to construct info about. | |
26 | |
27 Returns: | |
28 Tuple of device type, build id, report as a string, error messages, and | |
29 boolean indicating whether or not device can be used for testing. | |
30 """ | |
31 | |
32 def AdbShellCmd(cmd): | |
33 return GetCmdOutput('adb -s %s shell %s' % (serial, cmd), | |
34 shell=True).strip() | |
35 | |
36 device_adb = android_commands.AndroidCommands(serial) | |
37 | |
38 # TODO(navabi): Replace AdbShellCmd with device_adb. | |
39 device_type = AdbShellCmd('getprop ro.build.product') | |
40 device_build = AdbShellCmd('getprop ro.build.id') | |
41 device_build_type = AdbShellCmd('getprop ro.build.type') | |
42 device_product_name = AdbShellCmd('getprop ro.product.name') | |
43 | |
44 setup_wizard_disabled = AdbShellCmd( | |
45 'getprop ro.setupwizard.mode') == 'DISABLED' | |
46 battery = AdbShellCmd('dumpsys battery') | |
47 install_output = GetCmdOutput( | |
48 ['%s/build/android/adb_install_apk.py' % constants.DIR_SOURCE_ROOT, '--apk', | |
49 '%s/build/android/CheckInstallApk-debug.apk' % constants.DIR_SOURCE_ROOT]) | |
50 install_speed_found = re.findall('(\d+) KB/s', install_output) | |
51 if install_speed_found: | |
52 install_speed = int(install_speed_found[0]) | |
53 else: | |
54 install_speed = 'Unknown' | |
55 if 'Error' in battery: | |
56 ac_power = 'Unknown' | |
57 battery_level = 'Unknown' | |
58 battery_temp = 'Unknown' | |
59 else: | |
60 ac_power = re.findall('AC powered: (\w+)', battery)[0] | |
61 battery_level = int(re.findall('level: (\d+)', battery)[0]) | |
62 battery_temp = float(re.findall('temperature: (\d+)', battery)[0]) / 10 | |
63 report = ['Device %s (%s)' % (serial, device_type), | |
64 ' Build: %s (%s)' % (device_build, | |
65 AdbShellCmd('getprop ro.build.fingerprint')), | |
66 ' Battery: %s%%' % battery_level, | |
67 ' Battery temp: %s' % battery_temp, | |
68 ' IMEI slice: %s' % AdbShellCmd('dumpsys iphonesubinfo ' | |
69 '| grep Device' | |
70 "| awk '{print $4}'")[-6:], | |
71 ' Wifi IP: %s' % AdbShellCmd('getprop dhcp.wlan0.ipaddress'), | |
72 ' Install Speed: %s KB/s' % install_speed, | |
73 ''] | |
74 | |
75 errors = [] | |
76 if battery_level < 15: | |
77 errors += ['Device critically low in battery. Turning off device.'] | |
78 if (not setup_wizard_disabled and device_build_type != 'user' and | |
79 not options.no_provisioning_check): | |
80 errors += ['Setup wizard not disabled. Was it provisioned correctly?'] | |
81 if device_product_name == 'mantaray' and ac_power != 'true': | |
82 errors += ['Mantaray device not connected to AC power.'] | |
83 # TODO(navabi): Insert warning once we have a better handle of what install | |
84 # speeds to expect. The following lines were causing too many alerts. | |
85 # if install_speed < 500: | |
86 # errors += ['Device install speed too low. Do not use for testing.'] | |
87 | |
88 # Causing the device status check step fail for slow install speed or low | |
89 # battery currently is too disruptive to the bots (especially try bots). | |
90 # Turn off devices with low battery and the step does not fail. | |
91 if battery_level < 15: | |
92 device_adb.EnableAdbRoot() | |
93 AdbShellCmd('reboot -p') | |
94 return device_type, device_build, '\n'.join(report), errors, True | |
95 | |
96 | |
97 def CheckForMissingDevices(options, adb_online_devs): | |
98 """Uses file of previous online devices to detect broken phones. | |
99 | |
100 Args: | |
101 options: out_dir parameter of options argument is used as the base | |
102 directory to load and update the cache file. | |
103 adb_online_devs: A list of serial numbers of the currently visible | |
104 and online attached devices. | |
105 """ | |
106 # TODO(navabi): remove this once the bug that causes different number | |
107 # of devices to be detected between calls is fixed. | |
108 logger = logging.getLogger() | |
109 logger.setLevel(logging.INFO) | |
110 | |
111 out_dir = os.path.abspath(options.out_dir) | |
112 | |
113 def ReadDeviceList(file_name): | |
114 devices_path = os.path.join(out_dir, file_name) | |
115 devices = [] | |
116 try: | |
117 with open(devices_path) as f: | |
118 devices = f.read().splitlines() | |
119 except IOError: | |
120 # Ignore error, file might not exist | |
121 pass | |
122 return devices | |
123 | |
124 def WriteDeviceList(file_name, device_list): | |
125 path = os.path.join(out_dir, file_name) | |
126 if not os.path.exists(out_dir): | |
127 os.makedirs(out_dir) | |
128 with open(path, 'w') as f: | |
129 # Write devices currently visible plus devices previously seen. | |
130 f.write('\n'.join(set(device_list))) | |
131 | |
132 last_devices_path = os.path.join(out_dir, '.last_devices') | |
133 last_devices = ReadDeviceList('.last_devices') | |
134 missing_devs = list(set(last_devices) - set(adb_online_devs)) | |
135 | |
136 all_known_devices = list(set(adb_online_devs) | set(last_devices)) | |
137 WriteDeviceList('.last_devices', all_known_devices) | |
138 WriteDeviceList('.last_missing', missing_devs) | |
139 | |
140 if not all_known_devices: | |
141 # This can happen if for some reason the .last_devices file is not | |
142 # present or if it was empty. | |
143 return ['No online devices. Have any devices been plugged in?'] | |
144 if missing_devs: | |
145 devices_missing_msg = '%d devices not detected.' % len(missing_devs) | |
146 buildbot_report.PrintSummaryText(devices_missing_msg) | |
147 | |
148 # TODO(navabi): Debug by printing both output from GetCmdOutput and | |
149 # GetAttachedDevices to compare results. | |
150 return ['Current online devices: %s' % adb_online_devs, | |
151 '%s are no longer visible. Were they removed?\n' % missing_devs, | |
152 'SHERIFF: See go/chrome_device_monitor', | |
153 'Cache file: %s\n\n' % last_devices_path, | |
154 'adb devices: %s' % GetCmdOutput(['adb', 'devices']), | |
155 'adb devices(GetAttachedDevices): %s' % | |
156 android_commands.GetAttachedDevices()] | |
157 else: | |
158 new_devs = set(adb_online_devs) - set(last_devices) | |
159 if new_devs and os.path.exists(last_devices_path): | |
160 buildbot_report.PrintWarning() | |
161 buildbot_report.PrintSummaryText( | |
162 '%d new devices detected' % len(new_devs)) | |
163 print ('New devices detected %s. And now back to your ' | |
164 'regularly scheduled program.' % list(new_devs)) | |
165 | |
166 | |
167 def SendDeviceStatusAlert(msg): | |
168 from_address = 'buildbot@chromium.org' | |
169 to_address = 'chromium-android-device-alerts@google.com' | |
170 bot_name = os.environ.get('BUILDBOT_BUILDERNAME') | |
171 slave_name = os.environ.get('BUILDBOT_SLAVENAME') | |
172 subject = 'Device status check errors on %s, %s.' % (slave_name, bot_name) | |
173 msg_body = '\r\n'.join(['From: %s' % from_address, 'To: %s' % to_address, | |
174 'Subject: %s' % subject, '', msg]) | |
175 try: | |
176 server = smtplib.SMTP('localhost') | |
177 server.sendmail(from_address, [to_address], msg_body) | |
178 server.quit() | |
179 except Exception as e: | |
180 print 'Failed to send alert email. Error: %s' % e | |
181 | |
182 | |
183 def main(): | |
184 parser = optparse.OptionParser() | |
185 parser.add_option('', '--out-dir', | |
186 help='Directory where the device path is stored', | |
187 default=os.path.join(os.path.dirname(__file__), '..', | |
188 '..', 'out')) | |
189 parser.add_option('--no-provisioning-check', | |
190 help='Will not check if devices are provisioned properly.') | |
191 | |
192 options, args = parser.parse_args() | |
193 if args: | |
194 parser.error('Unknown options %s' % args) | |
195 devices = android_commands.GetAttachedDevices() | |
196 types, builds, reports, errors = [], [], [], [] | |
197 fail_step_lst = [] | |
198 if devices: | |
199 types, builds, reports, errors, fail_step_lst = ( | |
200 zip(*[DeviceInfo(dev, options) for dev in devices])) | |
201 | |
202 err_msg = CheckForMissingDevices(options, devices) or [] | |
203 | |
204 unique_types = list(set(types)) | |
205 unique_builds = list(set(builds)) | |
206 | |
207 buildbot_report.PrintMsg('Online devices: %d. Device types %s, builds %s' | |
208 % (len(devices), unique_types, unique_builds)) | |
209 print '\n'.join(reports) | |
210 | |
211 for serial, dev_errors in zip(devices, errors): | |
212 if dev_errors: | |
213 err_msg += ['%s errors:' % serial] | |
214 err_msg += [' %s' % error for error in dev_errors] | |
215 | |
216 if err_msg: | |
217 buildbot_report.PrintWarning() | |
218 msg = '\n'.join(err_msg) | |
219 print msg | |
220 SendDeviceStatusAlert(msg) | |
221 | |
222 if False in fail_step_lst: | |
223 # TODO(navabi): Build fails on device status check step if there exists any | |
224 # devices with critically low battery or install speed. Remove those devices | |
225 # from testing, allowing build to continue with good devices. | |
226 return 1 | |
227 | |
228 if not devices: | |
229 return 1 | |
230 | |
231 | |
232 if __name__ == '__main__': | |
233 sys.exit(main()) | |
OLD | NEW |