• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
6from telemetry.internal.util import atexit_with_log
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    self._iteration_info = None
68
69  def StartTracing(self, config, timeout):
70    if self.is_tracing_running:
71      return False
72
73    assert isinstance(config, tracing_config.TracingConfig)
74    assert len(self._active_agents_instances) == 0
75
76    self._current_state = _TracingState(config, timeout)
77    # Hack: chrome tracing agent may only depend on the number of alive chrome
78    # devtools processes, rather platform (when startup tracing is not
79    # supported), hence we add it to the list of supported agents here if it was
80    # not added.
81    if (chrome_tracing_agent.ChromeTracingAgent.IsSupported(
82        self._platform_backend) and
83        not chrome_tracing_agent.ChromeTracingAgent in
84        self._supported_agents_classes):
85      self._supported_agents_classes.append(
86          chrome_tracing_agent.ChromeTracingAgent)
87
88    self.StartAgentTracing(config, timeout)
89    for agent_class in self._supported_agents_classes:
90      agent = agent_class(self._platform_backend)
91      if agent.StartAgentTracing(config, timeout):
92        self._active_agents_instances.append(agent)
93    return True
94
95  def _GenerateClockSyncId(self):
96    return str(uuid.uuid4())
97
98  @contextlib.contextmanager
99  def _DisableGarbageCollection(self):
100    try:
101      gc.disable()
102      yield
103    finally:
104      gc.enable()
105
106  def StopTracing(self):
107    assert self.is_tracing_running, 'Can only stop tracing when tracing is on.'
108    self._IssueClockSyncMarker()
109    builder = self._current_state.builder
110
111    raised_exception_messages = []
112    for agent in self._active_agents_instances + [self]:
113      try:
114        agent.StopAgentTracing()
115      except Exception: # pylint: disable=broad-except
116        raised_exception_messages.append(
117            ''.join(traceback.format_exception(*sys.exc_info())))
118
119    for agent in self._active_agents_instances + [self]:
120      try:
121        agent.CollectAgentTraceData(builder)
122      except Exception: # pylint: disable=broad-except
123        raised_exception_messages.append(
124            ''.join(traceback.format_exception(*sys.exc_info())))
125
126    self._iteration_info = None
127    self._active_agents_instances = []
128    self._current_state = None
129
130    if raised_exception_messages:
131      raise TracingControllerStoppedError(
132          'Exceptions raised when trying to stop tracing:\n' +
133          '\n'.join(raised_exception_messages))
134
135    return builder.AsData()
136
137  def FlushTracing(self):
138    assert self.is_tracing_running, 'Can only flush tracing when tracing is on.'
139    self._IssueClockSyncMarker()
140
141    raised_exception_messages = []
142    # Flushing the controller's pytrace is not supported.
143    for agent in self._active_agents_instances:
144      try:
145        if agent.SupportsFlushingAgentTracing():
146          agent.FlushAgentTracing(self._current_state.config,
147                                  self._current_state.timeout,
148                                  self._current_state.builder)
149      except Exception: # pylint: disable=broad-except
150        raised_exception_messages.append(
151            ''.join(traceback.format_exception(*sys.exc_info())))
152
153    if raised_exception_messages:
154      raise TracingControllerStoppedError(
155          'Exceptions raised when trying to flush tracing:\n' +
156          '\n'.join(raised_exception_messages))
157
158  def StartAgentTracing(self, config, timeout):
159    self._is_tracing_controllable = self._IsTracingControllable()
160    if not self._is_tracing_controllable:
161      return False
162
163    tf = tempfile.NamedTemporaryFile(delete=False)
164    self._trace_log = tf.name
165    tf.close()
166    del config # unused
167    del timeout # unused
168    assert not trace_event.trace_is_enabled(), 'Tracing already running.'
169    trace_event.trace_enable(self._trace_log)
170    assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.'
171    return True
172
173  def StopAgentTracing(self):
174    if not self._is_tracing_controllable:
175      return
176    assert trace_event.trace_is_enabled(), 'Tracing not running'
177    trace_event.trace_disable()
178    assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.'
179
180  def SupportsExplicitClockSync(self):
181    return True
182
183  def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts):
184    """ Record clock sync event.
185
186    Args:
187      sync_id: Unqiue id for sync event.
188      issue_ts: timestamp before issuing clocksync to agent.
189    """
190    if self._is_tracing_controllable:
191      trace_event.clock_sync(sync_id, issue_ts=issue_ts)
192
193  def _IssueClockSyncMarker(self):
194    with self._DisableGarbageCollection():
195      for agent in self._active_agents_instances:
196        if agent.SupportsExplicitClockSync():
197          sync_id = self._GenerateClockSyncId()
198          with trace_event.trace(
199              'RecordClockSyncMarker',
200              agent=str(agent.__class__.__name__),
201              sync_id=sync_id):
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
241  @property
242  def iteration_info(self):
243    return self._iteration_info
244
245  @iteration_info.setter
246  def iteration_info(self, ii):
247    self._iteration_info = ii
248
249  def CollectAgentTraceData(self, trace_data_builder):
250    if not self._is_tracing_controllable:
251      return
252    assert not trace_event.trace_is_enabled(), 'Stop tracing before collection.'
253    with open(self._trace_log, 'r') as fp:
254      data = ast.literal_eval(fp.read() + ']')
255    trace_data_builder.SetTraceFor(trace_data_module.TELEMETRY_PART, {
256        "traceEvents": data,
257        "metadata": {
258            # TODO(charliea): For right now, we use "TELEMETRY" as the clock
259            # domain to guarantee that Telemetry is given its own clock
260            # domain. Telemetry isn't really a clock domain, though: it's a
261            # system that USES a clock domain like LINUX_CLOCK_MONOTONIC or
262            # WIN_QPC. However, there's a chance that a Telemetry controller
263            # running on Linux (using LINUX_CLOCK_MONOTONIC) is interacting with
264            # an Android phone (also using LINUX_CLOCK_MONOTONIC, but on a
265            # different machine). The current logic collapses clock domains
266            # based solely on the clock domain string, but we really should to
267            # collapse based on some (device ID, clock domain ID) tuple. Giving
268            # Telemetry its own clock domain is a work-around for this.
269            "clock-domain": "TELEMETRY",
270            "iteration-info": (self._iteration_info.AsDict()
271                if self._iteration_info else {}),
272        }
273    })
274    try:
275      os.remove(self._trace_log)
276      self._trace_log = None
277    except OSError:
278      logging.exception('Error when deleting %s, will try again at exit.',
279                        self._trace_log)
280      def DeleteAtExit(path):
281        os.remove(path)
282      atexit_with_log.Register(DeleteAtExit, self._trace_log)
283    self._trace_log = None
284