1#!/usr/bin/env python
2#
3# Copyright 2014 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.
6
7import logging
8import optparse
9import os
10import sys
11import webbrowser
12
13from profile_chrome import chrome_controller
14from profile_chrome import perf_controller
15from profile_chrome import profiler
16from profile_chrome import systrace_controller
17from profile_chrome import ui
18
19from pylib import android_commands
20from pylib.device import device_utils
21
22
23_DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
24
25
26def _ComputeChromeCategories(options):
27  categories = []
28  if options.trace_frame_viewer:
29    categories.append('disabled-by-default-cc.debug')
30  if options.trace_ubercompositor:
31    categories.append('disabled-by-default-cc.debug*')
32  if options.trace_gpu:
33    categories.append('disabled-by-default-gpu.debug*')
34  if options.trace_flow:
35    categories.append('disabled-by-default-toplevel.flow')
36  if options.trace_memory:
37    categories.append('disabled-by-default-memory')
38  if options.trace_scheduler:
39    categories.append('disabled-by-default-cc.debug.scheduler')
40    categories.append('disabled-by-default-blink.scheduler')
41  if options.chrome_categories:
42    categories += options.chrome_categories.split(',')
43  return categories
44
45
46def _ComputeSystraceCategories(options):
47  if not options.systrace_categories:
48    return []
49  return options.systrace_categories.split(',')
50
51
52def _ComputePerfCategories(options):
53  if not perf_controller.PerfProfilerController.IsSupported():
54    return []
55  if not options.perf_categories:
56    return []
57  return options.perf_categories.split(',')
58
59
60def _OptionalValueCallback(default_value):
61  def callback(option, _, __, parser):
62    value = default_value
63    if parser.rargs and not parser.rargs[0].startswith('-'):
64      value = parser.rargs.pop(0)
65    setattr(parser.values, option.dest, value)
66  return callback
67
68
69def _CreateOptionParser():
70  parser = optparse.OptionParser(description='Record about://tracing profiles '
71                                 'from Android browsers. See http://dev.'
72                                 'chromium.org/developers/how-tos/trace-event-'
73                                 'profiling-tool for detailed instructions for '
74                                 'profiling.')
75
76  timed_options = optparse.OptionGroup(parser, 'Timed tracing')
77  timed_options.add_option('-t', '--time', help='Profile for N seconds and '
78                          'download the resulting trace.', metavar='N',
79                           type='float')
80  parser.add_option_group(timed_options)
81
82  cont_options = optparse.OptionGroup(parser, 'Continuous tracing')
83  cont_options.add_option('--continuous', help='Profile continuously until '
84                          'stopped.', action='store_true')
85  cont_options.add_option('--ring-buffer', help='Use the trace buffer as a '
86                          'ring buffer and save its contents when stopping '
87                          'instead of appending events into one long trace.',
88                          action='store_true')
89  parser.add_option_group(cont_options)
90
91  chrome_opts = optparse.OptionGroup(parser, 'Chrome tracing options')
92  chrome_opts.add_option('-c', '--categories', help='Select Chrome tracing '
93                         'categories with comma-delimited wildcards, '
94                         'e.g., "*", "cat1*,-cat1a". Omit this option to trace '
95                         'Chrome\'s default categories. Chrome tracing can be '
96                         'disabled with "--categories=\'\'". Use "list" to '
97                         'see the available categories.',
98                         metavar='CHROME_CATEGORIES', dest='chrome_categories',
99                         default=_DEFAULT_CHROME_CATEGORIES)
100  chrome_opts.add_option('--trace-cc',
101                         help='Deprecated, use --trace-frame-viewer.',
102                         action='store_true')
103  chrome_opts.add_option('--trace-frame-viewer',
104                         help='Enable enough trace categories for '
105                         'compositor frame viewing.', action='store_true')
106  chrome_opts.add_option('--trace-ubercompositor',
107                         help='Enable enough trace categories for '
108                         'ubercompositor frame data.', action='store_true')
109  chrome_opts.add_option('--trace-gpu', help='Enable extra trace categories '
110                         'for GPU data.', action='store_true')
111  chrome_opts.add_option('--trace-flow', help='Enable extra trace categories '
112                         'for IPC message flows.', action='store_true')
113  chrome_opts.add_option('--trace-memory', help='Enable extra trace categories '
114                         'for memory profile. (tcmalloc required)',
115                         action='store_true')
116  chrome_opts.add_option('--trace-scheduler', help='Enable extra trace '
117                         'categories for scheduler state',
118                         action='store_true')
119  parser.add_option_group(chrome_opts)
120
121  systrace_opts = optparse.OptionGroup(parser, 'Systrace tracing options')
122  systrace_opts.add_option('-s', '--systrace', help='Capture a systrace with '
123                        'the chosen comma-delimited systrace categories. You '
124                        'can also capture a combined Chrome + systrace by '
125                        'enable both types of categories. Use "list" to see '
126                        'the available categories. Systrace is disabled by '
127                        'default.', metavar='SYS_CATEGORIES',
128                        dest='systrace_categories', default='')
129  parser.add_option_group(systrace_opts)
130
131  if perf_controller.PerfProfilerController.IsSupported():
132    perf_opts = optparse.OptionGroup(parser, 'Perf profiling options')
133    perf_opts.add_option('-p', '--perf', help='Capture a perf profile with '
134                         'the chosen comma-delimited event categories. '
135                         'Samples CPU cycles by default. Use "list" to see '
136                         'the available sample types.', action='callback',
137                         default='', callback=_OptionalValueCallback('cycles'),
138                         metavar='PERF_CATEGORIES', dest='perf_categories')
139    parser.add_option_group(perf_opts)
140
141  output_options = optparse.OptionGroup(parser, 'Output options')
142  output_options.add_option('-o', '--output', help='Save trace output to file.')
143  output_options.add_option('--json', help='Save trace as raw JSON instead of '
144                            'HTML.', action='store_true')
145  output_options.add_option('--view', help='Open resulting trace file in a '
146                            'browser.', action='store_true')
147  parser.add_option_group(output_options)
148
149  browsers = sorted(profiler.GetSupportedBrowsers().keys())
150  parser.add_option('-b', '--browser', help='Select among installed browsers. '
151                    'One of ' + ', '.join(browsers) + ', "stable" is used by '
152                    'default.', type='choice', choices=browsers,
153                    default='stable')
154  parser.add_option('-v', '--verbose', help='Verbose logging.',
155                    action='store_true')
156  parser.add_option('-z', '--compress', help='Compress the resulting trace '
157                    'with gzip. ', action='store_true')
158  return parser
159
160
161def main():
162  parser = _CreateOptionParser()
163  options, _args = parser.parse_args()
164  if options.trace_cc:
165    parser.parse_error("""--trace-cc is deprecated.
166
167For basic jank busting uses, use  --trace-frame-viewer
168For detailed study of ubercompositor, pass --trace-ubercompositor.
169
170When in doubt, just try out --trace-frame-viewer.
171""")
172
173  if options.verbose:
174    logging.getLogger().setLevel(logging.DEBUG)
175
176  devices = android_commands.GetAttachedDevices()
177  if len(devices) != 1:
178    parser.error('Exactly 1 device must be attached.')
179  device = device_utils.DeviceUtils(devices[0])
180  package_info = profiler.GetSupportedBrowsers()[options.browser]
181
182  if options.chrome_categories in ['list', 'help']:
183    ui.PrintMessage('Collecting record categories list...', eol='')
184    record_categories = []
185    disabled_by_default_categories = []
186    record_categories, disabled_by_default_categories = \
187        chrome_controller.ChromeTracingController.GetCategories(
188            device, package_info)
189
190    ui.PrintMessage('done')
191    ui.PrintMessage('Record Categories:')
192    ui.PrintMessage('\n'.join('\t%s' % item \
193        for item in sorted(record_categories)))
194
195    ui.PrintMessage('\nDisabled by Default Categories:')
196    ui.PrintMessage('\n'.join('\t%s' % item \
197        for item in sorted(disabled_by_default_categories)))
198
199    return 0
200
201  if options.systrace_categories in ['list', 'help']:
202    ui.PrintMessage('\n'.join(
203        systrace_controller.SystraceController.GetCategories(device)))
204    return 0
205
206  if (perf_controller.PerfProfilerController.IsSupported() and
207      options.perf_categories in ['list', 'help']):
208    ui.PrintMessage('\n'.join(
209        perf_controller.PerfProfilerController.GetCategories(device)))
210    return 0
211
212  if not options.time and not options.continuous:
213    ui.PrintMessage('Time interval or continuous tracing should be specified.')
214    return 1
215
216  chrome_categories = _ComputeChromeCategories(options)
217  systrace_categories = _ComputeSystraceCategories(options)
218  perf_categories = _ComputePerfCategories(options)
219
220  if chrome_categories and 'webview' in systrace_categories:
221    logging.warning('Using the "webview" category in systrace together with '
222                    'Chrome tracing results in duplicate trace events.')
223
224  enabled_controllers = []
225  if chrome_categories:
226    enabled_controllers.append(
227        chrome_controller.ChromeTracingController(device,
228                                                  package_info,
229                                                  chrome_categories,
230                                                  options.ring_buffer,
231                                                  options.trace_memory))
232  if systrace_categories:
233    enabled_controllers.append(
234        systrace_controller.SystraceController(device,
235                                               systrace_categories,
236                                               options.ring_buffer))
237
238  if perf_categories:
239    enabled_controllers.append(
240        perf_controller.PerfProfilerController(device,
241                                               perf_categories))
242
243  if not enabled_controllers:
244    ui.PrintMessage('No trace categories enabled.')
245    return 1
246
247  if options.output:
248    options.output = os.path.expanduser(options.output)
249  result = profiler.CaptureProfile(
250      enabled_controllers,
251      options.time if not options.continuous else 0,
252      output=options.output,
253      compress=options.compress,
254      write_json=options.json)
255  if options.view:
256    if sys.platform == 'darwin':
257      os.system('/usr/bin/open %s' % os.path.abspath(result))
258    else:
259      webbrowser.open(result)
260