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(['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, '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._perfhost_binary = perfhost_binary
80 perf_args = ['record', '--pid', str(pid)]
82 cmd_prefix = ['adb', '-s', browser_backend.adb.device_serial(), 'shell',
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)
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)
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"')
105 device = self._browser_backend.adb.device()
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()
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""" %
119 elif exit_code not in (0, -2):
121 'perf failed with exit code %d. Output:\n%s' % (exit_code,
124 self._tmp_output_file.close()
125 cmd = '%s report -n -i %s' % (_NicePath(self._perfhost_binary),
128 device = self._browser_backend.adb.device()
129 device.old_interface.Adb().Pull(self._device_output_file,
132 android_profiling_helper.GetRequiredLibrariesForPerfProfile(
134 symfs_root = os.path.dirname(self._output_file)
135 kallsyms = android_profiling_helper.CreateSymFs(device,
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):
144 objdump_path = android_profiling_helper.GetToolchainBinaryPath(
147 cmd += ' --objdump %s' % _NicePath(objdump_path)
150 print 'To view the profile, run:'
152 return self._output_file
154 def _GetStdOut(self):
155 self._tmp_output_file.flush()
157 with open(self._tmp_output_file.name) as f:
163 class PerfProfiler(profiler.Profiler):
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
172 perf_binary = perfhost_binary = _InstallPerfHost()
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()
180 _PrepareHostForPerf()
182 for pid, output_file in process_output_file_map.iteritems():
183 if 'zygote' in output_file:
185 self._process_profilers.append(
186 _SingleProcessPerfProfiler(
187 pid, output_file, browser_backend, platform_backend,
188 perf_binary, perfhost_binary))
190 if self._perf_control:
191 self._perf_control.SetDefaultPerfMode()
199 def is_supported(cls, browser_type):
200 if sys.platform != 'linux2':
202 if platform.GetHostPlatform().GetOSName() == 'chromeos':
207 def CustomizeBrowserOptions(cls, browser_type, options):
208 options.AppendExtraBrowserArgs([
210 '--allow-sandbox-debugging',
213 def CollectProfile(self):
214 if self._perf_control:
215 self._perf_control.SetDefaultPerfMode()
217 for single_process in self._process_profilers:
218 output_files.append(single_process.CollectProfile())
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.
226 assert os.path.exists(file_name)
227 with open(os.devnull, 'w') as devnull:
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('#'):
237 fields = line.split('^')
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:
247 return period_by_function