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