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