Make touch-action apply to double-tap zoom
[chromium-blink-merge.git] / build / android / adb_profile_chrome.py
blob19a72fef19aa8348c0948a9e13e0285aa5b65d14
1 #!/usr/bin/env python
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
5 # found in the LICENSE file.
7 import base64
8 import gzip
9 import logging
10 import optparse
11 import os
12 import re
13 import select
14 import shutil
15 import sys
16 import threading
17 import time
18 import webbrowser
19 import zipfile
20 import zlib
22 from pylib import android_commands
23 from pylib import cmd_helper
24 from pylib import constants
25 from pylib import pexpect
28 _TRACE_VIEWER_TEMPLATE = """<!DOCTYPE html>
29 <html>
30 <head>
31 <title>%(title)s</title>
32 <style>
33 %(timeline_css)s
34 </style>
35 <style>
36 .view {
37 overflow: hidden;
38 position: absolute;
39 top: 0;
40 bottom: 0;
41 left: 0;
42 right: 0;
44 </style>
45 <script>
46 %(timeline_js)s
47 </script>
48 <script>
49 document.addEventListener('DOMContentLoaded', function() {
50 var trace_data = window.atob('%(trace_data_base64)s');
51 var m = new tracing.TraceModel(trace_data);
52 var timelineViewEl = document.querySelector('.view');
53 ui.decorate(timelineViewEl, tracing.TimelineView);
54 timelineViewEl.model = m;
55 timelineViewEl.tabIndex = 1;
56 timelineViewEl.timeline.focusElement = timelineViewEl;
57 });
58 </script>
59 </head>
60 <body>
61 <div class="view"></view>
62 </body>
63 </html>"""
65 _DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
68 def _GetTraceTimestamp():
69 return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
72 def _PackageTraceAsHtml(trace_file_name, html_file_name):
73 trace_viewer_root = os.path.join(constants.DIR_SOURCE_ROOT,
74 'third_party', 'trace-viewer')
75 build_dir = os.path.join(trace_viewer_root, 'build')
76 src_dir = os.path.join(trace_viewer_root, 'src')
77 if not build_dir in sys.path:
78 sys.path.append(build_dir)
79 generate = __import__('generate', {}, {})
80 parse_deps = __import__('parse_deps', {}, {})
82 basename = os.path.splitext(trace_file_name)[0]
83 load_sequence = parse_deps.calc_load_sequence(
84 ['tracing/standalone_timeline_view.js'], [src_dir])
86 with open(trace_file_name) as trace_file:
87 trace_data = base64.b64encode(trace_file.read())
88 with open(html_file_name, 'w') as html_file:
89 html = _TRACE_VIEWER_TEMPLATE % {
90 'title': os.path.basename(os.path.splitext(trace_file_name)[0]),
91 'timeline_js': generate.generate_js(load_sequence),
92 'timeline_css': generate.generate_css(load_sequence),
93 'trace_data_base64': trace_data
95 html_file.write(html)
98 class ChromeTracingController(object):
99 def __init__(self, adb, package_info, categories, ring_buffer):
100 self._adb = adb
101 self._package_info = package_info
102 self._categories = categories
103 self._ring_buffer = ring_buffer
104 self._trace_file = None
105 self._trace_interval = None
106 self._trace_start_re = \
107 re.compile(r'Logging performance trace to file: (.*)')
108 self._trace_finish_re = \
109 re.compile(r'Profiler finished[.] Results are in (.*)[.]')
110 self._adb.StartMonitoringLogcat(clear=False)
112 def __str__(self):
113 return 'chrome trace'
115 def StartTracing(self, interval):
116 self._trace_interval = interval
117 self._adb.SyncLogCat()
118 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_START',
119 '-e categories "%s"' % ','.join(self._categories),
120 '-e continuous' if self._ring_buffer else '')
121 # Chrome logs two different messages related to tracing:
123 # 1. "Logging performance trace to file [...]"
124 # 2. "Profiler finished. Results are in [...]"
126 # The first one is printed when tracing starts and the second one indicates
127 # that the trace file is ready to be pulled.
128 try:
129 self._trace_file = self._adb.WaitForLogMatch(self._trace_start_re,
130 None,
131 timeout=5).group(1)
132 except pexpect.TIMEOUT:
133 raise RuntimeError('Trace start marker not found. Is the correct version '
134 'of the browser running?')
136 def StopTracing(self):
137 if not self._trace_file:
138 return
139 self._adb.BroadcastIntent(self._package_info.package, 'GPU_PROFILER_STOP')
140 self._adb.WaitForLogMatch(self._trace_finish_re, None, timeout=120)
142 def PullTrace(self):
143 # Wait a bit for the browser to finish writing the trace file.
144 time.sleep(self._trace_interval / 4 + 1)
146 trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/')
147 host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
148 self._adb.PullFileFromDevice(trace_file, host_file)
149 return host_file
152 _SYSTRACE_OPTIONS = [
153 # Compress the trace before sending it over USB.
154 '-z',
155 # Use a large trace buffer to increase the polling interval.
156 '-b', '16384'
159 # Interval in seconds for sampling systrace data.
160 _SYSTRACE_INTERVAL = 15
163 class SystraceController(object):
164 def __init__(self, adb, categories, ring_buffer):
165 self._adb = adb
166 self._categories = categories
167 self._ring_buffer = ring_buffer
168 self._done = threading.Event()
169 self._thread = None
170 self._trace_data = None
172 def __str__(self):
173 return 'systrace'
175 @staticmethod
176 def GetCategories(adb):
177 return adb.RunShellCommand('atrace --list_categories')
179 def StartTracing(self, interval):
180 self._thread = threading.Thread(target=self._CollectData)
181 self._thread.start()
183 def StopTracing(self):
184 self._done.set()
186 def PullTrace(self):
187 self._thread.join()
188 self._thread = None
189 if self._trace_data:
190 output_name = 'systrace-%s' % _GetTraceTimestamp()
191 with open(output_name, 'w') as out:
192 out.write(self._trace_data)
193 return output_name
195 def _RunATraceCommand(self, command):
196 # We use a separate interface to adb because the one from AndroidCommands
197 # isn't re-entrant.
198 device = ['-s', self._adb.GetDevice()] if self._adb.GetDevice() else []
199 cmd = ['adb'] + device + ['shell', 'atrace', '--%s' % command] + \
200 _SYSTRACE_OPTIONS + self._categories
201 return cmd_helper.GetCmdOutput(cmd)
203 def _CollectData(self):
204 trace_data = []
205 self._RunATraceCommand('async_start')
206 try:
207 while not self._done.is_set():
208 self._done.wait(_SYSTRACE_INTERVAL)
209 if not self._ring_buffer or self._done.is_set():
210 trace_data.append(
211 self._DecodeTraceData(self._RunATraceCommand('async_dump')))
212 finally:
213 trace_data.append(
214 self._DecodeTraceData(self._RunATraceCommand('async_stop')))
215 self._trace_data = ''.join([zlib.decompress(d) for d in trace_data])
217 @staticmethod
218 def _DecodeTraceData(trace_data):
219 try:
220 trace_start = trace_data.index('TRACE:')
221 except ValueError:
222 raise RuntimeError('Systrace start marker not found')
223 trace_data = trace_data[trace_start + 6:]
225 # Collapse CRLFs that are added by adb shell.
226 if trace_data.startswith('\r\n'):
227 trace_data = trace_data.replace('\r\n', '\n')
229 # Skip the initial newline.
230 return trace_data[1:]
233 def _GetSupportedBrowsers():
234 # Add aliases for backwards compatibility.
235 supported_browsers = {
236 'stable': constants.PACKAGE_INFO['chrome_stable'],
237 'beta': constants.PACKAGE_INFO['chrome_beta'],
238 'dev': constants.PACKAGE_INFO['chrome_dev'],
239 'build': constants.PACKAGE_INFO['chrome'],
241 supported_browsers.update(constants.PACKAGE_INFO)
242 unsupported_browsers = ['content_browsertests', 'gtest', 'legacy_browser']
243 for browser in unsupported_browsers:
244 del supported_browsers[browser]
245 return supported_browsers
248 def _CompressFile(host_file, output):
249 with gzip.open(output, 'wb') as out:
250 with open(host_file, 'rb') as input_file:
251 out.write(input_file.read())
252 os.unlink(host_file)
255 def _ArchiveFiles(host_files, output):
256 with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as z:
257 for host_file in host_files:
258 z.write(host_file)
259 os.unlink(host_file)
262 def _PrintMessage(heading, eol='\n'):
263 sys.stdout.write('%s%s' % (heading, eol))
264 sys.stdout.flush()
267 def _WaitForEnter(timeout):
268 select.select([sys.stdin], [], [], timeout)
271 def _StartTracing(controllers, interval):
272 for controller in controllers:
273 controller.StartTracing(interval)
276 def _StopTracing(controllers):
277 for controller in controllers:
278 controller.StopTracing()
281 def _PullTraces(controllers, output, compress, write_html):
282 _PrintMessage('Downloading...', eol='')
283 trace_files = []
284 for controller in controllers:
285 trace_files.append(controller.PullTrace())
287 if compress and len(trace_files) == 1:
288 result = output or trace_files[0] + '.gz'
289 _CompressFile(trace_files[0], result)
290 elif len(trace_files) > 1:
291 result = output or 'chrome-combined-trace-%s.zip' % _GetTraceTimestamp()
292 _ArchiveFiles(trace_files, result)
293 elif output:
294 result = output
295 shutil.move(trace_files[0], result)
296 else:
297 result = trace_files[0]
299 if write_html:
300 result, trace_file = os.path.splitext(result)[0] + '.html', result
301 _PackageTraceAsHtml(trace_file, result)
302 if trace_file != result:
303 os.unlink(trace_file)
305 _PrintMessage('done')
306 _PrintMessage('Trace written to %s' % os.path.abspath(result))
307 return result
310 def _CaptureAndPullTrace(controllers, interval, output, compress, write_html):
311 trace_type = ' + '.join(map(str, controllers))
312 try:
313 _StartTracing(controllers, interval)
314 if interval:
315 _PrintMessage('Capturing %d-second %s. Press Enter to stop early...' % \
316 (interval, trace_type), eol='')
317 _WaitForEnter(interval)
318 else:
319 _PrintMessage('Capturing %s. Press Enter to stop...' % trace_type, eol='')
320 raw_input()
321 finally:
322 _StopTracing(controllers)
323 if interval:
324 _PrintMessage('done')
326 return _PullTraces(controllers, output, compress, write_html)
329 def _ComputeChromeCategories(options):
330 categories = []
331 if options.trace_frame_viewer:
332 categories.append('disabled-by-default-cc.debug')
333 if options.trace_ubercompositor:
334 categories.append('disabled-by-default-cc.debug*')
335 if options.trace_gpu:
336 categories.append('disabled-by-default-gpu.debug*')
337 if options.chrome_categories:
338 categories += options.chrome_categories.split(',')
339 return categories
342 def _ComputeSystraceCategories(options):
343 if not options.systrace_categories:
344 return []
345 return options.systrace_categories.split(',')
348 def main():
349 parser = optparse.OptionParser(description='Record about://tracing profiles '
350 'from Android browsers. See http://dev.'
351 'chromium.org/developers/how-tos/trace-event-'
352 'profiling-tool for detailed instructions for '
353 'profiling.')
355 timed_options = optparse.OptionGroup(parser, 'Timed tracing')
356 timed_options.add_option('-t', '--time', help='Profile for N seconds and '
357 'download the resulting trace.', metavar='N',
358 type='float')
359 parser.add_option_group(timed_options)
361 cont_options = optparse.OptionGroup(parser, 'Continuous tracing')
362 cont_options.add_option('--continuous', help='Profile continuously until '
363 'stopped.', action='store_true')
364 cont_options.add_option('--ring-buffer', help='Use the trace buffer as a '
365 'ring buffer and save its contents when stopping '
366 'instead of appending events into one long trace.',
367 action='store_true')
368 parser.add_option_group(cont_options)
370 categories = optparse.OptionGroup(parser, 'Trace categories')
371 categories.add_option('-c', '--categories', help='Select Chrome tracing '
372 'categories with comma-delimited wildcards, '
373 'e.g., "*", "cat1*,-cat1a". Omit this option to trace '
374 'Chrome\'s default categories. Chrome tracing can be '
375 'disabled with "--categories=\'\'".',
376 metavar='CHROME_CATEGORIES', dest='chrome_categories',
377 default=_DEFAULT_CHROME_CATEGORIES)
378 categories.add_option('-s', '--systrace', help='Capture a systrace with the '
379 'chosen comma-delimited systrace categories. You can '
380 'also capture a combined Chrome + systrace by enabling '
381 'both types of categories. Use "list" to see the '
382 'available categories. Systrace is disabled by '
383 'default.', metavar='SYS_CATEGORIES',
384 dest='systrace_categories', default='')
385 categories.add_option('--trace-cc',
386 help='Deprecated, use --trace-frame-viewer.',
387 action='store_true')
388 categories.add_option('--trace-frame-viewer',
389 help='Enable enough trace categories for '
390 'compositor frame viewing.', action='store_true')
391 categories.add_option('--trace-ubercompositor',
392 help='Enable enough trace categories for '
393 'ubercompositor frame data.', action='store_true')
394 categories.add_option('--trace-gpu', help='Enable extra trace categories for '
395 'GPU data.', action='store_true')
396 parser.add_option_group(categories)
398 output_options = optparse.OptionGroup(parser, 'Output options')
399 output_options.add_option('-o', '--output', help='Save trace output to file.')
400 output_options.add_option('--html', help='Package trace into a standalone '
401 'html file.', action='store_true')
402 output_options.add_option('--view', help='Open resulting trace file in a '
403 'browser.', action='store_true')
404 parser.add_option_group(output_options)
406 browsers = sorted(_GetSupportedBrowsers().keys())
407 parser.add_option('-b', '--browser', help='Select among installed browsers. '
408 'One of ' + ', '.join(browsers) + ', "stable" is used by '
409 'default.', type='choice', choices=browsers,
410 default='stable')
411 parser.add_option('-v', '--verbose', help='Verbose logging.',
412 action='store_true')
413 parser.add_option('-z', '--compress', help='Compress the resulting trace '
414 'with gzip. ', action='store_true')
415 options, args = parser.parse_args()
416 if options.trace_cc:
417 parser.parse_error("""--trace-cc is deprecated.
419 For basic jank busting uses, use --trace-frame-viewer
420 For detailed study of ubercompositor, pass --trace-ubercompositor.
422 When in doubt, just try out --trace-frame-viewer.
423 """)
425 if options.verbose:
426 logging.getLogger().setLevel(logging.DEBUG)
428 adb = android_commands.AndroidCommands()
429 if options.systrace_categories in ['list', 'help']:
430 _PrintMessage('\n'.join(SystraceController.GetCategories(adb)))
431 return 0
433 if not options.time and not options.continuous:
434 _PrintMessage('Time interval or continuous tracing should be specified.')
435 return 1
437 chrome_categories = _ComputeChromeCategories(options)
438 systrace_categories = _ComputeSystraceCategories(options)
439 package_info = _GetSupportedBrowsers()[options.browser]
441 if chrome_categories and 'webview' in systrace_categories:
442 logging.warning('Using the "webview" category in systrace together with '
443 'Chrome tracing results in duplicate trace events.')
445 controllers = []
446 if chrome_categories:
447 controllers.append(ChromeTracingController(adb,
448 package_info,
449 chrome_categories,
450 options.ring_buffer))
451 if systrace_categories:
452 controllers.append(SystraceController(adb,
453 systrace_categories,
454 options.ring_buffer))
456 if not controllers:
457 _PrintMessage('No trace categories enabled.')
458 return 1
460 result = _CaptureAndPullTrace(controllers,
461 options.time if not options.continuous else 0,
462 options.output,
463 options.compress,
464 options.html)
465 if options.view:
466 webbrowser.open(result)
469 if __name__ == '__main__':
470 sys.exit(main())