Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(244)

Side by Side Diff: py/utils/android_utils.py

Issue 341193004: Add lots of utils, PRESUBMIT.py (Closed) Base URL: https://skia.googlesource.com/common.git@master
Patch Set: Address comments Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « py/utils/__init__.py ('k') | py/utils/git_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """ This module contains tools used by Android-specific buildbot scripts. """
7
8 import os
9 import re
10 import shell_utils
11 import shlex
12 import sys
13 import time
14
15
16 CPU_SCALING_MODES = ['performance', 'interactive']
17 DEVICE_LOOKUP = {'nexus_s': 'crespo',
18 'xoom': 'stingray',
19 'galaxy_nexus': 'toro',
20 'nexus_4': 'mako',
21 'nexus_7': 'grouper',
22 'nexus_10': 'manta'}
23 PROCESS_MONITOR_INTERVAL = 5.0 # Seconds
24 SKIA_RUNNING = 'running'
25 SKIA_RETURN_CODE_REPEATS = 10
26 SUBPROCESS_TIMEOUT = 30.0
27
28
29 def GotADB(adb):
30 """ Returns True iff ADB exists at the given location.
31
32 adb: string; possible path to the ADB executable.
33 """
34 try:
35 shell_utils.run([adb, 'version'], echo=False)
36 return True
37 except Exception:
38 return False
39
40
41 def FindADB(hint=None):
42 """ Attempt to find the ADB program using the following sequence of steps.
43 Returns the path to ADB if it can be found, or None otherwise.
44 1. If a hint was provided, is it a valid path to ADB?
45 2. Is ADB in PATH?
46 3. Is there an environment variable for ADB?
47 4. If the ANDROID_SDK_ROOT variable is set, try to find ADB in the SDK
48 directory.
49 5. Try to find ADB in a list of common locations.
50
51 hint: string indicating a possible path to ADB.
52 """
53 # 1. If a hint was provided, does it point to ADB?
54 if hint:
55 if os.path.basename(hint) == 'adb':
56 adb = hint
57 else:
58 adb = os.path.join(hint, 'adb')
59 if GotADB(adb):
60 return adb
61
62 # 2. Is 'adb' in our PATH?
63 adb = 'adb'
64 if GotADB(adb):
65 return adb
66
67 # 3. Is there an environment variable for ADB?
68 adb = os.environ.get('ADB')
69 if GotADB(adb):
70 return adb
71
72 # 4. If ANDROID_SDK_ROOT is set, try to find ADB in the SDK directory.
73 sdk_dir = os.environ.get('ANDROID_SDK_ROOT', '')
74 adb = os.path.join(sdk_dir, 'platform-tools', 'adb')
75 if GotADB(adb):
76 return adb
77
78 # 4. Try to find ADB relative to this file.
79 common_locations = []
80 os_dir = None
81 if sys.platform.startswith('linux'):
82 os_dir = 'linux'
83 elif sys.platform.startswith('darwin'):
84 os_dir = 'mac'
85 else:
86 os_dir = 'win'
87 common_locations.append(os.path.join('platform_tools', 'android', 'bin',
88 os_dir, 'adb'))
89 common_locations.append(os.path.join(os.environ.get('HOME', ''),
90 'android-sdk-%s' % os_dir))
91 for location in common_locations:
92 if GotADB(location):
93 return location
94
95 raise Exception('android_utils: Unable to find ADB!')
96
97
98 PATH_TO_ADB = FindADB(hint=os.path.join('platform_tools', 'android', 'bin',
99 'linux', 'adb'))
100
101
102 def RunADB(serial, cmd, echo=True, attempts=5, secs_between_attempts=10,
103 timeout=None):
104 """ Run 'cmd' on an Android device, using ADB. No return value; throws an
105 exception if the command fails more than the allotted number of attempts.
106
107 serial: string indicating the serial number of the target device
108 cmd: string; the command to issue on the device
109 attempts: number of times to attempt the command
110 secs_between_attempts: number of seconds to wait between attempts
111 timeout: optional, integer indicating the maximum elapsed time in seconds
112 """
113 adb_cmd = [PATH_TO_ADB, '-s', serial]
114 adb_cmd += cmd
115 shell_utils.run_retry(adb_cmd, echo=echo, attempts=attempts,
116 secs_between_attempts=secs_between_attempts)
117
118
119 def ADBShell(serial, cmd, echo=True):
120 """ Runs 'cmd' in the ADB shell on an Android device and returns the exit
121 code.
122
123 serial: string indicating the serial number of the target device
124 cmd: string; the command to issue on the device
125 """
126 # ADB doesn't exit with the exit code of the command we ran. It only exits
127 # non-zero when ADB itself encountered a problem. Therefore, we have to use
128 # the shell to print the exit code for the command and parse that from stdout.
129 adb_cmd = '%s -s %s shell "%s; echo \$?"' % (PATH_TO_ADB, serial,
130 ' '.join(cmd))
131 output = shell_utils.run(adb_cmd, shell=True, echo=echo)
132 output_lines = output.splitlines()
133 try:
134 real_exitcode = int(output_lines[-1].rstrip())
135 except ValueError:
136 real_exitcode = -1
137 if real_exitcode != 0:
138 raise Exception('Command failed with code %s' % real_exitcode)
139 return '\n'.join(output_lines[:-1])
140
141
142 def ADBKill(serial, process, kill_app=False):
143 """ Kill a process running on an Android device.
144
145 serial: string indicating the serial number of the target device
146 process: string indicating the name of the process to kill
147 kill_app: bool indicating whether the process is an Android app, as opposed
148 to a normal executable process.
149 """
150 if kill_app:
151 ADBShell(serial, ['am', 'kill', process])
152 else:
153 try:
154 stdout = shell_utils.run('%s -s %s shell ps | grep %s' % (PATH_TO_ADB,
155 serial,
156 process),
157 shell=True)
158 except Exception:
159 return
160 for line in stdout.split('\n'):
161 if line != '':
162 split = shlex.split(line)
163 if len(split) < 2:
164 continue
165 pid = split[1]
166 ADBShell(serial, ['kill', pid])
167 # Raise an exception if any Skia processes are still running.
168 try:
169 stdout = shell_utils.run('%s -s %s shell ps | grep %s' % (PATH_TO_ADB,
170 serial,
171 process),
172 shell=True)
173 except Exception:
174 return
175 if stdout:
176 raise Exception('There are still some skia processes running:\n%s\n'
177 'Maybe the device should be rebooted?' % stdout)
178
179
180 def GetSerial(device_type):
181 """ Determine the serial number of the *first* connected device with the
182 specified type. The ordering of 'adb devices' is not documented, and the
183 connected devices do not appear to be ordered by serial number. Therefore,
184 we have to assume that, in the case of multiple devices of the same type being
185 connected to one host, we cannot predict which device will be chosen.
186
187 device_type: string indicating the 'common name' for the target device
188 """
189 if not device_type in DEVICE_LOOKUP:
190 raise ValueError('Unknown device: %s!' % device_type)
191 device_name = DEVICE_LOOKUP[device_type]
192 output = shell_utils.run_retry('%s devices' % PATH_TO_ADB, shell=True,
193 attempts=5)
194 print output
195 lines = output.split('\n')
196 device_ids = []
197 for line in lines:
198 # Filter garbage lines
199 if line != '' and not ('List of devices attached' in line) and \
200 line[0] != '*':
201 device_ids.append(line.split('\t')[0])
202 for device_id in device_ids:
203 print 'Finding type for id %s' % device_id
204 # Get device name
205 name_line = shell_utils.run_retry(
206 '%s -s %s shell cat /system/build.prop | grep "ro.product.device="' % (
207 PATH_TO_ADB, device_id), shell=True, attempts=5)
208 print name_line
209 name = name_line.split('=')[-1].rstrip()
210 # Just return the first attached device of the requested model.
211 if device_name in name:
212 return device_id
213 raise Exception('No %s device attached!' % device_name)
214
215
216 def SetCPUScalingMode(serial, mode):
217 """ Set the CPU scaling governor for the device with the given serial number
218 to the given mode.
219
220 serial: string indicating the serial number of the device whose scaling mode
221 is to be modified
222 mode: string indicating the desired CPU scaling mode. Acceptable values
223 are listed in CPU_SCALING_MODES.
224 """
225 if mode not in CPU_SCALING_MODES:
226 raise ValueError('mode must be one of: %s' % CPU_SCALING_MODES)
227 cpu_dirs = shell_utils.run('%s -s %s shell ls /sys/devices/system/cpu' % (
228 PATH_TO_ADB, serial), echo=False, shell=True)
229 cpu_dirs_list = cpu_dirs.split('\n')
230 regex = re.compile('cpu\d')
231 for cpu_dir_from_list in cpu_dirs_list:
232 cpu_dir = cpu_dir_from_list.rstrip()
233 if regex.match(cpu_dir):
234 path = '/sys/devices/system/cpu/%s/cpufreq/scaling_governor' % cpu_dir
235 path_found = shell_utils.run('%s -s %s shell ls %s' % (
236 PATH_TO_ADB, serial, path),
237 echo=False, shell=True).rstrip()
238 if path_found == path:
239 # Unfortunately, we can't directly change the scaling_governor file over
240 # ADB. Instead, we write a script to do so, push it to the device, and
241 # run it.
242 old_mode = shell_utils.run('%s -s %s shell cat %s' % (
243 PATH_TO_ADB, serial, path),
244 echo=False, shell=True).rstrip()
245 print 'Current scaling mode for %s is: %s' % (cpu_dir, old_mode)
246 filename = 'skia_cpuscale.sh'
247 with open(filename, 'w') as script_file:
248 script_file.write('echo %s > %s\n' % (mode, path))
249 os.chmod(filename, 0777)
250 RunADB(serial, ['push', filename, '/system/bin'], echo=False)
251 RunADB(serial, ['shell', filename], echo=True)
252 RunADB(serial, ['shell', 'rm', '/system/bin/%s' % filename], echo=False)
253 os.remove(filename)
254 new_mode = shell_utils.run('%s -s %s shell cat %s' % (
255 PATH_TO_ADB, serial, path),
256 echo=False, shell=True).rstrip()
257 print 'New scaling mode for %s is: %s' % (cpu_dir, new_mode)
258
259
260 def IsAndroidShellRunning(serial):
261 """ Find the status of the Android shell for the device with the given serial
262 number. Returns True if the shell is running and False otherwise.
263
264 serial: string indicating the serial number of the target device.
265 """
266 if 'Error:' in ADBShell(serial, ['pm', 'path', 'android'], echo=False):
267 return False
268 return True
269
270
271 def StopShell(serial, timeout=60):
272 """ Halt the Android runtime on the device with the given serial number.
273 Blocks until the shell reports that it has stopped.
274
275 serial: string indicating the serial number of the target device.
276 timeout: maximum allotted time, in seconds.
277 """
278 ADBShell(serial, ['stop'])
279 start_time = time.time()
280 while IsAndroidShellRunning(serial):
281 time.sleep(1)
282 if time.time() - start_time > timeout:
283 raise Exception('Timeout while attempting to stop the Android runtime.')
284
285
286 def StartShell(serial, timeout=60):
287 """ Start the Android runtime on the device with the given serial number.
288 Blocks until the shell reports that it has started.
289
290 serial: string indicating the serial number of the target device.
291 timeout: maximum allotted time, in seconds.
292 """
293 ADBShell(serial, ['start'])
294 start_time = time.time()
295 while not IsAndroidShellRunning(serial):
296 time.sleep(1)
297 if time.time() - start_time > timeout:
298 raise Exception('Timeout while attempting to start the Android runtime.')
299
300
301 def RunSkia(serial, cmd, release, device):
302 """ Run the given command through skia_launcher on a given device.
303
304 serial: string indicating the serial number of the target device.
305 cmd: list of strings; the command line to run.
306 release: bool; whether or not to run the app in Release mode.
307 device: string indicating the target device.
308 """
309 RunADB(serial, ['logcat', '-c'])
310 try:
311 os.environ['SKIA_ANDROID_VERBOSE_SETUP'] = '1'
312 cmd_to_run = [os.path.join('platform_tools', 'android', 'bin',
313 'android_run_skia')]
314 if release:
315 cmd_to_run.extend(['--release'])
316 cmd_to_run.extend(['-d', device])
317 cmd_to_run.extend(['-s', serial])
318 cmd_to_run.extend(cmd)
319 shell_utils.run(cmd_to_run)
320 finally:
321 RunADB(serial, ['logcat', '-d', '-v', 'time'])
OLDNEW
« no previous file with comments | « py/utils/__init__.py ('k') | py/utils/git_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698