| # 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)) |