Roll src/third_party/WebKit 9d2dfea:3aea697 (svn 201972:201973)
[chromium-blink-merge.git] / tools / profile_chrome / perf_controller.py
blob18f01bb1dcd2503e811982f6de7e24b96fba1a59
1 # Copyright 2014 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.
5 import logging
6 import os
7 import signal
8 import subprocess
9 import sys
10 import tempfile
12 from devil.android import device_temp_file
13 from devil.android.perf import perf_control
15 from profile_chrome import controllers
16 from profile_chrome import ui
18 from pylib import constants
20 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT,
21 'tools',
22 'telemetry'))
23 try:
24 # pylint: disable=F0401
25 from telemetry.internal.platform.profiler import android_profiling_helper
26 from telemetry.internal.util import binary_manager
27 except ImportError:
28 android_profiling_helper = None
29 binary_manager = None
32 _PERF_OPTIONS = [
33 # Sample across all processes and CPUs to so that the current CPU gets
34 # recorded to each sample.
35 '--all-cpus',
36 # In perf 3.13 --call-graph requires an argument, so use the -g short-hand
37 # which does not.
38 '-g',
39 # Increase priority to avoid dropping samples. Requires root.
40 '--realtime', '80',
41 # Record raw samples to get CPU information.
42 '--raw-samples',
43 # Increase sampling frequency for better coverage.
44 '--freq', '2000',
48 class _PerfProfiler(object):
49 def __init__(self, device, perf_binary, categories):
50 self._device = device
51 self._output_file = device_temp_file.DeviceTempFile(
52 self._device.adb, prefix='perf_output')
53 self._log_file = tempfile.TemporaryFile()
55 # TODO(jbudorick) Look at providing a way to unhandroll this once the
56 # adb rewrite has fully landed.
57 device_param = (['-s', str(self._device)] if str(self._device) else [])
58 cmd = ['adb'] + device_param + \
59 ['shell', perf_binary, 'record',
60 '--output', self._output_file.name] + _PERF_OPTIONS
61 if categories:
62 cmd += ['--event', ','.join(categories)]
63 self._perf_control = perf_control.PerfControl(self._device)
64 self._perf_control.SetPerfProfilingMode()
65 self._perf_process = subprocess.Popen(cmd,
66 stdout=self._log_file,
67 stderr=subprocess.STDOUT)
69 def SignalAndWait(self):
70 self._device.KillAll('perf', signum=signal.SIGINT)
71 self._perf_process.wait()
72 self._perf_control.SetDefaultPerfMode()
74 def _FailWithLog(self, msg):
75 self._log_file.seek(0)
76 log = self._log_file.read()
77 raise RuntimeError('%s. Log output:\n%s' % (msg, log))
79 def PullResult(self, output_path):
80 if not self._device.FileExists(self._output_file.name):
81 self._FailWithLog('Perf recorded no data')
83 perf_profile = os.path.join(output_path,
84 os.path.basename(self._output_file.name))
85 self._device.PullFile(self._output_file.name, perf_profile)
86 if not os.stat(perf_profile).st_size:
87 os.remove(perf_profile)
88 self._FailWithLog('Perf recorded a zero-sized file')
90 self._log_file.close()
91 self._output_file.close()
92 return perf_profile
95 class PerfProfilerController(controllers.BaseController):
96 def __init__(self, device, categories):
97 controllers.BaseController.__init__(self)
98 self._device = device
99 self._categories = categories
100 self._perf_binary = self._PrepareDevice(device)
101 self._perf_instance = None
103 def __repr__(self):
104 return 'perf profile'
106 @staticmethod
107 def IsSupported():
108 return bool(android_profiling_helper)
110 @staticmethod
111 def _PrepareDevice(device):
112 if not 'BUILDTYPE' in os.environ:
113 os.environ['BUILDTYPE'] = 'Release'
114 binary_manager.InitDependencyManager(None)
115 return android_profiling_helper.PrepareDeviceForPerf(device)
117 @classmethod
118 def GetCategories(cls, device):
119 perf_binary = cls._PrepareDevice(device)
120 return device.RunShellCommand('%s list' % perf_binary)
122 def StartTracing(self, _):
123 self._perf_instance = _PerfProfiler(self._device,
124 self._perf_binary,
125 self._categories)
127 def StopTracing(self):
128 if not self._perf_instance:
129 return
130 self._perf_instance.SignalAndWait()
132 @staticmethod
133 def _GetInteractivePerfCommand(perfhost_path, perf_profile, symfs_dir,
134 required_libs, kallsyms):
135 cmd = '%s report -n -i %s --symfs %s --kallsyms %s' % (
136 os.path.relpath(perfhost_path, '.'), perf_profile, symfs_dir, kallsyms)
137 for lib in required_libs:
138 lib = os.path.join(symfs_dir, lib[1:])
139 if not os.path.exists(lib):
140 continue
141 objdump_path = android_profiling_helper.GetToolchainBinaryPath(
142 lib, 'objdump')
143 if objdump_path:
144 cmd += ' --objdump %s' % os.path.relpath(objdump_path, '.')
145 break
146 return cmd
148 def PullTrace(self):
149 symfs_dir = os.path.join(tempfile.gettempdir(),
150 os.path.expandvars('$USER-perf-symfs'))
151 if not os.path.exists(symfs_dir):
152 os.makedirs(symfs_dir)
153 required_libs = set()
155 # Download the recorded perf profile.
156 perf_profile = self._perf_instance.PullResult(symfs_dir)
157 required_libs = \
158 android_profiling_helper.GetRequiredLibrariesForPerfProfile(
159 perf_profile)
160 if not required_libs:
161 logging.warning('No libraries required by perf trace. Most likely there '
162 'are no samples in the trace.')
164 # Build a symfs with all the necessary libraries.
165 kallsyms = android_profiling_helper.CreateSymFs(self._device,
166 symfs_dir,
167 required_libs,
168 use_symlinks=False)
169 perfhost_path = binary_manager.FetchPath(
170 android_profiling_helper.GetPerfhostName(), 'x86_64', 'linux')
172 ui.PrintMessage('\nNote: to view the profile in perf, run:')
173 ui.PrintMessage(' ' + self._GetInteractivePerfCommand(perfhost_path,
174 perf_profile, symfs_dir, required_libs, kallsyms))
176 # Convert the perf profile into JSON.
177 perf_script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
178 'third_party', 'perf_to_tracing.py')
179 json_file_name = os.path.basename(perf_profile)
180 with open(os.devnull, 'w') as dev_null, \
181 open(json_file_name, 'w') as json_file:
182 cmd = [perfhost_path, 'script', '-s', perf_script_path, '-i',
183 perf_profile, '--symfs', symfs_dir, '--kallsyms', kallsyms]
184 if subprocess.call(cmd, stdout=json_file, stderr=dev_null):
185 logging.warning('Perf data to JSON conversion failed. The result will '
186 'not contain any perf samples. You can still view the '
187 'perf data manually as shown above.')
188 return None
190 return json_file_name