Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / platform / profiler / perf_profiler.py
1 # Copyright 2013 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 logging
6 import os
7 import re
8 import signal
9 import subprocess
10 import sys
11 import tempfile
12
13 from pylib.device import device_errors  # pylint: disable=F0401
14
15 from telemetry.core import platform
16 from telemetry.core import util
17 from telemetry.core.platform import profiler
18 from telemetry.core.platform.profiler import android_profiling_helper
19 from telemetry.util import support_binaries
20
21 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
22 from pylib.perf import perf_control  # pylint: disable=F0401
23
24
25 _PERF_OPTIONS = [
26     # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
27     # which does not.
28     '-g',
29     # Increase sampling frequency for better coverage.
30     '--freq', '2000',
31 ]
32
33 _PERF_OPTIONS_ANDROID = [
34     # Increase priority to avoid dropping samples. Requires root.
35     '--realtime', '80',
36 ]
37
38
39 def _NicePath(path):
40   rel_path = os.path.relpath(path, os.curdir)
41   return rel_path if len(rel_path) < len(path) else path
42
43
44 def _PrepareHostForPerf():
45   kptr_file = '/proc/sys/kernel/kptr_restrict'
46   with open(kptr_file) as f:
47     if f.read().strip() != '0':
48       logging.warning('Making kernel symbols unrestricted. You might have to '
49           'enter your password for "sudo".')
50       with tempfile.NamedTemporaryFile() as zero:
51         zero.write('0')
52         zero.flush()
53         subprocess.call(['/usr/bin/sudo', 'cp', zero.name, kptr_file])
54
55
56 def _InstallPerfHost():
57   perfhost_name = android_profiling_helper.GetPerfhostName()
58   host = platform.GetHostPlatform()
59   if not host.CanLaunchApplication(perfhost_name):
60     host.InstallApplication(perfhost_name)
61   return support_binaries.FindPath(perfhost_name, 'x86_64', 'linux')
62
63
64 class _SingleProcessPerfProfiler(object):
65   """An internal class for using perf for a given process.
66
67   On android, this profiler uses pre-built binaries from AOSP.
68   See more details in prebuilt/android/README.txt.
69   """
70   def __init__(self, pid, output_file, browser_backend, platform_backend,
71                perf_binary, perfhost_binary):
72     self._pid = pid
73     self._browser_backend = browser_backend
74     self._platform_backend = platform_backend
75     self._output_file = output_file
76     self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
77     self._is_android = platform_backend.GetOSName() == 'android'
78     self._perf_binary = perf_binary
79     self._perfhost_binary = perfhost_binary
80     cmd_prefix = []
81     perf_args = ['record', '--pid', str(pid)]
82     if self._is_android:
83       cmd_prefix = ['adb', '-s', browser_backend.adb.device_serial(), 'shell',
84                     perf_binary]
85       perf_args += _PERF_OPTIONS_ANDROID
86       output_file = os.path.join('/sdcard', 'perf_profiles',
87                                  os.path.basename(output_file))
88       self._device_output_file = output_file
89       browser_backend.adb.RunShellCommand(
90           'mkdir -p ' + os.path.dirname(self._device_output_file))
91       browser_backend.adb.RunShellCommand('rm -f ' + self._device_output_file)
92     else:
93       cmd_prefix = [perf_binary]
94     perf_args += ['--output', output_file] + _PERF_OPTIONS
95     self._proc = subprocess.Popen(cmd_prefix + perf_args,
96         stdout=self._tmp_output_file, stderr=subprocess.STDOUT)
97
98   def CollectProfile(self):
99     if ('renderer' in self._output_file and
100         not self._is_android and
101         not self._platform_backend.GetCommandLine(self._pid)):
102       logging.warning('Renderer was swapped out during profiling. '
103                       'To collect a full profile rerun with '
104                       '"--extra-browser-args=--single-process"')
105     if self._is_android:
106       device = self._browser_backend.adb.device()
107       try:
108         binary_name = os.path.basename(self._perf_binary)
109         device.KillAll(binary_name, signum=signal.SIGINT, blocking=True)
110       except device_errors.CommandFailedError:
111         logging.warning('The perf process could not be killed on the device.')
112     self._proc.send_signal(signal.SIGINT)
113     exit_code = self._proc.wait()
114     try:
115       if exit_code == 128:
116         raise Exception(
117             """perf failed with exit code 128.
118 Try rerunning this script under sudo or setting
119 /proc/sys/kernel/perf_event_paranoid to "-1".\nOutput:\n%s""" %
120             self._GetStdOut())
121       elif exit_code not in (0, -2):
122         raise Exception(
123             'perf failed with exit code %d. Output:\n%s' % (exit_code,
124                                                             self._GetStdOut()))
125     finally:
126       self._tmp_output_file.close()
127     cmd = '%s report -n -i %s' % (_NicePath(self._perfhost_binary),
128                                   self._output_file)
129     if self._is_android:
130       device = self._browser_backend.adb.device()
131       device.old_interface.Adb().Pull(self._device_output_file,
132                                       self._output_file)
133       required_libs = \
134           android_profiling_helper.GetRequiredLibrariesForPerfProfile(
135               self._output_file)
136       symfs_root = os.path.dirname(self._output_file)
137       kallsyms = android_profiling_helper.CreateSymFs(device,
138                                                       symfs_root,
139                                                       required_libs,
140                                                       use_symlinks=True)
141       cmd += ' --symfs %s --kallsyms %s' % (symfs_root, kallsyms)
142       for lib in required_libs:
143         lib = os.path.join(symfs_root, lib[1:])
144         if not os.path.exists(lib):
145           continue
146         objdump_path = android_profiling_helper.GetToolchainBinaryPath(
147             lib, 'objdump')
148         if objdump_path:
149           cmd += ' --objdump %s' % _NicePath(objdump_path)
150           break
151
152     print 'To view the profile, run:'
153     print ' ', cmd
154     return self._output_file
155
156   def _GetStdOut(self):
157     self._tmp_output_file.flush()
158     try:
159       with open(self._tmp_output_file.name) as f:
160         return f.read()
161     except IOError:
162       return ''
163
164
165 class PerfProfiler(profiler.Profiler):
166
167   def __init__(self, browser_backend, platform_backend, output_path, state):
168     super(PerfProfiler, self).__init__(
169         browser_backend, platform_backend, output_path, state)
170     process_output_file_map = self._GetProcessOutputFileMap()
171     self._process_profilers = []
172     self._perf_control = None
173
174     perf_binary = perfhost_binary = _InstallPerfHost()
175     try:
176       if platform_backend.GetOSName() == 'android':
177         device = browser_backend.adb.device()
178         perf_binary = android_profiling_helper.PrepareDeviceForPerf(device)
179         self._perf_control = perf_control.PerfControl(device)
180         self._perf_control.SetPerfProfilingMode()
181       else:
182         _PrepareHostForPerf()
183
184       for pid, output_file in process_output_file_map.iteritems():
185         if 'zygote' in output_file:
186           continue
187         self._process_profilers.append(
188             _SingleProcessPerfProfiler(
189                 pid, output_file, browser_backend, platform_backend,
190                 perf_binary, perfhost_binary))
191     except:
192       if self._perf_control:
193         self._perf_control.SetDefaultPerfMode()
194       raise
195
196   @classmethod
197   def name(cls):
198     return 'perf'
199
200   @classmethod
201   def is_supported(cls, browser_type):
202     if sys.platform != 'linux2':
203       return False
204     if platform.GetHostPlatform().GetOSName() == 'chromeos':
205       return False
206     return True
207
208   @classmethod
209   def CustomizeBrowserOptions(cls, browser_type, options):
210     options.AppendExtraBrowserArgs([
211         '--no-sandbox',
212         '--allow-sandbox-debugging',
213     ])
214
215   def CollectProfile(self):
216     if self._perf_control:
217       self._perf_control.SetDefaultPerfMode()
218     output_files = []
219     for single_process in self._process_profilers:
220       output_files.append(single_process.CollectProfile())
221     return output_files
222
223   @classmethod
224   def GetTopSamples(cls, file_name, number):
225     """Parses the perf generated profile in |file_name| and returns a
226     {function: period} dict of the |number| hottests functions.
227     """
228     assert os.path.exists(file_name)
229     with open(os.devnull, 'w') as devnull:
230       _InstallPerfHost()
231       report = subprocess.Popen(
232           [android_profiling_helper.GetPerfhostName(),
233            'report', '--show-total-period', '-U', '-t', '^', '-i', file_name],
234           stdout=subprocess.PIPE, stderr=devnull).communicate()[0]
235     period_by_function = {}
236     for line in report.split('\n'):
237       if not line or line.startswith('#'):
238         continue
239       fields = line.split('^')
240       if len(fields) != 5:
241         continue
242       period = int(fields[1])
243       function = fields[4].partition(' ')[2]
244       function = re.sub('<.*>', '', function)  # Strip template params.
245       function = re.sub('[(].*[)]', '', function)  # Strip function params.
246       period_by_function[function] = period
247       if len(period_by_function) == number:
248         break
249     return period_by_function