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.
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>
31 <title>%(title)s</title>
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;
61 <div class="view"></view>
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
98 class ChromeTracingController(object):
99 def __init__(self
, adb
, package_info
, categories
, ring_buffer
):
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)
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.
129 self
._trace
_file
= self
._adb
.WaitForLogMatch(self
._trace
_start
_re
,
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
:
139 self
._adb
.BroadcastIntent(self
._package
_info
.package
, 'GPU_PROFILER_STOP')
140 self
._adb
.WaitForLogMatch(self
._trace
_finish
_re
, None, timeout
=120)
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
)
152 _SYSTRACE_OPTIONS
= [
153 # Compress the trace before sending it over USB.
155 # Use a large trace buffer to increase the polling interval.
159 # Interval in seconds for sampling systrace data.
160 _SYSTRACE_INTERVAL
= 15
163 class SystraceController(object):
164 def __init__(self
, adb
, categories
, ring_buffer
):
166 self
._categories
= categories
167 self
._ring
_buffer
= ring_buffer
168 self
._done
= threading
.Event()
170 self
._trace
_data
= None
176 def GetCategories(adb
):
177 return adb
.RunShellCommand('atrace --list_categories')
179 def StartTracing(self
, interval
):
180 self
._thread
= threading
.Thread(target
=self
._CollectData
)
183 def StopTracing(self
):
190 output_name
= 'systrace-%s' % _GetTraceTimestamp()
191 with
open(output_name
, 'w') as out
:
192 out
.write(self
._trace
_data
)
195 def _RunATraceCommand(self
, command
):
196 # We use a separate interface to adb because the one from AndroidCommands
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
):
205 self
._RunATraceCommand
('async_start')
207 while not self
._done
.is_set():
208 self
._done
.wait(_SYSTRACE_INTERVAL
)
209 if not self
._ring
_buffer
or self
._done
.is_set():
211 self
._DecodeTraceData
(self
._RunATraceCommand
('async_dump')))
214 self
._DecodeTraceData
(self
._RunATraceCommand
('async_stop')))
215 self
._trace
_data
= ''.join([zlib
.decompress(d
) for d
in trace_data
])
218 def _DecodeTraceData(trace_data
):
220 trace_start
= trace_data
.index('TRACE:')
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())
255 def _ArchiveFiles(host_files
, output
):
256 with zipfile
.ZipFile(output
, 'w', zipfile
.ZIP_DEFLATED
) as z
:
257 for host_file
in host_files
:
262 def _PrintMessage(heading
, eol
='\n'):
263 sys
.stdout
.write('%s%s' % (heading
, eol
))
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
='')
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
)
295 shutil
.move(trace_files
[0], result
)
297 result
= trace_files
[0]
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
))
310 def _CaptureAndPullTrace(controllers
, interval
, output
, compress
, write_html
):
311 trace_type
= ' + '.join(map(str, controllers
))
313 _StartTracing(controllers
, interval
)
315 _PrintMessage('Capturing %d-second %s. Press Enter to stop early...' % \
316 (interval
, trace_type
), eol
='')
317 _WaitForEnter(interval
)
319 _PrintMessage('Capturing %s. Press Enter to stop...' % trace_type
, eol
='')
322 _StopTracing(controllers
)
324 _PrintMessage('done')
326 return _PullTraces(controllers
, output
, compress
, write_html
)
329 def _ComputeChromeCategories(options
):
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(',')
342 def _ComputeSystraceCategories(options
):
343 if not options
.systrace_categories
:
345 return options
.systrace_categories
.split(',')
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 '
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',
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.',
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.',
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
,
411 parser
.add_option('-v', '--verbose', help='Verbose logging.',
413 parser
.add_option('-z', '--compress', help='Compress the resulting trace '
414 'with gzip. ', action
='store_true')
415 options
, args
= parser
.parse_args()
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.
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
)))
433 if not options
.time
and not options
.continuous
:
434 _PrintMessage('Time interval or continuous tracing should be specified.')
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.')
446 if chrome_categories
:
447 controllers
.append(ChromeTracingController(adb
,
450 options
.ring_buffer
))
451 if systrace_categories
:
452 controllers
.append(SystraceController(adb
,
454 options
.ring_buffer
))
457 _PrintMessage('No trace categories enabled.')
460 result
= _CaptureAndPullTrace(controllers
,
461 options
.time
if not options
.continuous
else 0,
466 webbrowser
.open(result
)
469 if __name__
== '__main__':