| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides an interface to communicate with the device via the adb command. | 5 """Provides an interface to communicate with the device via the adb command. |
| 6 | 6 |
| 7 Assumes adb binary is currently on system path. | 7 Assumes adb binary is currently on system path. |
| 8 | |
| 9 Usage: | |
| 10 python android_commands.py wait-for-pm | |
| 11 """ | 8 """ |
| 12 | 9 |
| 13 import collections | 10 import collections |
| 14 import datetime | 11 import datetime |
| 12 import io_stats_parser |
| 15 import logging | 13 import logging |
| 16 import optparse | 14 import optparse |
| 17 import os | 15 import os |
| 18 import pexpect | 16 import pexpect |
| 19 import re | 17 import re |
| 18 import shlex |
| 20 import subprocess | 19 import subprocess |
| 21 import sys | 20 import sys |
| 22 import tempfile | 21 import tempfile |
| 23 import time | 22 import time |
| 24 | 23 |
| 24 |
| 25 # adb_interface.py is under ../../../third_party/android_testrunner/ | 25 # adb_interface.py is under ../../../third_party/android_testrunner/ |
| 26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', | 26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
| 27 '..', '..', 'third_party', 'android_testrunner')) | 27 '..', '..', 'third_party', 'android_testrunner')) |
| 28 import adb_interface | 28 import adb_interface |
| 29 import cmd_helper | 29 import cmd_helper |
| 30 import errors # is under ../../third_party/android_testrunner/errors.py | 30 import errors # is under ../../../third_party/android_testrunner/errors.py |
| 31 from run_tests_helper import IsRunningAsBuildbot | |
| 32 | 31 |
| 33 | 32 |
| 34 # Pattern to search for the next whole line of pexpect output and capture it | 33 # Pattern to search for the next whole line of pexpect output and capture it |
| 35 # into a match group. We can't use ^ and $ for line start end with pexpect, | 34 # into a match group. We can't use ^ and $ for line start end with pexpect, |
| 36 # see http://www.noah.org/python/pexpect/#doc for explanation why. | 35 # see http://www.noah.org/python/pexpect/#doc for explanation why. |
| 37 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') | 36 PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r') |
| 38 | 37 |
| 39 # Set the adb shell prompt to be a unique marker that will [hopefully] not | 38 # Set the adb shell prompt to be a unique marker that will [hopefully] not |
| 40 # appear at the start of any line of a command's output. | 39 # appear at the start of any line of a command's output. |
| 41 SHELL_PROMPT = '~+~PQ\x17RS~+~' | 40 SHELL_PROMPT = '~+~PQ\x17RS~+~' |
| 42 | 41 |
| 43 # This only works for single core devices. | 42 # This only works for single core devices. |
| 44 SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' | 43 SCALING_GOVERNOR = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor' |
| 45 DROP_CACHES = '/proc/sys/vm/drop_caches' | 44 DROP_CACHES = '/proc/sys/vm/drop_caches' |
| 46 | 45 |
| 47 # Java properties file | 46 # Java properties file |
| 48 LOCAL_PROPERTIES_PATH = '/data/local.prop' | 47 LOCAL_PROPERTIES_PATH = '/data/local.prop' |
| 49 | 48 |
| 50 # Property in /data/local.prop that controls Java assertions. | 49 # Property in /data/local.prop that controls Java assertions. |
| 51 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | 50 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
| 52 | 51 |
| 53 BOOT_COMPLETE_RE = re.compile( | 52 BOOT_COMPLETE_RE = re.compile( |
| 54 re.escape('android.intent.action.MEDIA_MOUNTED path: /mnt/sdcard') | 53 'android.intent.action.MEDIA_MOUNTED path: /\w+/sdcard\d?' |
| 55 + '|' + re.escape('PowerManagerService: bootCompleted')) | 54 + '|' + 'PowerManagerService(\(\s+\d+\))?: bootCompleted') |
| 55 |
| 56 MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$') |
| 57 NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*' |
| 58 '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$') |
| 56 | 59 |
| 57 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). | 60 # Keycode "enum" suitable for passing to AndroidCommands.SendKey(). |
| 61 KEYCODE_HOME = 3 |
| 62 KEYCODE_BACK = 4 |
| 63 KEYCODE_DPAD_UP = 19 |
| 64 KEYCODE_DPAD_DOWN = 20 |
| 58 KEYCODE_DPAD_RIGHT = 22 | 65 KEYCODE_DPAD_RIGHT = 22 |
| 59 KEYCODE_ENTER = 66 | 66 KEYCODE_ENTER = 66 |
| 60 KEYCODE_MENU = 82 | 67 KEYCODE_MENU = 82 |
| 61 KEYCODE_BACK = 4 | |
| 62 | 68 |
| 63 | 69 |
| 64 def GetEmulators(): | 70 def GetEmulators(): |
| 65 """Returns a list of emulators. Does not filter by status (e.g. offline). | 71 """Returns a list of emulators. Does not filter by status (e.g. offline). |
| 66 | 72 |
| 67 Both devices starting with 'emulator' will be returned in below output: | 73 Both devices starting with 'emulator' will be returned in below output: |
| 68 | 74 |
| 69 * daemon not running. starting it now on port 5037 * | 75 * daemon not running. starting it now on port 5037 * |
| 70 * daemon started successfully * | 76 * daemon started successfully * |
| 71 List of devices attached | 77 List of devices attached |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 182 if isinstance(utc_offset, str) and len(utc_offset) == 5: | 188 if isinstance(utc_offset, str) and len(utc_offset) == 5: |
| 183 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), | 189 utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]), |
| 184 minutes=int(utc_offset[3:5])) | 190 minutes=int(utc_offset[3:5])) |
| 185 if utc_offset[0:1] == '-': | 191 if utc_offset[0:1] == '-': |
| 186 utc_delta = -utc_delta; | 192 utc_delta = -utc_delta; |
| 187 lastmod -= utc_delta | 193 lastmod -= utc_delta |
| 188 files[filename] = (int(file_match.group('size')), lastmod) | 194 files[filename] = (int(file_match.group('size')), lastmod) |
| 189 return files | 195 return files |
| 190 | 196 |
| 191 | 197 |
| 192 def GetLogTimestamp(log_line): | 198 def GetLogTimestamp(log_line, year): |
| 193 """Returns the timestamp of the given |log_line|.""" | 199 """Returns the timestamp of the given |log_line| in the given year.""" |
| 194 try: | 200 try: |
| 195 return datetime.datetime.strptime(log_line[:18], '%m-%d %H:%M:%S.%f') | 201 return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]), |
| 202 '%Y-%m-%d %H:%M:%S.%f') |
| 196 except (ValueError, IndexError): | 203 except (ValueError, IndexError): |
| 197 logging.critical('Error reading timestamp from ' + log_line) | 204 logging.critical('Error reading timestamp from ' + log_line) |
| 198 return None | 205 return None |
| 199 | 206 |
| 200 | 207 |
| 201 class AndroidCommands(object): | 208 class AndroidCommands(object): |
| 202 """Helper class for communicating with Android device via adb. | 209 """Helper class for communicating with Android device via adb. |
| 203 | 210 |
| 204 Args: | 211 Args: |
| 205 device: If given, adb commands are only send to the device of this ID. | 212 device: If given, adb commands are only send to the device of this ID. |
| 206 Otherwise commands are sent to all attached devices. | 213 Otherwise commands are sent to all attached devices. |
| 207 wait_for_pm: If true, issues an adb wait-for-device command. | |
| 208 """ | 214 """ |
| 209 | 215 |
| 210 def __init__(self, device=None, wait_for_pm=False): | 216 def __init__(self, device=None): |
| 211 self._adb = adb_interface.AdbInterface() | 217 self._adb = adb_interface.AdbInterface() |
| 212 if device: | 218 if device: |
| 213 self._adb.SetTargetSerial(device) | 219 self._adb.SetTargetSerial(device) |
| 214 if wait_for_pm: | 220 # So many users require root that we just always do it. This could |
| 215 self.WaitForDevicePm() | 221 # be made more fine grain if necessary. |
| 222 self._adb.EnableAdbRoot() |
| 216 self._logcat = None | 223 self._logcat = None |
| 217 self._original_governor = None | 224 self._original_governor = None |
| 218 self._pushed_files = [] | 225 self._pushed_files = [] |
| 226 self._device_utc_offset = self.RunShellCommand('date +%z')[0] |
| 219 | 227 |
| 220 def Adb(self): | 228 def Adb(self): |
| 221 """Returns our AdbInterface to avoid us wrapping all its methods.""" | 229 """Returns our AdbInterface to avoid us wrapping all its methods.""" |
| 222 return self._adb | 230 return self._adb |
| 223 | 231 |
| 232 def GetDeviceYear(self): |
| 233 """Returns the year information of the date on device""" |
| 234 return self.RunShellCommand('date +%Y')[0] |
| 235 |
| 224 def WaitForDevicePm(self): | 236 def WaitForDevicePm(self): |
| 225 """Blocks until the device's package manager is available. | 237 """Blocks until the device's package manager is available. |
| 226 | 238 |
| 227 To workaround http://b/5201039, we restart the shell and retry if the | 239 To workaround http://b/5201039, we restart the shell and retry if the |
| 228 package manager isn't back after 120 seconds. | 240 package manager isn't back after 120 seconds. |
| 229 | 241 |
| 230 Raises: | 242 Raises: |
| 231 errors.WaitForResponseTimedOutError after max retries reached. | 243 errors.WaitForResponseTimedOutError after max retries reached. |
| 232 """ | 244 """ |
| 233 last_err = None | 245 last_err = None |
| (...skipping 28 matching lines...) Expand all Loading... |
| 262 # connection; work out if we can handle this better | 274 # connection; work out if we can handle this better |
| 263 if os.environ.get('USING_HIVE'): | 275 if os.environ.get('USING_HIVE'): |
| 264 logging.warning('Ignoring reboot request as we are on hive') | 276 logging.warning('Ignoring reboot request as we are on hive') |
| 265 return | 277 return |
| 266 if full_reboot: | 278 if full_reboot: |
| 267 self._adb.SendCommand('reboot') | 279 self._adb.SendCommand('reboot') |
| 268 else: | 280 else: |
| 269 self.RestartShell() | 281 self.RestartShell() |
| 270 self.WaitForDevicePm() | 282 self.WaitForDevicePm() |
| 271 self.StartMonitoringLogcat(timeout=120) | 283 self.StartMonitoringLogcat(timeout=120) |
| 272 self.WaitForLogMatch(BOOT_COMPLETE_RE) | 284 self.WaitForLogMatch(BOOT_COMPLETE_RE, None) |
| 273 self.UnlockDevice() | |
| 274 | 285 |
| 275 def Uninstall(self, package): | 286 def Uninstall(self, package): |
| 276 """Uninstalls the specified package from the device. | 287 """Uninstalls the specified package from the device. |
| 277 | 288 |
| 278 Args: | 289 Args: |
| 279 package: Name of the package to remove. | 290 package: Name of the package to remove. |
| 291 |
| 292 Returns: |
| 293 A status string returned by adb uninstall |
| 280 """ | 294 """ |
| 281 uninstall_command = 'uninstall %s' % package | 295 uninstall_command = 'uninstall %s' % package |
| 282 | 296 |
| 283 logging.info('>>> $' + uninstall_command) | 297 logging.info('>>> $' + uninstall_command) |
| 284 self._adb.SendCommand(uninstall_command, timeout_time=60) | 298 return self._adb.SendCommand(uninstall_command, timeout_time=60) |
| 285 | 299 |
| 286 def Install(self, package_file_path): | 300 def Install(self, package_file_path): |
| 287 """Installs the specified package to the device. | 301 """Installs the specified package to the device. |
| 288 | 302 |
| 289 Args: | 303 Args: |
| 290 package_file_path: Path to .apk file to install. | 304 package_file_path: Path to .apk file to install. |
| 305 |
| 306 Returns: |
| 307 A status string returned by adb install |
| 291 """ | 308 """ |
| 292 | |
| 293 assert os.path.isfile(package_file_path) | 309 assert os.path.isfile(package_file_path) |
| 294 | 310 |
| 295 install_command = 'install %s' % package_file_path | 311 install_command = 'install %s' % package_file_path |
| 296 | 312 |
| 297 logging.info('>>> $' + install_command) | 313 logging.info('>>> $' + install_command) |
| 298 self._adb.SendCommand(install_command, timeout_time=2*60) | 314 return self._adb.SendCommand(install_command, timeout_time=2*60) |
| 315 |
| 316 def MakeSystemFolderWritable(self): |
| 317 """Remounts the /system folder rw. """ |
| 318 out = self._adb.SendCommand('remount') |
| 319 if out.strip() != 'remount succeeded': |
| 320 raise errors.MsgException('Remount failed: %s' % out) |
| 299 | 321 |
| 300 # It is tempting to turn this function into a generator, however this is not | 322 # It is tempting to turn this function into a generator, however this is not |
| 301 # possible without using a private (local) adb_shell instance (to ensure no | 323 # possible without using a private (local) adb_shell instance (to ensure no |
| 302 # other command interleaves usage of it), which would defeat the main aim of | 324 # other command interleaves usage of it), which would defeat the main aim of |
| 303 # being able to reuse the adb shell instance across commands. | 325 # being able to reuse the adb shell instance across commands. |
| 304 def RunShellCommand(self, command, timeout_time=20, log_result=True): | 326 def RunShellCommand(self, command, timeout_time=20, log_result=True): |
| 305 """Send a command to the adb shell and return the result. | 327 """Send a command to the adb shell and return the result. |
| 306 | 328 |
| 307 Args: | 329 Args: |
| 308 command: String containing the shell command to send. Must not include | 330 command: String containing the shell command to send. Must not include |
| (...skipping 21 matching lines...) Expand all Loading... |
| 330 process: name of the process to kill off | 352 process: name of the process to kill off |
| 331 | 353 |
| 332 Returns: | 354 Returns: |
| 333 the number of processess killed | 355 the number of processess killed |
| 334 """ | 356 """ |
| 335 pids = self.ExtractPid(process) | 357 pids = self.ExtractPid(process) |
| 336 if pids: | 358 if pids: |
| 337 self.RunShellCommand('kill ' + ' '.join(pids)) | 359 self.RunShellCommand('kill ' + ' '.join(pids)) |
| 338 return len(pids) | 360 return len(pids) |
| 339 | 361 |
| 340 def StartActivity(self, package, activity, | 362 def StartActivity(self, package, activity, wait_for_completion=False, |
| 341 action='android.intent.action.VIEW', data=None, | 363 action='android.intent.action.VIEW', |
| 364 category=None, data=None, |
| 342 extras=None, trace_file_name=None): | 365 extras=None, trace_file_name=None): |
| 343 """Starts |package|'s activity on the device. | 366 """Starts |package|'s activity on the device. |
| 344 | 367 |
| 345 Args: | 368 Args: |
| 346 package: Name of package to start (e.g. 'com.android.chrome'). | 369 package: Name of package to start (e.g. 'com.google.android.apps.chrome'). |
| 347 activity: Name of activity (e.g. '.Main' or 'com.android.chrome.Main'). | 370 activity: Name of activity (e.g. '.Main' or |
| 371 'com.google.android.apps.chrome.Main'). |
| 372 wait_for_completion: wait for the activity to finish launching (-W flag). |
| 373 action: string (e.g. "android.intent.action.MAIN"). Default is VIEW. |
| 374 category: string (e.g. "android.intent.category.HOME") |
| 348 data: Data string to pass to activity (e.g. 'http://www.example.com/'). | 375 data: Data string to pass to activity (e.g. 'http://www.example.com/'). |
| 349 extras: Dict of extras to pass to activity. | 376 extras: Dict of extras to pass to activity. Values are significant. |
| 350 trace_file_name: If used, turns on and saves the trace to this file name. | 377 trace_file_name: If used, turns on and saves the trace to this file name. |
| 351 """ | 378 """ |
| 352 cmd = 'am start -a %s -n %s/%s' % (action, package, activity) | 379 cmd = 'am start -a %s' % action |
| 380 if wait_for_completion: |
| 381 cmd += ' -W' |
| 382 if category: |
| 383 cmd += ' -c %s' % category |
| 384 if package and activity: |
| 385 cmd += ' -n %s/%s' % (package, activity) |
| 353 if data: | 386 if data: |
| 354 cmd += ' -d "%s"' % data | 387 cmd += ' -d "%s"' % data |
| 355 if extras: | 388 if extras: |
| 356 cmd += ' -e' | |
| 357 for key in extras: | 389 for key in extras: |
| 358 cmd += ' %s %s' % (key, extras[key]) | 390 value = extras[key] |
| 391 if isinstance(value, str): |
| 392 cmd += ' --es' |
| 393 elif isinstance(value, bool): |
| 394 cmd += ' --ez' |
| 395 elif isinstance(value, int): |
| 396 cmd += ' --ei' |
| 397 else: |
| 398 raise NotImplementedError( |
| 399 'Need to teach StartActivity how to pass %s extras' % type(value)) |
| 400 cmd += ' %s %s' % (key, value) |
| 359 if trace_file_name: | 401 if trace_file_name: |
| 360 cmd += ' -S -P ' + trace_file_name | 402 cmd += ' --start-profiler ' + trace_file_name |
| 361 self.RunShellCommand(cmd) | 403 self.RunShellCommand(cmd) |
| 362 | 404 |
| 363 def EnableAdbRoot(self): | |
| 364 """Enable root on the device.""" | |
| 365 self._adb.EnableAdbRoot() | |
| 366 | 405 |
| 367 def CloseApplication(self, package): | 406 def CloseApplication(self, package): |
| 368 """Attempt to close down the application, using increasing violence. | 407 """Attempt to close down the application, using increasing violence. |
| 369 | 408 |
| 370 Args: | 409 Args: |
| 371 package: Name of the process to kill off, e.g. com.android.chrome | 410 package: Name of the process to kill off, e.g. |
| 411 com.google.android.apps.chrome |
| 372 """ | 412 """ |
| 373 self.RunShellCommand('am force-stop ' + package) | 413 self.RunShellCommand('am force-stop ' + package) |
| 374 | 414 |
| 375 def ClearApplicationState(self, package): | 415 def ClearApplicationState(self, package): |
| 376 """Closes and clears all state for the given |package|.""" | 416 """Closes and clears all state for the given |package|.""" |
| 377 self.CloseApplication(package) | 417 self.CloseApplication(package) |
| 418 self.RunShellCommand('rm -r /data/data/%s/app_*' % package) |
| 378 self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) | 419 self.RunShellCommand('rm -r /data/data/%s/cache/*' % package) |
| 379 self.RunShellCommand('rm -r /data/data/%s/files/*' % package) | 420 self.RunShellCommand('rm -r /data/data/%s/files/*' % package) |
| 380 self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) | 421 self.RunShellCommand('rm -r /data/data/%s/shared_prefs/*' % package) |
| 381 | 422 |
| 382 def SendKeyEvent(self, keycode): | 423 def SendKeyEvent(self, keycode): |
| 383 """Sends keycode to the device. | 424 """Sends keycode to the device. |
| 384 | 425 |
| 385 Args: | 426 Args: |
| 386 keycode: Numeric keycode to send (see "enum" at top of file). | 427 keycode: Numeric keycode to send (see "enum" at top of file). |
| 387 """ | 428 """ |
| (...skipping 28 matching lines...) Expand all Loading... |
| 416 # They don't match, so remove everything first and then create it. | 457 # They don't match, so remove everything first and then create it. |
| 417 if os.path.isdir(local_path): | 458 if os.path.isdir(local_path): |
| 418 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) | 459 self.RunShellCommand('rm -r %s' % device_path, timeout_time=2*60) |
| 419 self.RunShellCommand('mkdir -p %s' % device_path) | 460 self.RunShellCommand('mkdir -p %s' % device_path) |
| 420 | 461 |
| 421 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of | 462 # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of |
| 422 # 60 seconds which isn't sufficient for a lot of users of this method. | 463 # 60 seconds which isn't sufficient for a lot of users of this method. |
| 423 push_command = 'push %s %s' % (local_path, device_path) | 464 push_command = 'push %s %s' % (local_path, device_path) |
| 424 logging.info('>>> $' + push_command) | 465 logging.info('>>> $' + push_command) |
| 425 output = self._adb.SendCommand(push_command, timeout_time=30*60) | 466 output = self._adb.SendCommand(push_command, timeout_time=30*60) |
| 467 assert output |
| 426 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" | 468 # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)" |
| 427 # Errors look like this: "failed to copy ... " | 469 # Errors look like this: "failed to copy ... " |
| 428 if not re.search('^[0-9]', output): | 470 if not re.search('^[0-9]', output.splitlines()[-1]): |
| 429 logging.critical('PUSH FAILED: ' + output) | 471 logging.critical('PUSH FAILED: ' + output) |
| 430 | 472 |
| 431 def GetFileContents(self, filename): | 473 def GetFileContents(self, filename, log_result=True): |
| 432 """Gets contents from the file specified by |filename|.""" | 474 """Gets contents from the file specified by |filename|.""" |
| 433 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + | 475 return self.RunShellCommand('if [ -f "' + filename + '" ]; then cat "' + |
| 434 filename + '"; fi') | 476 filename + '"; fi', log_result=log_result) |
| 435 | 477 |
| 436 def SetFileContents(self, filename, contents): | 478 def SetFileContents(self, filename, contents): |
| 437 """Writes |contents| to the file specified by |filename|.""" | 479 """Writes |contents| to the file specified by |filename|.""" |
| 438 with tempfile.NamedTemporaryFile() as f: | 480 with tempfile.NamedTemporaryFile() as f: |
| 439 f.write(contents) | 481 f.write(contents) |
| 440 f.flush() | 482 f.flush() |
| 441 self._adb.Push(f.name, filename) | 483 self._adb.Push(f.name, filename) |
| 442 | 484 |
| 443 def RemovePushedFiles(self): | 485 def RemovePushedFiles(self): |
| 444 """Removes all files pushed with PushIfNeeded() from the device.""" | 486 """Removes all files pushed with PushIfNeeded() from the device.""" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 459 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt | 501 # -rw-r----- 1 user group 102 2011-05-12 12:29:54.131623387 +0100 baz.txt |
| 460 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' | 502 re_file = re.compile('^-(?P<perms>[^\s]+)\s+' |
| 461 '(?P<user>[^\s]+)\s+' | 503 '(?P<user>[^\s]+)\s+' |
| 462 '(?P<group>[^\s]+)\s+' | 504 '(?P<group>[^\s]+)\s+' |
| 463 '(?P<size>[^\s]+)\s+' | 505 '(?P<size>[^\s]+)\s+' |
| 464 '(?P<date>[^\s]+)\s+' | 506 '(?P<date>[^\s]+)\s+' |
| 465 '(?P<time>[^\s]+)\s+' | 507 '(?P<time>[^\s]+)\s+' |
| 466 '(?P<filename>[^\s]+)$') | 508 '(?P<filename>[^\s]+)$') |
| 467 return _GetFilesFromRecursiveLsOutput( | 509 return _GetFilesFromRecursiveLsOutput( |
| 468 path, self.RunShellCommand('ls -lR %s' % path), re_file, | 510 path, self.RunShellCommand('ls -lR %s' % path), re_file, |
| 469 self.RunShellCommand('date +%z')[0]) | 511 self._device_utc_offset) |
| 470 | 512 |
| 471 def SetupPerformanceTest(self): | 513 def SetupPerformanceTest(self): |
| 472 """Sets up performance tests.""" | 514 """Sets up performance tests.""" |
| 473 # Disable CPU scaling to reduce noise in tests | 515 # Disable CPU scaling to reduce noise in tests |
| 474 if not self._original_governor: | 516 if not self._original_governor: |
| 475 self._original_governor = self.RunShellCommand('cat ' + SCALING_GOVERNOR) | 517 self._original_governor = self.GetFileContents( |
| 518 SCALING_GOVERNOR, log_result=False) |
| 476 self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR) | 519 self.RunShellCommand('echo performance > ' + SCALING_GOVERNOR) |
| 477 self.DropRamCaches() | 520 self.DropRamCaches() |
| 478 | 521 |
| 479 def TearDownPerformanceTest(self): | 522 def TearDownPerformanceTest(self): |
| 480 """Tears down performance tests.""" | 523 """Tears down performance tests.""" |
| 481 if self._original_governor: | 524 if self._original_governor: |
| 482 self.RunShellCommand('echo %s > %s' % (self._original_governor[0], | 525 self.RunShellCommand('echo %s > %s' % (self._original_governor[0], |
| 483 SCALING_GOVERNOR)) | 526 SCALING_GOVERNOR)) |
| 484 self._original_governor = None | 527 self._original_governor = None |
| 485 | 528 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 528 """Starts monitoring the output of logcat, for use with WaitForLogMatch. | 571 """Starts monitoring the output of logcat, for use with WaitForLogMatch. |
| 529 | 572 |
| 530 Args: | 573 Args: |
| 531 clear: If True the existing logcat output will be cleared, to avoiding | 574 clear: If True the existing logcat output will be cleared, to avoiding |
| 532 matching historical output lurking in the log. | 575 matching historical output lurking in the log. |
| 533 timeout: How long WaitForLogMatch will wait for the given match | 576 timeout: How long WaitForLogMatch will wait for the given match |
| 534 filters: A list of logcat filters to be used. | 577 filters: A list of logcat filters to be used. |
| 535 """ | 578 """ |
| 536 if clear: | 579 if clear: |
| 537 self.RunShellCommand('logcat -c') | 580 self.RunShellCommand('logcat -c') |
| 538 args = ['logcat', '-v', 'threadtime'] | 581 args = [] |
| 582 if self._adb._target_arg: |
| 583 args += shlex.split(self._adb._target_arg) |
| 584 args += ['logcat', '-v', 'threadtime'] |
| 539 if filters: | 585 if filters: |
| 540 args.extend(filters) | 586 args.extend(filters) |
| 541 else: | 587 else: |
| 542 args.append('*:v') | 588 args.append('*:v') |
| 543 | 589 |
| 544 # Spawn logcat and syncronize with it. | 590 # Spawn logcat and syncronize with it. |
| 545 for _ in range(4): | 591 for _ in range(4): |
| 546 self._logcat = pexpect.spawn('adb', args, timeout=timeout, | 592 self._logcat = pexpect.spawn('adb', args, timeout=timeout, |
| 547 logfile=logfile) | 593 logfile=logfile) |
| 548 self.RunShellCommand('log startup_sync') | 594 self.RunShellCommand('log startup_sync') |
| 549 if self._logcat.expect(['startup_sync', pexpect.EOF, | 595 if self._logcat.expect(['startup_sync', pexpect.EOF, |
| 550 pexpect.TIMEOUT]) == 0: | 596 pexpect.TIMEOUT]) == 0: |
| 551 break | 597 break |
| 552 self._logcat.close(force=True) | 598 self._logcat.close(force=True) |
| 553 else: | 599 else: |
| 554 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) | 600 logging.critical('Error reading from logcat: ' + str(self._logcat.match)) |
| 555 sys.exit(1) | 601 sys.exit(1) |
| 556 | 602 |
| 557 def GetMonitoredLogCat(self): | 603 def GetMonitoredLogCat(self): |
| 558 """Returns an "adb logcat" command as created by pexpected.spawn.""" | 604 """Returns an "adb logcat" command as created by pexpected.spawn.""" |
| 559 if not self._logcat: | 605 if not self._logcat: |
| 560 self.StartMonitoringLogcat(clear=False) | 606 self.StartMonitoringLogcat(clear=False) |
| 561 return self._logcat | 607 return self._logcat |
| 562 | 608 |
| 563 def WaitForLogMatch(self, search_re): | 609 def WaitForLogMatch(self, success_re, error_re, clear=False): |
| 564 """Blocks until a line containing |line_re| is logged or a timeout occurs. | 610 """Blocks until a matching line is logged or a timeout occurs. |
| 565 | 611 |
| 566 Args: | 612 Args: |
| 567 search_re: The compiled re to search each line for. | 613 success_re: A compiled re to search each line for. |
| 614 error_re: A compiled re which, if found, terminates the search for |
| 615 |success_re|. If None is given, no error condition will be detected. |
| 616 clear: If True the existing logcat output will be cleared, defaults to |
| 617 false. |
| 618 |
| 619 Raises: |
| 620 pexpect.TIMEOUT upon the timeout specified by StartMonitoringLogcat(). |
| 568 | 621 |
| 569 Returns: | 622 Returns: |
| 570 The re match object. | 623 The re match object if |success_re| is matched first or None if |error_re| |
| 624 is matched first. |
| 571 """ | 625 """ |
| 572 if not self._logcat: | 626 if not self._logcat: |
| 573 self.StartMonitoringLogcat(clear=False) | 627 self.StartMonitoringLogcat(clear) |
| 574 logging.info('<<< Waiting for logcat:' + str(search_re.pattern)) | 628 logging.info('<<< Waiting for logcat:' + str(success_re.pattern)) |
| 575 t0 = time.time() | 629 t0 = time.time() |
| 576 try: | 630 try: |
| 577 while True: | 631 while True: |
| 578 # Note this will block for upto the timeout _per log line_, so we need | 632 # Note this will block for upto the timeout _per log line_, so we need |
| 579 # to calculate the overall timeout remaining since t0. | 633 # to calculate the overall timeout remaining since t0. |
| 580 time_remaining = t0 + self._logcat.timeout - time.time() | 634 time_remaining = t0 + self._logcat.timeout - time.time() |
| 581 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) | 635 if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat) |
| 582 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) | 636 self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining) |
| 583 line = self._logcat.match.group(1) | 637 line = self._logcat.match.group(1) |
| 584 search_match = search_re.search(line) | 638 if error_re: |
| 585 if search_match: | 639 error_match = error_re.search(line) |
| 586 return search_match | 640 if error_match: |
| 641 return None |
| 642 success_match = success_re.search(line) |
| 643 if success_match: |
| 644 return success_match |
| 587 logging.info('<<< Skipped Logcat Line:' + str(line)) | 645 logging.info('<<< Skipped Logcat Line:' + str(line)) |
| 588 except pexpect.TIMEOUT: | 646 except pexpect.TIMEOUT: |
| 589 raise pexpect.TIMEOUT( | 647 raise pexpect.TIMEOUT( |
| 590 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' | 648 'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv ' |
| 591 'to debug)' % | 649 'to debug)' % |
| 592 (self._logcat.timeout, search_re.pattern)) | 650 (self._logcat.timeout, success_re.pattern)) |
| 593 | 651 |
| 594 def StartRecordingLogcat(self, clear=True, filters=['*:v']): | 652 def StartRecordingLogcat(self, clear=True, filters=['*:v']): |
| 595 """Starts recording logcat output to eventually be saved as a string. | 653 """Starts recording logcat output to eventually be saved as a string. |
| 596 | 654 |
| 597 This call should come before some series of tests are run, with either | 655 This call should come before some series of tests are run, with either |
| 598 StopRecordingLogcat or SearchLogcatRecord following the tests. | 656 StopRecordingLogcat or SearchLogcatRecord following the tests. |
| 599 | 657 |
| 600 Args: | 658 Args: |
| 601 clear: True if existing log output should be cleared. | 659 clear: True if existing log output should be cleared. |
| 602 filters: A list of logcat filters to be used. | 660 filters: A list of logcat filters to be used. |
| 603 """ | 661 """ |
| 604 if clear: | 662 if clear: |
| 605 self._adb.SendCommand('logcat -c') | 663 self._adb.SendCommand('logcat -c') |
| 606 logcat_command = 'adb logcat -v threadtime %s' % ' '.join(filters) | 664 logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg, |
| 665 ' '.join(filters)) |
| 607 self.logcat_process = subprocess.Popen(logcat_command, shell=True, | 666 self.logcat_process = subprocess.Popen(logcat_command, shell=True, |
| 608 stdout=subprocess.PIPE) | 667 stdout=subprocess.PIPE) |
| 609 | 668 |
| 610 def StopRecordingLogcat(self): | 669 def StopRecordingLogcat(self): |
| 611 """Stops an existing logcat recording subprocess and returns output. | 670 """Stops an existing logcat recording subprocess and returns output. |
| 612 | 671 |
| 613 Returns: | 672 Returns: |
| 614 The logcat output as a string or an empty string if logcat was not | 673 The logcat output as a string or an empty string if logcat was not |
| 615 being recorded at the time. | 674 being recorded at the time. |
| 616 """ | 675 """ |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 665 return results | 724 return results |
| 666 | 725 |
| 667 def ExtractPid(self, process_name): | 726 def ExtractPid(self, process_name): |
| 668 """Extracts Process Ids for a given process name from Android Shell. | 727 """Extracts Process Ids for a given process name from Android Shell. |
| 669 | 728 |
| 670 Args: | 729 Args: |
| 671 process_name: name of the process on the device. | 730 process_name: name of the process on the device. |
| 672 | 731 |
| 673 Returns: | 732 Returns: |
| 674 List of all the process ids (as strings) that match the given name. | 733 List of all the process ids (as strings) that match the given name. |
| 734 If the name of a process exactly matches the given name, the pid of |
| 735 that process will be inserted to the front of the pid list. |
| 675 """ | 736 """ |
| 676 pids = [] | 737 pids = [] |
| 677 for line in self.RunShellCommand('ps'): | 738 for line in self.RunShellCommand('ps', log_result=False): |
| 678 data = line.split() | 739 data = line.split() |
| 679 try: | 740 try: |
| 680 if process_name in data[-1]: # name is in the last column | 741 if process_name in data[-1]: # name is in the last column |
| 681 pids.append(data[1]) # PID is in the second column | 742 if process_name == data[-1]: |
| 743 pids.insert(0, data[1]) # PID is in the second column |
| 744 else: |
| 745 pids.append(data[1]) |
| 682 except IndexError: | 746 except IndexError: |
| 683 pass | 747 pass |
| 684 return pids | 748 return pids |
| 685 | 749 |
| 686 def GetIoStats(self): | 750 def GetIoStats(self): |
| 687 """Gets cumulative disk IO stats since boot (for all processes). | 751 """Gets cumulative disk IO stats since boot (for all processes). |
| 688 | 752 |
| 689 Returns: | 753 Returns: |
| 690 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there | 754 Dict of {num_reads, num_writes, read_ms, write_ms} or None if there |
| 691 was an error. | 755 was an error. |
| 692 """ | 756 """ |
| 693 # Field definitions. | 757 for line in self.GetFileContents('/proc/diskstats', log_result=False): |
| 694 # http://www.kernel.org/doc/Documentation/iostats.txt | 758 stats = io_stats_parser.ParseIoStatsLine(line) |
| 695 device = 2 | 759 if stats.device == 'mmcblk0': |
| 696 num_reads_issued_idx = 3 | |
| 697 num_reads_merged_idx = 4 | |
| 698 num_sectors_read_idx = 5 | |
| 699 ms_spent_reading_idx = 6 | |
| 700 num_writes_completed_idx = 7 | |
| 701 num_writes_merged_idx = 8 | |
| 702 num_sectors_written_idx = 9 | |
| 703 ms_spent_writing_idx = 10 | |
| 704 num_ios_in_progress_idx = 11 | |
| 705 ms_spent_doing_io_idx = 12 | |
| 706 ms_spent_doing_io_weighted_idx = 13 | |
| 707 | |
| 708 for line in self.RunShellCommand('cat /proc/diskstats'): | |
| 709 fields = line.split() | |
| 710 if fields[device] == 'mmcblk0': | |
| 711 return { | 760 return { |
| 712 'num_reads': int(fields[num_reads_issued_idx]), | 761 'num_reads': stats.num_reads_issued, |
| 713 'num_writes': int(fields[num_writes_completed_idx]), | 762 'num_writes': stats.num_writes_completed, |
| 714 'read_ms': int(fields[ms_spent_reading_idx]), | 763 'read_ms': stats.ms_spent_reading, |
| 715 'write_ms': int(fields[ms_spent_writing_idx]), | 764 'write_ms': stats.ms_spent_writing, |
| 716 } | 765 } |
| 717 logging.warning('Could not find disk IO stats.') | 766 logging.warning('Could not find disk IO stats.') |
| 718 return None | 767 return None |
| 719 | 768 |
| 720 def GetMemoryUsage(self, package): | 769 def GetMemoryUsageForPid(self, pid): |
| 770 """Returns the memory usage for given pid. |
| 771 |
| 772 Args: |
| 773 pid: The pid number of the specific process running on device. |
| 774 |
| 775 Returns: |
| 776 A tuple containg: |
| 777 [0]: Dict of {metric:usage_kb}, for the process which has specified pid. |
| 778 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, |
| 779 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, |
| 780 KernelPageSize, MMUPageSize, Nvidia (tablet only). |
| 781 [1]: Detailed /proc/[PID]/smaps information. |
| 782 """ |
| 783 usage_dict = collections.defaultdict(int) |
| 784 smaps = collections.defaultdict(dict) |
| 785 current_smap = '' |
| 786 for line in self.GetFileContents('/proc/%s/smaps' % pid, log_result=False): |
| 787 items = line.split() |
| 788 # See man 5 proc for more details. The format is: |
| 789 # address perms offset dev inode pathname |
| 790 if len(items) > 5: |
| 791 current_smap = ' '.join(items[5:]) |
| 792 elif len(items) > 3: |
| 793 current_smap = ' '.join(items[3:]) |
| 794 match = re.match(MEMORY_INFO_RE, line) |
| 795 if match: |
| 796 key = match.group('key') |
| 797 usage_kb = int(match.group('usage_kb')) |
| 798 usage_dict[key] += usage_kb |
| 799 if key not in smaps[current_smap]: |
| 800 smaps[current_smap][key] = 0 |
| 801 smaps[current_smap][key] += usage_kb |
| 802 if not usage_dict or not any(usage_dict.values()): |
| 803 # Presumably the process died between ps and calling this method. |
| 804 logging.warning('Could not find memory usage for pid ' + str(pid)) |
| 805 |
| 806 for line in self.GetFileContents('/d/nvmap/generic-0/clients', |
| 807 log_result=False): |
| 808 match = re.match(NVIDIA_MEMORY_INFO_RE, line) |
| 809 if match and match.group('pid') == pid: |
| 810 usage_bytes = int(match.group('usage_bytes')) |
| 811 usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0)) # kB |
| 812 break |
| 813 |
| 814 return (usage_dict, smaps) |
| 815 |
| 816 def GetMemoryUsageForPackage(self, package): |
| 721 """Returns the memory usage for all processes whose name contains |pacakge|. | 817 """Returns the memory usage for all processes whose name contains |pacakge|. |
| 722 | 818 |
| 723 Args: | 819 Args: |
| 724 name: A string holding process name to lookup pid list for. | 820 name: A string holding process name to lookup pid list for. |
| 725 | 821 |
| 726 Returns: | 822 Returns: |
| 727 Dict of {metric:usage_kb}, summed over all pids associated with |name|. | 823 A tuple containg: |
| 728 The metric keys retruned are: Size, Rss, Pss, Shared_Clean, Shared_Dirty, | 824 [0]: Dict of {metric:usage_kb}, summed over all pids associated with |
| 729 Private_Clean, Private_Dirty, Referenced, Swap, KernelPageSize, | 825 |name|. |
| 730 MMUPageSize. | 826 The metric keys which may be included are: Size, Rss, Pss, Shared_Clean, |
| 827 Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap, |
| 828 KernelPageSize, MMUPageSize, Nvidia (tablet only). |
| 829 [1]: a list with detailed /proc/[PID]/smaps information. |
| 731 """ | 830 """ |
| 732 usage_dict = collections.defaultdict(int) | 831 usage_dict = collections.defaultdict(int) |
| 733 pid_list = self.ExtractPid(package) | 832 pid_list = self.ExtractPid(package) |
| 734 # We used to use the showmap command, but it is currently broken on | 833 smaps = collections.defaultdict(dict) |
| 735 # stingray so it's easier to just parse /proc/<pid>/smaps directly. | 834 |
| 736 memory_stat_re = re.compile('^(?P<key>\w+):\s+(?P<value>\d+) kB$') | |
| 737 for pid in pid_list: | 835 for pid in pid_list: |
| 738 for line in self.RunShellCommand('cat /proc/%s/smaps' % pid, | 836 usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid) |
| 739 log_result=False): | 837 smaps[pid] = smaps_per_pid |
| 740 match = re.match(memory_stat_re, line) | 838 for (key, value) in usage_dict_per_pid.items(): |
| 741 if match: usage_dict[match.group('key')] += int(match.group('value')) | 839 usage_dict[key] += value |
| 742 if not usage_dict or not any(usage_dict.values()): | |
| 743 # Presumably the process died between ps and showmap. | |
| 744 logging.warning('Could not find memory usage for pid ' + str(pid)) | |
| 745 return usage_dict | |
| 746 | 840 |
| 747 def UnlockDevice(self): | 841 return usage_dict, smaps |
| 748 """Unlocks the screen of the device.""" | |
| 749 # Make sure a menu button event will actually unlock the screen. | |
| 750 if IsRunningAsBuildbot(): | |
| 751 assert self.RunShellCommand('getprop ro.test_harness')[0].strip() == '1' | |
| 752 # The following keyevent unlocks the screen if locked. | |
| 753 self.SendKeyEvent(KEYCODE_MENU) | |
| 754 # If the screen wasn't locked the previous command will bring up the menu, | |
| 755 # which this will dismiss. Otherwise this shouldn't change anything. | |
| 756 self.SendKeyEvent(KEYCODE_BACK) | |
| OLD | NEW |