1#!/usr/bin/env python 2 3# Copyright 2016 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 7'''Tracing controller class. This class manages 8multiple tracing agents and collects data from all of them. It also 9manages the clock sync process. 10''' 11 12import ast 13import json 14import sys 15import py_utils 16import tempfile 17import uuid 18 19from systrace import trace_result 20from systrace import tracing_agents 21from py_trace_event import trace_event 22 23 24TRACE_DATA_CONTROLLER_NAME = 'systraceController' 25 26 27def ControllerAgentClockSync(issue_ts, name): 28 """Record the clock sync marker for controller tracing agent. 29 30 Unlike with the other tracing agents, the tracing controller should not 31 call this directly. Rather, it is called via callback from the other 32 tracing agents when they write a trace. 33 """ 34 trace_event.clock_sync(name, issue_ts=issue_ts) 35 36 37class TracingControllerAgent(tracing_agents.TracingAgent): 38 def __init__(self): 39 super(TracingControllerAgent, self).__init__() 40 self._log_path = None 41 42 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 43 def StartAgentTracing(self, config, timeout=None): 44 """Start tracing for the controller tracing agent. 45 46 Start tracing for the controller tracing agent. Note that 47 the tracing controller records the "controller side" 48 of the clock sync records, and nothing else. 49 """ 50 del config 51 if not trace_event.trace_can_enable(): 52 raise RuntimeError, ('Cannot enable trace_event;' 53 ' ensure py_utils is in PYTHONPATH') 54 55 controller_log_file = tempfile.NamedTemporaryFile(delete=False) 56 self._log_path = controller_log_file.name 57 controller_log_file.close() 58 trace_event.trace_enable(self._log_path) 59 return True 60 61 @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT) 62 def StopAgentTracing(self, timeout=None): 63 """Stops tracing for the controller tracing agent. 64 """ 65 # pylint: disable=no-self-use 66 # This function doesn't use self, but making it a member function 67 # for consistency with the other TracingAgents 68 trace_event.trace_disable() 69 return True 70 71 @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT) 72 def GetResults(self, timeout=None): 73 """Gets the log output from the controller tracing agent. 74 75 This output only contains the "controller side" of the clock sync records. 76 """ 77 with open(self._log_path, 'r') as outfile: 78 data = ast.literal_eval(outfile.read() + ']') 79 # Explicitly set its own clock domain. This will stop the Systrace clock 80 # domain from incorrectly being collapsed into the on device clock domain. 81 formatted_data = { 82 'traceEvents': data, 83 'metadata': { 84 'clock-domain': 'SYSTRACE', 85 } 86 } 87 return trace_result.TraceResult(TRACE_DATA_CONTROLLER_NAME, 88 json.dumps(formatted_data)) 89 90 def SupportsExplicitClockSync(self): 91 """Returns whether this supports explicit clock sync. 92 Although the tracing controller conceptually supports explicit clock 93 sync, it is not an agent controlled by other controllers so it does not 94 define RecordClockSyncMarker (rather, the recording of the "controller 95 side" of the clock sync marker is done in _IssueClockSyncMarker). Thus, 96 SupportsExplicitClockSync must return false. 97 """ 98 return False 99 100 # pylint: disable=unused-argument 101 def RecordClockSyncMarker(self, sync_id, callback): 102 raise NotImplementedError 103 104class TracingController(object): 105 def __init__(self, agents_with_config, controller_config): 106 """Create tracing controller. 107 108 Create a tracing controller object. Note that the tracing 109 controller is also a tracing agent. 110 111 Args: 112 agents_with_config: List of tracing agents for this controller with the 113 corresponding tracing configuration objects. 114 controller_config: Configuration options for the tracing controller. 115 """ 116 self._child_agents = None 117 self._child_agents_with_config = agents_with_config 118 self._controller_agent = TracingControllerAgent() 119 self._controller_config = controller_config 120 self._trace_in_progress = False 121 self.all_results = None 122 123 @property 124 def get_child_agents(self): 125 return self._child_agents 126 127 def StartTracing(self): 128 """Start tracing for all tracing agents. 129 130 This function starts tracing for both the controller tracing agent 131 and the child tracing agents. 132 133 Returns: 134 Boolean indicating whether or not the start tracing succeeded. 135 Start tracing is considered successful if at least the 136 controller tracing agent was started. 137 """ 138 assert not self._trace_in_progress, 'Trace already in progress.' 139 self._trace_in_progress = True 140 141 # Start the controller tracing agents. Controller tracing agent 142 # must be started successfully to proceed. 143 if not self._controller_agent.StartAgentTracing( 144 self._controller_config, 145 timeout=self._controller_config.timeout): 146 print 'Unable to start controller tracing agent.' 147 return False 148 149 # Start the child tracing agents. 150 succ_agents = [] 151 for agent_and_config in self._child_agents_with_config: 152 agent = agent_and_config.agent 153 config = agent_and_config.config 154 if agent.StartAgentTracing(config, 155 timeout=self._controller_config.timeout): 156 succ_agents.append(agent) 157 else: 158 print 'Agent %s not started.' % str(agent) 159 160 # Print warning if all agents not started. 161 na = len(self._child_agents_with_config) 162 ns = len(succ_agents) 163 if ns < na: 164 print 'Warning: Only %d of %d tracing agents started.' % (ns, na) 165 self._child_agents = succ_agents 166 return True 167 168 def StopTracing(self): 169 """Issue clock sync marker and stop tracing for all tracing agents. 170 171 This function stops both the controller tracing agent 172 and the child tracing agents. It issues a clock sync marker prior 173 to stopping tracing. 174 175 Returns: 176 Boolean indicating whether or not the stop tracing succeeded 177 for all agents. 178 """ 179 assert self._trace_in_progress, 'No trace in progress.' 180 self._trace_in_progress = False 181 182 # Issue the clock sync marker and stop the child tracing agents. 183 self._IssueClockSyncMarker() 184 succ_agents = [] 185 for agent in self._child_agents: 186 if agent.StopAgentTracing(timeout=self._controller_config.timeout): 187 succ_agents.append(agent) 188 else: 189 print 'Agent %s not stopped.' % str(agent) 190 191 # Stop the controller tracing agent. Controller tracing agent 192 # must be stopped successfully to proceed. 193 if not self._controller_agent.StopAgentTracing( 194 timeout=self._controller_config.timeout): 195 print 'Unable to stop controller tracing agent.' 196 return False 197 198 # Print warning if all agents not stopped. 199 na = len(self._child_agents) 200 ns = len(succ_agents) 201 if ns < na: 202 print 'Warning: Only %d of %d tracing agents stopped.' % (ns, na) 203 self._child_agents = succ_agents 204 205 # Collect the results from all the stopped tracing agents. 206 all_results = [] 207 for agent in self._child_agents + [self._controller_agent]: 208 try: 209 result = agent.GetResults( 210 timeout=self._controller_config.collection_timeout) 211 if not result: 212 print 'Warning: Timeout when getting results from %s.' % str(agent) 213 continue 214 if result.source_name in [r.source_name for r in all_results]: 215 print ('Warning: Duplicate tracing agents named %s.' % 216 result.source_name) 217 all_results.append(result) 218 # Check for exceptions. If any exceptions are seen, reraise and abort. 219 # Note that a timeout exception will be swalloed by the timeout 220 # mechanism and will not get to that point (it will return False instead 221 # of the trace result, which will be dealt with above) 222 except: 223 print 'Warning: Exception getting results from %s:' % str(agent) 224 print sys.exc_info()[0] 225 raise 226 self.all_results = all_results 227 return all_results 228 229 def GetTraceType(self): 230 """Return a string representing the child agents that are being traced.""" 231 sorted_agents = sorted(map(str, self._child_agents)) 232 return ' + '.join(sorted_agents) 233 234 def _IssueClockSyncMarker(self): 235 """Issue clock sync markers to all the child tracing agents.""" 236 for agent in self._child_agents: 237 if agent.SupportsExplicitClockSync(): 238 sync_id = GetUniqueSyncID() 239 agent.RecordClockSyncMarker(sync_id, ControllerAgentClockSync) 240 241def GetUniqueSyncID(): 242 """Get a unique sync ID. 243 244 Gets a unique sync ID by generating a UUID and converting it to a string 245 (since UUIDs are not JSON serializable) 246 """ 247 return str(uuid.uuid4()) 248 249 250class AgentWithConfig(object): 251 def __init__(self, agent, config): 252 self.agent = agent 253 self.config = config 254 255 256def CreateAgentsWithConfig(options, modules): 257 """Create tracing agents. 258 259 This function will determine which tracing agents are valid given the 260 options and create those agents along with their corresponding configuration 261 object. 262 Args: 263 options: The command-line options. 264 modules: The modules for either Systrace or profile_chrome. 265 TODO(washingtonp): After all profile_chrome agents are in 266 Systrace, this parameter will no longer be valid. 267 Returns: 268 A list of AgentWithConfig options containing agents and their corresponding 269 configuration object. 270 """ 271 result = [] 272 for module in modules: 273 config = module.get_config(options) 274 agent = module.try_create_agent(config) 275 if agent and config: 276 result.append(AgentWithConfig(agent, config)) 277 return [x for x in result if x and x.agent] 278 279 280class TracingControllerConfig(tracing_agents.TracingConfig): 281 def __init__(self, output_file, trace_time, write_json, 282 link_assets, asset_dir, timeout, collection_timeout, 283 device_serial_number, target): 284 tracing_agents.TracingConfig.__init__(self) 285 self.output_file = output_file 286 self.trace_time = trace_time 287 self.write_json = write_json 288 self.link_assets = link_assets 289 self.asset_dir = asset_dir 290 self.timeout = timeout 291 self.collection_timeout = collection_timeout 292 self.device_serial_number = device_serial_number 293 self.target = target 294 295 296def GetControllerConfig(options): 297 return TracingControllerConfig(options.output_file, options.trace_time, 298 options.write_json, 299 options.link_assets, options.asset_dir, 300 options.timeout, options.collection_timeout, 301 options.device_serial_number, options.target) 302 303def GetChromeStartupControllerConfig(options): 304 return TracingControllerConfig(None, options.trace_time, 305 options.write_json, None, None, None, None, 306 None, None) 307