#!/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):
|
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
|
|
|
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)
|
|
def GetChromeStartupControllerConfig(options):
|
return TracingControllerConfig(None, options.trace_time,
|
options.write_json, None, None, None, None,
|
None, None)
|