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 optparse 6import py_utils 7import threading 8import zlib 9 10from devil.utils import cmd_helper 11 12from systrace import trace_result 13from systrace import tracing_agents 14 15 16_ATRACE_OPTIONS = [ 17 # Compress the trace before sending it over USB. 18 '-z', 19 # Use a large trace buffer to increase the polling interval. 20 '-b', '16384' 21] 22 23# Interval in seconds for sampling atrace data. 24_ATRACE_INTERVAL = 15 25 26# If a custom list of categories is not specified, traces will include 27# these categories (if available on the device). 28_DEFAULT_CATEGORIES = 'sched gfx view dalvik webview input disk am wm'.split() 29 30_TRACING_ON_PATH = '/sys/kernel/debug/tracing/tracing_on' 31 32 33class AtraceAgent(tracing_agents.TracingAgent): 34 def __init__(self, device, ring_buffer): 35 tracing_agents.TracingAgent.__init__(self) 36 self._device = device 37 self._ring_buffer = ring_buffer 38 self._done = threading.Event() 39 self._thread = None 40 self._trace_data = None 41 self._categories = None 42 43 def __repr__(self): 44 return 'atrace' 45 46 @staticmethod 47 def GetCategories(device): 48 return device.RunShellCommand('atrace --list_categories') 49 50 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 51 def StartAgentTracing(self, config, timeout=None): 52 self._categories = _ComputeAtraceCategories(config) 53 self._thread = threading.Thread(target=self._CollectData) 54 self._thread.start() 55 return True 56 57 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 58 def StopAgentTracing(self, timeout=None): 59 self._done.set() 60 return True 61 62 @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) 63 def GetResults(self, timeout=None): 64 self._thread.join() 65 self._thread = None 66 return trace_result.TraceResult('systemTraceEvents', self._trace_data) 67 68 def SupportsExplicitClockSync(self): 69 return False 70 71 def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback): 72 # pylint: disable=unused-argument 73 assert self.SupportsExplicitClockSync(), ('Clock sync marker cannot be ' 74 'recorded since explicit clock sync is not supported.') 75 76 def IsTracingOn(self): 77 result = self._RunAdbShellCommand(['cat', _TRACING_ON_PATH]) 78 return result.strip() == '1' 79 80 def _RunAdbShellCommand(self, command): 81 # We use a separate interface to adb because the one from AndroidCommands 82 # isn't re-entrant. 83 # TODO(jbudorick) Look at providing a way to unhandroll this once the 84 # adb rewrite has fully landed. 85 device_param = (['-s', str(self._device)] if str(self._device) else []) 86 cmd = ['adb'] + device_param + ['shell'] + command 87 return cmd_helper.GetCmdOutput(cmd) 88 89 def _RunATraceCommand(self, command): 90 cmd = ['atrace', '--%s' % command] + _ATRACE_OPTIONS + self._categories 91 return self._RunAdbShellCommand(cmd) 92 93 def _ForceStopAtrace(self): 94 # atrace on pre-M Android devices cannot be stopped asynchronously 95 # correctly. Use synchronous mode to force stop. 96 cmd = ['atrace', '-t', '0'] 97 return self._RunAdbShellCommand(cmd) 98 99 def _CollectData(self): 100 trace_data = [] 101 self._RunATraceCommand('async_start') 102 try: 103 while not self._done.is_set(): 104 self._done.wait(_ATRACE_INTERVAL) 105 if not self._ring_buffer or self._done.is_set(): 106 trace_data.append( 107 self._DecodeTraceData(self._RunATraceCommand('async_dump'))) 108 finally: 109 trace_data.append( 110 self._DecodeTraceData(self._RunATraceCommand('async_stop'))) 111 if self.IsTracingOn(): 112 self._ForceStopAtrace() 113 self._trace_data = ''.join([zlib.decompress(d) for d in trace_data]) 114 115 @staticmethod 116 def _DecodeTraceData(trace_data): 117 try: 118 trace_start = trace_data.index('TRACE:') 119 except ValueError: 120 raise RuntimeError('Atrace start marker not found') 121 trace_data = trace_data[trace_start + 6:] 122 123 # Collapse CRLFs that are added by adb shell. 124 if trace_data.startswith('\r\n'): 125 trace_data = trace_data.replace('\r\n', '\n') 126 127 # Skip the initial newline. 128 return trace_data[1:] 129 130 131class AtraceConfig(tracing_agents.TracingConfig): 132 def __init__(self, atrace_categories, device, ring_buffer): 133 tracing_agents.TracingConfig.__init__(self) 134 self.atrace_categories = atrace_categories 135 self.device = device 136 self.ring_buffer = ring_buffer 137 138 139def try_create_agent(config): 140 if config.atrace_categories: 141 return AtraceAgent(config.device, config.ring_buffer) 142 return None 143 144def add_options(parser): 145 atrace_opts = optparse.OptionGroup(parser, 'Atrace tracing options') 146 atrace_opts.add_option('-s', '--systrace', help='Capture a systrace with ' 147 'the chosen comma-delimited systrace categories. You' 148 ' can also capture a combined Chrome + systrace by ' 149 'enabling both types of categories. Use "list" to ' 150 'see the available categories. Systrace is disabled' 151 ' by default. Note that in this case, Systrace is ' 152 'synonymous with Atrace.', 153 metavar='ATRACE_CATEGORIES', 154 dest='atrace_categories', default='') 155 return atrace_opts 156 157def get_config(options): 158 return AtraceConfig(options.atrace_categories, options.device, 159 options.ring_buffer) 160 161def _ComputeAtraceCategories(config): 162 if not config.atrace_categories: 163 return _DEFAULT_CATEGORIES 164 return config.atrace_categories.split(',') 165