Script to generalize synthetic descriptors in ART profile
Change-Id: I6ec4482c8cf1d16bd55d6e1b624c3123a18d6d5d
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index 44f70aa..60a7ee3 100755
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -14,6 +14,7 @@
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+import profile_utils
import utils
DEVNULL=subprocess.DEVNULL
@@ -199,39 +200,13 @@
def get_classes_and_methods_from_app_profile(app_id, device_id=None):
apk_path = get_apk_path(app_id, device_id)
profile_path = get_profile_path(app_id)
-
- # Generates a list of class and method descriptors, prefixed with one or more
- # flags 'H' (hot), 'S' (startup), 'P' (post startup).
- #
- # Example:
- #
- # HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
- # HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
- # HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
- # PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
- # HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
- # Landroidx/compose/runtime/ComposerImpl;
- #
- # See also https://developer.android.com/studio/profile/baselineprofiles.
cmd = create_adb_cmd(
'shell profman --dump-classes-and-methods'
' --profile-file=%s --apk=%s --dex-location=%s'
% (profile_path, apk_path, apk_path), device_id)
stdout = subprocess.check_output(cmd).decode('utf-8').strip()
lines = stdout.splitlines()
- classes_and_methods = {}
- flags_to_name = { 'H': 'hot', 'S': 'startup', 'P': 'post_startup' }
- for line in lines:
- flags = { 'hot': False, 'startup': False, 'post_startup': False }
- while line[0] in flags_to_name:
- flag_abbreviation = line[0]
- flag_name = flags_to_name.get(flag_abbreviation)
- flags[flag_name] = True
- line = line[1:]
- assert line.startswith('L')
- descriptor = line
- classes_and_methods[descriptor] = flags
- return classes_and_methods
+ return profile_utils.parse_art_profile(lines)
def get_screen_off_timeout(device_id=None):
cmd = create_adb_cmd(
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index c4fa05a..ee3f27a 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -4,6 +4,7 @@
# BSD-style license that can be found in the LICENSE file.
import adb_utils
+import profile_utils
import argparse
import os
@@ -28,12 +29,13 @@
write_tmp_profile_classes_and_methods(
profile_classes_and_methods, iteration, options)
current_startup_descriptors = \
- transform_classes_and_methods_to_r8_startup_descriptors(
+ profile_utils.transform_art_profile_to_r8_startup_list(
profile_classes_and_methods)
write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
new_startup_descriptors = add_r8_startup_descriptors(
startup_descriptors, current_startup_descriptors)
- number_of_new_startup_descriptors = len(new_startup_descriptors) - len(startup_descriptors)
+ number_of_new_startup_descriptors = \
+ len(new_startup_descriptors) - len(startup_descriptors)
if options.out is not None:
print(
'Found %i new startup descriptors in iteration %i'
@@ -167,16 +169,6 @@
def report_unrecognized_logcat_line(line):
print('Unrecognized line in logcat: %s' % line)
-def transform_classes_and_methods_to_r8_startup_descriptors(
- classes_and_methods):
- startup_descriptors = {}
- for startup_descriptor, flags in classes_and_methods.items():
- startup_descriptors[startup_descriptor] = {
- 'conditional_startup': False,
- 'post_startup': flags['post_startup']
- }
- return startup_descriptors
-
def add_r8_startup_descriptors(old_startup_descriptors, startup_descriptors_to_add):
new_startup_descriptors = {}
if len(old_startup_descriptors) == 0:
diff --git a/tools/startup/profile_utils.py b/tools/startup/profile_utils.py
new file mode 100755
index 0000000..0e69397
--- /dev/null
+++ b/tools/startup/profile_utils.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import argparse
+import sys
+
+COMPANION_CLASS_SUFFIX = '$-CC'
+EXTERNAL_SYNTHETIC_SUFFIX = '$$ExternalSynthetic'
+SYNTHETIC_PREFIX = 'S'
+
+# Parses a list of class and method descriptors, prefixed with one or more flags
+# 'H' (hot), 'S' (startup), 'P' (post startup).
+#
+# Example:
+#
+# HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V
+# HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I
+# HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V
+# PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V
+# HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I
+# Landroidx/compose/runtime/ComposerImpl;
+#
+# See also https://developer.android.com/studio/profile/baselineprofiles.
+def parse_art_profile(lines):
+ art_profile = {}
+ flags_to_name = { 'H': 'hot', 'S': 'startup', 'P': 'post_startup' }
+ for line in lines:
+ line = line.strip()
+ if not line:
+ continue
+ flags = { 'hot': False, 'startup': False, 'post_startup': False }
+ while line[0] in flags_to_name:
+ flag_abbreviation = line[0]
+ flag_name = flags_to_name.get(flag_abbreviation)
+ flags[flag_name] = True
+ line = line[1:]
+ assert line.startswith('L')
+ descriptor = line
+ art_profile[descriptor] = flags
+ return art_profile
+
+def transform_art_profile_to_r8_startup_list(art_profile):
+ r8_startup_list = {}
+ for startup_descriptor, flags in art_profile.items():
+ transformed_startup_descriptor = transform_synthetic_descriptor(
+ startup_descriptor)
+ r8_startup_list[transformed_startup_descriptor] = {
+ 'conditional_startup': False,
+ 'post_startup': flags['post_startup'],
+ 'startup': flags['startup']
+ }
+ return r8_startup_list
+
+def transform_synthetic_descriptor(descriptor):
+ companion_class_index = descriptor.find(COMPANION_CLASS_SUFFIX)
+ if companion_class_index >= 0:
+ return SYNTHETIC_PREFIX + descriptor[0:companion_class_index] + ';'
+ external_synthetic_index = descriptor.find(EXTERNAL_SYNTHETIC_SUFFIX)
+ if external_synthetic_index >= 0:
+ return SYNTHETIC_PREFIX + descriptor[0:external_synthetic_index] + ';'
+ return descriptor
+
+def filter_r8_startup_list(r8_startup_list, options):
+ filtered_r8_startup_list = {}
+ for startup_descriptor, flags in r8_startup_list.items():
+ if not options.include_post_startup \
+ and flags.get('post_startup') \
+ and not flags.get('startup'):
+ continue
+ filtered_r8_startup_list[startup_descriptor] = flags
+ return filtered_r8_startup_list
+
+def parse_options(argv):
+ result = argparse.ArgumentParser(
+ description='Utilities for converting an ART profile into an R8 startup '
+ 'list.')
+ result.add_argument('--art-profile', help='Path to the ART profile')
+ result.add_argument('--include-post-startup',
+ help='Include post startup classes and methods in the R8 '
+ 'startup list',
+ action='store_true',
+ default=False)
+ result.add_argument('--out', help='Where to store the R8 startup list')
+ options, args = result.parse_known_args(argv)
+ return options, args
+
+def main(argv):
+ (options, args) = parse_options(argv)
+ with open(options.art_profile, 'r') as f:
+ art_profile = parse_art_profile(f.read().splitlines())
+ r8_startup_list = transform_art_profile_to_r8_startup_list(art_profile)
+ filtered_r8_startup_list = filter_r8_startup_list(r8_startup_list, options)
+ if options.out is not None:
+ with open(options.out, 'w') as f:
+ for startup_descriptor, flags in filtered_r8_startup_list.items():
+ f.write(startup_descriptor)
+ f.write('\n')
+ else:
+ for startup_descriptor, flags in filtered_r8_startup_list.items():
+ print(startup_descriptor)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))