blob: 86f13104d94fc91c54b01de06e3d5cdc06d42c51 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2016 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.
'''Tracing controller class. This class manages
multiple tracing agents and collects data from all of them. It also
manages the clock sync process.
'''
import ast
import json
import sys
import tempfile
import uuid
import py_utils
from systrace import trace_result
from systrace import tracing_agents
from py_trace_event import trace_event
TRACE_DATA_CONTROLLER_NAME = 'systraceController'
def ControllerAgentClockSync(issue_ts, name):
"""Record the clock sync marker for controller tracing agent.
Unlike with the other tracing agents, the tracing controller should not
call this directly. Rather, it is called via callback from the other
tracing agents when they write a trace.
"""
trace_event.clock_sync(name, issue_ts=issue_ts)
class TracingControllerAgent(tracing_agents.TracingAgent):
def __init__(self):
super(TracingControllerAgent, self).__init__()
self._log_path = None
@py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
def StartAgentTracing(self, config, timeout=None):
"""Start tracing for the controller tracing agent.
Start tracing for the controller tracing agent. Note that
the tracing controller records the "controller side"
of the clock sync records, and nothing else.
"""
del config
if not trace_event.trace_can_enable():
raise RuntimeError, ('Cannot enable trace_event;'
' ensure py_utils is in PYTHONPATH')
controller_log_file = tempfile.NamedTemporaryFile(delete=False)
self._log_path = controller_log_file.name
controller_log_file.close()
trace_event.trace_enable(self._log_path)
return True
@py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
def StopAgentTracing(self, timeout=None):
"""Stops tracing for the controller tracing agent.
"""
# pylint: disable=no-self-use
# This function doesn't use self, but making it a member function
# for consistency with the other TracingAgents
trace_event.trace_disable()
return True
@py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
def GetResults(self, timeout=None):
"""Gets the log output from the controller tracing agent.
This output only contains the "controller side" of the clock sync records.
"""
with open(self._log_path, 'r') as outfile:
data = ast.literal_eval(outfile.read() + ']')
# Explicitly set its own clock domain. This will stop the Systrace clock
# domain from incorrectly being collapsed into the on device clock domain.
formatted_data = {
'traceEvents': data,
'metadata': {
'clock-domain': 'SYSTRACE',
}
}
return trace_result.TraceResult(TRACE_DATA_CONTROLLER_NAME,
json.dumps(formatted_data))
def SupportsExplicitClockSync(self):
"""Returns whether this supports explicit clock sync.
Although the tracing controller conceptually supports explicit clock
sync, it is not an agent controlled by other controllers so it does not
define RecordClockSyncMarker (rather, the recording of the "controller
side" of the clock sync marker is done in _IssueClockSyncMarker). Thus,
SupportsExplicitClockSync must return false.
"""
return False
# pylint: disable=unused-argument
def RecordClockSyncMarker(self, sync_id, callback):
raise NotImplementedError
class TracingController(object):
def __init__(self, agents_with_config, controller_config):
"""Create tracing controller.
Create a tracing controller object. Note that the tracing
controller is also a tracing agent.
Args:
agents_with_config: List of tracing agents for this controller with the
corresponding tracing configuration objects.
controller_config: Configuration options for the tracing controller.
"""
self._child_agents = None
self._child_agents_with_config = agents_with_config
self._controller_agent = TracingControllerAgent()
self._controller_config = controller_config
self._trace_in_progress = False
self.all_results = None
@property
def get_child_agents(self):
return self._child_agents
def StartTracing(self):
"""Start tracing for all tracing agents.
This function starts tracing for both the controller tracing agent
and the child tracing agents.
Returns:
Boolean indicating whether or not the start tracing succeeded.
Start tracing is considered successful if at least the
controller tracing agent was started.
"""
assert not self._trace_in_progress, 'Trace already in progress.'
self._trace_in_progress = True
# Start the controller tracing agents. Controller tracing agent
# must be started successfully to proceed.
if not self._controller_agent.StartAgentTracing(
self._controller_config,
timeout=self._controller_config.timeout):
print 'Unable to start controller tracing agent.'
return False
# Start the child tracing agents.
succ_agents = []
for agent_and_config in self._child_agents_with_config:
agent = agent_and_config.agent
config = agent_and_config.config
if agent.StartAgentTracing(config,
timeout=self._controller_config.timeout):
succ_agents.append(agent)
else:
print 'Agent %s not started.' % str(agent)
# Print warning if all agents not started.
na = len(self._child_agents_with_config)
ns = len(succ_agents)
if ns < na:
print 'Warning: Only %d of %d tracing agents started.' % (ns, na)
self._child_agents = succ_agents
return True
def StopTracing(self):
"""Issue clock sync marker and stop tracing for all tracing agents.
This function stops both the controller tracing agent
and the child tracing agents. It issues a clock sync marker prior
to stopping tracing.
Returns:
Boolean indicating whether or not the stop tracing succeeded
for all agents.
"""
assert self._trace_in_progress, 'No trace in progress.'
self._trace_in_progress = False
# Issue the clock sync marker and stop the child tracing agents.
self._IssueClockSyncMarker()
succ_agents = []
for agent in self._child_agents:
if agent.StopAgentTracing(timeout=self._controller_config.timeout):
succ_agents.append(agent)
else:
print 'Agent %s not stopped.' % str(agent)
# Stop the controller tracing agent. Controller tracing agent
# must be stopped successfully to proceed.
if not self._controller_agent.StopAgentTracing(
timeout=self._controller_config.timeout):
print 'Unable to stop controller tracing agent.'
return False
# Print warning if all agents not stopped.
na = len(self._child_agents)
ns = len(succ_agents)
if ns < na:
print 'Warning: Only %d of %d tracing agents stopped.' % (ns, na)
self._child_agents = succ_agents
# Collect the results from all the stopped tracing agents.
all_results = []
for agent in self._child_agents + [self._controller_agent]:
try:
result = agent.GetResults(
timeout=self._controller_config.collection_timeout)
if not result:
print 'Warning: Timeout when getting results from %s.' % str(agent)
continue
if result.source_name in [r.source_name for r in all_results]:
print ('Warning: Duplicate tracing agents named %s.' %
result.source_name)
all_results.append(result)
# Check for exceptions. If any exceptions are seen, reraise and abort.
# Note that a timeout exception will be swalloed by the timeout
# mechanism and will not get to that point (it will return False instead
# of the trace result, which will be dealt with above)
except:
print 'Warning: Exception getting results from %s:' % str(agent)
print sys.exc_info()[0]
raise
self.all_results = all_results
return all_results
def GetTraceType(self):
"""Return a string representing the child agents that are being traced."""
sorted_agents = sorted(map(str, self._child_agents))
return ' + '.join(sorted_agents)
def _IssueClockSyncMarker(self):
"""Issue clock sync markers to all the child tracing agents."""
for agent in self._child_agents:
if agent.SupportsExplicitClockSync():
sync_id = GetUniqueSyncID()
agent.RecordClockSyncMarker(sync_id, ControllerAgentClockSync)
def GetUniqueSyncID():
"""Get a unique sync ID.
Gets a unique sync ID by generating a UUID and converting it to a string
(since UUIDs are not JSON serializable)
"""
return str(uuid.uuid4())
class AgentWithConfig(object):
def __init__(self, agent, config):
self.agent = agent
self.config = config
def CreateAgentsWithConfig(options, modules):
"""Create tracing agents.
This function will determine which tracing agents are valid given the
options and create those agents along with their corresponding configuration
object.
Args:
options: The command-line options.
modules: The modules for either Systrace or profile_chrome.
TODO(washingtonp): After all profile_chrome agents are in
Systrace, this parameter will no longer be valid.
Returns:
A list of AgentWithConfig options containing agents and their corresponding
configuration object.
"""
result = []
for module in modules:
config = module.get_config(options)
agent = module.try_create_agent(config)
if agent and config:
result.append(AgentWithConfig(agent, config))
return [x for x in result if x and x.agent]
class TracingControllerConfig(tracing_agents.TracingConfig):
def __init__(self, output_file, trace_time, write_json,
link_assets, asset_dir, timeout, collection_timeout,
device_serial_number, target, trace_buf_size):
tracing_agents.TracingConfig.__init__(self)
self.output_file = output_file
self.trace_time = trace_time
self.write_json = write_json
self.link_assets = link_assets
self.asset_dir = asset_dir
self.timeout = timeout
self.collection_timeout = collection_timeout
self.device_serial_number = device_serial_number
self.target = target
self.trace_buf_size = trace_buf_size
def GetControllerConfig(options):
return TracingControllerConfig(options.output_file, options.trace_time,
options.write_json,
options.link_assets, options.asset_dir,
options.timeout, options.collection_timeout,
options.device_serial_number, options.target,
options.trace_buf_size)
def GetChromeStartupControllerConfig(options):
return TracingControllerConfig(None, options.trace_time,
options.write_json, None, None, None, None,
None, None, options.trace_buf_size)