1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import ast
6import atexit
7import contextlib
8import gc
9import logging
10import os
11import sys
12import tempfile
13import traceback
14import uuid
15
16from py_trace_event import trace_event
17from telemetry.core import discover
18from telemetry.core import util
19from telemetry.internal.platform import tracing_agent
20from telemetry.internal.platform.tracing_agent import chrome_tracing_agent
21from telemetry.timeline import trace_data as trace_data_module
22from telemetry.timeline import tracing_config
23
24
25def _IterAllTracingAgentClasses():
26  tracing_agent_dir = os.path.join(
27      os.path.dirname(os.path.realpath(__file__)), 'tracing_agent')
28  return discover.DiscoverClasses(
29      tracing_agent_dir, util.GetTelemetryDir(),
30      tracing_agent.TracingAgent).itervalues()
31
32
33class TracingControllerStoppedError(Exception):
34  pass
35
36
37class _TracingState(object):
38
39  def __init__(self, config, timeout):
40    self._builder = trace_data_module.TraceDataBuilder()
41    self._config = config
42    self._timeout = timeout
43
44  @property
45  def builder(self):
46    return self._builder
47
48  @property
49  def config(self):
50    return self._config
51
52  @property
53  def timeout(self):
54    return self._timeout
55
56
57class TracingControllerBackend(object):
58  def __init__(self, platform_backend):
59    self._platform_backend = platform_backend
60    self._current_state = None
61    self._supported_agents_classes = [
62        agent_classes for agent_classes in _IterAllTracingAgentClasses() if
63        agent_classes.IsSupported(platform_backend)]
64    self._active_agents_instances = []
65    self._trace_log = None
66    self._is_tracing_controllable = True
67
68  def StartTracing(self, config, timeout):
69    if self.is_tracing_running:
70      return False
71
72    assert isinstance(config, tracing_config.TracingConfig)
73    assert len(self._active_agents_instances) == 0
74
75    self._current_state = _TracingState(config, timeout)
76    # Hack: chrome tracing agent may only depend on the number of alive chrome
77    # devtools processes, rather platform (when startup tracing is not
78    # supported), hence we add it to the list of supported agents here if it was
79    # not added.
80    if (chrome_tracing_agent.ChromeTracingAgent.IsSupported(
81        self._platform_backend) and
82        not chrome_tracing_agent.ChromeTracingAgent in
83        self._supported_agents_classes):
84      self._supported_agents_classes.append(
85          chrome_tracing_agent.ChromeTracingAgent)
86
87    self.StartAgentTracing(config, timeout)
88    for agent_class in self._supported_agents_classes:
89      agent = agent_class(self._platform_backend)
90      if agent.StartAgentTracing(config, timeout):
91        self._active_agents_instances.append(agent)
92    return True
93
94  def _GenerateClockSyncId(self):
95    return str(uuid.uuid4())
96
97  @contextlib.contextmanager
98  def _DisableGarbageCollection(self):
99    try:
100      gc.disable()
101      yield
102    finally:
103      gc.enable()
104
105  def StopTracing(self):
106    assert self.is_tracing_running, 'Can only stop tracing when tracing is on.'
107    self._IssueClockSyncMarker()
108    builder = self._current_state.builder
109
110    raised_exception_messages = []
111    for agent in self._active_agents_instances + [self]:
112      try:
113        agent.StopAgentTracing(builder)
114      except Exception: # pylint: disable=broad-except
115        raised_exception_messages.append(
116            ''.join(traceback.format_exception(*sys.exc_info())))
117
118    self._active_agents_instances = []
119    self._current_state = None
120
121    if raised_exception_messages:
122      raise TracingControllerStoppedError(
123          'Exceptions raised when trying to stop tracing:\n' +
124          '\n'.join(raised_exception_messages))
125
126    return builder.AsData()
127
128  def FlushTracing(self):
129    assert self.is_tracing_running, 'Can only flush tracing when tracing is on.'
130    self._IssueClockSyncMarker()
131
132    raised_exception_messages = []
133    # Flushing the controller's pytrace is not supported.
134    for agent in self._active_agents_instances:
135      try:
136        if agent.SupportsFlushingAgentTracing():
137          agent.FlushAgentTracing(self._current_state.config,
138                                  self._current_state.timeout,
139                                  self._current_state.builder)
140      except Exception: # pylint: disable=broad-except
141        raised_exception_messages.append(
142            ''.join(traceback.format_exception(*sys.exc_info())))
143
144    if raised_exception_messages:
145      raise TracingControllerStoppedError(
146          'Exceptions raised when trying to stop tracing:\n' +
147          '\n'.join(raised_exception_messages))
148
149  def StartAgentTracing(self, config, timeout):
150    self._is_tracing_controllable = self._IsTracingControllable()
151    if not self._is_tracing_controllable:
152      return False
153
154    tf = tempfile.NamedTemporaryFile(delete=False)
155    self._trace_log = tf.name
156    tf.close()
157    del config # unused
158    del timeout # unused
159    assert not trace_event.trace_is_enabled(), 'Tracing already running.'
160    trace_event.trace_enable(self._trace_log)
161    assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.'
162    return True
163
164  def StopAgentTracing(self, trace_data_builder):
165    if not self._is_tracing_controllable:
166      return
167    assert trace_event.trace_is_enabled(), 'Tracing not running'
168    trace_event.trace_disable()
169    assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.'
170    with open(self._trace_log, 'r') as fp:
171      data = ast.literal_eval(fp.read() + ']')
172    trace_data_builder.AddEventsTo(trace_data_module.TELEMETRY_PART, data)
173    try:
174      os.remove(self._trace_log)
175      self._trace_log = None
176    except OSError:
177      logging.exception('Error when deleting %s, will try again at exit.',
178                        self._trace_log)
179      def DeleteAtExit(path):
180        os.remove(path)
181      atexit.register(DeleteAtExit, self._trace_log)
182    self._trace_log = None
183
184  def SupportsExplicitClockSync(self):
185    return True
186
187  def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts):
188    """ Record clock sync event.
189
190    Args:
191      sync_id: Unqiue id for sync event.
192      issue_ts: timestamp before issuing clocksync to agent.
193    """
194    if self._is_tracing_controllable:
195      trace_event.clock_sync(sync_id, issue_ts=issue_ts)
196
197  def _IssueClockSyncMarker(self):
198    with self._DisableGarbageCollection():
199      for agent in self._active_agents_instances:
200        if agent.SupportsExplicitClockSync():
201          sync_id = self._GenerateClockSyncId()
202          agent.RecordClockSyncMarker(sync_id,
203                                      self._RecordIssuerClockSyncMarker)
204
205  def IsChromeTracingSupported(self):
206    return chrome_tracing_agent.ChromeTracingAgent.IsSupported(
207        self._platform_backend)
208
209  @property
210  def is_tracing_running(self):
211    return self._current_state is not None
212
213  def _GetActiveChromeTracingAgent(self):
214    if not self.is_tracing_running:
215      return None
216    if not self._current_state.config.enable_chrome_trace:
217      return None
218    for agent in self._active_agents_instances:
219      if isinstance(agent, chrome_tracing_agent.ChromeTracingAgent):
220        return agent
221    return None
222
223  def GetChromeTraceConfig(self):
224    agent = self._GetActiveChromeTracingAgent()
225    if agent:
226      return agent.trace_config
227    return None
228
229  def GetChromeTraceConfigFile(self):
230    agent = self._GetActiveChromeTracingAgent()
231    if agent:
232      return agent.trace_config_file
233    return None
234
235  def _IsTracingControllable(self):
236    return trace_event.is_tracing_controllable()
237
238  def ClearStateIfNeeded(self):
239    chrome_tracing_agent.ClearStarupTracingStateIfNeeded(self._platform_backend)
240