Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(694)

Side by Side Diff: build/android/adb_profile_chrome.py

Issue 25686006: android: Add systrace and combined trace support to adb_profile_chrome (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: s/Download/Pull/ Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # 2 #
3 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Copyright 2013 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 import gzip 7 import gzip
8 import logging 8 import logging
9 import optparse 9 import optparse
10 import os 10 import os
11 import re 11 import re
12 import sys 12 import sys
13 import threading
13 import time 14 import time
15 import zipfile
16 import zlib
14 17
15 from pylib import android_commands 18 from pylib import android_commands
19 from pylib import cmd_helper
16 from pylib import constants 20 from pylib import constants
17 from pylib import pexpect 21 from pylib import pexpect
18 22
19 23
24 def _GetTraceTimestamp():
25 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
26
27
28 class ChromeTracingController(object):
29 def __init__(self, adb, package_info, categories, ring_buffer):
30 self._adb = adb
31 self._package_info = package_info
32 self._categories = categories
33 self._ring_buffer = ring_buffer
34 self._trace_file = None
35 self._trace_interval = None
36 self._trace_start_re = \
37 re.compile(r'Logging performance trace to file: (.*)')
38 self._trace_finish_re = \
39 re.compile(r'Profiler finished[.] Results are in (.*)[.]')
40 self._adb.StartMonitoringLogcat(clear=False)
41
42 def __str__(self):
43 return 'chrome trace'
44
45 def StartTracing(self, interval):
46 self._trace_interval = interval
47 self._adb.SyncLogCat()
48 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_START',
49 '-e categories "%s"' % ','.join(self._categories),
50 '-e continuous' if self._ring_buffer else '')
51 # Chrome logs two different messages related to tracing:
52 #
53 # 1. "Logging performance trace to file [...]"
54 # 2. "Profiler finished. Results are in [...]"
55 #
56 # The first one is printed when tracing starts and the second one indicates
57 # that the trace file is ready to be pulled.
58 try:
59 self._trace_file = self._adb.WaitForLogMatch(self._trace_start_re,
60 None,
61 timeout=5).group(1)
62 except pexpect.TIMEOUT:
63 raise RuntimeError('Trace start marker not found. Is the correct version '
64 'of the browser running?')
65
66 def StopTracing(self):
67 if not self._trace_file:
68 return
69 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_STOP')
70 self._adb.WaitForLogMatch(self._trace_finish_re, None, timeout=120)
71
72 def PullTrace(self):
73 # Wait a bit for the browser to finish writing the trace file.
74 time.sleep(self._trace_interval / 4 + 1)
75
76 trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/')
77 host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
78 self._adb.PullFileFromDevice(trace_file, host_file)
79 return host_file
80
81
82 _SYSTRACE_OPTIONS = [
83 # Compress the trace before sending it over USB.
84 '-z',
85 # Use a large trace buffer to increase the polling interval.
86 '-b', '16384'
87 ]
88
89 # Interval in seconds for sampling systrace data.
90 _SYSTRACE_INTERVAL = 15
91
92
93 class SystraceController(object):
94 def __init__(self, adb, categories, ring_buffer):
95 self._adb = adb
96 self._categories = categories
97 self._ring_buffer = ring_buffer
98 self._done = threading.Event()
99 self._thread = None
100 self._trace_data = None
101
102 def __str__(self):
103 return 'systrace'
104
105 @staticmethod
106 def GetCategories(adb):
107 return adb.RunShellCommand('atrace --list_categories')
108
109 def StartTracing(self, interval):
110 self._thread = threading.Thread(target=self._CollectData)
111 self._thread.start()
112
113 def StopTracing(self):
114 self._done.set()
115
116 def PullTrace(self):
117 self._thread.join()
118 self._thread = None
119 if self._trace_data:
120 output_name = 'systrace-%s' % _GetTraceTimestamp()
121 with open(output_name, 'w') as out:
122 out.write(self._trace_data)
123 return output_name
124
125 def _RunATraceCommand(self, command):
126 # We use a separate interface to adb because the one from AndroidCommands
127 # isn't re-entrant.
128 device = ['-s', self._adb.GetDevice()] if self._adb.GetDevice() else []
129 cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \
130 _SYSTRACE_OPTIONS + self._categories
131 return cmd_helper.GetCmdOutput(cmd)
132
133 def _CollectData(self):
134 trace_data = []
135 self._RunATraceCommand('async_start')
136 try:
137 while not self._done.is_set():
138 self._done.wait(_SYSTRACE_INTERVAL)
139 if not self._ring_buffer or self._done.is_set():
140 trace_data.append(
141 self._DecodeTraceData(self._RunATraceCommand('async_dump')))
142 finally:
143 trace_data.append(
144 self._DecodeTraceData(self._RunATraceCommand('async_stop')))
145 self._trace_data = ''.join([zlib.decompress(d) for d in trace_data])
146
147 @staticmethod
148 def _DecodeTraceData(trace_data):
149 try:
150 trace_start = trace_data.index('TRACE:')
151 except ValueError:
152 raise RuntimeError('Systrace start marker not found')
153 trace_data = trace_data[trace_start + 6:]
154
155 # Collapse CRLFs that are added by adb shell.
156 if trace_data.startswith('\r\n'):
157 trace_data = trace_data.replace('\r\n', '\n')
158
159 # Skip the initial newline.
160 return trace_data[1:]
161
162
20 def _GetSupportedBrowsers(): 163 def _GetSupportedBrowsers():
21 # Add aliases for backwards compatibility. 164 # Add aliases for backwards compatibility.
22 supported_browsers = { 165 supported_browsers = {
23 'stable': constants.PACKAGE_INFO['chrome_stable'], 166 'stable': constants.PACKAGE_INFO['chrome_stable'],
24 'beta': constants.PACKAGE_INFO['chrome_beta'], 167 'beta': constants.PACKAGE_INFO['chrome_beta'],
25 'dev': constants.PACKAGE_INFO['chrome_dev'], 168 'dev': constants.PACKAGE_INFO['chrome_dev'],
26 'build': constants.PACKAGE_INFO['chrome'], 169 'build': constants.PACKAGE_INFO['chrome'],
27 } 170 }
28 supported_browsers.update(constants.PACKAGE_INFO) 171 supported_browsers.update(constants.PACKAGE_INFO)
29 unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser'] 172 unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser']
30 for browser in unsupported_browsers: 173 for browser in unsupported_browsers:
31 del supported_browsers[browser] 174 del supported_browsers[browser]
32 return supported_browsers 175 return supported_browsers
33 176
34 177
35 def _StartTracing(adb, package_info, categories, continuous): 178 def _CompressFile(host_file, output):
36 adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_START', 179 with gzip.open(output, 'wb') as out:
37 '-e categories "%s"' % categories,
38 '-e continuous' if continuous else '')
39
40
41 def _StopTracing(adb, package_info):
42 adb.BroadcastIntent(package_info.package, 'GPU_PROFILER_STOP')
43
44
45 def _GetLatestTraceFileName(adb, check_for_multiple_traces=True):
46 # Chrome logs two different messages related to tracing:
47 #
48 # 1. "Logging performance trace to file [...]"
49 # 2. "Profiler finished. Results are in [...]"
50 #
51 # The first one is printed when tracing starts and the second one indicates
52 # that the trace file is ready to be downloaded.
53 #
54 # We have to look for both of these messages to make sure we get the results
55 # from the latest trace and that the trace file is complete.
56 trace_start_re = re.compile(r'Logging performance trace to file')
57 trace_finish_re = re.compile(r'Profiler finished[.] Results are in (.*)[.]')
58
59 start_timeout = 5
60 start_match = None
61 finish_match = None
62
63 while True:
64 try:
65 start_match = adb.WaitForLogMatch(trace_start_re, None,
66 timeout=start_timeout)
67 except pexpect.TIMEOUT:
68 if start_match:
69 break
70 raise RuntimeError('Trace start marker not found. Is the correct version '
71 'of the browser running?')
72 finish_match = adb.WaitForLogMatch(trace_finish_re, None, timeout=120)
73 if not check_for_multiple_traces:
74 break
75 # Now that we've found one trace file, switch to polling for the rest of the
76 # log to see if there are more.
77 start_timeout = 1
78 return finish_match.group(1)
79
80
81 def _DownloadTrace(adb, trace_file):
82 trace_file = trace_file.replace('/storage/emulated/0/', '/sdcard/')
83 host_file = os.path.abspath(os.path.basename(trace_file))
84 adb.PullFileFromDevice(trace_file, host_file)
85 return host_file
86
87
88 def _CompressFile(host_file):
89 compressed_file = host_file + '.gz'
90 with gzip.open(compressed_file, 'wb') as out:
91 with open(host_file, 'rb') as input_file: 180 with open(host_file, 'rb') as input_file:
92 out.write(input_file.read()) 181 out.write(input_file.read())
93 os.unlink(host_file) 182 os.unlink(host_file)
94 return compressed_file 183
184
185 def _ArchiveFiles(host_files, output):
186 with zipfile.ZipFile(output, 'w') as z:
187 for host_file in host_files:
188 z.write(host_file)
189 os.unlink(host_file)
95 190
96 191
97 def _PrintMessage(heading, eol='\n'): 192 def _PrintMessage(heading, eol='\n'):
98 sys.stdout.write(heading + eol) 193 sys.stdout.write('%s%s' % (heading, eol))
99 sys.stdout.flush() 194 sys.stdout.flush()
100 195
101 196
102 def _DownloadLatestTrace(adb, compress, check_for_multiple_traces=True): 197 def _StartTracing(controllers, interval):
103 _PrintMessage('Downloading trace...', eol='') 198 for controller in controllers:
104 trace_file = _GetLatestTraceFileName(adb, check_for_multiple_traces) 199 controller.StartTracing(interval)
105 host_file = _DownloadTrace(adb, trace_file)
106 if compress:
107 host_file = _CompressFile(host_file)
108 _PrintMessage('done')
109 _PrintMessage('Trace written to %s' % host_file)
110 200
111 201
112 def _CaptureAndDownloadTimedTrace(adb, package_info, categories, interval, 202 def _StopTracing(controllers):
113 continuous, compress): 203 for controller in controllers:
114 adb.StartMonitoringLogcat(clear=False) 204 controller.StopTracing()
115 adb.SyncLogCat()
116
117 try:
118 _PrintMessage('Capturing %d-second trace. Press Ctrl-C to stop early...' \
119 % interval, eol='')
120 _StartTracing(adb, package_info, categories, continuous)
121 time.sleep(interval)
122 except KeyboardInterrupt:
123 _PrintMessage('\nInterrupted, stopping...', eol='')
124 _StopTracing(adb, package_info)
125 _PrintMessage('done')
126
127 # Wait a bit for the browser to finish writing the trace file.
128 time.sleep(interval / 4 + 1)
129
130 _DownloadLatestTrace(adb, compress, check_for_multiple_traces=False)
131 205
132 206
133 def _ComputeCategories(options): 207 def _PullTraces(controllers, output, compress):
134 categories = [options.categories] 208 _PrintMessage('Downloading...', eol='')
209 trace_files = []
210 for controller in controllers:
211 trace_files.append(controller.PullTrace())
212
213 if compress and len(trace_files) == 1:
214 result = output or trace_files[0] + '.gz'
215 _CompressFile(trace_files[0], result)
216 elif len(trace_files) > 1:
217 result = output or 'chrome-combined-trace-%s.zip' % _GetTraceTimestamp()
218 _ArchiveFiles(trace_files, result)
219 elif output:
220 result = output
221 os.rename(trace_files[0], result)
222 else:
223 result = trace_files[0]
224
225 _PrintMessage('done')
226 _PrintMessage('Trace written to %s' % os.path.abspath(result))
227
228
229 def _CaptureAndPullTrace(controllers, interval, output, compress):
230 trace_type = ' + '.join(map(str, controllers))
231 try:
232 _StartTracing(controllers, interval)
233 if interval:
234 _PrintMessage('Capturing %d-second %s. Press Ctrl-C to stop early...' % \
235 (interval, trace_type), eol='')
236 time.sleep(interval)
237 else:
238 _PrintMessage('Capturing %s. Press Enter to stop...' % trace_type, eol='')
239 raw_input()
240 except KeyboardInterrupt:
241 _PrintMessage('\nInterrupted...', eol='')
242 finally:
243 _StopTracing(controllers)
244 if interval:
245 _PrintMessage('done')
246
247 _PullTraces(controllers, output, compress)
248
249
250 def _ComputeChromeCategories(options):
251 categories = []
135 if options.trace_cc: 252 if options.trace_cc:
136 categories.append('disabled-by-default-cc.debug*') 253 categories.append('disabled-by-default-cc.debug*')
137 if options.trace_gpu: 254 if options.trace_gpu:
138 categories.append('disabled-by-default-gpu.debug*') 255 categories.append('disabled-by-default-gpu.debug*')
139 return ",".join(categories) 256 if options.chrome_categories:
257 categories += options.chrome_categories.split(',')
258 return categories
259
260
261 def _ComputeSystraceCategories(options):
262 if not options.systrace_categories:
263 return []
264 return options.systrace_categories.split(',')
140 265
141 266
142 def main(): 267 def main():
143 parser = optparse.OptionParser(description='Record about://tracing profiles ' 268 parser = optparse.OptionParser(description='Record about://tracing profiles '
144 'from Android browsers. See http://dev.' 269 'from Android browsers. See http://dev.'
145 'chromium.org/developers/how-tos/trace-event-' 270 'chromium.org/developers/how-tos/trace-event-'
146 'profiling-tool for detailed instructions for ' 271 'profiling-tool for detailed instructions for '
147 'profiling.') 272 'profiling.')
148 manual_options = optparse.OptionGroup(parser, 'Manual tracing')
149 manual_options.add_option('--start', help='Start tracing.',
150 action='store_true')
151 manual_options.add_option('--stop', help='Stop tracing.',
152 action='store_true')
153 manual_options.add_option('-d', '--download', help='Download latest trace.',
154 action='store_true')
155 parser.add_option_group(manual_options)
156 273
157 auto_options = optparse.OptionGroup(parser, 'Automated tracing') 274 timed_options = optparse.OptionGroup(parser, 'Timed tracing')
158 auto_options.add_option('-t', '--time', help='Profile for N seconds and ' 275 timed_options.add_option('-t', '--time', help='Profile for N seconds and '
159 'download the resulting trace.', metavar='N', 276 'download the resulting trace.', metavar='N',
160 type='float') 277 type='float')
161 parser.add_option_group(auto_options) 278 parser.add_option_group(timed_options)
279
280 cont_options = optparse.OptionGroup(parser, 'Continuous tracing')
281 cont_options.add_option('--continuous', help='Profile continuously until '
282 'stopped.', action='store_true')
283 cont_options.add_option('--ring-buffer', help='Use the trace buffer as a '
284 'ring buffer and save its contents when stopping '
285 'instead of appending events into one long trace.',
286 action='store_true')
287 parser.add_option_group(cont_options)
162 288
163 categories = optparse.OptionGroup(parser, 'Trace categories') 289 categories = optparse.OptionGroup(parser, 'Trace categories')
164 categories.add_option('-c', '--categories', help='Select categories to trace ' 290 categories.add_option('-c', '--categories', help='Select categories to trace '
165 'with comma-delimited wildcards, e.g., ' 291 'with comma-delimited wildcards, e.g., '
166 '"*", "cat1*,-cat1a". Default is "*".', default='*') 292 '"*", "cat1*,-cat1a". Default is "*".', default='*',
293 dest='chrome_categories')
294 categories.add_option('-s', '--systrace', help='Capture a systrace with the '
295 'chosen comma-delimited systrace categories. You can '
296 'also capture a combined Chrome + systrace by enabling '
297 'both types of categories. Use "list" to see the '
298 'available categories.', metavar='SYS_CATEGORIES',
299 dest='systrace_categories', default='')
167 categories.add_option('--trace-cc', help='Enable extra trace categories for ' 300 categories.add_option('--trace-cc', help='Enable extra trace categories for '
168 'compositor frame viewer data.', action='store_true') 301 'compositor frame viewer data.', action='store_true')
169 categories.add_option('--trace-gpu', help='Enable extra trace categories for ' 302 categories.add_option('--trace-gpu', help='Enable extra trace categories for '
170 'GPU data.', action='store_true') 303 'GPU data.', action='store_true')
171 parser.add_option_group(categories) 304 parser.add_option_group(categories)
172 305
173 parser.add_option('-o', '--output', help='Save profile output to file. ' 306 parser.add_option('-o', '--output', help='Save profile output to file.')
174 'Default is "/sdcard/Download/chrome-profile-results-*".')
175 parser.add_option('--continuous', help='Using the trace buffer as a ring '
176 'buffer, continuously profile until stopped.',
177 action='store_true')
178 browsers = sorted(_GetSupportedBrowsers().keys()) 307 browsers = sorted(_GetSupportedBrowsers().keys())
179 parser.add_option('-b', '--browser', help='Select among installed browsers. ' 308 parser.add_option('-b', '--browser', help='Select among installed browsers. '
180 'One of ' + ', '.join(browsers) + ', "stable" is used by ' 309 'One of ' + ', '.join(browsers) + ', "stable" is used by '
181 'default.', type='choice', choices=browsers, 310 'default.', type='choice', choices=browsers,
182 default='stable') 311 default='stable')
183 parser.add_option('-v', '--verbose', help='Verbose logging.', 312 parser.add_option('-v', '--verbose', help='Verbose logging.',
184 action='store_true') 313 action='store_true')
185 parser.add_option('-z', '--compress', help='Compress the resulting trace ' 314 parser.add_option('-z', '--compress', help='Compress the resulting trace '
186 'with gzip. ', action='store_true') 315 'with gzip. ', action='store_true')
187 options, args = parser.parse_args() 316 options, args = parser.parse_args()
188 317
189 if not any([options.start, options.stop, options.time, options.download]):
190 _PrintMessage('One of start/stop/download/time should be specified.')
191 return 1
192
193 if options.verbose: 318 if options.verbose:
194 logging.getLogger().setLevel(logging.DEBUG) 319 logging.getLogger().setLevel(logging.DEBUG)
195 320
196 adb = android_commands.AndroidCommands() 321 adb = android_commands.AndroidCommands()
197 categories = _ComputeCategories(options) 322 if options.systrace_categories in ['list', 'help']:
323 _PrintMessage('\n'.join(SystraceController.GetCategories(adb)))
324 return 0
325
326 if not options.time and not options.continuous:
327 _PrintMessage('Time interval or continuous tracing should be specified.')
328 return 1
329
330 chrome_categories = _ComputeChromeCategories(options)
331 systrace_categories = _ComputeSystraceCategories(options)
198 package_info = _GetSupportedBrowsers()[options.browser] 332 package_info = _GetSupportedBrowsers()[options.browser]
199 333
200 if options.start: 334 if chrome_categories and 'webview' in systrace_categories:
201 _StartTracing(adb, package_info, categories, options.continuous) 335 logging.warning('Using the "webview" category in systrace together with '
202 elif options.stop: 336 'Chrome tracing results in duplicate trace events.')
203 _StopTracing(adb, package_info) 337
204 elif options.download: 338 controllers = []
205 _DownloadLatestTrace(adb, options.compress) 339 if chrome_categories:
206 elif options.time: 340 controllers.append(ChromeTracingController(adb,
207 _CaptureAndDownloadTimedTrace(adb, package_info, categories, options.time, 341 package_info,
208 options.continuous, options.compress) 342 chrome_categories,
343 options.ring_buffer))
344 if systrace_categories:
345 controllers.append(SystraceController(adb,
346 systrace_categories,
347 options.ring_buffer))
348
349 if not controllers:
350 _PrintMessage('No trace categories enabled.')
351 return 1
352
353 _CaptureAndPullTrace(controllers,
354 options.time if not options.continuous else 0,
355 options.output,
356 options.compress)
209 357
210 358
211 if __name__ == '__main__': 359 if __name__ == '__main__':
212 sys.exit(main()) 360 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698