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