Support for identifying startup classes from instrumentation
This also updates generate_startup_descriptors.py to store the artifacts in an (optional) out directory, so that the generated profiles are accessible after script executing.
Change-Id: I3fa67d13d10a110f6c32e0696297f8a292b5c759
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index ea0fdf3..f1faee43 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -6,16 +6,23 @@
import adb_utils
import argparse
+import os
import sys
import time
def extend_startup_descriptors(startup_descriptors, iteration, options):
- generate_startup_profile_on_device(options)
- classes_and_methods = adb_utils.get_classes_and_methods_from_app_profile(
- options.app_id, options.device_id)
- current_startup_descriptors = \
- transform_classes_and_methods_to_r8_startup_descriptors(
- classes_and_methods, options)
+ (logcat, profile, profile_classes_and_methods) = \
+ generate_startup_profile(options)
+ if options.logcat:
+ write_tmp_logcat(logcat, iteration, options)
+ current_startup_descriptors = get_r8_startup_descriptors_from_logcat(logcat)
+ else:
+ write_tmp_profile(profile, iteration, options)
+ write_tmp_profile_classes_and_methods(profile_classes_and_methods, iteration, options)
+ current_startup_descriptors = \
+ transform_classes_and_methods_to_r8_startup_descriptors(
+ profile_classes_and_methods, options)
+ write_tmp_startup_descriptors(current_startup_descriptors, iteration, options)
number_of_new_startup_descriptors = add_r8_startup_descriptors(
startup_descriptors, current_startup_descriptors)
if options.out is not None:
@@ -24,15 +31,32 @@
% (number_of_new_startup_descriptors, iteration + 1))
return number_of_new_startup_descriptors
-def generate_startup_profile_on_device(options):
- if not options.use_existing_profile:
- # Clear existing profile data.
- adb_utils.clear_profile_data(options.app_id, options.device_id)
-
+def generate_startup_profile(options):
+ logcat = None
+ profile = None
+ profile_classes_and_methods = None
+ if options.use_existing_profile:
+ # Verify presence of profile.
+ adb_utils.check_app_has_profile_data(options.app_id, options.device_id)
+ profile = adb_utils.get_profile_data(options.app_id, options.device_id)
+ profile_classes_and_methods = \
+ adb_utils.get_classes_and_methods_from_app_profile(
+ options.app_id, options.device_id)
+ else:
# Unlock device.
tear_down_options = adb_utils.prepare_for_interaction_with_device(
options.device_id, options.device_pin)
+ logcat_process = None
+ if options.logcat:
+ # Clear logcat and start capturing logcat.
+ adb_utils.clear_logcat(options.device_id)
+ logcat_process = adb_utils.start_logcat(
+ options.device_id, format='raw', filter='r8:I *:S')
+ else:
+ # Clear existing profile data.
+ adb_utils.clear_profile_data(options.app_id, options.device_id)
+
# Launch activity to generate startup profile on device.
adb_utils.launch_activity(
options.app_id, options.main_activity, options.device_id)
@@ -40,17 +64,36 @@
# Wait for activity startup.
time.sleep(options.startup_duration)
- # Capture startup profile.
- adb_utils.capture_app_profile_data(options.app_id, options.device_id)
+ if options.logcat:
+ # Get startup descriptors from logcat.
+ logcat = adb_utils.stop_logcat(logcat_process)
+ else:
+ # Capture startup profile.
+ adb_utils.capture_app_profile_data(options.app_id, options.device_id)
+ profile = adb_utils.get_profile_data(options.app_id, options.device_id)
+ profile_classes_and_methods = \
+ adb_utils.get_classes_and_methods_from_app_profile(
+ options.app_id, options.device_id)
# Shutdown app.
adb_utils.stop_app(options.app_id, options.device_id)
-
adb_utils.tear_down_after_interaction_with_device(
tear_down_options, options.device_id)
- # Verify presence of profile.
- adb_utils.check_app_has_profile_data(options.app_id, options.device_id)
+ return (logcat, profile, profile_classes_and_methods)
+
+def get_r8_startup_descriptors_from_logcat(logcat):
+ startup_descriptors = []
+ for line in logcat:
+ if line == '--------- beginning of main':
+ continue
+ if line == '--------- beginning of system':
+ continue
+ if not line.startswith('L') or not line.endswith(';'):
+ print('Unrecognized line in logcat: %s' % line)
+ continue
+ startup_descriptors.append(line)
+ return startup_descriptors
def transform_classes_and_methods_to_r8_startup_descriptors(
classes_and_methods, options):
@@ -72,6 +115,53 @@
return new_number_of_startup_descriptors \
- previous_number_of_startup_descriptors
+def write_tmp_binary_artifact(artifact, iteration, options, name):
+ if not options.tmp_dir:
+ return
+ out_dir = os.path.join(options.tmp_dir, str(iteration))
+ os.makedirs(out_dir, exist_ok=True)
+ path = os.path.join(out_dir, name)
+ with open(path, 'wb') as f:
+ f.write(artifact)
+
+def write_tmp_textual_artifact(artifact, iteration, options, name, item_to_string=None):
+ if not options.tmp_dir:
+ return
+ out_dir = os.path.join(options.tmp_dir, str(iteration))
+ os.makedirs(out_dir, exist_ok=True)
+ path = os.path.join(out_dir, name)
+ with open(path, 'w') as f:
+ for item in artifact:
+ f.write(item if item_to_string is None else item_to_string(item))
+ f.write('\n')
+
+def write_tmp_logcat(logcat, iteration, options):
+ write_tmp_textual_artifact(logcat, iteration, options, 'logcat.txt')
+
+def write_tmp_profile(profile, iteration, options):
+ write_tmp_binary_artifact(profile, iteration, options, 'primary.prof')
+
+def write_tmp_profile_classes_and_methods(
+ profile_classes_and_methods, iteration, options):
+ def item_to_string(item):
+ descriptor = item.get('descriptor')
+ flags = item.get('flags')
+ return '%s%s%s%s' % (
+ 'H' if flags.get('hot') else '',
+ 'S' if flags.get('startup') else '',
+ 'P' if flags.get('post_startup') else '',
+ descriptor)
+ write_tmp_textual_artifact(
+ profile_classes_and_methods,
+ iteration,
+ options,
+ 'profile.txt',
+ item_to_string)
+
+def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
+ write_tmp_textual_artifact(
+ startup_descriptors, iteration, options, 'startup-descriptors.txt')
+
def parse_options(argv):
result = argparse.ArgumentParser(
description='Generate a perfetto trace file.')
@@ -84,6 +174,9 @@
help='Device id (e.g., emulator-5554).')
result.add_argument('--device-pin',
help='Device pin code (e.g., 1234)')
+ result.add_argument('--logcat',
+ action='store_true',
+ default=False)
result.add_argument('--include-post-startup',
help='Include post startup classes and methods in the R8 '
'startup descriptors',
@@ -102,6 +195,9 @@
help='Duration in seconds before shutting down app',
default=15,
type=int)
+ result.add_argument('--tmp-dir',
+ help='Directory where to store intermediate artifacts'
+ ' (by default these are not emitted)')
result.add_argument('--until-stable',
help='Repeat profile generation until no new startup '
'descriptors are found',