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 |