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 chrome_profiler import controllers | |
12 | |
13 from pylib import android_commands | |
14 from pylib import constants | |
15 | |
16 sys.path.append(os.path.join(constants.DIR_SOURCE_ROOT, | |
17 'tools', | |
18 'telemetry')) | |
19 try: | |
20 # pylint: disable=F0401 | |
21 from telemetry.core.platform.profiler import android_profiling_helper | |
22 from telemetry.util import support_binaries | |
23 except ImportError: | |
24 android_profiling_helper = None | |
25 support_binaries = None | |
26 | |
27 | |
28 _PERF_OPTIONS = [ | |
29 '--all-cpus', | |
30 '--call-graph', | |
31 '--realtime', '80', | |
32 '--raw-samples', | |
33 '--freq', '2000', | |
34 ] | |
35 | |
36 | |
37 class _PerfProfilerInstance(object): | |
bulach
2014/06/04 10:34:12
nit: how about just _PerfProfiler? or _PerfProfile
Sami
2014/06/04 15:16:59
_PerfProfiler sgtm.
| |
38 def __init__(self, device, perf_binary, categories): | |
39 self._device = device | |
40 self._output_file = android_commands.DeviceTempFile( | |
41 self._device.old_interface, prefix='perf_output') | |
42 self._log_file = tempfile.TemporaryFile() | |
43 | |
44 device_param = (['-s', self._device.old_interface.GetDevice()] | |
45 if self._device.old_interface.GetDevice() else []) | |
46 cmd = ['adb'] + device_param + \ | |
47 ['shell', perf_binary, 'record', | |
48 '--output', self._output_file.name] + _PERF_OPTIONS | |
49 if categories: | |
50 cmd += ['--event', ','.join(categories)] | |
51 self._perf_process = subprocess.Popen(cmd, | |
52 stdout=self._log_file, | |
53 stderr=subprocess.STDOUT) | |
54 | |
55 def SignalAndWait(self): | |
56 perf_pids = self._device.old_interface.ExtractPid('perf') | |
57 self._device.old_interface.RunShellCommand( | |
58 'kill -SIGINT ' + ' '.join(perf_pids)) | |
59 self._perf_process.wait() | |
60 | |
61 def _FailWithLog(self, msg): | |
62 self._log_file.seek(0) | |
63 log = self._log_file.read() | |
64 raise RuntimeError('%s. Log output:\n%s' % (msg, log)) | |
65 | |
66 def PullResult(self, output_path): | |
67 if not self._device.old_interface.FileExistsOnDevice( | |
68 self._output_file.name): | |
69 self._FailWithLog('Perf recorded no data') | |
70 | |
71 perf_profile = os.path.join(output_path, | |
72 os.path.basename(self._output_file.name)) | |
73 self._device.old_interface.PullFileFromDevice(self._output_file.name, | |
74 perf_profile) | |
75 if not os.stat(perf_profile).st_size: | |
76 os.remove(perf_profile) | |
77 self._FailWithLog('Perf recorded a zero-sized file') | |
78 | |
79 self._log_file.close() | |
80 self._output_file.close() | |
81 return perf_profile | |
82 | |
83 | |
84 class PerfProfilerController(controllers.BaseController): | |
85 def __init__(self, device, categories): | |
86 controllers.BaseController.__init__(self) | |
87 self._device = device | |
88 self._categories = categories | |
89 self._perf_binary = self._PrepareDevice(device) | |
90 self._perf_instance = None | |
91 | |
92 def __repr__(self): | |
93 return 'perf profile' | |
94 | |
95 @staticmethod | |
96 def IsSupported(): | |
97 return bool(android_profiling_helper) | |
98 | |
99 @staticmethod | |
100 def _PrepareDevice(device): | |
101 if not 'BUILDTYPE' in os.environ: | |
102 os.environ['BUILDTYPE'] = 'Release' | |
103 return android_profiling_helper.PrepareDeviceForPerf(device) | |
104 | |
105 @classmethod | |
106 def GetCategories(cls, device): | |
107 perf_binary = cls._PrepareDevice(device) | |
108 return device.old_interface.RunShellCommand('%s list' % perf_binary) | |
109 | |
110 def StartTracing(self, _): | |
111 self._perf_instance = _PerfProfilerInstance(self._device, | |
112 self._perf_binary, | |
113 self._categories) | |
114 | |
115 def StopTracing(self): | |
116 if not self._perf_instance: | |
117 return | |
118 self._perf_instance.SignalAndWait() | |
119 | |
120 def PullTrace(self): | |
121 symfs_dir = os.path.join(tempfile.gettempdir(), | |
122 os.path.expandvars('$USER-perf-symfs')) | |
123 if not os.path.exists(symfs_dir): | |
124 os.makedirs(symfs_dir) | |
125 required_libs = set() | |
126 | |
127 # Download the recorded perf profile. | |
128 perf_profile = self._perf_instance.PullResult(symfs_dir) | |
129 required_libs = \ | |
130 android_profiling_helper.GetRequiredLibrariesForPerfProfile( | |
131 perf_profile) | |
132 if not required_libs: | |
133 logging.warning('No libraries required by perf trace. Most likely there ' | |
134 'are no samples in the trace.') | |
135 | |
136 # Build a symfs with all the necessary libraries. | |
137 kallsyms = android_profiling_helper.CreateSymFs(self._device, | |
138 symfs_dir, | |
139 required_libs, | |
140 use_symlinks=False) | |
141 # Convert the perf profile into JSON. | |
142 perfhost_path = \ | |
bulach
2014/06/04 10:34:12
nit: avoid \, something like:
perfhost_path = os.
Sami
2014/06/04 15:16:59
Well spotted, done. I just got used to having long
| |
143 os.path.abspath(support_binaries.FindPath('perfhost', 'linux')) | |
144 perf_script_path = \ | |
bulach
2014/06/04 10:34:12
ditto..
Sami
2014/06/04 15:16:59
Done.
| |
145 os.path.join(constants.DIR_SOURCE_ROOT, | |
146 'tools', 'telemetry', 'telemetry', 'core', 'platform', | |
147 'profiler', 'perf_vis', 'perf_to_tracing.py') | |
148 json_file_name = os.path.basename(perf_profile) | |
149 with open(os.devnull, 'w') as dev_null: | |
150 with open(json_file_name, 'w') as json_file: | |
bulach
2014/06/04 10:34:12
nit: I think we're on python2.7 everywhere, so mul
Sami
2014/06/04 15:16:59
Oh thanks, I forgot about that syntax!
| |
151 cmd = [perfhost_path, 'script', '-s', perf_script_path, | |
152 '-i', perf_profile, '--symfs', symfs_dir, '--kallsyms', | |
153 kallsyms] | |
154 subprocess.call(cmd, stdout=json_file, stderr=dev_null) | |
155 return json_file_name | |
OLD | NEW |