• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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