| 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 signal |
| 8 import subprocess |
| 9 import sys |
| 10 import tempfile |
| 11 |
| 12 from devil.android import device_temp_file |
| 13 from devil.android.perf import perf_control |
| 14 |
| 15 from profile_chrome import controllers |
| 16 from profile_chrome import ui |
| 17 |
| 18 _CATAPULT_DIR = os.path.join( |
| 19 os.path.dirname(os.path.abspath(__file__)), '..', '..') |
| 20 sys.path.append(os.path.join(_CATAPULT_DIR, 'telemetry')) |
| 21 try: |
| 22 # pylint: disable=F0401 |
| 23 from telemetry.internal.platform.profiler import android_profiling_helper |
| 24 from telemetry.internal.util import binary_manager |
| 25 except ImportError: |
| 26 android_profiling_helper = None |
| 27 binary_manager = 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 = device_temp_file.DeviceTempFile( |
| 50 self._device.adb, prefix='perf_output') |
| 51 self._log_file = tempfile.TemporaryFile() |
| 52 |
| 53 # TODO(jbudorick) Look at providing a way to unhandroll this once the |
| 54 # adb rewrite has fully landed. |
| 55 device_param = (['-s', str(self._device)] if str(self._device) else []) |
| 56 cmd = ['adb'] + device_param + \ |
| 57 ['shell', perf_binary, 'record', |
| 58 '--output', self._output_file.name] + _PERF_OPTIONS |
| 59 if categories: |
| 60 cmd += ['--event', ','.join(categories)] |
| 61 self._perf_control = perf_control.PerfControl(self._device) |
| 62 self._perf_control.SetPerfProfilingMode() |
| 63 self._perf_process = subprocess.Popen(cmd, |
| 64 stdout=self._log_file, |
| 65 stderr=subprocess.STDOUT) |
| 66 |
| 67 def SignalAndWait(self): |
| 68 self._device.KillAll('perf', signum=signal.SIGINT) |
| 69 self._perf_process.wait() |
| 70 self._perf_control.SetDefaultPerfMode() |
| 71 |
| 72 def _FailWithLog(self, msg): |
| 73 self._log_file.seek(0) |
| 74 log = self._log_file.read() |
| 75 raise RuntimeError('%s. Log output:\n%s' % (msg, log)) |
| 76 |
| 77 def PullResult(self, output_path): |
| 78 if not self._device.FileExists(self._output_file.name): |
| 79 self._FailWithLog('Perf recorded no data') |
| 80 |
| 81 perf_profile = os.path.join(output_path, |
| 82 os.path.basename(self._output_file.name)) |
| 83 self._device.PullFile(self._output_file.name, perf_profile) |
| 84 if not os.stat(perf_profile).st_size: |
| 85 os.remove(perf_profile) |
| 86 self._FailWithLog('Perf recorded a zero-sized file') |
| 87 |
| 88 self._log_file.close() |
| 89 self._output_file.close() |
| 90 return perf_profile |
| 91 |
| 92 |
| 93 class PerfProfilerController(controllers.BaseController): |
| 94 def __init__(self, device, categories): |
| 95 controllers.BaseController.__init__(self) |
| 96 self._device = device |
| 97 self._categories = categories |
| 98 self._perf_binary = self._PrepareDevice(device) |
| 99 self._perf_instance = None |
| 100 |
| 101 def __repr__(self): |
| 102 return 'perf profile' |
| 103 |
| 104 @staticmethod |
| 105 def IsSupported(): |
| 106 return bool(android_profiling_helper) |
| 107 |
| 108 @staticmethod |
| 109 def _PrepareDevice(device): |
| 110 if not 'BUILDTYPE' in os.environ: |
| 111 os.environ['BUILDTYPE'] = 'Release' |
| 112 if binary_manager.NeedsInit(): |
| 113 binary_manager.InitDependencyManager(None) |
| 114 return android_profiling_helper.PrepareDeviceForPerf(device) |
| 115 |
| 116 @classmethod |
| 117 def GetCategories(cls, device): |
| 118 perf_binary = cls._PrepareDevice(device) |
| 119 return device.RunShellCommand('%s list' % perf_binary) |
| 120 |
| 121 def StartTracing(self, _): |
| 122 self._perf_instance = _PerfProfiler(self._device, |
| 123 self._perf_binary, |
| 124 self._categories) |
| 125 |
| 126 def StopTracing(self): |
| 127 if not self._perf_instance: |
| 128 return |
| 129 self._perf_instance.SignalAndWait() |
| 130 |
| 131 @staticmethod |
| 132 def _GetInteractivePerfCommand(perfhost_path, perf_profile, symfs_dir, |
| 133 required_libs, kallsyms): |
| 134 cmd = '%s report -n -i %s --symfs %s --kallsyms %s' % ( |
| 135 os.path.relpath(perfhost_path, '.'), perf_profile, symfs_dir, kallsyms) |
| 136 for lib in required_libs: |
| 137 lib = os.path.join(symfs_dir, lib[1:]) |
| 138 if not os.path.exists(lib): |
| 139 continue |
| 140 objdump_path = android_profiling_helper.GetToolchainBinaryPath( |
| 141 lib, 'objdump') |
| 142 if objdump_path: |
| 143 cmd += ' --objdump %s' % os.path.relpath(objdump_path, '.') |
| 144 break |
| 145 return cmd |
| 146 |
| 147 def PullTrace(self): |
| 148 symfs_dir = os.path.join(tempfile.gettempdir(), |
| 149 os.path.expandvars('$USER-perf-symfs')) |
| 150 if not os.path.exists(symfs_dir): |
| 151 os.makedirs(symfs_dir) |
| 152 required_libs = set() |
| 153 |
| 154 # Download the recorded perf profile. |
| 155 perf_profile = self._perf_instance.PullResult(symfs_dir) |
| 156 required_libs = \ |
| 157 android_profiling_helper.GetRequiredLibrariesForPerfProfile( |
| 158 perf_profile) |
| 159 if not required_libs: |
| 160 logging.warning('No libraries required by perf trace. Most likely there ' |
| 161 'are no samples in the trace.') |
| 162 |
| 163 # Build a symfs with all the necessary libraries. |
| 164 kallsyms = android_profiling_helper.CreateSymFs(self._device, |
| 165 symfs_dir, |
| 166 required_libs, |
| 167 use_symlinks=False) |
| 168 perfhost_path = binary_manager.FetchPath( |
| 169 android_profiling_helper.GetPerfhostName(), 'x86_64', 'linux') |
| 170 |
| 171 ui.PrintMessage('\nNote: to view the profile in perf, run:') |
| 172 ui.PrintMessage(' ' + self._GetInteractivePerfCommand(perfhost_path, |
| 173 perf_profile, symfs_dir, required_libs, kallsyms)) |
| 174 |
| 175 # Convert the perf profile into JSON. |
| 176 perf_script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), |
| 177 'third_party', 'perf_to_tracing.py') |
| 178 json_file_name = os.path.basename(perf_profile) |
| 179 with open(os.devnull, 'w') as dev_null, \ |
| 180 open(json_file_name, 'w') as json_file: |
| 181 cmd = [perfhost_path, 'script', '-s', perf_script_path, '-i', |
| 182 perf_profile, '--symfs', symfs_dir, '--kallsyms', kallsyms] |
| 183 if subprocess.call(cmd, stdout=json_file, stderr=dev_null): |
| 184 logging.warning('Perf data to JSON conversion failed. The result will ' |
| 185 'not contain any perf samples. You can still view the ' |
| 186 'perf data manually as shown above.') |
| 187 return None |
| 188 |
| 189 return json_file_name |
| OLD | NEW |