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
5import json
6import optparse
7import os
8import py_utils
9import re
10
11from devil.android import device_errors
12from devil.android.sdk import intent
13from systrace import trace_result
14from systrace import tracing_agents
15
16
17_DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
18_HEAP_PROFILE_MMAP_PROPERTY = 'heapprof.mmap'
19
20
21class ChromeTracingAgent(tracing_agents.TracingAgent):
22  def __init__(self, device, package_info, ring_buffer, trace_memory=False):
23    tracing_agents.TracingAgent.__init__(self)
24    self._device = device
25    self._package_info = package_info
26    self._ring_buffer = ring_buffer
27    self._logcat_monitor = self._device.GetLogcatMonitor()
28    self._trace_file = None
29    self._trace_memory = trace_memory
30    self._is_tracing = False
31    self._trace_start_re = \
32       re.compile(r'Logging performance trace to file')
33    self._trace_finish_re = \
34       re.compile(r'Profiler finished[.] Results are in (.*)[.]')
35    self._categories = None
36
37  def __repr__(self):
38    return 'chrome trace'
39
40  @staticmethod
41  def GetCategories(device, package_info):
42    with device.GetLogcatMonitor() as logmon:
43      device.BroadcastIntent(intent.Intent(
44          action='%s.GPU_PROFILER_LIST_CATEGORIES' % package_info.package))
45      try:
46        json_category_list = logmon.WaitFor(
47            re.compile(r'{"traceCategoriesList(.*)'), timeout=5).group(0)
48      except device_errors.CommandTimeoutError:
49        raise RuntimeError('Performance trace category list marker not found. '
50                           'Is the correct version of the browser running?')
51
52    record_categories = set()
53    disabled_by_default_categories = set()
54    json_data = json.loads(json_category_list)['traceCategoriesList']
55    for item in json_data:
56      for category in item.split(','):
57        if category.startswith('disabled-by-default'):
58          disabled_by_default_categories.add(category)
59        else:
60          record_categories.add(category)
61
62    return list(record_categories), list(disabled_by_default_categories)
63
64  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
65  def StartAgentTracing(self, config, timeout=None):
66    self._categories = _ComputeChromeCategories(config)
67    self._logcat_monitor.Start()
68    start_extras = {'categories': ','.join(self._categories)}
69    if self._ring_buffer:
70      start_extras['continuous'] = None
71    self._device.BroadcastIntent(intent.Intent(
72        action='%s.GPU_PROFILER_START' % self._package_info.package,
73        extras=start_extras))
74
75    if self._trace_memory:
76      self._device.EnableRoot()
77      self._device.SetProp(_HEAP_PROFILE_MMAP_PROPERTY, 1)
78
79    # Chrome logs two different messages related to tracing:
80    #
81    # 1. "Logging performance trace to file"
82    # 2. "Profiler finished. Results are in [...]"
83    #
84    # The first one is printed when tracing starts and the second one indicates
85    # that the trace file is ready to be pulled.
86    try:
87      self._logcat_monitor.WaitFor(self._trace_start_re, timeout=5)
88      self._is_tracing = True
89    except device_errors.CommandTimeoutError:
90      raise RuntimeError(
91          'Trace start marker not found. Possible causes: 1) Is the correct '
92          'version of the browser running? 2) Is the browser already launched?')
93    return True
94
95  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
96  def StopAgentTracing(self, timeout=None):
97    if self._is_tracing:
98      self._device.BroadcastIntent(intent.Intent(
99          action='%s.GPU_PROFILER_STOP' % self._package_info.package))
100      self._trace_file = self._logcat_monitor.WaitFor(
101          self._trace_finish_re, timeout=120).group(1)
102      self._is_tracing = False
103    if self._trace_memory:
104      self._device.SetProp(_HEAP_PROFILE_MMAP_PROPERTY, 0)
105    return True
106
107  @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
108  def GetResults(self, timeout=None):
109    with open(self._PullTrace(), 'r') as f:
110      trace_data = f.read()
111    return trace_result.TraceResult('traceEvents', trace_data)
112
113  def _PullTrace(self):
114    trace_file = self._trace_file.replace('/storage/emulated/0/', '/sdcard/')
115    host_file = os.path.join(os.path.curdir, os.path.basename(trace_file))
116    try:
117      self._device.PullFile(trace_file, host_file)
118    except device_errors.AdbCommandFailedError:
119      raise RuntimeError(
120          'Cannot pull the trace file. Have you granted Storage permission to '
121          'the browser? (Android Settings -> Apps -> [the browser app] -> '
122          'Permissions -> Storage)')
123    return host_file
124
125  def SupportsExplicitClockSync(self):
126    return False
127
128  def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback):
129    # pylint: disable=unused-argument
130    assert self.SupportsExplicitClockSync(), ('Clock sync marker cannot be '
131        'recorded since explicit clock sync is not supported.')
132
133
134class ChromeConfig(tracing_agents.TracingConfig):
135  def __init__(self, chrome_categories, trace_cc, trace_frame_viewer,
136               trace_ubercompositor, trace_gpu, trace_flow, trace_memory,
137               trace_scheduler, ring_buffer, device, package_info):
138    tracing_agents.TracingConfig.__init__(self)
139    self.chrome_categories = chrome_categories
140    self.trace_cc = trace_cc
141    self.trace_frame_viewer = trace_frame_viewer
142    self.trace_ubercompositor = trace_ubercompositor
143    self.trace_gpu = trace_gpu
144    self.trace_flow = trace_flow
145    self.trace_memory = trace_memory
146    self.trace_scheduler = trace_scheduler
147    self.ring_buffer = ring_buffer
148    self.device = device
149    self.package_info = package_info
150
151
152def try_create_agent(config):
153  if config.chrome_categories:
154    return ChromeTracingAgent(config.device, config.package_info,
155                              config.ring_buffer, config.trace_memory)
156  return None
157
158def add_options(parser):
159  chrome_opts = optparse.OptionGroup(parser, 'Chrome tracing options')
160  chrome_opts.add_option('-c', '--categories', help='Select Chrome tracing '
161                         'categories with comma-delimited wildcards, '
162                         'e.g., "*", "cat1*,-cat1a". Omit this option to trace '
163                         'Chrome\'s default categories. Chrome tracing can be '
164                         'disabled with "--categories=\'\'". Use "list" to '
165                         'see the available categories.',
166                         metavar='CHROME_CATEGORIES', dest='chrome_categories',
167                         default=_DEFAULT_CHROME_CATEGORIES)
168  chrome_opts.add_option('--trace-cc',
169                         help='Deprecated, use --trace-frame-viewer.',
170                         action='store_true')
171  chrome_opts.add_option('--trace-frame-viewer',
172                         help='Enable enough trace categories for '
173                         'compositor frame viewing.', action='store_true')
174  chrome_opts.add_option('--trace-ubercompositor',
175                         help='Enable enough trace categories for '
176                         'ubercompositor frame data.', action='store_true')
177  chrome_opts.add_option('--trace-gpu', help='Enable extra trace categories '
178                         'for GPU data.', action='store_true')
179  chrome_opts.add_option('--trace-flow', help='Enable extra trace categories '
180                         'for IPC message flows.', action='store_true')
181  chrome_opts.add_option('--trace-memory', help='Enable extra trace categories '
182                         'for memory profile. (tcmalloc required)',
183                         action='store_true')
184  chrome_opts.add_option('--trace-scheduler', help='Enable extra trace '
185                         'categories for scheduler state',
186                         action='store_true')
187  return chrome_opts
188
189def get_config(options):
190  return ChromeConfig(options.chrome_categories, options.trace_cc,
191                      options.trace_frame_viewer, options.trace_ubercompositor,
192                      options.trace_gpu, options.trace_flow,
193                      options.trace_memory, options.trace_scheduler,
194                      options.ring_buffer, options.device,
195                      options.package_info)
196
197def _ComputeChromeCategories(config):
198  categories = []
199  if config.trace_frame_viewer:
200    categories.append('disabled-by-default-cc.debug')
201  if config.trace_ubercompositor:
202    categories.append('disabled-by-default-cc.debug*')
203  if config.trace_gpu:
204    categories.append('disabled-by-default-gpu.debug*')
205  if config.trace_flow:
206    categories.append('disabled-by-default-toplevel.flow')
207  if config.trace_memory:
208    categories.append('disabled-by-default-memory')
209  if config.trace_scheduler:
210    categories.append('disabled-by-default-blink.scheduler')
211    categories.append('disabled-by-default-cc.debug.scheduler')
212    categories.append('disabled-by-default-renderer.scheduler')
213  if config.chrome_categories:
214    categories += config.chrome_categories.split(',')
215  return categories
216