1 # Copyright (c) 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.
14 from telemetry.core import util
15 from telemetry.core.platform import profiler
16 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
19 class _SingleProcessPerfProfiler(object):
20 """An internal class for using perf for a given process.
22 On android, this profiler uses pre-built binaries from AOSP.
23 See more details in prebuilt/android/README.txt.
25 def __init__(self, pid, output_file, browser_backend, platform_backend):
27 self._browser_backend = browser_backend
28 self._platform_backend = platform_backend
29 self._output_file = output_file
30 self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
31 self._is_android = platform_backend.GetOSName() == 'android'
34 perf_binary = android_prebuilt_profiler_helper.GetDevicePath(
36 cmd_prefix = ['adb', '-s', browser_backend.adb.device(), 'shell',
38 output_file = os.path.join('/sdcard', 'perf_profiles',
39 os.path.basename(output_file))
40 self._device_output_file = output_file
41 browser_backend.adb.RunShellCommand(
42 'mkdir -p ' + os.path.dirname(self._device_output_file))
45 self._proc = subprocess.Popen(cmd_prefix +
46 ['record', '--call-graph',
47 '--pid', str(pid), '--output', output_file],
48 stdout=self._tmp_output_file, stderr=subprocess.STDOUT)
50 def CollectProfile(self):
51 if ('renderer' in self._output_file and
52 not self._is_android and
53 not self._platform_backend.GetCommandLine(self._pid)):
54 logging.warning('Renderer was swapped out during profiling. '
55 'To collect a full profile rerun with '
56 '"--extra-browser-args=--single-process"')
58 perf_pids = self._browser_backend.adb.Adb().ExtractPid('perf')
59 self._browser_backend.adb.Adb().RunShellCommand(
60 'kill -SIGINT ' + ' '.join(perf_pids))
62 lambda: not self._browser_backend.adb.Adb().ExtractPid('perf'),
64 self._proc.send_signal(signal.SIGINT)
65 exit_code = self._proc.wait()
69 """perf failed with exit code 128.
70 Try rerunning this script under sudo or setting
71 /proc/sys/kernel/perf_event_paranoid to "-1".\nOutput:\n%s""" %
73 elif exit_code not in (0, -2):
75 'perf failed with exit code %d. Output:\n%s' % (exit_code,
78 self._tmp_output_file.close()
81 self._browser_backend.adb.Adb().Adb().Pull(
82 self._device_output_file,
84 host_symfs = os.path.join(os.path.dirname(self._output_file),
86 if not os.path.exists(host_symfs):
87 os.makedirs(host_symfs)
88 # On Android, the --symfs parameter needs to map a directory structure
89 # similar to the device, that is:
90 # --symfs=/tmp/foobar and then inside foobar there'll be something like
91 # /tmp/foobar/data/app-lib/$PACKAGE/libname.so
92 # Assume the symbolized library under out/Release/lib is equivalent to
93 # the one in the device, and symlink it in the host to match --symfs.
95 lambda app_lib: app_lib.startswith(self._browser_backend.package),
96 self._browser_backend.adb.Adb().RunShellCommand('ls /data/app-lib'))
97 os.symlink(os.path.abspath(
98 os.path.join(util.GetChromiumSrcDir(),
99 os.environ.get('CHROMIUM_OUT_DIR', 'out'),
101 os.path.join(host_symfs, device_dir[0]))
102 print 'On Android, assuming $CHROMIUM_OUT_DIR/Release/lib has a fresh'
103 print 'symbolized library matching the one on device.'
104 cmd = (android_prebuilt_profiler_helper.GetHostPath('perfhost') +
105 ' report --symfs %s' % os.path.dirname(self._output_file))
106 print 'To view the profile, run:'
107 print ' %s -n -i %s' % (cmd, self._output_file)
108 return self._output_file
110 def _GetStdOut(self):
111 self._tmp_output_file.flush()
113 with open(self._tmp_output_file.name) as f:
119 class PerfProfiler(profiler.Profiler):
121 def __init__(self, browser_backend, platform_backend, output_path, state):
122 super(PerfProfiler, self).__init__(
123 browser_backend, platform_backend, output_path, state)
124 process_output_file_map = self._GetProcessOutputFileMap()
125 self._process_profilers = []
126 if platform_backend.GetOSName() == 'android':
127 android_prebuilt_profiler_helper.GetIfChanged('perfhost')
128 os.chmod(android_prebuilt_profiler_helper.GetHostPath('perfhost'),
129 stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
130 android_prebuilt_profiler_helper.InstallOnDevice(
131 browser_backend.adb, 'perf')
132 for pid, output_file in process_output_file_map.iteritems():
133 if 'zygote' in output_file:
135 self._process_profilers.append(
136 _SingleProcessPerfProfiler(
137 pid, output_file, browser_backend, platform_backend))
144 def is_supported(cls, browser_type):
145 if sys.platform != 'linux2':
147 if browser_type.startswith('cros'):
149 return cls._CheckLinuxPerf() or browser_type.startswith('android')
152 def _CheckLinuxPerf(cls):
154 return not subprocess.Popen(['perf', '--version'],
155 stderr=subprocess.STDOUT,
156 stdout=subprocess.PIPE).wait()
161 def CustomizeBrowserOptions(cls, browser_type, options):
162 options.AppendExtraBrowserArgs([
164 '--allow-sandbox-debugging',
167 def CollectProfile(self):
169 for single_process in self._process_profilers:
170 output_files.append(single_process.CollectProfile())
174 def GetTopSamples(cls, os_name, file_name, number):
175 """Parses the perf generated profile in |file_name| and returns a
176 {function: period} dict of the |number| hottests functions.
178 assert os.path.exists(file_name)
180 if os_name == 'android':
182 report = subprocess.Popen(
183 [cmd, 'report', '--show-total-period', '-U', '-t', '^', '-i',
185 stdout=subprocess.PIPE, stderr=open(os.devnull, 'w')).communicate()[0]
186 period_by_function = {}
187 for line in report.split('\n'):
188 if not line or line.startswith('#'):
190 fields = line.split('^')
193 period = int(fields[1])
194 function = fields[4].partition(' ')[2]
195 function = re.sub('<.*>', '', function) # Strip template params.
196 function = re.sub('[(].*[)]', '', function) # Strip function params.
197 period_by_function[function] = period
198 if len(period_by_function) == number:
200 return period_by_function