| # Copyright 2022 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Helper functions for Perfetto profiling (i.e. callstack sampling) in Telemetry |
| # benchmarks. |
| |
| import logging |
| import os |
| import shutil |
| import subprocess |
| import threading |
| |
| # traceconv is used for symbolization and generating pprof profiles. |
| _TRACECONV_PATH = os.path.normpath( |
| os.path.join(os.path.abspath(__file__), |
| '../../../../../third_party/perfetto/tools/traceconv')) |
| # traceconv is not guaranteed to be thread-safe. |
| _TRACECONV_LOCK = threading.Lock() |
| |
| |
| def _ConcatenateFiles(files_to_concatenate, output_path): |
| """Concatenates files in the order provided. |
| |
| Args: |
| files_to_concatenate: Paths for input files to concatenate. |
| output_path: Path to the resultant output file. |
| """ |
| with open(output_path, 'wb') as output_file: |
| for input_path in files_to_concatenate: |
| with open(input_path, 'rb') as input_file: |
| shutil.copyfileobj(input_file, output_file) |
| |
| |
| def _CopyFiles(source_directory_path, destination_directory_path): |
| """Copies all files in from the source to the destination directory.""" |
| file_names = os.listdir(source_directory_path) |
| if file_names is None: |
| return |
| for file_name in file_names: |
| shutil.copy(os.path.join(source_directory_path, file_name), |
| destination_directory_path) |
| |
| |
| def SymbolizeTrace(trace_path): |
| """Attempts symbolization of a Perfetto proto trace, if symbols are available. |
| |
| If symbolization is successful, the original trace file is replace with the |
| symbolized one. |
| |
| Args: |
| trace_path: The path to the trace file. |
| """ |
| binary_path = os.getenv('PERFETTO_BINARY_PATH') |
| if binary_path is None: |
| logging.warning( |
| 'Not symbolizing trace at %s since PERFETTO_BINARY_PATH is not set.', |
| trace_path) |
| return |
| |
| symbols = None |
| # Symbolize the trace. |
| with _TRACECONV_LOCK: |
| popen = subprocess.Popen([_TRACECONV_PATH, 'symbolize', trace_path], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| stdout, stderr = popen.communicate() |
| |
| if popen.return_code == 0: |
| symbols = stdout |
| else: |
| logging.error('Failed to symbolize trace at %s: %s', trace_path, |
| stderr.decode('utf-8')) |
| if symbols is None: |
| return |
| |
| parent_dir = os.path.dirname(trace_path) |
| symbols_path = os.path.join(parent_dir, 'symbols') |
| symbolized_trace_path = os.path.join( |
| parent_dir, 'symbolized_{}'.format(os.path.basename(trace_path))) |
| |
| # We write the symbols to a file in case they are useful for debugging, etc. |
| with open(symbols_path, 'wb') as symbols_file: |
| symbols_file.write(symbols) |
| |
| # Add symbols to the trace file. |
| _ConcatenateFiles([trace_path, symbols_path], symbolized_trace_path) |
| # Replace the original trace file. |
| os.remove(trace_path) |
| shutil.move(symbolized_trace_path, trace_path) |
| logging.info('Successfully symbolized trace at %s', trace_path) |
| |
| |
| def GenerateProfiles(trace_path): |
| """Generates pprof profiles from a Perfetto proto trace. |
| |
| Args: |
| trace_path: The path to the potentially symbolized Perfetto trace file. |
| """ |
| traceconv_output = None |
| with _TRACECONV_LOCK: |
| popen = subprocess.Popen([_TRACECONV_PATH, 'profile', '--perf', trace_path], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| stdout, stderr = popen.communicate() |
| if popen.return_code == 0: |
| traceconv_output = stdout.decode('utf-8') |
| else: |
| logging.error('Unable to extract pprof profiles from trace at %s: %s', |
| trace_path, stderr.decode('utf-8')) |
| if traceconv_output is None: |
| return |
| |
| # Copy profiles to the same directory as the source trace. |
| profiles_output_directory = None |
| for word in traceconv_output.split(): |
| if 'perf_profile-' in word: |
| profiles_output_directory = word |
| if profiles_output_directory is None: |
| logging.error('No profiles were extracted from trace at %s.', trace_path) |
| else: |
| _CopyFiles(profiles_output_directory, os.path.dirname(trace_path)) |
| logging.info('Successfully generated pprof profiles from trace at %s', |
| trace_path) |