Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/art/art/tools/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 14 kB image not shown  

Quelle  oatdump_pprof.py

  Sprache: Python
 

#!/usr/bin/env python3
#
# Copyright 2025, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A script to generate a pprof report from Android OAT (oatdump) files."""

import argparse
import gzip
import os
import re
import subprocess
import sys

# BEGIN profile_pb2 import
try:
  import profile_pb2
except ImportError:
  # Allow running the script from a checkout, not as a python_binary_host
  ANDROID_BUILD_TOP = os.environ.get('ANDROID_BUILD_TOP')
  if ANDROID_BUILD_TOP:
    import_path = ANDROID_BUILD_TOP + '/system/extras/simpleperf/scripts'
  else:
    import_path = os.getcwd() + '/system/extras/simpleperf/scripts'
  sys.path.append(import_path)
  import profile_pb2
# END profile_pb2 import

# Common file extensions for OAT/ODEX files.
OAT_FILE_EXTENSIONS = (
    '.odex',
    '.oat',
    '@classes.dex',
    '@classes.odex',
)


def run_command(command, decode=True):
  """Executes a shell command and returns its output, handling errors."""
  try:
    process = subprocess.run(
        command, check=True, capture_output=True, shell=True
    )
    if decode:
      return process.stdout.decode('utf-8', errors='ignore').strip()
    return process.stdout
  except subprocess.CalledProcessError as e:
    sys.stderr.write(
        f"Error running command: '{command}'\nStderr:"
        f" {e.stderr.decode('utf-8', errors='ignore')}\n"
    )
    return None


