• 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 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