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