def load_proguard_map(map_file_path):
  """Parse a Proguard map file and return a deobfuscation mapping."""
  mapping = {'classes': {}, 'methods': {}}
  current_class_map = None
  class_regex = re.compile(r'^(.*) -> (.*):')
  method_regex = re.compile(r'^\s+\S+\s+([^\s(]+)\(.*\)\s+->\s+(.*)')

  with open(map_file_path, 'r', encoding='utf-8', errors='ignore'as f:
    for line in f:
      class_match = class_regex.match(line)
      if class_match:
        original_name, obfuscated_name = class_match.groups()
        mapping['classes'][obfuscated_name] = original_name
        current_class_map = {
            'obfuscated': obfuscated_name,
            'original': original_name,
        }
      elif current_class_map:
        method_match = method_regex.match(line)
        if method_match:
          original_method, obfuscated_method = method_match.groups()
          full_obfuscated_key = (
              f"{current_class_map['obfuscated']}.{obfuscated_method}"
          )
          mapping['methods'][full_obfuscated_key] = original_method
  return mapping


def stream_and_parse_oatdump(paths, proguard_map=None, is_host_file=False):
  """Yields (oat_file, dex_file, package, class, method, size) tuples from oatdump."""
  current_dex_file = None
  current_obfuscated_class_name = None
  current_method_name = None

  dex_file_regex = re.compile(r'^location:\s*(.*?)$')
  class_regex = re.compile(r'^\s*[0-9a-fA-F]+:\s+L([^;]+);')
  method_regex = re.compile(
      r'\s(?:[^\s.]+\.)*([a-zA-Z0-9_$<>]+\(.*\))\s+\(dex_method_idx='
  )
  code_size_regex = re.compile(r'^\s+CODE:.*size=(\d+)')

  for path in paths:
    print(f'Parsing {path}...')
    current_oat_file = path
    process = None

    if is_host_file:
      try:
        lines_iterator = open(path, 'r', encoding='utf-8', errors='ignore')
      except FileNotFoundError:
        sys.stderr.write(f'Error: Oatdump file not found at {path}.\n')
        continue
    else:
      oatdump_command = (
          f'adb shell oatdump --oat-file={path} --no-disassemble --no-dump:vmap'
      )
      process = subprocess.Popen(
          oatdump_command,
          shell=True,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
          text=True,
          errors='ignore',
      )
      lines_iterator = process.stdout

    for line in lines_iterator:
      oat_dex_file_match = dex_file_regex.search(line)
      if oat_dex_file_match:
        current_dex_file = oat_dex_file_match.group(1)
        current_obfuscated_class_name = None
        current_method_name = None
        continue

      if current_dex_file:
        class_match = class_regex.search(line)
        if class_match:
          current_obfuscated_class_name = class_match.group(1).replace('/''.')
          current_method_name = None
          continue

      if current_obfuscated_class_name:
        method_match = method_regex.search(line)
        if method_match:
          current_method_name = method_match.group(1)
          continue

      if not current_method_name:
        continue

      size_match = code_size_regex.search(line)
      if not size_match:
        continue

      code_size = int(size_match.group(1))
      if code_size > 0:
        class_to_process = current_obfuscated_class_name
        method_to_process = current_method_name

        if proguard_map:
          class_to_process = proguard_map['classes'].get(
              current_obfuscated_class_name, current_obfuscated_class_name
          )

          method_name_part = method_to_process.split('(')[0]
          params = method_to_process[len(method_name_part) :]
          full_obfuscated_key = (
              f'{current_obfuscated_class_name}.{method_name_part}'
          )

          deobfuscated_method_name = proguard_map['methods'].get(
              full_obfuscated_key
          )
          if deobfuscated_method_name:
            method_to_process = deobfuscated_method_name + params

        package, class_name = (
            (class_to_process.rsplit('.'1))
            if '.' in class_to_process
            else ('default', class_to_process)
        )
        if package and class_name and method_to_process:
          yield (
              current_oat_file,
              current_dex_file,
              package,
              class_name,
              method_to_process,
              code_size,
          )

      current_method_name = None

    if is_host_file and lines_iterator:
      lines_iterator.close()

    if process:
      stderr_output = process.stderr.read()
      if process.wait() != 0:
        sys.stderr.write(
            f'Warning: Failed to get oatdump for {path}.\nStderr:'
            f' {stderr_output}\n'
        )


def generate_pprof_report(data, output_path, dex_file_oriented, argv):
  """Generates a pprof file from the parsed oatdump data."""
  profile = profile_pb2.Profile()
  # Use a dictionary for efficient string-to-ID mapping and a list to maintain
  # the order for the protobuf string table.
  string_to_id = {''0}
  id_to_string = ['']
  functions = {}
  locations = {}
  mappings = {}

  def get_string_id(s):
    """Adds a string to the string table and returns its ID."""
    string_id = string_to_id.get(s)
    if string_id is None:
      new_id = len(id_to_string)
      string_to_id[s] = new_id
      id_to_string.append(s)
      string_id = new_id
    return string_id

  st_size = profile.sample_type.add()
  st_size.type = get_string_id('space')
  st_size.unit = get_string_id('bytes')

  for oat_file, dex_file, package_name, class_name, method_name, size in data:
    if size == 0:
      continue

    sample = profile.sample.add()
    sample.value.append(size)

    # Generate a stack trace for the sample with the following hierarchy:
    #   - Oat file path (to identify where the code comes from)
    #   - [Optional] Dex file path (to identify where the dex code comes from)
    #   - Package name parts, e.g. 'android', 'app'
    #   - Class name, e.g. 'Activity'
    #   - Fully qualified method name, e.g. 'android.app.Activity.onCreate(android.os.Bundle)'
    stack_frames = []
    stack_frames.append((oat_file, os.path.basename(oat_file)))
    if dex_file_oriented:
      stack_frames.append((dex_file, dex_file))
    package_parts = package_name.split('.')
    current_package = ''
    for part in package_parts:
      current_package = f'{current_package}.{part}' if current_package else part
      stack_frames.append((current_package, current_package))
    full_class_name = f'{current_package}.{class_name}'
    stack_frames.append((full_class_name, full_class_name))
    full_method_name = f'{package_name}.{class_name}.{method_name}'
    stack_frames.append((full_method_name, full_method_name))

    location_ids = []

    if oat_file not in mappings:
      mapping = profile.mapping.add()
      mapping.id = len(profile.mapping)
      mapping.filename = get_string_id(oat_file)
      mappings[oat_file] = mapping.id
    mapping_id = mappings[oat_file]

    for i, (unique_id, label) in enumerate(stack_frames):
      if unique_id not in functions:
        func = profile.function.add()
        func.id = len(profile.function)
        func.name = get_string_id(label)
        func.filename = get_string_id(oat_file)
        functions[unique_id] = func.id

      func_id = functions[unique_id]

      loc_key = (func_id, mapping_id)
      if loc_key not in locations:
        loc = profile.location.add()
        loc.id = len(profile.location)
        loc.mapping_id = mapping_id
        line = loc.line.add()
        line.function_id = func_id
        locations[loc_key] = loc.id

      location_ids.append(locations[loc_key])

    sample.location_id.extend(reversed(location_ids))

  comment = f"{os.path.basename(argv[0])} {' '.join(argv[1:])}"
  profile.comment.append(get_string_id(comment))
  profile.string_table.extend(id_to_string)

  with gzip.open(output_path, 'wb'as f:
    f.write(profile.SerializeToString())


def get_oat_files_for_package(package_name):
  """Finds all OAT/ODEX files for a given package under the primary ABI."""
  command_output = run_command(f'adb shell pm art dump {package_name}')
  if not command_output:
    sys.stderr.write(f'Error: Could not find oat file for {package_name}.\n')
    return []

  paths = set()
  expect_location_for_primary_abi = False
  location_regex = re.compile(r'^\s*\[location is (.*?)\]$')

  for line in command_output.splitlines():
    if expect_location_for_primary_abi:
      location_match = location_regex.search(line)
      if location_match:
        location = location_match.group(1)
        if location.endswith(OAT_FILE_EXTENSIONS):
          paths.add(location)
        expect_location_for_primary_abi = False
    elif '[primary-abi]' in line:
      expect_location_for_primary_abi = True
      continue

  if not paths:
    sys.stderr.write(f'Error: Failed to parse art dump for {package_name}:\n')
    sys.stderr.write(command_output)
    sys.stderr.write('\n')
    return None
  return list(paths)


def get_oat_files_for_process(process_identifier):
  """Finds all .oat and .odex files for a given process name or PID."""
  pid = run_command(f'adb shell pidof -s {process_identifier}')
  if not pid:
    sys.stderr.write(f'Error: Could not find PID for "{process_identifier}".\n')
    return None

  maps_content = run_command(f'adb shell cat /proc/{pid}/maps')
  if not maps_content:
    sys.stderr.write(f'Error: Could not read maps for PID {pid}.\n')
    return None

  oat_files = set()
  for line in maps_content.splitlines():
    path = line.split()[-1]
    if path.endswith(OAT_FILE_EXTENSIONS):
      oat_files.add(path)
  return list(oat_files)


def ensure_adb_root():
  """Ensures adb connection has root access.

  Raises:
    subprocess.CalledProcessError: If an adb command fails.
    FileNotFoundError: If 'adb' command is not found.
    SystemExit: If root access cannot be obtained.
  """
  try:
    # Attempt to restart adbd as root. This command will block until adbd restarts.
    run_command('adb root')
    # Wait for the device to reconnect after adb root.
    run_command('adb wait-for-device')
    # Verify root access by checking the user ID.
    result_id = run_command('adb shell id -u')
    if result_id != '0':
      sys.stderr.write(
          'Error: Failed to get root access via adb. Please ensure your device'
          ' is rooted or adbd can be restarted as root.\n'
      )
      sys.exit(1)
  except subprocess.CalledProcessError as e:
    command = e.cmd if isinstance(e.cmd, str) else ' '.join(e.cmd)
    sys.stderr.write(
        f'Error: adb command failed during root check. Is adb installed and'
        f' device connected?\n'
    )
    sys.stderr.write(f'Command: {command}\n')
    sys.stderr.write(f'Stderr: {e.stderr}\n')
    sys.exit(1)
  except FileNotFoundError:
    sys.stderr.write(
        "Error: 'adb' command not found. Please ensure adb is in your PATH.\n"
    )
    sys.exit(1)


def main(argv):
  parser = argparse.ArgumentParser(
      description='Generate a pprof report from Android OAT files.'
  )
  group = parser.add_mutually_exclusive_group(required=True)
  group.add_argument(
      '--package', help='Package name of the app on the connected device.'
  )
  group.add_argument(
      '--process', help='Process name or PID on the connected device.'
  )
  group.add_argument(
      '--oatdump-file', help='Path to a single oatdump output file on the host.'
  )

  parser.add_argument(
      '--output',
      default='oatdump_report.pprof',
      help='Path to save the generated pprof file.',
  )
  parser.add_argument(
      '--map', help='Optional path to a Proguard deobfuscation map file.'
  )
  parser.add_argument(
      '--dex-file-oriented',
      action='store_true',
      help='If set, groups the flame graph by dex files.',
  )
  args = parser.parse_args(argv)

  oat_files = []
  is_host = False
  if args.oatdump_file:
    oat_files = [args.oatdump_file]
    is_host = True
  else:
    ensure_adb_root()
    if args.package:
      oat_files = get_oat_files_for_package(args.package)
    elif args.process:
      oat_files = get_oat_files_for_process(args.process)

  if not oat_files:
    sys.stderr.write('Error: Could not find any OAT/ODEX files to process.\n')
    sys.exit(1)

  proguard_map = load_proguard_map(args.map) if args.map else None

  parsed_data = list(
      stream_and_parse_oatdump(oat_files, proguard_map, is_host_file=is_host)
  )

  if not parsed_data:
    print('No methods found in oatdump. The report will be empty.')
  else:
    print(f'Generating pprof report to {args.output}...')
    generate_pprof_report(parsed_data, args.output, args.dex_file_oriented, sys.argv)
    print('Done.')
    print(f'Report saved to: {os.path.abspath(args.output)}')
    print(f'Tip: To upload to pprof web UI, run:')
    print(f'pprof -flame {os.path.abspath(args.output)}')


if __name__ == '__main__':
  main(sys.argv[1:])

Messung V0.5 in Prozent
C=82 H=88 G=84

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.