Home | History | Annotate | Download | only in profile_chrome
      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 
      5 import json
      6 import optparse
      7 import os
      8 import py_utils
      9 import re
     10 
     11 from devil.android import device_errors
     12 from devil.android.sdk import intent
     13 from systrace import trace_result
     14 from systrace import tracing_agents
     15 
     16 
     17 DEFAULT_CHROME_CATEGORIES = '_DEFAULT_CHROME_CATEGORIES'
     18 _HEAP_PROFILE_MMAP_PROPERTY = 'heapprof.mmap'
     19 
     20 
     21 class 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 
    134 class 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 
    152 def 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 
    158 def 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   chrome_opts.add_option('--trace-cc',
    168                          help='Deprecated, use --trace-frame-viewer.',
    169                          action='store_true')
    170   chrome_opts.add_option('--trace-frame-viewer',
    171                          help='Enable enough trace categories for '
    172                          'compositor frame viewing.', action='store_true')
    173   chrome_opts.add_option('--trace-ubercompositor',
    174                          help='Enable enough trace categories for '
    175                          'ubercompositor frame data.', action='store_true')
    176   chrome_opts.add_option('--trace-gpu', help='Enable extra trace categories '
    177                          'for GPU data.', action='store_true')
    178   chrome_opts.add_option('--trace-flow', help='Enable extra trace categories '
    179                          'for IPC message flows.', action='store_true')
    180   chrome_opts.add_option('--trace-memory', help='Enable extra trace categories '
    181                          'for memory profile. (tcmalloc required)',
    182                          action='store_true')
    183   chrome_opts.add_option('--trace-scheduler', help='Enable extra trace '
    184                          'categories for scheduler state',
    185                          action='store_true')
    186   return chrome_opts
    187 
    188 def get_config(options):
    189   return ChromeConfig(options.chrome_categories, options.trace_cc,
    190                       options.trace_frame_viewer, options.trace_ubercompositor,
    191                       options.trace_gpu, options.trace_flow,
    192                       options.trace_memory, options.trace_scheduler,
    193                       options.ring_buffer, options.device,
    194                       options.package_info)
    195 
    196 def _ComputeChromeCategories(config):
    197   categories = []
    198   if config.trace_frame_viewer:
    199     categories.append('disabled-by-default-cc.debug')
    200   if config.trace_ubercompositor:
    201     categories.append('disabled-by-default-cc.debug*')
    202   if config.trace_gpu:
    203     categories.append('disabled-by-default-gpu.debug*')
    204   if config.trace_flow:
    205     categories.append('disabled-by-default-toplevel.flow')
    206   if config.trace_memory:
    207     categories.append('disabled-by-default-memory')
    208   if config.trace_scheduler:
    209     categories.append('disabled-by-default-blink.scheduler')
    210     categories.append('disabled-by-default-cc.debug.scheduler')
    211     categories.append('disabled-by-default-renderer.scheduler')
    212   if config.chrome_categories:
    213     categories += config.chrome_categories.split(',')
    214   return categories
    215