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