Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Unified Diff: tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper.py

Issue 290173006: telemetry: Improve symfs generation on Android (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Mock out perf binary. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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')
« no previous file with comments | « no previous file | tools/telemetry/telemetry/core/platform/profiler/android_profiling_helper_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698