blob: c80530a6f54ebdf8242fdb3030fcf4f6b67ccbf5 [file] [log] [blame]
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Symbolizes a perfetto trace file."""
import logging
import os
import shutil
import subprocess
import sys
import tempfile
sys.path.insert(
0,
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, 'third_party',
'catapult', 'systrace'))
import breakpad_file_extractor
import flag_utils
import metadata_extractor
import rename_breakpad
import symbol_fetcher
def SymbolizeTrace(trace_file, options):
"""Symbolizes a perfetto trace file.
Args:
trace_file: path to proto trace file to symbolize.
options: The options set by the command line args.
Raises:
Exception: if breakpad_output_dir is not empty or if path to local breakpad
directory is invalid.
"""
print('Symbolizing file')
need_cleanup = False
if options.local_breakpad_dir is None:
breakpad_file_extractor.EnsureDumpSymsBinary(options.dump_syms_path,
options.local_build_dir)
# Ensure valid |breakpad_output_dir|
if options.breakpad_output_dir is None:
# Create a temp dir if output dir is not provided.
# Temp dir must be cleaned up later.
options.breakpad_output_dir = tempfile.mkdtemp()
need_cleanup = True
flag_utils.GetTracingLogger().warning(
'Created temporary directory to hold symbol files. These symbols '
'will be cleaned up after symbolization. Please specify '
'--breakpad_output_dir=<cached_dir> to save the symbols, if you need '
'to profile multiple times. The future runs need to use '
'--local_breakpad_dir=<cached_dir> flag so the symbolizer uses the '
'cache.')
else:
if os.path.isdir(options.breakpad_output_dir):
if os.listdir(options.breakpad_output_dir):
raise Exception('Breakpad output directory is not empty: ' +
options.breakpad_output_dir)
else:
os.makedirs(options.breakpad_output_dir)
flag_utils.GetTracingLogger().debug(
'Created directory to hold symbol files.')
else:
if not os.path.isdir(options.local_breakpad_dir):
raise Exception('Local breakpad directory is not valid.')
options.breakpad_output_dir = options.local_breakpad_dir
_EnsureBreakpadSymbols(trace_file, options)
# Set output file to write trace data and symbols to.
if options.output_file is None:
options.output_file = os.path.join(
os.path.dirname(trace_file),
os.path.basename(trace_file) + '_symbolized_trace')
_Symbolize(trace_file, options.symbolizer_path, options.breakpad_output_dir,
options.output_file)
print('Symbolized trace saved to: ' + os.path.abspath(options.output_file))
# Cleanup
if need_cleanup:
flag_utils.GetTracingLogger().debug('Cleaning up symbol files.')
shutil.rmtree(options.breakpad_output_dir)
def _EnsureBreakpadSymbols(trace_file, options):
"""Ensures that there are breakpad symbols to symbolize with.
Args:
trace_file: The trace file to be symbolized.
options: The options set by the command line args. This is used to check if
symbols need to be fetched, extracted, or if they are already present.
Raises:
Exception: if no breakpad files could be extracted.
"""
# If |options.local_breakpad_dir| is not None, then this can be skipped and
# |trace_file| can be symbolized using those symbols.
if options.local_breakpad_dir is not None:
return
# Extract Metadata
flag_utils.GetTracingLogger().info('Extracting proto trace metadata.')
trace_metadata = metadata_extractor.MetadataExtractor(
options.trace_processor_path, trace_file)
trace_metadata.Initialize()
flag_utils.GetTracingLogger().info(trace_metadata)
if options.local_build_dir is not None:
# Extract breakpad symbol files from binaries in |options.local_build_dir|.
if not breakpad_file_extractor.ExtractBreakpadFiles(
options.dump_syms_path,
options.local_build_dir,
options.breakpad_output_dir,
search_unstripped=True,
module_ids=breakpad_file_extractor.GetModuleIdsToSymbolize(
trace_metadata)):
raise Exception(
'No breakpad symbols could be extracted from files in: %s xor %s' %
(options.local_build_dir,
os.path.join(options.local_build_dir, 'lib.unstripped')))
rename_breakpad.RenameBreakpadFiles(options.breakpad_output_dir,
options.breakpad_output_dir)
return
# Fetch trace breakpad symbols from GCS
flag_utils.GetTracingLogger().info(
'Fetching and extracting trace breakpad symbols.')
symbol_fetcher.GetTraceBreakpadSymbols(options.cloud_storage_bucket,
trace_metadata,
options.breakpad_output_dir,
options.dump_syms_path)
def _Symbolize(trace_file, symbolizer_path, breakpad_output_dir, output_file):
""""Symbolizes a trace.
Args:
trace_file: The trace file to be symbolized.
symbolizer_path: The path to the trace_to_text tool to use.
breakpad_output_dir: Contains the breakpad symbols to use for symbolization.
output_file: The path to the file to output symbols to.
Raises:
RuntimeError: If _RunSymbolizer() fails to execute the given command.
"""
# Set environment variable as location of stored breakpad files.
symbolize_env = os.environ.copy()
symbolize_env['BREAKPAD_SYMBOL_DIR'] = os.path.join(breakpad_output_dir, '')
cmd = [symbolizer_path, 'symbolize', trace_file]
# Open temporary file where symbols can be stored.
with tempfile.TemporaryFile(mode='wb+') as temp_symbol_file:
_RunSymbolizer(cmd, symbolize_env, temp_symbol_file)
# Write trace data and symbol data to the same file.
temp_symbol_file.seek(0)
symbol_data = temp_symbol_file.read()
with open(trace_file, 'rb') as f:
trace_data = f.read()
with open(output_file, 'wb') as f:
f.write(trace_data)
f.write(symbol_data)
flag_utils.GetTracingLogger().info(
'Symbolized %s(%d bytes) with %d bytes of symbol data',
os.path.abspath(trace_file), len(trace_data), len(symbol_data))
def _RunSymbolizer(cmd, env, stdout):
proc = subprocess.Popen(cmd, env=env, stdout=stdout, stderr=subprocess.PIPE)
out, stderr = proc.communicate()
flag_utils.GetTracingLogger().debug('STDOUT:%s', str(out))
flag_utils.GetTracingLogger().debug('STDERR:%s', str(stderr))
if proc.returncode != 0:
raise RuntimeError(str(stderr))