blob: 4765e73ff139c204bc2226283e1c6b2c392c58fb [file] [log] [blame]
# 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.
import os
import re
import stat
import subprocess
import sys
import urllib2
import py_utils
from systrace import trace_result
from systrace import tracing_agents
from systrace.tracing_agents import atrace_agent
# ADB sends this text to indicate the beginning of the trace data.
TRACE_START_REGEXP = r'TRACE\:'
# Text that ADB sends, but does not need to be displayed to the user.
ADB_IGNORE_REGEXP = r'^capturing trace\.\.\. done|^capturing trace\.\.\.'
T2T_OUTPUT = 'trace.systrace'
def try_create_agent(options):
if options.from_file is not None:
with open(options.from_file, 'rb') as f_in:
if is_perfetto(f_in):
if convert_perfetto_trace(options.from_file):
options.from_file = T2T_OUTPUT
else:
print ('Perfetto trace file: ' + options.from_file +
' could not be converted.')
sys.exit(1)
return AtraceFromFileAgent(options)
else:
return False
def convert_perfetto_trace(in_file):
traceconv_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
'../traceconv'))
try:
traceconv = urllib2.urlopen('https://get.perfetto.dev/traceconv')
with open(traceconv_path, 'w') as out:
out.write(traceconv.read())
except urllib2.URLError:
print 'Could not download traceconv to convert the Perfetto trace.'
sys.exit(1)
os.chmod(traceconv_path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
return subprocess.call([traceconv_path, 'systrace', in_file, T2T_OUTPUT]) == 0
def is_perfetto(from_file):
# Starts with a preamble for field ID=1 (TracePacket)
if ord(from_file.read(1)) != 0x0a:
return False
for _ in range(10): # Check the first 10 packets are structured correctly
# Then a var int that specifies field size
field_size = 0
shift = 0
while True:
c = ord(from_file.read(1))
field_size |= (c & 0x7f) << shift
shift += 7
if not c & 0x80:
break
# The packet itself
from_file.seek(field_size, os.SEEK_CUR)
# The preamble for the next field ID=1 (TracePacket)
if ord(from_file.read(1)) != 0x0a:
return False
# Go back to the beginning of the file
from_file.seek(0)
return True
class AtraceFromFileConfig(tracing_agents.TracingConfig):
def __init__(self, from_file):
tracing_agents.TracingConfig.__init__(self)
self.fix_circular = True
self.from_file = from_file
def add_options(parser): # pylint: disable=unused-argument
# The atrace_from_file_agent is not currently used, so don't display
# any options.
return None
def get_config(options):
return AtraceFromFileConfig(options.from_file)
class AtraceFromFileAgent(tracing_agents.TracingAgent):
def __init__(self, options):
super(AtraceFromFileAgent, self).__init__()
self._filename = os.path.expanduser(options.from_file)
self._trace_data = False
@py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
def StartAgentTracing(self, config, timeout=None):
# pylint: disable=unused-argument
return True
@py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
def StopAgentTracing(self, timeout=None):
self._trace_data = self._read_trace_data()
return True
def SupportsExplicitClockSync(self):
return False
def RecordClockSyncMarker(self, sync_id, did_record_clock_sync_callback):
raise NotImplementedError
@py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
def GetResults(self, timeout=None):
return trace_result.TraceResult('trace-data', self._trace_data)
def _read_trace_data(self):
with open(self._filename, 'rb') as f:
result = f.read()
data_start = re.search(TRACE_START_REGEXP, result).end(0)
data = re.sub(ADB_IGNORE_REGEXP, '', result[data_start:])
return self._preprocess_data(data)
# pylint: disable=no-self-use
def _preprocess_data(self, data):
# TODO: add fix_threads and fix_tgids options back in here
# once we embed the dump data in the file (b/27504068)
data = atrace_agent.strip_and_decompress_trace(data)
data = atrace_agent.fix_circular_traces(data)
return data