blob: 7c4e62e6de404d58276a2a4beefdd6f0f95265c7 [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 contextlib
import gc
import logging
import os
import sys
import traceback
import uuid
from py_trace_event import trace_event
from telemetry.internal.platform.tracing_agent import atrace_tracing_agent
from telemetry.internal.platform.tracing_agent import chrome_report_events_tracing_agent
from telemetry.internal.platform.tracing_agent import chrome_return_as_stream_tracing_agent
from telemetry.internal.platform.tracing_agent import chrome_tracing_agent
from telemetry.internal.platform.tracing_agent import cpu_tracing_agent
from telemetry.internal.platform.tracing_agent import display_tracing_agent
from telemetry.internal.platform.tracing_agent import perfetto_tracing_agent
from telemetry.internal.platform.tracing_agent import telemetry_tracing_agent
from telemetry.timeline import tracing_config
from tracing.trace_data import trace_data
# Note: TelemetryTracingAgent should be first so that we can record debug
# trace events when the other agents start/stop.
_TRACING_AGENT_CLASSES = (
telemetry_tracing_agent.TelemetryTracingAgent,
chrome_report_events_tracing_agent.ChromeReportEventsTracingAgent,
chrome_return_as_stream_tracing_agent.ChromeReturnAsStreamTracingAgent,
atrace_tracing_agent.AtraceTracingAgent,
cpu_tracing_agent.CpuTracingAgent,
display_tracing_agent.DisplayTracingAgent
)
_EXPERIMENTAL_TRACING_AGENTS = (
telemetry_tracing_agent.TelemetryTracingAgent,
perfetto_tracing_agent.PerfettoTracingAgent
)
def _GenerateClockSyncId():
return str(uuid.uuid4())
@contextlib.contextmanager
def _DisableGarbageCollection():
try:
gc.disable()
yield
finally:
gc.enable()
class _TraceDataDiscarder(object):
"""A do-nothing data builder that just discards trace data.
TODO(crbug.com/928278): This should be moved as a "discarding mode" in
TraceDataBuilder itself.
"""
def OpenTraceHandleFor(self, part, suffix):
del part, suffix # Unused.
return open(os.devnull, 'wb')
def AddTraceFor(self, part, data, allow_unstructured=False):
assert not allow_unstructured
del part # Unused.
del data # Unused.
def RecordTraceDataException(self):
logging.info('Ignoring exception while flushing to TraceDataDiscarder:\n%s',
''.join(traceback.format_exception(*sys.exc_info())))
class _TracingState(object):
def __init__(self, config, timeout):
self._builder = trace_data.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._active_agents_instances = []
self._is_tracing_controllable = True
def RecordBenchmarkMetadata(self, results):
"""Write benchmark metadata into the trace being currently recorded."""
telemetry_tracing_agent.RecordBenchmarkMetadata(results)
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)
if config.enable_experimental_system_tracing:
agent_classes = _EXPERIMENTAL_TRACING_AGENTS
else:
agent_classes = _TRACING_AGENT_CLASSES
for agent_class in agent_classes:
if agent_class.IsSupported(self._platform_backend):
agent = agent_class(self._platform_backend, config)
if agent.StartAgentTracing(config, timeout):
self._active_agents_instances.append(agent)
return True
def StopTracing(self):
assert self.is_tracing_running, 'Can only stop tracing when tracing is on.'
self._IssueClockSyncMarker()
builder = self._current_state.builder
for agent in reversed(self._active_agents_instances):
try:
agent.StopAgentTracing()
except Exception: # pylint: disable=broad-except
builder.RecordTraceDataException()
for agent in self._active_agents_instances:
try:
agent.CollectAgentTraceData(builder)
except Exception: # pylint: disable=broad-except
builder.RecordTraceDataException()
self._active_agents_instances = []
self._current_state = None
return builder.Freeze()
def FlushTracing(self, discard_current=False):
assert self.is_tracing_running, 'Can only flush tracing when tracing is on.'
self._IssueClockSyncMarker()
# pylint: disable=redefined-variable-type
# See: https://github.com/PyCQA/pylint/issues/710
if discard_current:
trace_builder = _TraceDataDiscarder()
else:
trace_builder = self._current_state.builder
for agent in self._active_agents_instances:
try:
if agent.SupportsFlushingAgentTracing():
agent.FlushAgentTracing(self._current_state.config,
self._current_state.timeout,
trace_builder)
except Exception: # pylint: disable=broad-except
trace_builder.RecordTraceDataException()
def _IssueClockSyncMarker(self):
if not telemetry_tracing_agent.IsAgentEnabled():
return
with _DisableGarbageCollection():
for agent in self._active_agents_instances:
if agent.SupportsExplicitClockSync():
sync_id = _GenerateClockSyncId()
with trace_event.trace('RecordClockSyncMarker',
agent=str(agent.__class__.__name__),
sync_id=sync_id):
agent.RecordClockSyncMarker(
sync_id, telemetry_tracing_agent.RecordIssuerClockSyncMarker)
@property
def is_tracing_running(self):
return self._current_state is not None
@property
def is_chrome_tracing_running(self):
return self._GetActiveChromeTracingAgent() is not None
@property
def current_state(self):
return self._current_state
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 ClearStateIfNeeded(self):
chrome_return_as_stream_tracing_agent.ClearStarupTracingStateIfNeeded(
self._platform_backend)