| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2013 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 import logging | 5 import logging |
| 6 import os | 6 import os |
| 7 import posixpath | 7 import posixpath |
| 8 import re | 8 import re |
| 9 import subprocess | 9 import subprocess |
| 10 import tempfile | 10 import tempfile |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 265 This can be used to make memory measurements more stable. Requires root. | 265 This can be used to make memory measurements more stable. Requires root. |
| 266 """ | 266 """ |
| 267 if not self._can_elevate_privilege: | 267 if not self._can_elevate_privilege: |
| 268 logging.warning('Cannot run purge_ashmem. Requires a rooted device.') | 268 logging.warning('Cannot run purge_ashmem. Requires a rooted device.') |
| 269 return | 269 return |
| 270 | 270 |
| 271 if not android_prebuilt_profiler_helper.InstallOnDevice( | 271 if not android_prebuilt_profiler_helper.InstallOnDevice( |
| 272 self._device, 'purge_ashmem'): | 272 self._device, 'purge_ashmem'): |
| 273 raise Exception('Error installing purge_ashmem.') | 273 raise Exception('Error installing purge_ashmem.') |
| 274 output = self._device.RunShellCommand([ | 274 output = self._device.RunShellCommand([ |
| 275 android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem')]) | 275 android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem')], |
| 276 check_return=True) |
| 276 for l in output: | 277 for l in output: |
| 277 logging.info(l) | 278 logging.info(l) |
| 278 | 279 |
| 279 @decorators.Deprecated( | 280 @decorators.Deprecated( |
| 280 2017, 11, 4, | 281 2017, 11, 4, |
| 281 'Clients should use tracing and memory-infra in new Telemetry ' | 282 'Clients should use tracing and memory-infra in new Telemetry ' |
| 282 'benchmarks. See for context: https://crbug.com/632021') | 283 'benchmarks. See for context: https://crbug.com/632021') |
| 283 def GetMemoryStats(self, pid): | 284 def GetMemoryStats(self, pid): |
| 284 memory_usage = self._device.GetMemoryUsageForPid(pid) | 285 memory_usage = self._device.GetMemoryUsageForPid(pid) |
| 285 if not memory_usage: | 286 if not memory_usage: |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 | 331 |
| 331 def FlushEntireSystemCache(self): | 332 def FlushEntireSystemCache(self): |
| 332 cache = cache_control.CacheControl(self._device) | 333 cache = cache_control.CacheControl(self._device) |
| 333 cache.DropRamCaches() | 334 cache.DropRamCaches() |
| 334 | 335 |
| 335 def FlushSystemCacheForDirectory(self, directory): | 336 def FlushSystemCacheForDirectory(self, directory): |
| 336 raise NotImplementedError() | 337 raise NotImplementedError() |
| 337 | 338 |
| 338 def FlushDnsCache(self): | 339 def FlushDnsCache(self): |
| 339 self._device.RunShellCommand( | 340 self._device.RunShellCommand( |
| 340 ['ndc', 'resolver', 'flushdefaultif'], as_root=True) | 341 ['ndc', 'resolver', 'flushdefaultif'], as_root=True, check_return=True) |
| 341 | 342 |
| 342 def StopApplication(self, application): | 343 def StopApplication(self, application): |
| 343 """Stop the given |application|. | 344 """Stop the given |application|. |
| 344 | 345 |
| 345 Args: | 346 Args: |
| 346 application: The full package name string of the application to stop. | 347 application: The full package name string of the application to stop. |
| 347 """ | 348 """ |
| 348 self._device.ForceStop(application) | 349 self._device.ForceStop(application) |
| 349 | 350 |
| 350 def KillApplication(self, application): | 351 def KillApplication(self, application): |
| (...skipping 11 matching lines...) Expand all Loading... |
| 362 self, application, parameters=None, elevate_privilege=False): | 363 self, application, parameters=None, elevate_privilege=False): |
| 363 """Launches the given |application| with a list of |parameters| on the OS. | 364 """Launches the given |application| with a list of |parameters| on the OS. |
| 364 | 365 |
| 365 Args: | 366 Args: |
| 366 application: The full package name string of the application to launch. | 367 application: The full package name string of the application to launch. |
| 367 parameters: A list of parameters to be passed to the ActivityManager. | 368 parameters: A list of parameters to be passed to the ActivityManager. |
| 368 elevate_privilege: Currently unimplemented on Android. | 369 elevate_privilege: Currently unimplemented on Android. |
| 369 """ | 370 """ |
| 370 if elevate_privilege: | 371 if elevate_privilege: |
| 371 raise NotImplementedError("elevate_privilege isn't supported on android.") | 372 raise NotImplementedError("elevate_privilege isn't supported on android.") |
| 373 # TODO(catapult:#3215): Migrate to StartActivity. |
| 372 cmd = ['am', 'start'] | 374 cmd = ['am', 'start'] |
| 373 if parameters: | 375 if parameters: |
| 374 cmd.extend(parameters) | 376 cmd.extend(parameters) |
| 375 cmd.append(application) | 377 cmd.append(application) |
| 376 result_lines = self._device.RunShellCommand(cmd) | 378 result_lines = self._device.RunShellCommand(cmd, check_return=True) |
| 377 for line in result_lines: | 379 for line in result_lines: |
| 378 if line.startswith('Error: '): | 380 if line.startswith('Error: '): |
| 379 raise ValueError('Failed to start "%s" with error\n %s' % | 381 raise ValueError('Failed to start "%s" with error\n %s' % |
| 380 (application, line)) | 382 (application, line)) |
| 381 | 383 |
| 382 def IsApplicationRunning(self, application): | 384 def IsApplicationRunning(self, application): |
| 383 return len(self._device.GetPids(application)) > 0 | 385 return len(self._device.GetPids(application)) > 0 |
| 384 | 386 |
| 385 def CanLaunchApplication(self, application): | 387 def CanLaunchApplication(self, application): |
| 386 if not self._installed_applications: | 388 if not self._installed_applications: |
| 387 self._installed_applications = self._device.RunShellCommand( | 389 self._installed_applications = self._device.RunShellCommand( |
| 388 ['pm', 'list', 'packages']) | 390 ['pm', 'list', 'packages'], check_return=True) |
| 389 return 'package:' + application in self._installed_applications | 391 return 'package:' + application in self._installed_applications |
| 390 | 392 |
| 391 def InstallApplication(self, application): | 393 def InstallApplication(self, application): |
| 392 self._installed_applications = None | 394 self._installed_applications = None |
| 393 self._device.Install(application) | 395 self._device.Install(application) |
| 394 | 396 |
| 395 @decorators.Cache | 397 @decorators.Cache |
| 396 def CanCaptureVideo(self): | 398 def CanCaptureVideo(self): |
| 397 return self.GetOSVersionName() >= 'K' | 399 return self.GetOSVersionName() >= 'K' |
| 398 | 400 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 446 | 448 |
| 447 def GetFileContents(self, fname): | 449 def GetFileContents(self, fname): |
| 448 if not self._can_elevate_privilege: | 450 if not self._can_elevate_privilege: |
| 449 logging.warning('%s cannot be retrieved on non-rooted device.', fname) | 451 logging.warning('%s cannot be retrieved on non-rooted device.', fname) |
| 450 return '' | 452 return '' |
| 451 return self._device.ReadFile(fname, as_root=True) | 453 return self._device.ReadFile(fname, as_root=True) |
| 452 | 454 |
| 453 def GetPsOutput(self, columns, pid=None): | 455 def GetPsOutput(self, columns, pid=None): |
| 454 assert columns == ['pid', 'name'] or columns == ['pid'], \ | 456 assert columns == ['pid', 'name'] or columns == ['pid'], \ |
| 455 'Only know how to return pid and name. Requested: ' + columns | 457 'Only know how to return pid and name. Requested: ' + columns |
| 458 # TODO(catapult:#3215): Migrate to GetPids. |
| 456 cmd = ['ps'] | 459 cmd = ['ps'] |
| 457 if pid: | 460 if pid: |
| 458 cmd.extend(['-p', str(pid)]) | 461 cmd.extend(['-p', str(pid)]) |
| 459 ps = self._device.RunShellCommand(cmd, large_output=True)[1:] | 462 ps = self._device.RunShellCommand( |
| 463 cmd, check_return=True, large_output=True)[1:] |
| 460 output = [] | 464 output = [] |
| 461 for line in ps: | 465 for line in ps: |
| 462 data = line.split() | 466 data = line.split() |
| 463 curr_pid = data[1] | 467 curr_pid = data[1] |
| 464 curr_name = data[-1] | 468 curr_name = data[-1] |
| 465 if columns == ['pid', 'name']: | 469 if columns == ['pid', 'name']: |
| 466 output.append([curr_pid, curr_name]) | 470 output.append([curr_pid, curr_name]) |
| 467 else: | 471 else: |
| 468 output.append([curr_pid]) | 472 output.append([curr_pid]) |
| 469 return output | 473 return output |
| 470 | 474 |
| 471 def RunCommand(self, command): | 475 def RunCommand(self, command): |
| 472 return '\n'.join(self._device.RunShellCommand(command)) | 476 return self._device.RunShellCommand( |
| 477 command, check_return=True, raw_output=True) |
| 473 | 478 |
| 474 @staticmethod | 479 @staticmethod |
| 475 def ParseCStateSample(sample): | 480 def ParseCStateSample(sample): |
| 476 sample_stats = {} | 481 sample_stats = {} |
| 477 for cpu in sample: | 482 for cpu in sample: |
| 478 values = sample[cpu].splitlines() | 483 values = sample[cpu].splitlines() |
| 479 # Each state has three values after excluding the time value. | 484 # Each state has three values after excluding the time value. |
| 480 num_states = (len(values) - 1) / 3 | 485 num_states = (len(values) - 1) / 3 |
| 481 names = values[:num_states] | 486 names = values[:num_states] |
| 482 times = values[num_states:2 * num_states] | 487 times = values[num_states:2 * num_states] |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 583 # the base name; so we now need to get the base name from the directory. | 588 # the base name; so we now need to get the base name from the directory. |
| 584 if not profile_base: | 589 if not profile_base: |
| 585 profile_base = os.path.basename(profile_parent) | 590 profile_base = os.path.basename(profile_parent) |
| 586 | 591 |
| 587 saved_profile_location = '/sdcard/profile/%s' % profile_base | 592 saved_profile_location = '/sdcard/profile/%s' % profile_base |
| 588 self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)]) | 593 self._device.PushChangedFiles([(new_profile_dir, saved_profile_location)]) |
| 589 | 594 |
| 590 profile_dir = self._GetProfileDir(package) | 595 profile_dir = self._GetProfileDir(package) |
| 591 self._EfficientDeviceDirectoryCopy( | 596 self._EfficientDeviceDirectoryCopy( |
| 592 saved_profile_location, profile_dir) | 597 saved_profile_location, profile_dir) |
| 593 dumpsys = self._device.RunShellCommand(['dumpsys', 'package', package]) | 598 dumpsys = self._device.RunShellCommand( |
| 599 ['dumpsys', 'package', package], check_return=True) |
| 594 id_line = next(line for line in dumpsys if 'userId=' in line) | 600 id_line = next(line for line in dumpsys if 'userId=' in line) |
| 595 uid = re.search(r'\d+', id_line).group() | 601 uid = re.search(r'\d+', id_line).group() |
| 596 files = self._device.ListDirectory(profile_dir, as_root=True) | 602 files = self._device.ListDirectory(profile_dir, as_root=True) |
| 597 paths = [posixpath.join(profile_dir, f) for f in files if f != 'lib'] | 603 paths = [posixpath.join(profile_dir, f) for f in files if f != 'lib'] |
| 598 for path in paths: | 604 for path in paths: |
| 605 # TODO(crbug.com/628617): Implement without ignoring shell errors. |
| 599 # Note: need to pass command as a string for the shell to expand the *'s. | 606 # Note: need to pass command as a string for the shell to expand the *'s. |
| 600 extended_path = '%s %s/* %s/*/* %s/*/*/*' % (path, path, path, path) | 607 extended_path = '%s %s/* %s/*/* %s/*/*/*' % (path, path, path, path) |
| 601 self._device.RunShellCommand( | 608 self._device.RunShellCommand( |
| 602 'chown %s.%s %s' % (uid, uid, extended_path)) | 609 'chown %s.%s %s' % (uid, uid, extended_path), check_return=False) |
| 603 | 610 |
| 604 def _EfficientDeviceDirectoryCopy(self, source, dest): | 611 def _EfficientDeviceDirectoryCopy(self, source, dest): |
| 605 if not self._device_copy_script: | 612 if not self._device_copy_script: |
| 606 self._device.adb.Push( | 613 self._device.adb.Push( |
| 607 _DEVICE_COPY_SCRIPT_FILE, | 614 _DEVICE_COPY_SCRIPT_FILE, |
| 608 _DEVICE_COPY_SCRIPT_LOCATION) | 615 _DEVICE_COPY_SCRIPT_LOCATION) |
| 609 self._device_copy_script = _DEVICE_COPY_SCRIPT_FILE | 616 self._device_copy_script = _DEVICE_COPY_SCRIPT_LOCATION |
| 610 self._device.RunShellCommand( | 617 self._device.RunShellCommand( |
| 611 ['sh', self._device_copy_script, source, dest]) | 618 ['sh', self._device_copy_script, source, dest], check_return=True) |
| 612 | 619 |
| 613 def GetPortPairForForwarding(self, local_port): | 620 def GetPortPairForForwarding(self, local_port): |
| 614 return forwarders.PortPair(local_port=local_port, remote_port=0) | 621 return forwarders.PortPair(local_port=local_port, remote_port=0) |
| 615 | 622 |
| 616 def RemoveProfile(self, package, ignore_list): | 623 def RemoveProfile(self, package, ignore_list): |
| 617 """Delete application profile on device. | 624 """Delete application profile on device. |
| 618 | 625 |
| 619 Args: | 626 Args: |
| 620 package: The full package name string of the application for which the | 627 package: The full package name string of the application for which the |
| 621 profile is to be deleted. | 628 profile is to be deleted. |
| 622 ignore_list: List of files to keep. | 629 ignore_list: List of files to keep. |
| 623 """ | 630 """ |
| 624 profile_dir = self._GetProfileDir(package) | 631 profile_dir = self._GetProfileDir(package) |
| 625 if not self._device.PathExists(profile_dir): | 632 if not self._device.PathExists(profile_dir): |
| 626 return | 633 return |
| 627 files = [ | 634 files = [ |
| 628 posixpath.join(profile_dir, f) | 635 posixpath.join(profile_dir, f) |
| 629 for f in self._device.ListDirectory(profile_dir, as_root=True) | 636 for f in self._device.ListDirectory(profile_dir, as_root=True) |
| 630 if f not in ignore_list] | 637 if f not in ignore_list] |
| 631 if not files: | 638 if not files: |
| 632 return | 639 return |
| 633 cmd = ['rm', '-r'] | 640 self._device.RemovePath(files, recursive=True, as_root=True) |
| 634 cmd.extend(files) | |
| 635 self._device.RunShellCommand(cmd, as_root=True, check_return=True) | |
| 636 | 641 |
| 637 def PullProfile(self, package, output_profile_path): | 642 def PullProfile(self, package, output_profile_path): |
| 638 """Copy application profile from device to host machine. | 643 """Copy application profile from device to host machine. |
| 639 | 644 |
| 640 Args: | 645 Args: |
| 641 package: The full package name string of the application for which the | 646 package: The full package name string of the application for which the |
| 642 profile is to be copied. | 647 profile is to be copied. |
| 643 output_profile_dir: Location where profile to be stored on host machine. | 648 output_profile_dir: Location where profile to be stored on host machine. |
| 644 """ | 649 """ |
| 645 profile_dir = self._GetProfileDir(package) | 650 profile_dir = self._GetProfileDir(package) |
| 646 logging.info("Pulling profile directory from device: '%s'->'%s'.", | 651 logging.info("Pulling profile directory from device: '%s'->'%s'.", |
| 647 profile_dir, output_profile_path) | 652 profile_dir, output_profile_path) |
| 648 # To minimize bandwidth it might be good to look at whether all the data | 653 # To minimize bandwidth it might be good to look at whether all the data |
| 649 # pulled down is really needed e.g. .pak files. | 654 # pulled down is really needed e.g. .pak files. |
| 650 if not os.path.exists(output_profile_path): | 655 if not os.path.exists(output_profile_path): |
| 651 os.makedirs(output_profile_path) | 656 os.makedirs(output_profile_path) |
| 652 files = self._device.ListDirectory(profile_dir, as_root=True) | 657 problem_files = [] |
| 653 for f in files: | 658 for filename in self._device.ListDirectory(profile_dir, as_root=True): |
| 654 # Don't pull lib, since it is created by the installer. | 659 # Don't pull lib, since it is created by the installer. |
| 655 if f != 'lib': | 660 if filename == 'lib': |
| 656 source = posixpath.join(profile_dir, f) | 661 continue |
| 657 dest = os.path.join(output_profile_path, f) | 662 source = posixpath.join(profile_dir, filename) |
| 658 try: | 663 dest = os.path.join(output_profile_path, filename) |
| 659 self._device.PullFile(source, dest, timeout=240) | 664 try: |
| 660 except device_errors.CommandFailedError: | 665 self._device.PullFile(source, dest, timeout=240) |
| 661 logging.exception('Failed to pull %s to %s', source, dest) | 666 except device_errors.CommandFailedError: |
| 667 problem_files.append(source) |
| 668 if problem_files: |
| 669 # Some paths (e.g. 'files', 'app_textures') consistently fail to be |
| 670 # pulled from the device. |
| 671 logging.warning( |
| 672 'There were errors retrieving the following paths from the profile:') |
| 673 for filepath in problem_files: |
| 674 logging.warning('- %s', filepath) |
| 662 | 675 |
| 663 def _GetProfileDir(self, package): | 676 def _GetProfileDir(self, package): |
| 664 """Returns the on-device location where the application profile is stored | 677 """Returns the on-device location where the application profile is stored |
| 665 based on Android convention. | 678 based on Android convention. |
| 666 | 679 |
| 667 Args: | 680 Args: |
| 668 package: The full package name string of the application. | 681 package: The full package name string of the application. |
| 669 """ | 682 """ |
| 670 return '/data/data/%s/' % package | 683 return '/data/data/%s/' % package |
| 671 | 684 |
| 672 def SetDebugApp(self, package): | 685 def SetDebugApp(self, package): |
| 673 """Set application to debugging. | 686 """Set application to debugging. |
| 674 | 687 |
| 675 Args: | 688 Args: |
| 676 package: The full package name string of the application. | 689 package: The full package name string of the application. |
| 677 """ | 690 """ |
| 678 if self._device.IsUserBuild(): | 691 if self._device.IsUserBuild(): |
| 679 logging.debug('User build device, setting debug app') | 692 logging.debug('User build device, setting debug app') |
| 680 self._device.RunShellCommand( | 693 self._device.RunShellCommand( |
| 681 ['am', 'set-debug-app', '--persistent', package]) | 694 ['am', 'set-debug-app', '--persistent', package], |
| 695 check_return=True) |
| 682 | 696 |
| 683 def GetLogCat(self, number_of_lines=500): | 697 def GetLogCat(self, number_of_lines=500): |
| 684 """Returns most recent lines of logcat dump. | 698 """Returns most recent lines of logcat dump. |
| 685 | 699 |
| 686 Args: | 700 Args: |
| 687 number_of_lines: Number of lines of log to return. | 701 number_of_lines: Number of lines of log to return. |
| 688 """ | 702 """ |
| 689 def decode_line(line): | 703 def decode_line(line): |
| 690 try: | 704 try: |
| 691 uline = unicode(line, encoding='utf-8') | 705 uline = unicode(line, encoding='utf-8') |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 787 # Use linux instead of Android because when determining what tests to run on | 801 # Use linux instead of Android because when determining what tests to run on |
| 788 # a bot the individual device could be down, which would make BattOr tests | 802 # a bot the individual device could be down, which would make BattOr tests |
| 789 # not run on any device. BattOrs communicate with the host and not android | 803 # not run on any device. BattOrs communicate with the host and not android |
| 790 # devices. | 804 # devices. |
| 791 return battor_wrapper.IsBattOrConnected('linux') | 805 return battor_wrapper.IsBattOrConnected('linux') |
| 792 | 806 |
| 793 def Log(self, message): | 807 def Log(self, message): |
| 794 """Prints line to logcat.""" | 808 """Prints line to logcat.""" |
| 795 TELEMETRY_LOGCAT_TAG = 'Telemetry' | 809 TELEMETRY_LOGCAT_TAG = 'Telemetry' |
| 796 self._device.RunShellCommand( | 810 self._device.RunShellCommand( |
| 797 ['log', '-p', 'i', '-t', TELEMETRY_LOGCAT_TAG, message]) | 811 ['log', '-p', 'i', '-t', TELEMETRY_LOGCAT_TAG, message], |
| 812 check_return=True) |
| 798 | 813 |
| 799 def WaitForTemperature(self, temp): | 814 def WaitForTemperature(self, temp): |
| 800 # Temperature is in tenths of a degree C, so we convert to that scale. | 815 # Temperature is in tenths of a degree C, so we convert to that scale. |
| 801 self._battery.LetBatteryCoolToTemperature(temp * 10) | 816 self._battery.LetBatteryCoolToTemperature(temp * 10) |
| 802 | 817 |
| 803 def _FixPossibleAdbInstability(): | 818 def _FixPossibleAdbInstability(): |
| 804 """Host side workaround for crbug.com/268450 (adb instability). | 819 """Host side workaround for crbug.com/268450 (adb instability). |
| 805 | 820 |
| 806 The adb server has a race which is mitigated by binding to a single core. | 821 The adb server has a race which is mitigated by binding to a single core. |
| 807 """ | 822 """ |
| 808 if not psutil: | 823 if not psutil: |
| 809 return | 824 return |
| 810 for process in psutil.process_iter(): | 825 for process in psutil.process_iter(): |
| 811 try: | 826 try: |
| 812 if psutil.version_info >= (2, 0): | 827 if psutil.version_info >= (2, 0): |
| 813 if 'adb' in process.name(): | 828 if 'adb' in process.name(): |
| 814 process.cpu_affinity([0]) | 829 process.cpu_affinity([0]) |
| 815 else: | 830 else: |
| 816 if 'adb' in process.name: | 831 if 'adb' in process.name: |
| 817 process.set_cpu_affinity([0]) | 832 process.set_cpu_affinity([0]) |
| 818 except (psutil.NoSuchProcess, psutil.AccessDenied): | 833 except (psutil.NoSuchProcess, psutil.AccessDenied): |
| 819 logging.warn('Failed to set adb process CPU affinity') | 834 logging.warn('Failed to set adb process CPU affinity') |
| OLD | NEW |