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.
13 from pylib.device import device_errors # pylint: disable=F0401
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
21 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
22 from pylib.perf import perf_control # pylint: disable=F0401
26 # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
29 # Increase sampling frequency for better coverage.
33 _PERF_OPTIONS_ANDROID = [
34 # Increase priority to avoid dropping samples. Requires root.
40 rel_path = os.path.relpath(path, os.curdir)
41 return rel_path if len(rel_path) < len(path) else path
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:
53 subprocess.call(['/usr/bin/sudo', 'cp', zero.name, kptr_file])
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')
64 class _SingleProcessPerfProfiler(object):
65 """An internal class for using perf for a given process.
67 On android, this profiler uses pre-built binaries from AOSP.
68 See more details in prebuilt/android/README.txt.
70 def __init__(self, pid, output_file, browser_backend, platform_backend,
71 perf_binary, perfhost_binary):
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
81 perf_args = ['record', '--pid', str(pid)]
83 cmd_prefix = ['adb', '-s', browser_backend.adb.device_serial(), 'shell',
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)
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)
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"')
106 device = self._browser_backend.adb.device()
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()
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""" %
121 elif exit_code not in (0, -2):
123 'perf failed with exit code %d. Output:\n%s' % (exit_code,
126 self._tmp_output_file.close()
127 cmd = '%s report -n -i %s' % (_NicePath(self._perfhost_binary),
130 device = self._browser_backend.adb.device()
131 device.old_interface.Adb().Pull(self._device_output_file,
134 android_profiling_helper.GetRequiredLibrariesForPerfProfile(
136 symfs_root = os.path.dirname(self._output_file)
137 kallsyms = android_profiling_helper.CreateSymFs(device,
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):
146 objdump_path = android_profiling_helper.GetToolchainBinaryPath(
149 cmd += ' --objdump %s' % _NicePath(objdump_path)
152 print 'To view the profile, run:'
154 return self._output_file
156 def _GetStdOut(self):
157 self._tmp_output_file.flush()
159 with open(self._tmp_output_file.name) as f:
165 class PerfProfiler(profiler.Profiler):
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
174 perf_binary = perfhost_binary = _InstallPerfHost()
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()
182 _PrepareHostForPerf()
184 for pid, output_file in process_output_file_map.iteritems():
185 if 'zygote' in output_file:
187 self._process_profilers.append(
188 _SingleProcessPerfProfiler(
189 pid, output_file, browser_backend, platform_backend,
190 perf_binary, perfhost_binary))
192 if self._perf_control:
193 self._perf_control.SetDefaultPerfMode()
201 def is_supported(cls, browser_type):
202 if sys.platform != 'linux2':
204 if platform.GetHostPlatform().GetOSName() == 'chromeos':
209 def CustomizeBrowserOptions(cls, browser_type, options):
210 options.AppendExtraBrowserArgs([
212 '--allow-sandbox-debugging',
215 def CollectProfile(self):
216 if self._perf_control:
217 self._perf_control.SetDefaultPerfMode()
219 for single_process in self._process_profilers:
220 output_files.append(single_process.CollectProfile())
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.
228 assert os.path.exists(file_name)
229 with open(os.devnull, 'w') as devnull:
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('#'):
239 fields = line.split('^')
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:
249 return period_by_function