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."""
18 sys
.path
.append(os
.path
.abspath(os
.path
.join(os
.path
.dirname(__file__
), '..')))
19 from devil
.android
import battery_utils
20 from devil
.android
import device_blacklist
21 from devil
.android
import device_errors
22 from devil
.android
import device_list
23 from devil
.android
import device_utils
24 from devil
.android
.sdk
import adb_wrapper
25 from devil
.utils
import lsusb
26 from devil
.utils
import reset_usb
27 from devil
.utils
import run_tests_helper
28 from pylib
import constants
30 _RE_DEVICE_ID
= re
.compile(r
'Device ID = (\d+)')
35 for p
in psutil
.process_iter():
39 except (psutil
.NoSuchProcess
, psutil
.AccessDenied
):
42 for sig
in [signal
.SIGTERM
, signal
.SIGQUIT
, signal
.SIGKILL
]:
45 logging
.info('kill %d %d (%s [%s])', sig
, p
.pid
, p
.name
,
48 except (psutil
.NoSuchProcess
, psutil
.AccessDenied
):
52 logging
.error('Unable to kill %d (%s [%s])', p
.pid
, p
.name
,
54 except (psutil
.NoSuchProcess
, psutil
.AccessDenied
):
58 def _BatteryStatus(device
, blacklist
):
61 battery
= battery_utils
.BatteryUtils(device
)
62 battery_info
= battery
.GetBatteryInfo(timeout
=5)
63 battery_level
= int(battery_info
.get('level', 100))
65 if battery_level
< 15:
66 logging
.error('Critically low battery level (%d)', battery_level
)
67 battery
= battery_utils
.BatteryUtils(device
)
68 if not battery
.GetCharging():
69 battery
.SetCharging(True)
71 blacklist
.Extend([device
.GetDeviceSerial()])
73 except device_errors
.CommandFailedError
:
74 logging
.exception('Failed to get battery information for %s',
80 def _IMEISlice(device
):
83 for l
in device
.RunShellCommand(['dumpsys', 'iphonesubinfo'],
84 check_return
=True, timeout
=5):
85 m
= _RE_DEVICE_ID
.match(l
)
87 imei_slice
= m
.group(1)[-6:]
88 except device_errors
.CommandFailedError
:
89 logging
.exception('Failed to get IMEI slice for %s', str(device
))
94 def DeviceStatus(devices
, blacklist
):
95 """Generates status information for the given devices.
98 devices: The devices to generate status for.
99 blacklist: The current device blacklist.
101 A dict of the following form:
104 'serial': '<serial>',
108 # only if the device is connected and not blacklisted
109 'type': ro.build.product,
110 'build': ro.build.id,
111 'build_detail': ro.build.fingerprint,
122 a
[0].GetDeviceSerial(): a
123 for a
in adb_wrapper
.AdbWrapper
.Devices(desired_state
=None, long_list
=True)
125 usb_devices
= set(lsusb
.get_android_devices())
127 def blacklisting_device_status(device
):
128 serial
= device
.adb
.GetDeviceSerial()
130 adb_devices
[serial
][1] if serial
in adb_devices
132 usb_status
= bool(serial
in usb_devices
)
136 'adb_status': adb_status
,
137 'usb_status': usb_status
,
140 if adb_status
== 'device':
141 if not serial
in blacklist
.Read():
143 build_product
= device
.build_product
144 build_id
= device
.build_id
145 build_fingerprint
= device
.GetProp('ro.build.fingerprint', cache
=True)
146 wifi_ip
= device
.GetProp('dhcp.wlan0.ipaddress')
147 battery_info
= _BatteryStatus(device
, blacklist
)
148 imei_slice
= _IMEISlice(device
)
150 if (device
.product_name
== 'mantaray' and
151 battery_info
.get('AC powered', None) != 'true'):
152 logging
.error('Mantaray device not connected to AC power.')
154 device_status
.update({
155 'ro.build.product': build_product
,
156 'ro.build.id': build_id
,
157 'ro.build.fingerprint': build_fingerprint
,
158 'battery': battery_info
,
159 'imei_slice': imei_slice
,
162 # TODO(jbudorick): Remove these once no clients depend on them.
163 'type': build_product
,
165 'build_detail': build_fingerprint
,
168 except device_errors
.CommandFailedError
:
169 logging
.exception('Failure while getting device status for %s.',
172 blacklist
.Extend([serial
])
174 except device_errors
.CommandTimeoutError
:
175 logging
.exception('Timeout while getting device status for %s.',
178 blacklist
.Extend([serial
])
181 blacklist
.Extend([serial
])
183 device_status
['blacklisted'] = serial
in blacklist
.Read()
187 parallel_devices
= device_utils
.DeviceUtils
.parallel(devices
)
188 statuses
= parallel_devices
.pMap(blacklisting_device_status
).pGet(None)
192 def RecoverDevices(devices
, blacklist
):
193 """Attempts to recover any inoperable devices in the provided list.
196 devices: The list of devices to attempt to recover.
197 blacklist: The current device blacklist, which will be used then
203 statuses
= DeviceStatus(devices
, blacklist
)
205 should_restart_usb
= set(
206 status
['serial'] for status
in statuses
207 if (not status
['usb_status']
208 or status
['adb_status'] == 'unknown'))
209 should_restart_adb
= should_restart_usb
.union(set(
210 status
['serial'] for status
in statuses
211 if status
['adb_status'] in ('offline', 'unauthorized')))
212 should_reboot_device
= should_restart_adb
.union(set(
213 status
['serial'] for status
in statuses
214 if status
['blacklisted']))
216 logging
.debug('Should restart USB for:')
217 for d
in should_restart_usb
:
218 logging
.debug(' %s', d
)
219 logging
.debug('Should restart ADB for:')
220 for d
in should_restart_adb
:
221 logging
.debug(' %s', d
)
222 logging
.debug('Should reboot:')
223 for d
in should_reboot_device
:
224 logging
.debug(' %s', d
)
228 if should_restart_adb
:
230 for serial
in should_restart_usb
:
232 reset_usb
.reset_android_usb(serial
)
233 except (IOError, device_errors
.DeviceUnreachableError
):
234 logging
.exception('Unable to reset USB for %s.', serial
)
236 blacklist
.Extend([serial
])
238 def blacklisting_recovery(device
):
239 if blacklist
and device
.adb
.GetDeviceSerial() in blacklist
.Read():
240 logging
.debug('%s is blacklisted, skipping recovery.', str(device
))
243 if device
in should_reboot_device
:
245 device
.WaitUntilFullyBooted()
247 except (device_errors
.CommandTimeoutError
,
248 device_errors
.CommandFailedError
):
249 logging
.exception('Failure while waiting for %s. '
250 'Attempting to recover.', str(device
))
254 device
.WaitUntilFullyBooted()
255 except device_errors
.CommandFailedError
:
256 logging
.exception('Failure while waiting for %s.', str(device
))
258 blacklist
.Extend([str(device
)])
259 except device_errors
.CommandTimeoutError
:
260 logging
.exception('Timed out while waiting for %s. ', str(device
))
262 blacklist
.Extend([str(device
)])
264 device_utils
.DeviceUtils
.parallel(devices
).pMap(blacklisting_recovery
)
268 parser
= argparse
.ArgumentParser()
269 parser
.add_argument('--out-dir',
270 help='Directory where the device path is stored',
271 default
=os
.path
.join(constants
.DIR_SOURCE_ROOT
, 'out'))
272 parser
.add_argument('--restart-usb', action
='store_true',
274 'This script now always tries to reset USB.')
275 parser
.add_argument('--json-output',
276 help='Output JSON information into a specified file.')
277 parser
.add_argument('--blacklist-file', help='Device blacklist JSON file.')
278 parser
.add_argument('-v', '--verbose', action
='count', default
=1,
279 help='Log more information.')
281 args
= parser
.parse_args()
283 run_tests_helper
.SetLogLevel(args
.verbose
)
285 if args
.blacklist_file
:
286 blacklist
= device_blacklist
.Blacklist(args
.blacklist_file
)
288 # TODO(jbudorick): Remove this once bots pass the blacklist file.
289 blacklist
= device_blacklist
.Blacklist(device_blacklist
.BLACKLIST_JSON
)
291 last_devices_path
= os
.path
.join(
292 args
.out_dir
, device_list
.LAST_DEVICES_FILENAME
)
294 expected_devices
= set(
295 device_list
.GetPersistentDeviceList(last_devices_path
))
297 expected_devices
= set()
299 usb_devices
= set(lsusb
.get_android_devices())
300 devices
= [device_utils
.DeviceUtils(s
)
301 for s
in expected_devices
.union(usb_devices
)]
303 RecoverDevices(devices
, blacklist
)
304 statuses
= DeviceStatus(devices
, blacklist
)
306 # Log the state of all devices.
307 for status
in statuses
:
308 logging
.info(status
['serial'])
309 adb_status
= status
.get('adb_status')
310 blacklisted
= status
.get('blacklisted')
311 logging
.info(' USB status: %s',
312 'online' if status
.get('usb_status') else 'offline')
313 logging
.info(' ADB status: %s', adb_status
)
314 logging
.info(' Blacklisted: %s', str(blacklisted
))
315 if adb_status
== 'device' and not blacklisted
:
316 logging
.info(' Device type: %s', status
.get('ro.build.product'))
317 logging
.info(' OS build: %s', status
.get('ro.build.id'))
318 logging
.info(' OS build fingerprint: %s',
319 status
.get('ro.build.fingerprint'))
320 logging
.info(' Battery state:')
321 for k
, v
in status
.get('battery', {}).iteritems():
322 logging
.info(' %s: %s', k
, v
)
323 logging
.info(' IMEI slice: %s', status
.get('imei_slice'))
324 logging
.info(' WiFi IP: %s', status
.get('wifi_ip'))
326 # Update the last devices file.
327 device_list
.WritePersistentDeviceList(
329 [status
['serial'] for status
in statuses
])
331 # Write device info to file for buildbot info display.
332 if os
.path
.exists('/home/chrome-bot'):
333 with
open('/home/chrome-bot/.adb_device_info', 'w') as f
:
334 for status
in statuses
:
336 if status
['adb_status'] == 'device':
337 f
.write('{serial} {adb_status} {build_product} {build_id} '
338 '{temperature:.1f}C {level}%\n'.format(
339 serial
=status
['serial'],
340 adb_status
=status
['adb_status'],
341 build_product
=status
['type'],
342 build_id
=status
['build'],
343 temperature
=float(status
['battery']['temperature']) / 10,
344 level
=status
['battery']['level']
347 f
.write('{serial} {adb_status}'.format(
348 serial
=status
['serial'],
349 adb_status
=status
['adb_status']
351 except Exception: # pylint: disable=broad-except
354 # Dump the device statuses to JSON.
356 with
open(args
.json_output
, 'wb') as f
:
357 f
.write(json
.dumps(statuses
, indent
=4))
359 live_devices
= [status
['serial'] for status
in statuses
360 if (status
['adb_status'] == 'device'
361 and status
['serial'] not in blacklist
.Read())]
363 # If all devices failed, or if there are no devices, it's an infra error.
364 return 0 if live_devices
else constants
.INFRA_EXIT_CODE
367 if __name__
== '__main__':