| Index: tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper.py
|
| diff --git a/tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper.py b/tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b746251b6b81c4b8b9c84f2ebd93607fa0d236b9
|
| --- /dev/null
|
| +++ b/tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper.py
|
| @@ -0,0 +1,178 @@
|
| +# 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 re
|
| +import shutil
|
| +import subprocess
|
| +
|
| +from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
|
| +
|
| +
|
| +_TEXT_SECTION = '.text'
|
| +
|
| +
|
| +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):
|
| + out_path = os.path.join(os.environ.get('CHROMIUM_OUT_DIR', 'out'), 'Release')
|
| + lib_base = os.path.basename(lib)
|
| +
|
| + device_md5 = device.old_interface.RunShellCommandWithSU('md5 "%s"' % lib)[0]
|
| + device_md5 = device_md5.split(' ', 1)[0]
|
| +
|
| + # 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:
|
| + 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=W0631
|
| + if (_ElfSectionMd5Sum(unstripped_host_lib, _TEXT_SECTION) !=
|
| + _ElfSectionMd5Sum(stripped_host_lib, _TEXT_SECTION)):
|
| + return None
|
| + return unstripped_host_lib
|
| +
|
| +
|
| +# 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:
|
| + perf = subprocess.Popen(['perf', 'script', '-i', profile_file],
|
| + stdout=dev_null, stderr=subprocess.PIPE)
|
| + _, output = perf.communicate()
|
| + missing_lib_re = re.compile(
|
| + r'^Failed to open (.*), continuing without symbols')
|
| + libs = set()
|
| + for line in output.split('\n'):
|
| + lib = missing_lib_re.match(line)
|
| + if lib:
|
| + lib = lib.group(1)
|
| + path = os.path.dirname(lib)
|
| + if any(path.startswith(ignored_path)
|
| + for ignored_path in _IGNORED_LIB_PATHS) or path == '/':
|
| + continue
|
| + libs.add(lib)
|
| + return libs
|
| +
|
| +
|
| +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)
|
| +
|
| + mismatching_files = {}
|
| + 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-lib/'):
|
| + # 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. Add one second to the modification time to guard against file
|
| + # systems with poor timestamp resolution.
|
| + elif not os.path.exists(output_lib) or \
|
| + (os.stat(unstripped_host_lib).st_mtime >
|
| + os.stat(output_lib).st_mtime + 1):
|
| + 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 device_dir in mismatching_files:
|
| + changed_files = device.old_interface.GetFilesChanged(output_dir,
|
| + device_dir)
|
| + mismatching_files[device_dir] = [
|
| + device_path for _, device_path in changed_files]
|
| +
|
| + if not os.path.exists(output_lib) or lib in mismatching_files[device_dir]:
|
| + logging.info('Pulling %s to %s' % (lib, output_lib))
|
| + device.old_interface.PullFileFromDevice(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.old_interface.PullFileFromDevice('/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.old_interface.SetProtectedFileContents(
|
| + '/proc/sys/kernel/kptr_restrict', '0')
|
| + return android_prebuilt_profiler_helper.GetDevicePath('perf')
|
|
|