Upstream version 10.39.225.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(['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, '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._perfhost_binary = perfhost_binary
79     cmd_prefix = []
80     perf_args = ['record', '--pid', str(pid)]
81     if self._is_android:
82       cmd_prefix = ['adb', '-s', browser_backend.adb.device_serial(), 'shell',
83                     perf_binary]
84       perf_args += _PERF_OPTIONS_ANDROID
85       output_file = os.path.join('/sdcard', 'perf_profiles',
86                                  os.path.basename(output_file))
87       self._device_output_file = output_file
88       browser_backend.adb.RunShellCommand(
89           'mkdir -p ' + os.path.dirname(self._device_output_file))
90       browser_backend.adb.RunShellCommand('rm -f ' + self._device_output_file)
91     else:
92       cmd_prefix = [perf_binary]
93     perf_args += ['--output', output_file] + _PERF_OPTIONS
94     self._proc = subprocess.Popen(cmd_prefix + perf_args,
95         stdout=self._tmp_output_file, stderr=subprocess.STDOUT)
96
97   def CollectProfile(self):
98     if ('renderer' in self._output_file and
99         not self._is_android and
100         not self._platform_backend.GetCommandLine(self._pid)):
101       logging.warning('Renderer was swapped out during profiling. '
102                       'To collect a full profile rerun with '
103                       '"--extra-browser-args=--single-process"')
104     if self._is_android:
105       device = self._browser_backend.adb.device()
106       try:
107         device.KillAll('perf', signum=signal.SIGINT, blocking=True)
108       except device_errors.CommandFailedError:
109         logging.warning('The perf process could not be killed on the device.')
110     self._proc.send_signal(signal.SIGINT)
111     exit_code = self._proc.wait()
112     try:
113       if exit_code == 128:
114         raise Exception(
115             """perf failed with exit code 128.
116 Try rerunning this script under sudo or setting
117 /proc/sys/kernel/perf_event_paranoid to "-1".\nOutput:\n%s""" %
118             self._GetStdOut())
119       elif exit_code not in (0, -2):
120         raise Exception(
121             'perf failed with exit code %d. Output:\n%s' % (exit_code,
122                                                             self._GetStdOut()))
123     finally:
124       self._tmp_output_file.close()
125     cmd = '%s report -n -i %s' % (_NicePath(self._perfhost_binary),
126                                   self._output_file)
127     if self._is_android:
128       device = self._browser_backend.adb.device()
129       device.old_interface.Adb().Pull(self._device_output_file,
130                                       self._output_file)
131       required_libs = \
132           android_profiling_helper.GetRequiredLibrariesForPerfProfile(
133               self._output_file)
134       symfs_root = os.path.dirname(self._output_file)
135       kallsyms = android_profiling_helper.CreateSymFs(device,
136                                                       symfs_root,
137                                                       required_libs,
138                                                       use_symlinks=True)
139       cmd += ' --symfs %s --kallsyms %s' % (symfs_root, kallsyms)
140       for lib in required_libs:
141         lib = os.path.join(symfs_root, lib[1:])
142         if not os.path.exists(lib):
143           continue
144         objdump_path = android_profiling_helper.GetToolchainBinaryPath(
145             lib, 'objdump')
146         if objdump_path:
147           cmd += ' --objdump %s' % _NicePath(objdump_path)
148           break
149
150     print 'To view the profile, run:'
151     print ' ', cmd
152     return self._output_file
153
154   def _GetStdOut(self):
155     self._tmp_output_file.flush()
156     try:
157       with open(self._tmp_output_file.name) as f:
158         return f.read()
159     except IOError:
160       return ''
161
162
163 class PerfProfiler(profiler.Profiler):
164
165   def __init__(self, browser_backend, platform_backend, output_path, state):
166     super(PerfProfiler, self).__init__(
167         browser_backend, platform_backend, output_path, state)
168     process_output_file_map = self._GetProcessOutputFileMap()
169     self._process_profilers = []
170     self._perf_control = None
171
172     perf_binary = perfhost_binary = _InstallPerfHost()
173     try:
174       if platform_backend.GetOSName() == 'android':
175         device = browser_backend.adb.device()
176         perf_binary = android_profiling_helper.PrepareDeviceForPerf(device)
177         self._perf_control = perf_control.PerfControl(device)
178         self._perf_control.SetPerfProfilingMode()
179       else:
180         _PrepareHostForPerf()
181
182       for pid, output_file in process_output_file_map.iteritems():
183         if 'zygote' in output_file:
184           continue
185         self._process_profilers.append(
186             _SingleProcessPerfProfiler(
187                 pid, output_file, browser_backend, platform_backend,
188                 perf_binary, perfhost_binary))
189     except:
190       if self._perf_control:
191         self._perf_control.SetDefaultPerfMode()
192       raise
193
194   @classmethod
195   def name(cls):
196     return 'perf'
197
198   @classmethod
199   def is_supported(cls, browser_type):
200     if sys.platform != 'linux2':
201       return False
202     if platform.GetHostPlatform().GetOSName() == 'chromeos':
203       return False
204     return True
205
206   @classmethod
207   def CustomizeBrowserOptions(cls, browser_type, options):
208     options.AppendExtraBrowserArgs([
209         '--no-sandbox',
210         '--allow-sandbox-debugging',
211     ])
212
213   def CollectProfile(self):
214     if self._perf_control:
215       self._perf_control.SetDefaultPerfMode()
216     output_files = []
217     for single_process in self._process_profilers:
218       output_files.append(single_process.CollectProfile())
219     return output_files
220
221   @classmethod
222   def GetTopSamples(cls, file_name, number):
223     """Parses the perf generated profile in |file_name| and returns a
224     {function: period} dict of the |number| hottests functions.
225     """
226     assert os.path.exists(file_name)
227     with open(os.devnull, 'w') as devnull:
228       _InstallPerfHost()
229       report = subprocess.Popen(
230           [android_profiling_helper.GetPerfhostName(),
231            'report', '--show-total-period', '-U', '-t', '^', '-i', file_name],
232           stdout=subprocess.PIPE, stderr=devnull).communicate()[0]
233     period_by_function = {}
234     for line in report.split('\n'):
235       if not line or line.startswith('#'):
236         continue
237       fields = line.split('^')
238       if len(fields) != 5:
239         continue
240       period = int(fields[1])
241       function = fields[4].partition(' ')[2]
242       function = re.sub('<.*>', '', function)  # Strip template params.
243       function = re.sub('[(].*[)]', '', function)  # Strip function params.
244       period_by_function[function] = period
245       if len(period_by_function) == number:
246         break
247     return period_by_function