blob: 31365b074ae5536d7c1bbb5918c517d79ee93061 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import ast
from telemetry.internal.util import atexit_with_log
import contextlib
import gc
import logging
import os
import sys
import tempfile
import traceback
import uuid
from py_trace_event import trace_event
from telemetry.core import discover
from telemetry.core import exceptions
from telemetry.core import util
from telemetry.internal.platform import tracing_agent
from telemetry.internal.platform.tracing_agent import chrome_tracing_agent
from telemetry.timeline import tracing_config
from tracing.trace_data import trace_data as trace_data_module
def _IterAllTracingAgentClasses():
tracing_agent_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'tracing_agent')
return discover.DiscoverClasses(
tracing_agent_dir, util.GetTelemetryDir(),
tracing_agent.TracingAgent).itervalues()
class _TracingState(object):
def __init__(self, config, timeout):
self._builder = trace_data_module.TraceDataBuilder()
self._config = config
self._timeout = timeout
@property
def builder(self):
return self._builder
@property
def config(self):
return self._config
@property
def timeout(self):
return self._timeout
class TracingControllerBackend(object):
def __init__(self, platform_backend):
self._platform_backend = platform_backend
self._current_state = None
self._supported_agents_classes = [
agent_classes for agent_classes in _IterAllTracingAgentClasses() if
agent_classes.IsSupported(platform_backend)]
self._active_agents_instances = []
self._trace_log = None
self._is_tracing_controllable = True
self._telemetry_info = None
def StartTracing(self, config, timeout):
if self.is_tracing_running:
return False
assert isinstance(config, tracing_config.TracingConfig)
assert len(self._active_agents_instances) == 0
self._current_state = _TracingState(config, timeout)
# Hack: chrome tracing agent may only depend on the number of alive chrome
# devtools processes, rather platform (when startup tracing is not
# supported), hence we add it to the list of supported agents here if it was
# not added.
if (chrome_tracing_agent.ChromeTracingAgent.IsSupported(
self._platform_backend) and
not chrome_tracing_agent.ChromeTracingAgent in
self._supported_agents_classes):
self._supported_agents_classes.append(
chrome_tracing_agent.ChromeTracingAgent)
self.StartAgentTracing(config, timeout)
for agent_class in self._supported_agents_classes:
agent = agent_class(self._platform_backend)
with trace_event.trace(
'StartAgentTracing',
agent=str(agent.__class__.__name__)):
started = agent.StartAgentTracing(config, timeout)
if started:
self._active_agents_instances.append(agent)
return True
def _GenerateClockSyncId(self):
return str(uuid.uuid4())
@contextlib.contextmanager
def _DisableGarbageCollection(self):
try:
gc.disable()
yield
finally:
gc.enable()
def StopTracing(self):
assert self.is_tracing_running, 'Can only stop tracing when tracing is on.'
self._IssueClockSyncMarker()
builder = self._current_state.builder
raised_exception_messages = []
for agent in self._active_agents_instances + [self]:
try:
with trace_event.trace(
'StopAgentTracing',
agent=str(agent.__class__.__name__)):
agent.StopAgentTracing()
except Exception: # pylint: disable=broad-except
raised_exception_messages.append(
''.join(traceback.format_exception(*sys.exc_info())))
for agent in self._active_agents_instances + [self]:
try:
with trace_event.trace(
'CollectAgentTraceData',
agent=str(agent.__class__.__name__)):
agent.CollectAgentTraceData(builder)
except Exception: # pylint: disable=broad-except
raised_exception_messages.append(
''.join(traceback.format_exception(*sys.exc_info())))
self._telemetry_info = None
self._active_agents_instances = []
self._current_state = None
if raised_exception_messages:
raise exceptions.Error(
'Exceptions raised when trying to stop tracing:\n' +
'\n'.join(raised_exception_messages))
return builder.AsData()
def FlushTracing(self):
assert self.is_tracing_running, 'Can only flush tracing when tracing is on.'
self._IssueClockSyncMarker()
raised_exception_messages = []
# Flushing the controller's pytrace is not supported.
for agent in self._active_agents_instances:
try:
if agent.SupportsFlushingAgentTracing():
with trace_event.trace(
'FlushAgentTracing',
agent=str(agent.__class__.__name__)):
agent.FlushAgentTracing(self._current_state.config,
self._current_state.timeout,
self._current_state.builder)
except Exception: # pylint: disable=broad-except
raised_exception_messages.append(
''.join(traceback.format_exception(*sys.exc_info())))
if raised_exception_messages:
raise exceptions.Error(
'Exceptions raised when trying to flush tracing:\n' +
'\n'.join(raised_exception_messages))
def StartAgentTracing(self, config, timeout):
self._is_tracing_controllable = self._IsTracingControllable()
if not self._is_tracing_controllable:
return False
tf = tempfile.NamedTemporaryFile(delete=False)
self._trace_log = tf.name
tf.close()
del config # unused
del timeout # unused
assert not trace_event.trace_is_enabled(), 'Tracing already running.'
trace_event.trace_enable(self._trace_log)
assert trace_event.trace_is_enabled(), 'Tracing didn\'t enable properly.'
return True
def StopAgentTracing(self):
if not self._is_tracing_controllable:
return
assert trace_event.trace_is_enabled(), 'Tracing not running'
trace_event.trace_disable()
assert not trace_event.trace_is_enabled(), 'Tracing didnt disable properly.'
def SupportsExplicitClockSync(self):
return True
def _RecordIssuerClockSyncMarker(self, sync_id, issue_ts):
""" Record clock sync event.
Args:
sync_id: Unqiue id for sync event.
issue_ts: timestamp before issuing clocksync to agent.
"""
if self._is_tracing_controllable:
trace_event.clock_sync(sync_id, issue_ts=issue_ts)
def _IssueClockSyncMarker(self):
with self._DisableGarbageCollection():
for agent in self._active_agents_instances:
if agent.SupportsExplicitClockSync():
sync_id = self._GenerateClockSyncId()
with trace_event.trace(
'RecordClockSyncMarker',
agent=str(agent.__class__.__name__),
sync_id=sync_id):
agent.RecordClockSyncMarker(sync_id,
self._RecordIssuerClockSyncMarker)
def IsChromeTracingSupported(self):
return chrome_tracing_agent.ChromeTracingAgent.IsSupported(
self._platform_backend)
@property
def is_tracing_running(self):
return self._current_state is not None
def _GetActiveChromeTracingAgent(self):
if not self.is_tracing_running:
return None
if not self._current_state.config.enable_chrome_trace:
return None
for agent in self._active_agents_instances:
if isinstance(agent, chrome_tracing_agent.ChromeTracingAgent):
return agent
return None
def GetChromeTraceConfig(self):
agent = self._GetActiveChromeTracingAgent()
if agent:
return agent.trace_config
return None
def GetChromeTraceConfigFile(self):
agent = self._GetActiveChromeTracingAgent()
if agent:
return agent.trace_config_file
return None
def _IsTracingControllable(self):
return trace_event.is_tracing_controllable()
def ClearStateIfNeeded(self):
chrome_tracing_agent.ClearStarupTracingStateIfNeeded(self._platform_backend)
@property
def telemetry_info(self):
return self._telemetry_info
@telemetry_info.setter
def telemetry_info(self, ii):
self._telemetry_info = ii
def CollectAgentTraceData(self, trace_data_builder):
if not self._is_tracing_controllable:
return
assert not trace_event.trace_is_enabled(), 'Stop tracing before collection.'
with open(self._trace_log, 'r') as fp:
data = ast.literal_eval(fp.read() + ']')
trace_data_builder.AddTraceFor(trace_data_module.TELEMETRY_PART, {
"traceEvents": data,
"metadata": {
# TODO(charliea): For right now, we use "TELEMETRY" as the clock
# domain to guarantee that Telemetry is given its own clock
# domain. Telemetry isn't really a clock domain, though: it's a
# system that USES a clock domain like LINUX_CLOCK_MONOTONIC or
# WIN_QPC. However, there's a chance that a Telemetry controller
# running on Linux (using LINUX_CLOCK_MONOTONIC) is interacting with
# an Android phone (also using LINUX_CLOCK_MONOTONIC, but on a
# different machine). The current logic collapses clock domains
# based solely on the clock domain string, but we really should to
# collapse based on some (device ID, clock domain ID) tuple. Giving
# Telemetry its own clock domain is a work-around for this.
"clock-domain": "TELEMETRY",
"telemetry": (self._telemetry_info.AsDict()
if self._telemetry_info else {}),
}
})
try:
os.remove(self._trace_log)
self._trace_log = None
except OSError:
logging.exception('Error when deleting %s, will try again at exit.',
self._trace_log)
def DeleteAtExit(path):
os.remove(path)
atexit_with_log.Register(DeleteAtExit, self._trace_log)
self._trace_log = None