Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / platform / profiler / android_profiling_helper.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import glob
6 import hashlib
7 import logging
8 import os
9 import platform
10 import re
11 import shutil
12 import subprocess
13
14 from telemetry import decorators
15 from telemetry.core import platform as telemetry_platform
16 from telemetry.core import util
17 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
18 from telemetry.util import support_binaries
19
20 try:
21   import sqlite3
22 except ImportError:
23   sqlite3 = None
24
25
26
27 _TEXT_SECTION = '.text'
28
29
30 def _ElfMachineId(elf_file):
31   headers = subprocess.check_output(['readelf', '-h', elf_file])
32   return re.match(r'.*Machine:\s+(\w+)', headers, re.DOTALL).group(1)
33
34
35 def _ElfSectionAsString(elf_file, section):
36   return subprocess.check_output(['readelf', '-p', section, elf_file])
37
38
39 def _ElfSectionMd5Sum(elf_file, section):
40   result = subprocess.check_output(
41       'readelf -p%s "%s" | md5sum' % (section, elf_file), shell=True)
42   return result.split(' ', 1)[0]
43
44
45 def _FindMatchingUnstrippedLibraryOnHost(device, lib):
46   lib_base = os.path.basename(lib)
47
48   device_md5 = device.RunShellCommand('md5 "%s"' % lib, as_root=True)[0]
49   device_md5 = device_md5.split(' ', 1)[0]
50
51   def FindMatchingStrippedLibrary(out_path):
52     # First find a matching stripped library on the host. This avoids the need
53     # to pull the stripped library from the device, which can take tens of
54     # seconds.
55     host_lib_pattern = os.path.join(out_path, '*_apk', 'libs', '*', lib_base)
56     for stripped_host_lib in glob.glob(host_lib_pattern):
57       with open(stripped_host_lib) as f:
58         host_md5 = hashlib.md5(f.read()).hexdigest()
59         if host_md5 == device_md5:
60           return stripped_host_lib
61
62   for build_dir, build_type in util.GetBuildDirectories():
63     out_path = os.path.join(build_dir, build_type)
64     stripped_host_lib = FindMatchingStrippedLibrary(out_path)
65     if stripped_host_lib:
66       break
67   else:
68     return None
69
70   # The corresponding unstripped library will be under out/Release/lib.
71   unstripped_host_lib = os.path.join(out_path, 'lib', lib_base)
72
73   # Make sure the unstripped library matches the stripped one. We do this
74   # by comparing the hashes of text sections in both libraries. This isn't an
75   # exact guarantee, but should still give reasonable confidence that the
76   # libraries are compatible.
77   # TODO(skyostil): Check .note.gnu.build-id instead once we're using
78   # --build-id=sha1.
79   # pylint: disable=W0631
80   if (_ElfSectionMd5Sum(unstripped_host_lib, _TEXT_SECTION) !=
81       _ElfSectionMd5Sum(stripped_host_lib, _TEXT_SECTION)):
82     return None
83   return unstripped_host_lib
84
85
86 @decorators.Cache
87 def GetPerfhostName():
88   return 'perfhost_' + telemetry_platform.GetHostPlatform().GetOSVersionName()
89
90
91 # Ignored directories for libraries that aren't useful for symbolization.
92 _IGNORED_LIB_PATHS = [
93   '/data/dalvik-cache',
94   '/tmp'
95 ]
96
97
98 def GetRequiredLibrariesForPerfProfile(profile_file):
99   """Returns the set of libraries necessary to symbolize a given perf profile.
100
101   Args:
102     profile_file: Path to perf profile to analyse.
103
104   Returns:
105     A set of required library file names.
106   """
107   with open(os.devnull, 'w') as dev_null:
108     perfhost_path = support_binaries.FindPath(
109         GetPerfhostName(), 'x86_64', 'linux')
110     perf = subprocess.Popen([perfhost_path, 'script', '-i', profile_file],
111                              stdout=dev_null, stderr=subprocess.PIPE)
112     _, output = perf.communicate()
113   missing_lib_re = re.compile(
114       r'^Failed to open (.*), continuing without symbols')
115   libs = set()
116   for line in output.split('\n'):
117     lib = missing_lib_re.match(line)
118     if lib:
119       lib = lib.group(1)
120       path = os.path.dirname(lib)
121       if (any(path.startswith(ignored_path)
122               for ignored_path in _IGNORED_LIB_PATHS)
123           or path == '/' or not path):
124         continue
125       libs.add(lib)
126   return libs
127
128
129 def GetRequiredLibrariesForVTuneProfile(profile_file):
130   """Returns the set of libraries necessary to symbolize a given VTune profile.
131
132   Args:
133     profile_file: Path to VTune profile to analyse.
134
135   Returns:
136     A set of required library file names.
137   """
138   db_file = os.path.join(profile_file, 'sqlite-db', 'dicer.db')
139   conn = sqlite3.connect(db_file)
140
141   try:
142     # The 'dd_module_file' table lists all libraries on the device. Only the
143     # ones with 'bin_located_path' are needed for the profile.
144     query = 'SELECT bin_path, bin_located_path FROM dd_module_file'
145     return set(row[0] for row in conn.cursor().execute(query) if row[1])
146   finally:
147     conn.close()
148
149
150 def CreateSymFs(device, symfs_dir, libraries, use_symlinks=True):
151   """Creates a symfs directory to be used for symbolizing profiles.
152
153   Prepares a set of files ("symfs") to be used with profilers such as perf for
154   converting binary addresses into human readable function names.
155
156   Args:
157     device: DeviceUtils instance identifying the target device.
158     symfs_dir: Path where the symfs should be created.
159     libraries: Set of library file names that should be included in the symfs.
160     use_symlinks: If True, link instead of copy unstripped libraries into the
161       symfs. This will speed up the operation, but the resulting symfs will no
162       longer be valid if the linked files are modified, e.g., by rebuilding.
163
164   Returns:
165     The absolute path to the kernel symbols within the created symfs.
166   """
167   logging.info('Building symfs into %s.' % symfs_dir)
168
169   mismatching_files = {}
170   for lib in libraries:
171     device_dir = os.path.dirname(lib)
172     output_dir = os.path.join(symfs_dir, device_dir[1:])
173     if not os.path.exists(output_dir):
174       os.makedirs(output_dir)
175     output_lib = os.path.join(output_dir, os.path.basename(lib))
176
177     if lib.startswith('/data/app/'):
178       # If this is our own library instead of a system one, look for a matching
179       # unstripped library under the out directory.
180       unstripped_host_lib = _FindMatchingUnstrippedLibraryOnHost(device, lib)
181       if not unstripped_host_lib:
182         logging.warning('Could not find symbols for %s.' % lib)
183         logging.warning('Is the correct output directory selected '
184                         '(CHROMIUM_OUT_DIR)? Did you install the APK after '
185                         'building?')
186         continue
187       if use_symlinks:
188         if os.path.lexists(output_lib):
189           os.remove(output_lib)
190         os.symlink(os.path.abspath(unstripped_host_lib), output_lib)
191       # Copy the unstripped library only if it has been changed to avoid the
192       # delay. Add one second to the modification time to guard against file
193       # systems with poor timestamp resolution.
194       elif not os.path.exists(output_lib) or \
195           (os.stat(unstripped_host_lib).st_mtime >
196            os.stat(output_lib).st_mtime + 1):
197         logging.info('Copying %s to %s' % (unstripped_host_lib, output_lib))
198         shutil.copy2(unstripped_host_lib, output_lib)
199     else:
200       # Otherwise save a copy of the stripped system library under the symfs so
201       # the profiler can at least use the public symbols of that library. To
202       # speed things up, only pull files that don't match copies we already
203       # have in the symfs.
204       if not device_dir in mismatching_files:
205         changed_files = device.old_interface.GetFilesChanged(output_dir,
206                                                              device_dir)
207         mismatching_files[device_dir] = [
208             device_path for _, device_path in changed_files]
209
210       if not os.path.exists(output_lib) or lib in mismatching_files[device_dir]:
211         logging.info('Pulling %s to %s' % (lib, output_lib))
212         device.PullFile(lib, output_lib)
213
214   # Also pull a copy of the kernel symbols.
215   output_kallsyms = os.path.join(symfs_dir, 'kallsyms')
216   if not os.path.exists(output_kallsyms):
217     device.PullFile('/proc/kallsyms', output_kallsyms)
218   return output_kallsyms
219
220
221 def PrepareDeviceForPerf(device):
222   """Set up a device for running perf.
223
224   Args:
225     device: DeviceUtils instance identifying the target device.
226
227   Returns:
228     The path to the installed perf binary on the device.
229   """
230   android_prebuilt_profiler_helper.InstallOnDevice(device, 'perf')
231   # Make sure kernel pointers are not hidden.
232   device.WriteFile('/proc/sys/kernel/kptr_restrict', '0', as_root=True)
233   return android_prebuilt_profiler_helper.GetDevicePath('perf')
234
235
236 def GetToolchainBinaryPath(library_file, binary_name):
237   """Return the path to an Android toolchain binary on the host.
238
239   Args:
240     library_file: ELF library which is used to identify the used ABI,
241         architecture and toolchain.
242     binary_name: Binary to search for, e.g., 'objdump'
243   Returns:
244     Full path to binary or None if the binary was not found.
245   """
246   # Mapping from ELF machine identifiers to GNU toolchain names.
247   toolchain_configs = {
248     'x86': 'i686-linux-android',
249     'MIPS': 'mipsel-linux-android',
250     'ARM': 'arm-linux-androideabi',
251     'x86-64': 'x86_64-linux-android',
252     'AArch64': 'aarch64-linux-android',
253   }
254   toolchain_config = toolchain_configs[_ElfMachineId(library_file)]
255   host_os = platform.uname()[0].lower()
256   host_machine = platform.uname()[4]
257
258   elf_comment = _ElfSectionAsString(library_file, '.comment')
259   toolchain_version = re.match(r'.*GCC: \(GNU\) ([\w.]+)',
260                                elf_comment, re.DOTALL)
261   if not toolchain_version:
262     return None
263   toolchain_version = toolchain_version.group(1)
264
265   path = os.path.join(util.GetChromiumSrcDir(), 'third_party', 'android_tools',
266                       'ndk', 'toolchains',
267                       '%s-%s' % (toolchain_config, toolchain_version),
268                       'prebuilt', '%s-%s' % (host_os, host_machine), 'bin',
269                       '%s-%s' % (toolchain_config, binary_name))
270   path = os.path.abspath(path)
271   return path if os.path.exists(path) else None