| Index: tools/telemetry/telemetry/internal/platform/profiler/android_profiling_helper.py
|
| diff --git a/tools/telemetry/telemetry/internal/platform/profiler/android_profiling_helper.py b/tools/telemetry/telemetry/internal/platform/profiler/android_profiling_helper.py
|
| deleted file mode 100644
|
| index 3fddf923c7166d96eb6a5a8d8f91f5eb8d8a9992..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/telemetry/internal/platform/profiler/android_profiling_helper.py
|
| +++ /dev/null
|
| @@ -1,311 +0,0 @@
|
| -# Copyright 2014 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -
|
| -import glob
|
| -import hashlib
|
| -import logging
|
| -import os
|
| -import platform
|
| -import re
|
| -import shutil
|
| -import subprocess
|
| -
|
| -from telemetry.internal.util import binary_manager
|
| -from telemetry.core import platform as telemetry_platform
|
| -from telemetry.core import util
|
| -from telemetry import decorators
|
| -from telemetry.internal.platform.profiler import android_prebuilt_profiler_helper
|
| -
|
| -from devil.android import md5sum # pylint: disable=import-error
|
| -
|
| -
|
| -try:
|
| - import sqlite3
|
| -except ImportError:
|
| - sqlite3 = None
|
| -
|
| -
|
| -
|
| -_TEXT_SECTION = '.text'
|
| -
|
| -
|
| -def _ElfMachineId(elf_file):
|
| - headers = subprocess.check_output(['readelf', '-h', elf_file])
|
| - return re.match(r'.*Machine:\s+(\w+)', headers, re.DOTALL).group(1)
|
| -
|
| -
|
| -def _ElfSectionAsString(elf_file, section):
|
| - return subprocess.check_output(['readelf', '-p', section, elf_file])
|
| -
|
| -
|
| -def _ElfSectionMd5Sum(elf_file, section):
|
| - result = subprocess.check_output(
|
| - 'readelf -p%s "%s" | md5sum' % (section, elf_file), shell=True)
|
| - return result.split(' ', 1)[0]
|
| -
|
| -
|
| -def _FindMatchingUnstrippedLibraryOnHost(device, lib):
|
| - lib_base = os.path.basename(lib)
|
| -
|
| - device_md5 = device.RunShellCommand('md5 "%s"' % lib, as_root=True)[0]
|
| - device_md5 = device_md5.split(' ', 1)[0]
|
| -
|
| - def FindMatchingStrippedLibrary(out_path):
|
| - # First find a matching stripped library on the host. This avoids the need
|
| - # to pull the stripped library from the device, which can take tens of
|
| - # seconds.
|
| - host_lib_pattern = os.path.join(out_path, '*_apk', 'libs', '*', lib_base)
|
| - for stripped_host_lib in glob.glob(host_lib_pattern):
|
| - with open(stripped_host_lib) as f:
|
| - host_md5 = hashlib.md5(f.read()).hexdigest()
|
| - if host_md5 == device_md5:
|
| - return stripped_host_lib
|
| -
|
| - for build_dir, build_type in util.GetBuildDirectories():
|
| - out_path = os.path.join(build_dir, build_type)
|
| - stripped_host_lib = FindMatchingStrippedLibrary(out_path)
|
| - if stripped_host_lib:
|
| - break
|
| - else:
|
| - return None
|
| -
|
| - # The corresponding unstripped library will be under out/Release/lib.
|
| - unstripped_host_lib = os.path.join(out_path, 'lib', lib_base)
|
| -
|
| - # Make sure the unstripped library matches the stripped one. We do this
|
| - # by comparing the hashes of text sections in both libraries. This isn't an
|
| - # exact guarantee, but should still give reasonable confidence that the
|
| - # libraries are compatible.
|
| - # TODO(skyostil): Check .note.gnu.build-id instead once we're using
|
| - # --build-id=sha1.
|
| - # pylint: disable=undefined-loop-variable
|
| - if (_ElfSectionMd5Sum(unstripped_host_lib, _TEXT_SECTION) !=
|
| - _ElfSectionMd5Sum(stripped_host_lib, _TEXT_SECTION)):
|
| - return None
|
| - return unstripped_host_lib
|
| -
|
| -
|
| -@decorators.Cache
|
| -def GetPerfhostName():
|
| - return 'perfhost_' + telemetry_platform.GetHostPlatform().GetOSVersionName()
|
| -
|
| -
|
| -# Ignored directories for libraries that aren't useful for symbolization.
|
| -_IGNORED_LIB_PATHS = [
|
| - '/data/dalvik-cache',
|
| - '/tmp'
|
| -]
|
| -
|
| -
|
| -def GetRequiredLibrariesForPerfProfile(profile_file):
|
| - """Returns the set of libraries necessary to symbolize a given perf profile.
|
| -
|
| - Args:
|
| - profile_file: Path to perf profile to analyse.
|
| -
|
| - Returns:
|
| - A set of required library file names.
|
| - """
|
| - with open(os.devnull, 'w') as dev_null:
|
| - perfhost_path = binary_manager.FetchPath(
|
| - GetPerfhostName(), 'x86_64', 'linux')
|
| - perf = subprocess.Popen([perfhost_path, 'script', '-i', profile_file],
|
| - stdout=dev_null, stderr=subprocess.PIPE)
|
| - _, output = perf.communicate()
|
| - missing_lib_re = re.compile(
|
| - ('^Failed to open (.*), continuing without symbols|'
|
| - '^(.*[.]so).*not found, continuing without symbols'))
|
| - libs = set()
|
| - for line in output.split('\n'):
|
| - lib = missing_lib_re.match(line)
|
| - if lib:
|
| - lib = lib.group(1) or lib.group(2)
|
| - path = os.path.dirname(lib)
|
| - if (any(path.startswith(ignored_path)
|
| - for ignored_path in _IGNORED_LIB_PATHS)
|
| - or path == '/' or not path):
|
| - continue
|
| - libs.add(lib)
|
| - return libs
|
| -
|
| -
|
| -def GetRequiredLibrariesForVTuneProfile(profile_file):
|
| - """Returns the set of libraries necessary to symbolize a given VTune profile.
|
| -
|
| - Args:
|
| - profile_file: Path to VTune profile to analyse.
|
| -
|
| - Returns:
|
| - A set of required library file names.
|
| - """
|
| - db_file = os.path.join(profile_file, 'sqlite-db', 'dicer.db')
|
| - conn = sqlite3.connect(db_file)
|
| -
|
| - try:
|
| - # The 'dd_module_file' table lists all libraries on the device. Only the
|
| - # ones with 'bin_located_path' are needed for the profile.
|
| - query = 'SELECT bin_path, bin_located_path FROM dd_module_file'
|
| - return set(row[0] for row in conn.cursor().execute(query) if row[1])
|
| - finally:
|
| - conn.close()
|
| -
|
| -
|
| -def _FileMetadataMatches(filea, fileb):
|
| - """Check if the metadata of two files matches."""
|
| - assert os.path.exists(filea)
|
| - if not os.path.exists(fileb):
|
| - return False
|
| -
|
| - fields_to_compare = [
|
| - 'st_ctime', 'st_gid', 'st_mode', 'st_mtime', 'st_size', 'st_uid']
|
| -
|
| - filea_stat = os.stat(filea)
|
| - fileb_stat = os.stat(fileb)
|
| - for field in fields_to_compare:
|
| - # shutil.copy2 doesn't get ctime/mtime identical when the file system
|
| - # provides sub-second accuracy.
|
| - if int(getattr(filea_stat, field)) != int(getattr(fileb_stat, field)):
|
| - return False
|
| - return True
|
| -
|
| -
|
| -def CreateSymFs(device, symfs_dir, libraries, use_symlinks=True):
|
| - """Creates a symfs directory to be used for symbolizing profiles.
|
| -
|
| - Prepares a set of files ("symfs") to be used with profilers such as perf for
|
| - converting binary addresses into human readable function names.
|
| -
|
| - Args:
|
| - device: DeviceUtils instance identifying the target device.
|
| - symfs_dir: Path where the symfs should be created.
|
| - libraries: Set of library file names that should be included in the symfs.
|
| - use_symlinks: If True, link instead of copy unstripped libraries into the
|
| - symfs. This will speed up the operation, but the resulting symfs will no
|
| - longer be valid if the linked files are modified, e.g., by rebuilding.
|
| -
|
| - Returns:
|
| - The absolute path to the kernel symbols within the created symfs.
|
| - """
|
| - logging.info('Building symfs into %s.' % symfs_dir)
|
| -
|
| - for lib in libraries:
|
| - device_dir = os.path.dirname(lib)
|
| - output_dir = os.path.join(symfs_dir, device_dir[1:])
|
| - if not os.path.exists(output_dir):
|
| - os.makedirs(output_dir)
|
| - output_lib = os.path.join(output_dir, os.path.basename(lib))
|
| -
|
| - if lib.startswith('/data/app'):
|
| - # If this is our own library instead of a system one, look for a matching
|
| - # unstripped library under the out directory.
|
| - unstripped_host_lib = _FindMatchingUnstrippedLibraryOnHost(device, lib)
|
| - if not unstripped_host_lib:
|
| - logging.warning('Could not find symbols for %s.' % lib)
|
| - logging.warning('Is the correct output directory selected '
|
| - '(CHROMIUM_OUT_DIR)? Did you install the APK after '
|
| - 'building?')
|
| - continue
|
| - if use_symlinks:
|
| - if os.path.lexists(output_lib):
|
| - os.remove(output_lib)
|
| - os.symlink(os.path.abspath(unstripped_host_lib), output_lib)
|
| - # Copy the unstripped library only if it has been changed to avoid the
|
| - # delay.
|
| - elif not _FileMetadataMatches(unstripped_host_lib, output_lib):
|
| - logging.info('Copying %s to %s' % (unstripped_host_lib, output_lib))
|
| - shutil.copy2(unstripped_host_lib, output_lib)
|
| - else:
|
| - # Otherwise save a copy of the stripped system library under the symfs so
|
| - # the profiler can at least use the public symbols of that library. To
|
| - # speed things up, only pull files that don't match copies we already
|
| - # have in the symfs.
|
| - if not os.path.exists(output_lib):
|
| - pull = True
|
| - else:
|
| - host_md5sums = md5sum.CalculateHostMd5Sums([output_lib])
|
| - try:
|
| - device_md5sums = md5sum.CalculateDeviceMd5Sums([lib], device)
|
| - except:
|
| - logging.exception('New exception caused by DeviceUtils conversion')
|
| - raise
|
| -
|
| - pull = True
|
| - if host_md5sums and device_md5sums and output_lib in host_md5sums \
|
| - and lib in device_md5sums:
|
| - pull = host_md5sums[output_lib] != device_md5sums[lib]
|
| -
|
| - if pull:
|
| - logging.info('Pulling %s to %s', lib, output_lib)
|
| - device.PullFile(lib, output_lib)
|
| -
|
| - # Also pull a copy of the kernel symbols.
|
| - output_kallsyms = os.path.join(symfs_dir, 'kallsyms')
|
| - if not os.path.exists(output_kallsyms):
|
| - device.PullFile('/proc/kallsyms', output_kallsyms)
|
| - return output_kallsyms
|
| -
|
| -
|
| -def PrepareDeviceForPerf(device):
|
| - """Set up a device for running perf.
|
| -
|
| - Args:
|
| - device: DeviceUtils instance identifying the target device.
|
| -
|
| - Returns:
|
| - The path to the installed perf binary on the device.
|
| - """
|
| - android_prebuilt_profiler_helper.InstallOnDevice(device, 'perf')
|
| - # Make sure kernel pointers are not hidden.
|
| - device.WriteFile('/proc/sys/kernel/kptr_restrict', '0', as_root=True)
|
| - return android_prebuilt_profiler_helper.GetDevicePath('perf')
|
| -
|
| -
|
| -def GetToolchainBinaryPath(library_file, binary_name):
|
| - """Return the path to an Android toolchain binary on the host.
|
| -
|
| - Args:
|
| - library_file: ELF library which is used to identify the used ABI,
|
| - architecture and toolchain.
|
| - binary_name: Binary to search for, e.g., 'objdump'
|
| - Returns:
|
| - Full path to binary or None if the binary was not found.
|
| - """
|
| - # Mapping from ELF machine identifiers to GNU toolchain names.
|
| - toolchain_configs = {
|
| - 'x86': 'i686-linux-android',
|
| - 'MIPS': 'mipsel-linux-android',
|
| - 'ARM': 'arm-linux-androideabi',
|
| - 'x86-64': 'x86_64-linux-android',
|
| - 'AArch64': 'aarch64-linux-android',
|
| - }
|
| - toolchain_config = toolchain_configs[_ElfMachineId(library_file)]
|
| - host_os = platform.uname()[0].lower()
|
| - host_machine = platform.uname()[4]
|
| -
|
| - elf_comment = _ElfSectionAsString(library_file, '.comment')
|
| - toolchain_version = re.match(r'.*GCC: \(GNU\) ([\w.]+)',
|
| - elf_comment, re.DOTALL)
|
| - if not toolchain_version:
|
| - return None
|
| - toolchain_version = toolchain_version.group(1)
|
| -
|
| - toolchain_path = os.path.abspath(os.path.join(
|
| - util.GetChromiumSrcDir(), 'third_party', 'android_tools', 'ndk',
|
| - 'toolchains', '%s-%s' % (toolchain_config, toolchain_version)))
|
| - if not os.path.exists(toolchain_path):
|
| - logging.warning(
|
| - 'Unable to find toolchain binary %s: toolchain not found at %s',
|
| - binary_name, toolchain_path)
|
| - return None
|
| -
|
| - path = os.path.join(
|
| - toolchain_path, 'prebuilt', '%s-%s' % (host_os, host_machine), 'bin',
|
| - '%s-%s' % (toolchain_config, binary_name))
|
| - if not os.path.exists(path):
|
| - logging.warning(
|
| - 'Unable to find toolchain binary %s: binary not found at %s',
|
| - binary_name, path)
|
| - return None
|
| -
|
| - return path
|
|
|