|
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 |
|
14 from pylib import buildbot_report |
|
15 from pylib.android_commands import GetAttachedDevices |
|
16 from pylib.cmd_helper import GetCmdOutput |
|
17 |
|
18 |
|
19 def DeviceInfo(serial): |
|
20 """Gathers info on a device via various adb calls. |
|
21 |
|
22 Args: |
|
23 serial: The serial of the attached device to construct info about. |
|
24 |
|
25 Returns: |
|
26 Tuple of device type, build id and report as a string. |
|
27 """ |
|
28 |
|
29 def AdbShellCmd(cmd): |
|
30 return GetCmdOutput('adb -s %s shell %s' % (serial, cmd), |
|
31 shell=True).strip() |
|
32 |
|
33 device_type = AdbShellCmd('getprop ro.build.product') |
|
34 device_build = AdbShellCmd('getprop ro.build.id') |
|
35 |
|
36 report = ['Device %s (%s)' % (serial, device_type), |
|
37 ' Build: %s (%s)' % (device_build, |
|
38 AdbShellCmd('getprop ro.build.fingerprint')), |
|
39 ' Battery: %s%%' % AdbShellCmd('dumpsys battery | grep level ' |
|
40 "| awk '{print $2}'"), |
|
41 ' Battery temp: %s' % AdbShellCmd('dumpsys battery' |
|
42 '| grep temp ' |
|
43 "| awk '{print $2}'"), |
|
44 ' IMEI slice: %s' % AdbShellCmd('dumpsys iphonesubinfo ' |
|
45 '| grep Device' |
|
46 "| awk '{print $4}'")[-6:], |
|
47 ' Wifi IP: %s' % AdbShellCmd('getprop dhcp.wlan0.ipaddress'), |
|
48 ''] |
|
49 |
|
50 return device_type, device_build, '\n'.join(report) |
|
51 |
|
52 |
|
53 def CheckForMissingDevices(options, adb_online_devs): |
|
54 """Uses file of previous online devices to detect broken phones. |
|
55 |
|
56 Args: |
|
57 options: out_dir parameter of options argument is used as the base |
|
58 directory to load and update the cache file. |
|
59 adb_online_devs: A list of serial numbers of the currently visible |
|
60 and online attached devices. |
|
61 """ |
|
62 # TODO(navabi): remove this once the bug that causes different number |
|
63 # of devices to be detected between calls is fixed. |
|
64 logger = logging.getLogger() |
|
65 logger.setLevel(logging.INFO) |
|
66 |
|
67 out_dir = os.path.abspath(options.out_dir) |
|
68 |
|
69 def ReadDeviceList(file_name): |
|
70 devices_path = os.path.join(out_dir, file_name) |
|
71 devices = [] |
|
72 try: |
|
73 with open(devices_path) as f: |
|
74 devices = f.read().splitlines() |
|
75 except IOError: |
|
76 # Ignore error, file might not exist |
|
77 pass |
|
78 return devices |
|
79 |
|
80 def WriteDeviceList(file_name, device_list): |
|
81 path = os.path.join(out_dir, file_name) |
|
82 if not os.path.exists(out_dir): |
|
83 os.makedirs(out_dir) |
|
84 with open(path, 'w') as f: |
|
85 # Write devices currently visible plus devices previously seen. |
|
86 f.write('\n'.join(set(device_list))) |
|
87 |
|
88 last_devices_path = os.path.join(out_dir, '.last_devices') |
|
89 last_devices = ReadDeviceList('.last_devices') |
|
90 |
|
91 missing_devs = list(set(last_devices) - set(adb_online_devs)) |
|
92 if missing_devs: |
|
93 from_address = 'buildbot@chromium.org' |
|
94 to_address = 'chromium-android-device-alerts@google.com' |
|
95 bot_name = os.environ['BUILDBOT_BUILDERNAME'] |
|
96 slave_name = os.environ['BUILDBOT_SLAVENAME'] |
|
97 num_online_devs = len(adb_online_devs) |
|
98 subject = 'Devices offline on %s, %s (%d remaining).' % (slave_name, |
|
99 bot_name, |
|
100 num_online_devs) |
|
101 buildbot_report.PrintWarning() |
|
102 devices_missing_msg = '%d devices not detected.' % len(missing_devs) |
|
103 buildbot_report.PrintSummaryText(devices_missing_msg) |
|
104 |
|
105 # TODO(navabi): Debug by printing both output from GetCmdOutput and |
|
106 # GetAttachedDevices to compare results. |
|
107 body = '\n'.join( |
|
108 ['Current online devices: %s' % adb_online_devs, |
|
109 '%s are no longer visible. Were they removed?\n' % missing_devs, |
|
110 'SHERIFF: See go/chrome_device_monitor', |
|
111 'Cache file: %s\n\n' % last_devices_path, |
|
112 'adb devices: %s' % GetCmdOutput(['adb', 'devices']), |
|
113 'adb devices(GetAttachedDevices): %s' % GetAttachedDevices()]) |
|
114 |
|
115 print body |
|
116 |
|
117 # Only send email if the first time a particular device goes offline |
|
118 last_missing = ReadDeviceList('.last_missing') |
|
119 new_missing_devs = set(missing_devs) - set(last_missing) |
|
120 |
|
121 if new_missing_devs: |
|
122 msg_body = '\r\n'.join( |
|
123 ['From: %s' % from_address, |
|
124 'To: %s' % to_address, |
|
125 'Subject: %s' % subject, |
|
126 '', body]) |
|
127 try: |
|
128 server = smtplib.SMTP('localhost') |
|
129 server.sendmail(from_address, [to_address], msg_body) |
|
130 server.quit() |
|
131 except Exception as e: |
|
132 print 'Failed to send alert email. Error: %s' % e |
|
133 else: |
|
134 new_devs = set(adb_online_devs) - set(last_devices) |
|
135 if new_devs and os.path.exists(last_devices_path): |
|
136 buildbot_report.PrintWarning() |
|
137 buildbot_report.PrintSummaryText( |
|
138 '%d new devices detected' % len(new_devs)) |
|
139 print ('New devices detected %s. And now back to your ' |
|
140 'regularly scheduled program.' % list(new_devs)) |
|
141 WriteDeviceList('.last_devices', (adb_online_devs + last_devices)) |
|
142 WriteDeviceList('.last_missing', missing_devs) |
|
143 |
|
144 |
|
145 def main(): |
|
146 parser = optparse.OptionParser() |
|
147 parser.add_option('', '--out-dir', |
|
148 help='Directory where the device path is stored', |
|
149 default=os.path.join(os.path.dirname(__file__), '..', |
|
150 '..', 'out')) |
|
151 |
|
152 options, args = parser.parse_args() |
|
153 if args: |
|
154 parser.error('Unknown options %s' % args) |
|
155 buildbot_report.PrintNamedStep('Device Status Check') |
|
156 devices = GetAttachedDevices() |
|
157 types, builds, reports = [], [], [] |
|
158 if devices: |
|
159 types, builds, reports = zip(*[DeviceInfo(dev) for dev in devices]) |
|
160 |
|
161 unique_types = list(set(types)) |
|
162 unique_builds = list(set(builds)) |
|
163 |
|
164 buildbot_report.PrintMsg('Online devices: %d. Device types %s, builds %s' |
|
165 % (len(devices), unique_types, unique_builds)) |
|
166 print '\n'.join(reports) |
|
167 CheckForMissingDevices(options, devices) |
|
168 |
|
169 if __name__ == '__main__': |
|
170 sys.exit(main()) |