OLD | NEW |
(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']) |
OLD | NEW |