Format python files using yapf
Change-Id: I8b7b97efb6bfdcceef9efc533cdaa0675ab7db40
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
index d4f70a1..6ce19ab 100755
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -17,458 +17,511 @@
import profile_utils
import utils
-DEVNULL=subprocess.DEVNULL
+DEVNULL = subprocess.DEVNULL
+
class ProcessReader(threading.Thread):
- def __init__(self, process):
- threading.Thread.__init__(self)
- self.lines = []
- self.process = process
+ def __init__(self, process):
+ threading.Thread.__init__(self)
+ self.lines = []
+ self.process = process
- def run(self):
- for line in self.process.stdout:
- line = line.decode('utf-8').strip()
- self.lines.append(line)
+ def run(self):
+ for line in self.process.stdout:
+ line = line.decode('utf-8').strip()
+ self.lines.append(line)
- def stop(self):
- self.process.kill()
+ def stop(self):
+ self.process.kill()
+
class ScreenState(Enum):
- OFF_LOCKED = 1,
- OFF_UNLOCKED = 2
- ON_LOCKED = 3
- ON_UNLOCKED = 4
+ OFF_LOCKED = 1,
+ OFF_UNLOCKED = 2
+ ON_LOCKED = 3
+ ON_UNLOCKED = 4
- def is_off(self):
- return self == ScreenState.OFF_LOCKED or self == ScreenState.OFF_UNLOCKED
+ def is_off(self):
+ return self == ScreenState.OFF_LOCKED or self == ScreenState.OFF_UNLOCKED
- def is_on(self):
- return self == ScreenState.ON_LOCKED or self == ScreenState.ON_UNLOCKED
+ def is_on(self):
+ return self == ScreenState.ON_LOCKED or self == ScreenState.ON_UNLOCKED
- def is_on_and_locked(self):
- return self == ScreenState.ON_LOCKED
+ def is_on_and_locked(self):
+ return self == ScreenState.ON_LOCKED
- def is_on_and_unlocked(self):
- return self == ScreenState.ON_UNLOCKED
+ def is_on_and_unlocked(self):
+ return self == ScreenState.ON_UNLOCKED
+
def broadcast(action, component, device_id=None):
- print('Sending broadcast %s' % action)
- cmd = create_adb_cmd('shell am broadcast -a %s %s' % (action, component), device_id)
- return subprocess.check_output(cmd).decode('utf-8').strip().splitlines()
+ print('Sending broadcast %s' % action)
+ cmd = create_adb_cmd('shell am broadcast -a %s %s' % (action, component),
+ device_id)
+ return subprocess.check_output(cmd).decode('utf-8').strip().splitlines()
+
def build_apks_from_bundle(bundle, output, overwrite=False):
- print('Building %s' % bundle)
- cmd = [
- 'java', '-jar', utils.BUNDLETOOL_JAR,
- 'build-apks',
- '--bundle=%s' % bundle,
- '--output=%s' % output]
- if overwrite:
- cmd.append('--overwrite')
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ print('Building %s' % bundle)
+ cmd = [
+ 'java', '-jar', utils.BUNDLETOOL_JAR, 'build-apks',
+ '--bundle=%s' % bundle,
+ '--output=%s' % output
+ ]
+ if overwrite:
+ cmd.append('--overwrite')
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def capture_screen(target, device_id=None):
- print('Taking screenshot to %s' % target)
- tmp = '/sdcard/screencap.png'
- cmd = create_adb_cmd('shell screencap -p %s' % tmp, device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
- pull(tmp, target, device_id)
+ print('Taking screenshot to %s' % target)
+ tmp = '/sdcard/screencap.png'
+ cmd = create_adb_cmd('shell screencap -p %s' % tmp, device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ pull(tmp, target, device_id)
+
def create_adb_cmd(arguments, device_id=None):
- assert isinstance(arguments, list) or isinstance(arguments, str)
- cmd = ['adb']
- if device_id is not None:
- cmd.append('-s')
- cmd.append(device_id)
- cmd.extend(arguments if isinstance(arguments, list) else arguments.split(' '))
- return cmd
+ assert isinstance(arguments, list) or isinstance(arguments, str)
+ cmd = ['adb']
+ if device_id is not None:
+ cmd.append('-s')
+ cmd.append(device_id)
+ cmd.extend(
+ arguments if isinstance(arguments, list) else arguments.split(' '))
+ return cmd
+
def capture_app_profile_data(app_id, device_id=None):
- ps_cmd = create_adb_cmd('shell ps -o NAME', device_id)
- stdout = subprocess.check_output(ps_cmd).decode('utf-8').strip()
- killed_any = False
- for process_name in stdout.splitlines():
- if process_name.startswith(app_id):
- print('Flushing profile for process %s' % process_name)
- killall_cmd = create_adb_cmd(
- 'shell killall -s SIGUSR1 %s' % process_name, device_id)
- killall_result = subprocess.run(killall_cmd, capture_output=True)
- stdout = killall_result.stdout.decode('utf-8')
- stderr = killall_result.stderr.decode('utf-8')
- if killall_result.returncode == 0:
- killed_any = True
- else:
- print('Error: stdout: %s, stderr: %s' % (stdout, stderr))
- time.sleep(5)
- assert killed_any, 'Expected to find at least one process'
+ ps_cmd = create_adb_cmd('shell ps -o NAME', device_id)
+ stdout = subprocess.check_output(ps_cmd).decode('utf-8').strip()
+ killed_any = False
+ for process_name in stdout.splitlines():
+ if process_name.startswith(app_id):
+ print('Flushing profile for process %s' % process_name)
+ killall_cmd = create_adb_cmd(
+ 'shell killall -s SIGUSR1 %s' % process_name, device_id)
+ killall_result = subprocess.run(killall_cmd, capture_output=True)
+ stdout = killall_result.stdout.decode('utf-8')
+ stderr = killall_result.stderr.decode('utf-8')
+ if killall_result.returncode == 0:
+ killed_any = True
+ else:
+ print('Error: stdout: %s, stderr: %s' % (stdout, stderr))
+ time.sleep(5)
+ assert killed_any, 'Expected to find at least one process'
+
def check_app_has_profile_data(app_id, device_id=None):
- profile_path = get_profile_path(app_id)
- cmd = create_adb_cmd(
- 'shell du /data/misc/profiles/cur/0/%s/primary.prof' % app_id,
- device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- size_str = stdout[:stdout.index('\t')]
- assert size_str.isdigit()
- size = int(size_str)
- if size == 4:
- raise ValueError('Expected size of profile at %s to be > 4K' % profile_path)
+ profile_path = get_profile_path(app_id)
+ cmd = create_adb_cmd(
+ 'shell du /data/misc/profiles/cur/0/%s/primary.prof' % app_id,
+ device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ size_str = stdout[:stdout.index('\t')]
+ assert size_str.isdigit()
+ size = int(size_str)
+ if size == 4:
+ raise ValueError('Expected size of profile at %s to be > 4K' %
+ profile_path)
+
def clear_logcat(device_id=None):
- cmd = create_adb_cmd('logcat -c', device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ cmd = create_adb_cmd('logcat -c', device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def clear_profile_data(app_id, device_id=None):
- cmd = create_adb_cmd(
- 'shell cmd package compile --reset %s' % app_id, device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ cmd = create_adb_cmd('shell cmd package compile --reset %s' % app_id,
+ device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def drop_caches(device_id=None):
- cmd = create_adb_cmd(
- ['shell', 'echo 3 > /proc/sys/vm/drop_caches'], device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ cmd = create_adb_cmd(['shell', 'echo 3 > /proc/sys/vm/drop_caches'],
+ device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def ensure_screen_on(device_id=None):
- if get_screen_state(device_id).is_off():
- toggle_screen(device_id)
- assert get_screen_state(device_id).is_on()
+ if get_screen_state(device_id).is_off():
+ toggle_screen(device_id)
+ assert get_screen_state(device_id).is_on()
+
def ensure_screen_off(device_id=None):
- if get_screen_state(device_id).is_on():
- toggle_screen(device_id)
- assert get_screen_state(device_id).is_off()
+ if get_screen_state(device_id).is_on():
+ toggle_screen(device_id)
+ assert get_screen_state(device_id).is_off()
+
def force_compilation(app_id, device_id=None):
- print('Applying AOT (full)')
- cmd = create_adb_cmd(
- 'shell cmd package compile -m speed -f %s' % app_id, device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ print('Applying AOT (full)')
+ cmd = create_adb_cmd('shell cmd package compile -m speed -f %s' % app_id,
+ device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def force_profile_compilation(app_id, device_id=None):
- print('Applying AOT (profile)')
- cmd = create_adb_cmd(
- 'shell cmd package compile -m speed-profile -f %s' % app_id, device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+ print('Applying AOT (profile)')
+ cmd = create_adb_cmd(
+ 'shell cmd package compile -m speed-profile -f %s' % app_id, device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
def get_apk_path(app_id, device_id=None):
- cmd = create_adb_cmd('shell pm path %s' % app_id, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- if not stdout.startswith('package:'):
- raise ValueError(
- 'Expected stdout to start with "package:", was: %s' % stdout)
- apk_path = stdout[len('package:'):]
- if not apk_path.endswith('.apk'):
- raise ValueError(
- 'Expected stdout to end with ".apk", was: %s' % stdout)
- return apk_path
+ cmd = create_adb_cmd('shell pm path %s' % app_id, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ if not stdout.startswith('package:'):
+ raise ValueError('Expected stdout to start with "package:", was: %s' %
+ stdout)
+ apk_path = stdout[len('package:'):]
+ if not apk_path.endswith('.apk'):
+ raise ValueError('Expected stdout to end with ".apk", was: %s' % stdout)
+ return apk_path
+
def get_component_name(app_id, activity):
- if activity.startswith(app_id):
- return '%s/.%s' % (app_id, activity[len(app_id)+1:])
- else:
- return '%s/%s' % (app_id, activity)
+ if activity.startswith(app_id):
+ return '%s/.%s' % (app_id, activity[len(app_id) + 1:])
+ else:
+ return '%s/%s' % (app_id, activity)
+
def get_meminfo(app_id, device_id=None):
- cmd = create_adb_cmd('shell dumpsys meminfo -s %s' % app_id, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- for line in stdout.splitlines():
- if 'TOTAL PSS: ' in line:
- elements = [s for s in line.replace('TOTAL ', 'TOTAL_').split()]
- assert elements[0] == 'TOTAL_PSS:', elements[0]
- assert elements[1].isdigit()
- assert elements[2] == 'TOTAL_RSS:'
- assert elements[3].isdigit()
- return { 'total_pss': int(elements[1]), 'total_rss': int(elements[3]) }
- raise ValueError('Unexpected stdout: %s' % stdout)
+ cmd = create_adb_cmd('shell dumpsys meminfo -s %s' % app_id, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ for line in stdout.splitlines():
+ if 'TOTAL PSS: ' in line:
+ elements = [s for s in line.replace('TOTAL ', 'TOTAL_').split()]
+ assert elements[0] == 'TOTAL_PSS:', elements[0]
+ assert elements[1].isdigit()
+ assert elements[2] == 'TOTAL_RSS:'
+ assert elements[3].isdigit()
+ return {
+ 'total_pss': int(elements[1]),
+ 'total_rss': int(elements[3])
+ }
+ raise ValueError('Unexpected stdout: %s' % stdout)
+
def get_profile_data(app_id, device_id=None):
- with utils.TempDir() as temp:
- source = get_profile_path(app_id)
- target = os.path.join(temp, 'primary.prof')
- pull(source, target, device_id)
- with open(target, 'rb') as f:
- return f.read()
+ with utils.TempDir() as temp:
+ source = get_profile_path(app_id)
+ target = os.path.join(temp, 'primary.prof')
+ pull(source, target, device_id)
+ with open(target, 'rb') as f:
+ return f.read()
+
def get_profile_path(app_id):
- return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id
+ return '/data/misc/profiles/cur/0/%s/primary.prof' % app_id
+
def get_minor_major_page_faults(app_id, device_id=None):
- pid = get_pid(app_id, device_id)
- cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8')
- lines_it = iter(stdout.splitlines())
- first_line = next(lines_it)
- assert first_line == ' MINFL MAJFL'
- second_line = next(lines_it)
- minfl, majfl = second_line.split()
- assert minfl.isdigit()
- assert majfl.isdigit()
- return (int(minfl), int(majfl))
+ pid = get_pid(app_id, device_id)
+ cmd = create_adb_cmd('shell ps -p %i -o MINFL,MAJFL' % pid, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8')
+ lines_it = iter(stdout.splitlines())
+ first_line = next(lines_it)
+ assert first_line == ' MINFL MAJFL'
+ second_line = next(lines_it)
+ minfl, majfl = second_line.split()
+ assert minfl.isdigit()
+ assert majfl.isdigit()
+ return (int(minfl), int(majfl))
+
def get_pid(app_id, device_id=None):
- cmd = create_adb_cmd('shell pidof %s' % app_id, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- assert stdout.isdigit()
- pid = int(stdout)
- return pid
+ cmd = create_adb_cmd('shell pidof %s' % app_id, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ assert stdout.isdigit()
+ pid = int(stdout)
+ return pid
+
def get_screen_state(device_id=None):
- cmd = create_adb_cmd('shell dumpsys nfc', device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- screen_state_value = None
- for line in stdout.splitlines():
- if line.startswith('mScreenState='):
- value_start_index = len('mScreenState=')
- screen_state_value=line[value_start_index:]
- if screen_state_value is None:
- raise ValueError('Expected to find mScreenState in: adb shell dumpsys nfc')
- if not hasattr(ScreenState, screen_state_value):
- raise ValueError(
- 'Expected mScreenState to be a value of ScreenState, was: %s'
- % screen_state_value)
- return ScreenState[screen_state_value]
+ cmd = create_adb_cmd('shell dumpsys nfc', device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ screen_state_value = None
+ for line in stdout.splitlines():
+ if line.startswith('mScreenState='):
+ value_start_index = len('mScreenState=')
+ screen_state_value = line[value_start_index:]
+ if screen_state_value is None:
+ raise ValueError(
+ 'Expected to find mScreenState in: adb shell dumpsys nfc')
+ if not hasattr(ScreenState, screen_state_value):
+ raise ValueError(
+ 'Expected mScreenState to be a value of ScreenState, was: %s' %
+ screen_state_value)
+ return ScreenState[screen_state_value]
+
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)
- 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()
- return profile_utils.parse_art_profile(lines)
+ apk_path = get_apk_path(app_id, device_id)
+ profile_path = get_profile_path(app_id)
+ 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()
+ return profile_utils.parse_art_profile(lines)
+
def get_screen_off_timeout(device_id=None):
- cmd = create_adb_cmd(
- 'shell settings get system screen_off_timeout', device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- assert stdout.isdigit()
- screen_off_timeout = int(stdout)
- return screen_off_timeout
+ cmd = create_adb_cmd('shell settings get system screen_off_timeout',
+ device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ assert stdout.isdigit()
+ screen_off_timeout = int(stdout)
+ return screen_off_timeout
+
def grant(app_id, permission, device_id=None):
- cmd = create_adb_cmd('shell pm grant %s %s' % (app_id, permission), device_id)
- subprocess.check_call(cmd)
+ cmd = create_adb_cmd('shell pm grant %s %s' % (app_id, permission),
+ device_id)
+ subprocess.check_call(cmd)
+
def install(apk, device_id=None):
- print('Installing %s' % apk)
- cmd = create_adb_cmd('install %s' % apk, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8')
- assert 'Success' in stdout
+ print('Installing %s' % apk)
+ cmd = create_adb_cmd('install %s' % apk, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8')
+ assert 'Success' in stdout
+
def install_apks(apks, device_id=None, max_attempts=3):
- print('Installing %s' % apks)
- cmd = [
- 'java', '-jar', utils.BUNDLETOOL_JAR,
- 'install-apks',
- '--apks=%s' % apks]
- if device_id is not None:
- cmd.append('--device-id=%s' % device_id)
- for i in range(max_attempts):
+ print('Installing %s' % apks)
+ cmd = [
+ 'java', '-jar', utils.BUNDLETOOL_JAR, 'install-apks',
+ '--apks=%s' % apks
+ ]
+ if device_id is not None:
+ cmd.append('--device-id=%s' % device_id)
+ for i in range(max_attempts):
+ process_result = subprocess.run(cmd, capture_output=True)
+ stdout = process_result.stdout.decode('utf-8')
+ stderr = process_result.stderr.decode('utf-8')
+ if process_result.returncode == 0:
+ return
+ print('Failed to install %s' % apks)
+ print('Stdout: %s' % stdout)
+ print('Stderr: %s' % stderr)
+ print('Retrying...')
+ raise Exception('Unable to install apks in %s attempts' % max_attempts)
+
+
+def install_bundle(bundle, device_id=None):
+ print('Installing %s' % bundle)
+ with utils.TempDir() as temp:
+ apks = os.path.join(temp, 'Bundle.apks')
+ build_apks_from_bundle(bundle, apks)
+ install_apks(apks, device_id)
+
+
+def install_profile_using_adb(app_id, host_profile_path, device_id=None):
+ device_profile_path = get_profile_path(app_id)
+ cmd = create_adb_cmd('push %s %s' %
+ (host_profile_path, device_profile_path))
+ subprocess.check_call(cmd)
+ stop_app(app_id, device_id)
+ force_profile_compilation(app_id, device_id)
+
+
+def install_profile_using_profileinstaller(app_id, device_id=None):
+ # This assumes that the profileinstaller library has been added to the app,
+ # https://developer.android.com/jetpack/androidx/releases/profileinstaller.
+ action = 'androidx.profileinstaller.action.INSTALL_PROFILE'
+ component = '%s/androidx.profileinstaller.ProfileInstallReceiver' % app_id
+ stdout = broadcast(action, component, device_id)
+ assert len(stdout) == 2
+ assert stdout[0] == ('Broadcasting: Intent { act=%s flg=0x400000 cmp=%s }' %
+ (action, component))
+ assert stdout[1] == 'Broadcast completed: result=1', stdout[1]
+ stop_app(app_id, device_id)
+ force_profile_compilation(app_id, device_id)
+
+
+def issue_key_event(key_event, device_id=None, sleep_in_seconds=1):
+ cmd = create_adb_cmd('shell input keyevent %s' % key_event, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ assert len(stdout) == 0
+ time.sleep(sleep_in_seconds)
+
+
+def launch_activity(app_id,
+ activity,
+ device_id=None,
+ intent_data_uri=None,
+ wait_for_activity_to_launch=False):
+ args = ['shell', 'am', 'start', '-n', '%s/%s' % (app_id, activity)]
+ if intent_data_uri:
+ args.extend(['-d', intent_data_uri])
+ if wait_for_activity_to_launch:
+ args.append('-W')
+ cmd = create_adb_cmd(args, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ assert stdout.startswith('Starting: Intent {')
+ expected_component = 'cmp=%s' % get_component_name(app_id, activity)
+ assert expected_component in stdout, \
+ 'was %s, expected %s' % (stdout, expected_component)
+ lines = stdout.splitlines()
+ result = {}
+ for line in lines:
+ if line.startswith('TotalTime: '):
+ total_time_str = line.removeprefix('TotalTime: ')
+ assert total_time_str.isdigit()
+ result['total_time'] = int(total_time_str)
+ assert not wait_for_activity_to_launch or 'total_time' in result, lines
+ return result
+
+
+def navigate_to_home_screen(device_id=None):
+ cmd = create_adb_cmd('shell input keyevent KEYCODE_HOME', device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
+
+def prepare_for_interaction_with_device(device_id=None, device_pin=None):
+ # Increase screen off timeout to avoid device screen turns off.
+ twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
+ previous_screen_off_timeout = get_screen_off_timeout(device_id)
+ set_screen_off_timeout(twenty_four_hours_in_millis, device_id)
+
+ # Unlock device.
+ unlock(device_id, device_pin)
+
+ teardown_options = {
+ 'previous_screen_off_timeout': previous_screen_off_timeout
+ }
+ return teardown_options
+
+
+def pull(source, target, device_id=None):
+ cmd = create_adb_cmd('pull %s %s' % (source, target), device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
+
+def root(device_id=None):
+ cmd = create_adb_cmd('root', device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
+
+def set_screen_off_timeout(screen_off_timeout_in_millis, device_id=None):
+ cmd = create_adb_cmd(
+ 'shell settings put system screen_off_timeout %i' %
+ screen_off_timeout_in_millis, device_id)
+ stdout = subprocess.check_output(cmd).decode('utf-8').strip()
+ assert len(stdout) == 0
+
+
+def start_logcat(device_id=None, format=None, filter=None, silent=False):
+ args = ['logcat']
+ if format:
+ args.extend(['--format', format])
+ if silent:
+ args.append('-s')
+ if filter:
+ args.append(filter)
+ cmd = create_adb_cmd(args, device_id)
+ logcat_process = subprocess.Popen(cmd,
+ bufsize=1024 * 1024,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ reader = ProcessReader(logcat_process)
+ reader.start()
+ return reader
+
+
+def stop_logcat(logcat_reader):
+ logcat_reader.stop()
+ logcat_reader.join()
+ return logcat_reader.lines
+
+
+def stop_app(app_id, device_id=None):
+ print('Shutting down %s' % app_id)
+ cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id)
+ subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
+
+
+def teardown_after_interaction_with_device(teardown_options, device_id=None):
+ # Reset screen off timeout.
+ set_screen_off_timeout(teardown_options['previous_screen_off_timeout'],
+ device_id)
+
+
+def toggle_screen(device_id=None):
+ issue_key_event('KEYCODE_POWER', device_id)
+
+
+def uninstall(app_id, device_id=None):
+ print('Uninstalling %s' % app_id)
+ cmd = create_adb_cmd('uninstall %s' % app_id, device_id)
process_result = subprocess.run(cmd, capture_output=True)
stdout = process_result.stdout.decode('utf-8')
stderr = process_result.stderr.decode('utf-8')
if process_result.returncode == 0:
- return
- print('Failed to install %s' % apks)
- print('Stdout: %s' % stdout)
- print('Stderr: %s' % stderr)
- print('Retrying...')
- raise Exception('Unable to install apks in %s attempts' % max_attempts)
+ assert 'Success' in stdout
+ elif stdout.startswith('cmd: Failure calling service package: Broken pipe'):
+ assert app_id == 'com.google.android.youtube'
+ print('Waiting after broken pipe')
+ time.sleep(15)
+ else:
+ expected_error = (
+ 'java.lang.IllegalArgumentException: Unknown package: %s' % app_id)
+ assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \
+ or expected_error in stderr, \
+ 'stdout: %s, stderr: %s' % (stdout, stderr)
-def install_bundle(bundle, device_id=None):
- print('Installing %s' % bundle)
- with utils.TempDir() as temp:
- apks = os.path.join(temp, 'Bundle.apks')
- build_apks_from_bundle(bundle, apks)
- install_apks(apks, device_id)
-
-def install_profile_using_adb(app_id, host_profile_path, device_id=None):
- device_profile_path = get_profile_path(app_id)
- cmd = create_adb_cmd('push %s %s' % (host_profile_path, device_profile_path))
- subprocess.check_call(cmd)
- stop_app(app_id, device_id)
- force_profile_compilation(app_id, device_id)
-
-def install_profile_using_profileinstaller(app_id, device_id=None):
- # This assumes that the profileinstaller library has been added to the app,
- # https://developer.android.com/jetpack/androidx/releases/profileinstaller.
- action = 'androidx.profileinstaller.action.INSTALL_PROFILE'
- component = '%s/androidx.profileinstaller.ProfileInstallReceiver' % app_id
- stdout = broadcast(action, component, device_id)
- assert len(stdout) == 2
- assert stdout[0] == ('Broadcasting: Intent { act=%s flg=0x400000 cmp=%s }' % (action, component))
- assert stdout[1] == 'Broadcast completed: result=1', stdout[1]
- stop_app(app_id, device_id)
- force_profile_compilation(app_id, device_id)
-
-def issue_key_event(key_event, device_id=None, sleep_in_seconds=1):
- cmd = create_adb_cmd('shell input keyevent %s' % key_event, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- assert len(stdout) == 0
- time.sleep(sleep_in_seconds)
-
-def launch_activity(
- app_id,
- activity,
- device_id=None,
- intent_data_uri=None,
- wait_for_activity_to_launch=False):
- args = ['shell', 'am', 'start', '-n', '%s/%s' % (app_id, activity)]
- if intent_data_uri:
- args.extend(['-d', intent_data_uri])
- if wait_for_activity_to_launch:
- args.append('-W')
- cmd = create_adb_cmd(args, device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- assert stdout.startswith('Starting: Intent {')
- expected_component = 'cmp=%s' % get_component_name(app_id, activity)
- assert expected_component in stdout, \
- 'was %s, expected %s' % (stdout, expected_component)
- lines = stdout.splitlines()
- result = {}
- for line in lines:
- if line.startswith('TotalTime: '):
- total_time_str = line.removeprefix('TotalTime: ')
- assert total_time_str.isdigit()
- result['total_time'] = int(total_time_str)
- assert not wait_for_activity_to_launch or 'total_time' in result, lines
- return result
-
-def navigate_to_home_screen(device_id=None):
- cmd = create_adb_cmd('shell input keyevent KEYCODE_HOME', device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
-
-def prepare_for_interaction_with_device(device_id=None, device_pin=None):
- # Increase screen off timeout to avoid device screen turns off.
- twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
- previous_screen_off_timeout = get_screen_off_timeout(device_id)
- set_screen_off_timeout(twenty_four_hours_in_millis, device_id)
-
- # Unlock device.
- unlock(device_id, device_pin)
-
- teardown_options = {
- 'previous_screen_off_timeout': previous_screen_off_timeout
- }
- return teardown_options
-
-def pull(source, target, device_id=None):
- cmd = create_adb_cmd('pull %s %s' % (source, target), device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
-
-def root(device_id=None):
- cmd = create_adb_cmd('root', device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
-
-def set_screen_off_timeout(screen_off_timeout_in_millis, device_id=None):
- cmd = create_adb_cmd(
- 'shell settings put system screen_off_timeout %i'
- % screen_off_timeout_in_millis,
- device_id)
- stdout = subprocess.check_output(cmd).decode('utf-8').strip()
- assert len(stdout) == 0
-
-def start_logcat(device_id=None, format=None, filter=None, silent=False):
- args = ['logcat']
- if format:
- args.extend(['--format', format])
- if silent:
- args.append('-s')
- if filter:
- args.append(filter)
- cmd = create_adb_cmd(args, device_id)
- logcat_process = subprocess.Popen(
- cmd, bufsize=1024*1024, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- reader = ProcessReader(logcat_process)
- reader.start()
- return reader
-
-def stop_logcat(logcat_reader):
- logcat_reader.stop()
- logcat_reader.join()
- return logcat_reader.lines
-
-def stop_app(app_id, device_id=None):
- print('Shutting down %s' % app_id)
- cmd = create_adb_cmd('shell am force-stop %s' % app_id, device_id)
- subprocess.check_call(cmd, stdout=DEVNULL, stderr=DEVNULL)
-
-def teardown_after_interaction_with_device(teardown_options, device_id=None):
- # Reset screen off timeout.
- set_screen_off_timeout(
- teardown_options['previous_screen_off_timeout'],
- device_id)
-
-def toggle_screen(device_id=None):
- issue_key_event('KEYCODE_POWER', device_id)
-
-def uninstall(app_id, device_id=None):
- print('Uninstalling %s' % app_id)
- cmd = create_adb_cmd('uninstall %s' % app_id, device_id)
- process_result = subprocess.run(cmd, capture_output=True)
- stdout = process_result.stdout.decode('utf-8')
- stderr = process_result.stderr.decode('utf-8')
- if process_result.returncode == 0:
- assert 'Success' in stdout
- elif stdout.startswith('cmd: Failure calling service package: Broken pipe'):
- assert app_id == 'com.google.android.youtube'
- print('Waiting after broken pipe')
- time.sleep(15)
- else:
- expected_error = (
- 'java.lang.IllegalArgumentException: Unknown package: %s' % app_id)
- assert 'Failure [DELETE_FAILED_INTERNAL_ERROR]' in stdout \
- or expected_error in stderr, \
- 'stdout: %s, stderr: %s' % (stdout, stderr)
def unlock(device_id=None, device_pin=None):
- ensure_screen_on(device_id)
- screen_state = get_screen_state(device_id)
- assert screen_state.is_on(), 'was %s' % screen_state
- if screen_state.is_on_and_locked():
- if device_pin is not None:
- raise NotImplementedError('Device unlocking with pin not implemented')
- issue_key_event('KEYCODE_MENU', device_id)
+ ensure_screen_on(device_id)
screen_state = get_screen_state(device_id)
- assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state
+ assert screen_state.is_on(), 'was %s' % screen_state
+ if screen_state.is_on_and_locked():
+ if device_pin is not None:
+ raise NotImplementedError(
+ 'Device unlocking with pin not implemented')
+ issue_key_event('KEYCODE_MENU', device_id)
+ screen_state = get_screen_state(device_id)
+ assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state
+
def parse_options(argv):
- result = argparse.ArgumentParser(description='Run adb utils.')
- result.add_argument('--capture-screen',
- help='Capture screen to given file')
- result.add_argument('--device-id',
- help='Device id (e.g., emulator-5554).')
- result.add_argument('--device-pin',
- help='Device pin code (e.g., 1234)')
- result.add_argument('--ensure-screen-off',
- help='Ensure screen off',
- action='store_true',
- default=False)
- result.add_argument('--get-screen-state',
- help='Get screen state',
- action='store_true',
- default=False)
- result.add_argument('--unlock',
- help='Unlock device',
- action='store_true',
- default=False)
- options, args = result.parse_known_args(argv)
- return options, args
+ result = argparse.ArgumentParser(description='Run adb utils.')
+ result.add_argument('--capture-screen', help='Capture screen to given file')
+ result.add_argument('--device-id', help='Device id (e.g., emulator-5554).')
+ result.add_argument('--device-pin', help='Device pin code (e.g., 1234)')
+ result.add_argument('--ensure-screen-off',
+ help='Ensure screen off',
+ action='store_true',
+ default=False)
+ result.add_argument('--get-screen-state',
+ help='Get screen state',
+ action='store_true',
+ default=False)
+ result.add_argument('--unlock',
+ help='Unlock device',
+ action='store_true',
+ default=False)
+ options, args = result.parse_known_args(argv)
+ return options, args
+
def main(argv):
- (options, args) = parse_options(argv)
- if options.capture_screen:
- capture_screen(options.capture_screen, options.device_id)
- if options.ensure_screen_off:
- ensure_screen_off(options.device_id)
- elif options.get_screen_state:
- print(get_screen_state(options.device_id))
- elif options.unlock:
- unlock(options.device_id, options.device_pin)
+ (options, args) = parse_options(argv)
+ if options.capture_screen:
+ capture_screen(options.capture_screen, options.device_id)
+ if options.ensure_screen_off:
+ ensure_screen_off(options.device_id)
+ elif options.get_screen_state:
+ print(get_screen_state(options.device_id))
+ elif options.unlock:
+ unlock(options.device_id, options.device_pin)
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/generate_startup_descriptors.py b/tools/startup/generate_startup_descriptors.py
index adae239..211be89 100755
--- a/tools/startup/generate_startup_descriptors.py
+++ b/tools/startup/generate_startup_descriptors.py
@@ -11,445 +11,474 @@
import sys
import time
+
class Device:
- def __init__(self, device_id, device_pin):
- self.device_id = device_id
- self.device_pin = device_pin
+ def __init__(self, device_id, device_pin):
+ self.device_id = device_id
+ self.device_pin = device_pin
+
def extend_startup_descriptors(startup_descriptors, iteration, device, options):
- (logcat, profile, profile_classes_and_methods) = \
- generate_startup_profile(device, options)
- if options.logcat:
- write_tmp_logcat(logcat, iteration, options)
- current_startup_descriptors = get_r8_startup_descriptors_from_logcat(
- logcat, options)
- else:
- write_tmp_profile(profile, iteration, options)
- write_tmp_profile_classes_and_methods(
- profile_classes_and_methods, iteration, options)
- current_startup_descriptors = \
- profile_utils.transform_art_profile_to_r8_startup_list(
- profile_classes_and_methods, options.generalize_synthetics)
- 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)
- if options.out is not None:
- print(
- 'Found %i new startup descriptors in iteration %i'
- % (number_of_new_startup_descriptors, iteration + 1))
- return new_startup_descriptors
+ (logcat, profile, profile_classes_and_methods) = \
+ generate_startup_profile(device, options)
+ if options.logcat:
+ write_tmp_logcat(logcat, iteration, options)
+ current_startup_descriptors = get_r8_startup_descriptors_from_logcat(
+ logcat, options)
+ else:
+ write_tmp_profile(profile, iteration, options)
+ write_tmp_profile_classes_and_methods(profile_classes_and_methods,
+ iteration, options)
+ current_startup_descriptors = \
+ profile_utils.transform_art_profile_to_r8_startup_list(
+ profile_classes_and_methods, options.generalize_synthetics)
+ 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)
+ if options.out is not None:
+ print('Found %i new startup descriptors in iteration %i' %
+ (number_of_new_startup_descriptors, iteration + 1))
+ return new_startup_descriptors
+
def generate_startup_profile(device, 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, device.device_id)
- profile = adb_utils.get_profile_data(options.app_id, device.device_id)
- profile_classes_and_methods = \
- adb_utils.get_classes_and_methods_from_app_profile(
- options.app_id, device.device_id)
- else:
- # Unlock device.
- tear_down_options = adb_utils.prepare_for_interaction_with_device(
- device.device_id, device.device_pin)
-
- logcat_process = None
- if options.logcat:
- # Clear logcat and start capturing logcat.
- adb_utils.clear_logcat(device.device_id)
- logcat_process = adb_utils.start_logcat(
- device.device_id, format='tag', filter='R8:I ActivityTaskManager:I *:S')
+ 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, device.device_id)
+ profile = adb_utils.get_profile_data(options.app_id, device.device_id)
+ profile_classes_and_methods = \
+ adb_utils.get_classes_and_methods_from_app_profile(
+ options.app_id, device.device_id)
else:
- # Clear existing profile data.
- adb_utils.clear_profile_data(options.app_id, device.device_id)
+ # Unlock device.
+ tear_down_options = adb_utils.prepare_for_interaction_with_device(
+ device.device_id, device.device_pin)
- # Launch activity to generate startup profile on device.
- adb_utils.launch_activity(
- options.app_id, options.main_activity, device.device_id)
+ logcat_process = None
+ if options.logcat:
+ # Clear logcat and start capturing logcat.
+ adb_utils.clear_logcat(device.device_id)
+ logcat_process = adb_utils.start_logcat(
+ device.device_id,
+ format='tag',
+ filter='R8:I ActivityTaskManager:I *:S')
+ else:
+ # Clear existing profile data.
+ adb_utils.clear_profile_data(options.app_id, device.device_id)
- # Wait for activity startup.
- time.sleep(options.startup_duration)
+ # Launch activity to generate startup profile on device.
+ adb_utils.launch_activity(options.app_id, options.main_activity,
+ device.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, device.device_id)
- profile = adb_utils.get_profile_data(options.app_id, device.device_id)
- profile_classes_and_methods = \
- adb_utils.get_classes_and_methods_from_app_profile(
- options.app_id, device.device_id)
+ # Wait for activity startup.
+ time.sleep(options.startup_duration)
- # Shutdown app.
- adb_utils.stop_app(options.app_id, device.device_id)
- adb_utils.teardown_after_interaction_with_device(
- tear_down_options, device.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, device.device_id)
+ profile = adb_utils.get_profile_data(options.app_id,
+ device.device_id)
+ profile_classes_and_methods = \
+ adb_utils.get_classes_and_methods_from_app_profile(
+ options.app_id, device.device_id)
- return (logcat, profile, profile_classes_and_methods)
+ # Shutdown app.
+ adb_utils.stop_app(options.app_id, device.device_id)
+ adb_utils.teardown_after_interaction_with_device(
+ tear_down_options, device.device_id)
+
+ return (logcat, profile, profile_classes_and_methods)
+
def get_r8_startup_descriptors_from_logcat(logcat, options):
- post_startup = False
- startup_descriptors = {}
- for line in logcat:
- line_elements = parse_logcat_line(line)
- if line_elements is None:
- continue
- (priority, tag, message) = line_elements
- if tag == 'ActivityTaskManager':
- if message.startswith('START') \
- or message.startswith('Activity pause timeout for') \
- or message.startswith('Activity top resumed state loss timeout for') \
- or message.startswith('Force removing') \
- or message.startswith(
- 'Launch timeout has expired, giving up wake lock!'):
- continue
- elif message.startswith('Displayed %s/' % options.app_id):
- print('Entering post startup: %s' % message)
- post_startup = True
- continue
- elif tag == 'R8':
- if is_startup_descriptor(message):
- startup_descriptors[message] = {
- 'conditional_startup': False,
- 'hot': False,
- 'post_startup': post_startup,
- 'startup': True
- }
- continue
- # Reaching here means we didn't expect this line.
- report_unrecognized_logcat_line(line)
- return startup_descriptors
+ post_startup = False
+ startup_descriptors = {}
+ for line in logcat:
+ line_elements = parse_logcat_line(line)
+ if line_elements is None:
+ continue
+ (priority, tag, message) = line_elements
+ if tag == 'ActivityTaskManager':
+ if message.startswith('START') \
+ or message.startswith('Activity pause timeout for') \
+ or message.startswith('Activity top resumed state loss timeout for') \
+ or message.startswith('Force removing') \
+ or message.startswith(
+ 'Launch timeout has expired, giving up wake lock!'):
+ continue
+ elif message.startswith('Displayed %s/' % options.app_id):
+ print('Entering post startup: %s' % message)
+ post_startup = True
+ continue
+ elif tag == 'R8':
+ if is_startup_descriptor(message):
+ startup_descriptors[message] = {
+ 'conditional_startup': False,
+ 'hot': False,
+ 'post_startup': post_startup,
+ 'startup': True
+ }
+ continue
+ # Reaching here means we didn't expect this line.
+ report_unrecognized_logcat_line(line)
+ return startup_descriptors
+
def is_startup_descriptor(string):
- # The descriptor should start with the holder (possibly prefixed with 'S').
- if not any(string.startswith('%sL' % flags) for flags in ['', 'S']):
- return False
- # The descriptor should end with ';', a primitive type, or void.
- if not string.endswith(';') \
- and not any(string.endswith(c) for c in get_primitive_descriptors()) \
- and not string.endswith('V'):
- return False
- return True
+ # The descriptor should start with the holder (possibly prefixed with 'S').
+ if not any(string.startswith('%sL' % flags) for flags in ['', 'S']):
+ return False
+ # The descriptor should end with ';', a primitive type, or void.
+ if not string.endswith(';') \
+ and not any(string.endswith(c) for c in get_primitive_descriptors()) \
+ and not string.endswith('V'):
+ return False
+ return True
+
def get_primitive_descriptors():
- return ['Z', 'B', 'S', 'C', 'I', 'F', 'J', 'D']
+ return ['Z', 'B', 'S', 'C', 'I', 'F', 'J', 'D']
+
def parse_logcat_line(line):
- if line == '--------- beginning of kernel':
- return None
- if line == '--------- beginning of main':
- return None
- if line == '--------- beginning of system':
- return None
+ if line == '--------- beginning of kernel':
+ return None
+ if line == '--------- beginning of main':
+ return None
+ if line == '--------- beginning of system':
+ return None
- priority = None
- tag = None
+ priority = None
+ tag = None
- try:
- priority_end = line.index('/')
- priority = line[0:priority_end]
- line = line[priority_end + 1:]
- except ValueError:
- return report_unrecognized_logcat_line(line)
+ try:
+ priority_end = line.index('/')
+ priority = line[0:priority_end]
+ line = line[priority_end + 1:]
+ except ValueError:
+ return report_unrecognized_logcat_line(line)
- try:
- tag_end = line.index(':')
- tag = line[0:tag_end].strip()
- line = line[tag_end + 1 :]
- except ValueError:
- return report_unrecognized_logcat_line(line)
+ try:
+ tag_end = line.index(':')
+ tag = line[0:tag_end].strip()
+ line = line[tag_end + 1:]
+ except ValueError:
+ return report_unrecognized_logcat_line(line)
- message = line.strip()
- return (priority, tag, message)
+ message = line.strip()
+ return (priority, tag, message)
+
def report_unrecognized_logcat_line(line):
- print('Unrecognized line in logcat: %s' % line)
+ print('Unrecognized line in logcat: %s' % line)
-def add_r8_startup_descriptors(old_startup_descriptors, startup_descriptors_to_add):
- new_startup_descriptors = {}
- if len(old_startup_descriptors) == 0:
- for startup_descriptor, flags in startup_descriptors_to_add.items():
- new_startup_descriptors[startup_descriptor] = flags.copy()
- else:
- # Merge the new startup descriptors with the old descriptors in a way so
- # that new startup descriptors are added next to the startup descriptors
- # they are close to in the newly generated list of startup descriptors.
- startup_descriptors_to_add_after_key = {}
- startup_descriptors_to_add_in_the_end = {}
- closest_seen_startup_descriptor = None
- for startup_descriptor, flags in startup_descriptors_to_add.items():
- if startup_descriptor in old_startup_descriptors:
- closest_seen_startup_descriptor = startup_descriptor
- else:
- if closest_seen_startup_descriptor is None:
- # Insert this new startup descriptor in the end of the result.
- startup_descriptors_to_add_in_the_end[startup_descriptor] = flags
- else:
- # Record that this should be inserted after
- # closest_seen_startup_descriptor.
- pending_startup_descriptors = \
- startup_descriptors_to_add_after_key.setdefault(
- closest_seen_startup_descriptor, {})
- pending_startup_descriptors[startup_descriptor] = flags
- for startup_descriptor, flags in old_startup_descriptors.items():
- # Merge flags if this also exists in startup_descriptors_to_add.
- if startup_descriptor in startup_descriptors_to_add:
- merged_flags = flags.copy()
- other_flags = startup_descriptors_to_add[startup_descriptor]
- assert not other_flags['conditional_startup']
- merged_flags['hot'] = \
- merged_flags['hot'] or other_flags['hot']
- merged_flags['startup'] = \
- merged_flags['startup'] or other_flags['startup']
- merged_flags['post_startup'] = \
- merged_flags['post_startup'] or other_flags['post_startup']
- new_startup_descriptors[startup_descriptor] = merged_flags
- else:
- new_startup_descriptors[startup_descriptor] = flags.copy()
- # Flush startup descriptors that followed this item in the new trace.
- if startup_descriptor in startup_descriptors_to_add_after_key:
- pending_startup_descriptors = \
- startup_descriptors_to_add_after_key[startup_descriptor]
- for pending_startup_descriptor, pending_flags \
- in pending_startup_descriptors.items():
- new_startup_descriptors[pending_startup_descriptor] = \
- pending_flags.copy()
- # Insert remaining new startup descriptors in the end.
- for startup_descriptor, flags \
- in startup_descriptors_to_add_in_the_end.items():
- assert startup_descriptor not in new_startup_descriptors
- new_startup_descriptors[startup_descriptor] = flags.copy()
- return new_startup_descriptors
+
+def add_r8_startup_descriptors(old_startup_descriptors,
+ startup_descriptors_to_add):
+ new_startup_descriptors = {}
+ if len(old_startup_descriptors) == 0:
+ for startup_descriptor, flags in startup_descriptors_to_add.items():
+ new_startup_descriptors[startup_descriptor] = flags.copy()
+ else:
+ # Merge the new startup descriptors with the old descriptors in a way so
+ # that new startup descriptors are added next to the startup descriptors
+ # they are close to in the newly generated list of startup descriptors.
+ startup_descriptors_to_add_after_key = {}
+ startup_descriptors_to_add_in_the_end = {}
+ closest_seen_startup_descriptor = None
+ for startup_descriptor, flags in startup_descriptors_to_add.items():
+ if startup_descriptor in old_startup_descriptors:
+ closest_seen_startup_descriptor = startup_descriptor
+ else:
+ if closest_seen_startup_descriptor is None:
+ # Insert this new startup descriptor in the end of the result.
+ startup_descriptors_to_add_in_the_end[
+ startup_descriptor] = flags
+ else:
+ # Record that this should be inserted after
+ # closest_seen_startup_descriptor.
+ pending_startup_descriptors = \
+ startup_descriptors_to_add_after_key.setdefault(
+ closest_seen_startup_descriptor, {})
+ pending_startup_descriptors[startup_descriptor] = flags
+ for startup_descriptor, flags in old_startup_descriptors.items():
+ # Merge flags if this also exists in startup_descriptors_to_add.
+ if startup_descriptor in startup_descriptors_to_add:
+ merged_flags = flags.copy()
+ other_flags = startup_descriptors_to_add[startup_descriptor]
+ assert not other_flags['conditional_startup']
+ merged_flags['hot'] = \
+ merged_flags['hot'] or other_flags['hot']
+ merged_flags['startup'] = \
+ merged_flags['startup'] or other_flags['startup']
+ merged_flags['post_startup'] = \
+ merged_flags['post_startup'] or other_flags['post_startup']
+ new_startup_descriptors[startup_descriptor] = merged_flags
+ else:
+ new_startup_descriptors[startup_descriptor] = flags.copy()
+ # Flush startup descriptors that followed this item in the new trace.
+ if startup_descriptor in startup_descriptors_to_add_after_key:
+ pending_startup_descriptors = \
+ startup_descriptors_to_add_after_key[startup_descriptor]
+ for pending_startup_descriptor, pending_flags \
+ in pending_startup_descriptors.items():
+ new_startup_descriptors[pending_startup_descriptor] = \
+ pending_flags.copy()
+ # Insert remaining new startup descriptors in the end.
+ for startup_descriptor, flags \
+ in startup_descriptors_to_add_in_the_end.items():
+ assert startup_descriptor not in new_startup_descriptors
+ new_startup_descriptors[startup_descriptor] = flags.copy()
+ return new_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)
+ 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_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')
+ 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')
+ 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, flags) = item
- 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.items(),
- iteration,
- options,
- 'profile.txt',
- item_to_string)
+
+def write_tmp_profile_classes_and_methods(profile_classes_and_methods,
+ iteration, options):
+
+ def item_to_string(item):
+ (descriptor, flags) = item
+ 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.items(), iteration,
+ options, 'profile.txt', item_to_string)
+
def write_tmp_startup_descriptors(startup_descriptors, iteration, options):
- lines = [
- startup_descriptor_to_string(startup_descriptor, flags)
- for startup_descriptor, flags in startup_descriptors.items()]
- write_tmp_textual_artifact(
- lines, iteration, options, 'startup-descriptors.txt')
+ lines = [
+ startup_descriptor_to_string(startup_descriptor, flags)
+ for startup_descriptor, flags in startup_descriptors.items()
+ ]
+ write_tmp_textual_artifact(lines, iteration, options,
+ 'startup-descriptors.txt')
+
def startup_descriptor_to_string(startup_descriptor, flags):
- result = ''
- if flags['hot']:
- result += 'H'
- if flags['startup']:
- result += 'S'
- if flags['post_startup']:
- result += 'P'
- result += startup_descriptor
- return result
+ result = ''
+ if flags['hot']:
+ result += 'H'
+ if flags['startup']:
+ result += 'S'
+ if flags['post_startup']:
+ result += 'P'
+ result += startup_descriptor
+ return result
+
def should_include_startup_descriptor(descriptor, flags, options):
- if flags.get('conditional_startup') \
- and not options.include_conditional_startup:
- return False
- if flags.get('post_startup') \
- and not flags.get('startup') \
- and not options.include_post_startup:
- return False
- return True
+ if flags.get('conditional_startup') \
+ and not options.include_conditional_startup:
+ return False
+ if flags.get('post_startup') \
+ and not flags.get('startup') \
+ and not options.include_post_startup:
+ return False
+ return True
+
def parse_options(argv):
- result = argparse.ArgumentParser(
- description='Generate a perfetto trace file.')
- result.add_argument('--apk',
- help='Path to the .apk')
- result.add_argument('--apks',
- help='Path to the .apks')
- result.add_argument('--app-id',
- help='The application ID of interest',
- required=True)
- result.add_argument('--bundle',
- help='Path to the .aab')
- result.add_argument('--device-id',
- help='Device id (e.g., emulator-5554).',
- action='append')
- result.add_argument('--device-pin',
- help='Device pin code (e.g., 1234)',
- action='append')
- result.add_argument('--generalize-synthetics',
- help='Whether synthetics should be abstracted into their '
- 'synthetic contexts',
- action='store_true',
- default=False)
- result.add_argument('--grant-post-notification-permission',
- help='Grants the android.permission.POST_NOTIFICATIONS '
- 'permission before launching the app',
- default=False,
- action='store_true')
- result.add_argument('--logcat',
- action='store_true',
- default=False)
- result.add_argument('--include-conditional-startup',
- help='Include conditional startup classes and methods in '
- 'the R8 startup descriptors',
- action='store_true',
- default=False)
- result.add_argument('--include-post-startup',
- help='Include post startup classes and methods in the R8 '
- 'startup descriptors',
- action='store_true',
- default=False)
- result.add_argument('--iterations',
- help='Number of profiles to generate',
- default=1,
- type=int)
- result.add_argument('--main-activity',
- help='Main activity class name')
- result.add_argument('--out',
- help='File where to store startup descriptors (defaults '
- 'to stdout)')
- result.add_argument('--startup-duration',
- 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',
- action='store_true',
- default=False)
- result.add_argument('--until-stable-iterations',
- help='Number of times that profile generation must must '
- 'not find new startup descriptors before exiting',
- default=1,
- type=int)
- result.add_argument('--use-existing-profile',
- help='Do not launch app to generate startup profile',
- action='store_true',
- default=False)
- options, args = result.parse_known_args(argv)
+ result = argparse.ArgumentParser(
+ description='Generate a perfetto trace file.')
+ result.add_argument('--apk', help='Path to the .apk')
+ result.add_argument('--apks', help='Path to the .apks')
+ result.add_argument('--app-id',
+ help='The application ID of interest',
+ required=True)
+ result.add_argument('--bundle', help='Path to the .aab')
+ result.add_argument('--device-id',
+ help='Device id (e.g., emulator-5554).',
+ action='append')
+ result.add_argument('--device-pin',
+ help='Device pin code (e.g., 1234)',
+ action='append')
+ result.add_argument(
+ '--generalize-synthetics',
+ help='Whether synthetics should be abstracted into their '
+ 'synthetic contexts',
+ action='store_true',
+ default=False)
+ result.add_argument('--grant-post-notification-permission',
+ help='Grants the android.permission.POST_NOTIFICATIONS '
+ 'permission before launching the app',
+ default=False,
+ action='store_true')
+ result.add_argument('--logcat', action='store_true', default=False)
+ result.add_argument(
+ '--include-conditional-startup',
+ help='Include conditional startup classes and methods in '
+ 'the R8 startup descriptors',
+ action='store_true',
+ default=False)
+ result.add_argument(
+ '--include-post-startup',
+ help='Include post startup classes and methods in the R8 '
+ 'startup descriptors',
+ action='store_true',
+ default=False)
+ result.add_argument('--iterations',
+ help='Number of profiles to generate',
+ default=1,
+ type=int)
+ result.add_argument('--main-activity', help='Main activity class name')
+ result.add_argument(
+ '--out',
+ help='File where to store startup descriptors (defaults '
+ 'to stdout)')
+ result.add_argument('--startup-duration',
+ 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',
+ action='store_true',
+ default=False)
+ result.add_argument(
+ '--until-stable-iterations',
+ help='Number of times that profile generation must must '
+ 'not find new startup descriptors before exiting',
+ default=1,
+ type=int)
+ result.add_argument('--use-existing-profile',
+ help='Do not launch app to generate startup profile',
+ action='store_true',
+ default=False)
+ options, args = result.parse_known_args(argv)
- # Read the device pins.
- device_pins = options.device_pin or []
- del options.device_pin
+ # Read the device pins.
+ device_pins = options.device_pin or []
+ del options.device_pin
- # Convert the device ids and pins into a list of devices.
- options.devices = []
- if options.device_id is None:
- # Assume a single device is attached.
- options.devices.append(
- Device(None, device_pins[0] if len(device_pins) > 0 else None))
- else:
- for i in range(len(options.device_id)):
- device_id = options.device_id[i]
- device_pin = device_pins[i] if i < len(device_pins) else None
- options.devices.append(Device(device_id, device_pin))
- del options.device_id
+ # Convert the device ids and pins into a list of devices.
+ options.devices = []
+ if options.device_id is None:
+ # Assume a single device is attached.
+ options.devices.append(
+ Device(None, device_pins[0] if len(device_pins) > 0 else None))
+ else:
+ for i in range(len(options.device_id)):
+ device_id = options.device_id[i]
+ device_pin = device_pins[i] if i < len(device_pins) else None
+ options.devices.append(Device(device_id, device_pin))
+ del options.device_id
- paths = [
- path for path in [options.apk, options.apks, options.bundle]
- if path is not None]
- assert len(paths) <= 1, 'Expected at most one .apk, .apks, or .aab file.'
- assert options.main_activity is not None or options.use_existing_profile, \
- 'Argument --main-activity is required except when running with ' \
- '--use-existing-profile.'
+ paths = [
+ path for path in [options.apk, options.apks, options.bundle]
+ if path is not None
+ ]
+ assert len(paths) <= 1, 'Expected at most one .apk, .apks, or .aab file.'
+ assert options.main_activity is not None or options.use_existing_profile, \
+ 'Argument --main-activity is required except when running with ' \
+ '--use-existing-profile.'
- return options, args
+ return options, args
+
def run_on_device(device, options, startup_descriptors):
- adb_utils.root(device.device_id)
- if options.apk:
- adb_utils.uninstall(options.app_id, device.device_id)
- adb_utils.install(options.apk, device.device_id)
- elif options.apks:
- adb_utils.uninstall(options.app_id, device.device_id)
- adb_utils.install_apks(options.apks, device.device_id)
- elif options.bundle:
- adb_utils.uninstall(options.app_id, device.device_id)
- adb_utils.install_bundle(options.bundle, device.device_id)
- # Grant notifications.
- if options.grant_post_notification_permission:
- adb_utils.grant(
- options.app_id,
- 'android.permission.POST_NOTIFICATIONS',
- device.device_id)
- if options.until_stable:
- iteration = 0
- stable_iterations = 0
- while True:
- old_startup_descriptors = startup_descriptors
- startup_descriptors = extend_startup_descriptors(
- old_startup_descriptors, iteration, device, options)
- diff = len(startup_descriptors) - len(old_startup_descriptors)
- if diff == 0:
- stable_iterations = stable_iterations + 1
- if stable_iterations == options.until_stable_iterations:
- break
- else:
+ adb_utils.root(device.device_id)
+ if options.apk:
+ adb_utils.uninstall(options.app_id, device.device_id)
+ adb_utils.install(options.apk, device.device_id)
+ elif options.apks:
+ adb_utils.uninstall(options.app_id, device.device_id)
+ adb_utils.install_apks(options.apks, device.device_id)
+ elif options.bundle:
+ adb_utils.uninstall(options.app_id, device.device_id)
+ adb_utils.install_bundle(options.bundle, device.device_id)
+ # Grant notifications.
+ if options.grant_post_notification_permission:
+ adb_utils.grant(options.app_id, 'android.permission.POST_NOTIFICATIONS',
+ device.device_id)
+ if options.until_stable:
+ iteration = 0
stable_iterations = 0
- iteration = iteration + 1
- else:
- for iteration in range(options.iterations):
- startup_descriptors = extend_startup_descriptors(
- startup_descriptors, iteration, device, options)
- return startup_descriptors
+ while True:
+ old_startup_descriptors = startup_descriptors
+ startup_descriptors = extend_startup_descriptors(
+ old_startup_descriptors, iteration, device, options)
+ diff = len(startup_descriptors) - len(old_startup_descriptors)
+ if diff == 0:
+ stable_iterations = stable_iterations + 1
+ if stable_iterations == options.until_stable_iterations:
+ break
+ else:
+ stable_iterations = 0
+ iteration = iteration + 1
+ else:
+ for iteration in range(options.iterations):
+ startup_descriptors = extend_startup_descriptors(
+ startup_descriptors, iteration, device, options)
+ return startup_descriptors
+
def main(argv):
- (options, args) = parse_options(argv)
- startup_descriptors = {}
- for device in options.devices:
- startup_descriptors = run_on_device(device, options, startup_descriptors)
- if options.out is not None:
- with open(options.out, 'w') as f:
- for startup_descriptor, flags in startup_descriptors.items():
- if should_include_startup_descriptor(startup_descriptor, flags, options):
- f.write(startup_descriptor_to_string(startup_descriptor, flags))
- f.write('\n')
- else:
- for startup_descriptor, flags in startup_descriptors.items():
- if should_include_startup_descriptor(startup_descriptor, flags, options):
- print(startup_descriptor_to_string(startup_descriptor, flags))
+ (options, args) = parse_options(argv)
+ startup_descriptors = {}
+ for device in options.devices:
+ startup_descriptors = run_on_device(device, options,
+ startup_descriptors)
+ if options.out is not None:
+ with open(options.out, 'w') as f:
+ for startup_descriptor, flags in startup_descriptors.items():
+ if should_include_startup_descriptor(startup_descriptor, flags,
+ options):
+ f.write(
+ startup_descriptor_to_string(startup_descriptor, flags))
+ f.write('\n')
+ else:
+ for startup_descriptor, flags in startup_descriptors.items():
+ if should_include_startup_descriptor(startup_descriptor, flags,
+ options):
+ print(startup_descriptor_to_string(startup_descriptor, flags))
+
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/instrument.py b/tools/startup/instrument.py
index ecefe55..9752670 100755
--- a/tools/startup/instrument.py
+++ b/tools/startup/instrument.py
@@ -18,117 +18,126 @@
import utils
import zip_utils
+
def parse_options(argv):
- result = argparse.ArgumentParser(
- description='Instrument the dex files of a given apk to print what is '
- 'executed.')
- result.add_argument('--apk',
- help='Path to the .apk',
- required=True)
- result.add_argument('--dex-files',
- action='append',
- help='Name of dex files to instrument')
- result.add_argument('--discard',
- action='append',
- help='Name of dex files to discard')
- result.add_argument('--out',
- help='Destination of resulting apk',
- required=True)
- options, args = result.parse_known_args(argv)
- return options, args
+ result = argparse.ArgumentParser(
+ description='Instrument the dex files of a given apk to print what is '
+ 'executed.')
+ result.add_argument('--apk', help='Path to the .apk', required=True)
+ result.add_argument('--dex-files',
+ action='append',
+ help='Name of dex files to instrument')
+ result.add_argument('--discard',
+ action='append',
+ help='Name of dex files to discard')
+ result.add_argument('--out',
+ help='Destination of resulting apk',
+ required=True)
+ options, args = result.parse_known_args(argv)
+ return options, args
+
def add_instrumented_dex(dex_file, instrumented_dex_index, instrumented_dir):
- dex_name = get_dex_name(instrumented_dex_index)
- destination = os.path.join(instrumented_dir, dex_name)
- shutil.move(dex_file, destination)
+ dex_name = get_dex_name(instrumented_dex_index)
+ destination = os.path.join(instrumented_dir, dex_name)
+ shutil.move(dex_file, destination)
+
def get_dex_name(dex_index):
- assert dex_index > 0
- return 'classes.dex' if dex_index == 1 else ('classes%s.dex' % dex_index)
+ assert dex_index > 0
+ return 'classes.dex' if dex_index == 1 else ('classes%s.dex' % dex_index)
-def instrument_dex_file(dex_file, include_instrumentation_server, options, tmp_dir):
- d8_cmd = [
- 'java',
- '-cp', utils.R8_JAR,
- '-Dcom.android.tools.r8.startup.instrumentation.instrument=1',
- '-Dcom.android.tools.r8.startup.instrumentation.instrumentationtag=R8']
- if not include_instrumentation_server:
- # We avoid injecting the InstrumentationServer by specifying it should only
- # be added if foo.bar.Baz is in the program.
- d8_cmd.append(
- '-Dcom.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext=foo.bar.Baz')
- d8_cmd.extend([
- 'com.android.tools.r8.D8',
- '--min-api', str(apk_utils.get_min_api(options.apk)),
- '--output', tmp_dir,
- '--release',
- dex_file])
- subprocess.check_call(d8_cmd)
- instrumented_dex_files = []
- instrumented_dex_index = 1
- while True:
- instrumented_dex_name = get_dex_name(instrumented_dex_index)
- instrumented_dex_file = os.path.join(tmp_dir, instrumented_dex_name)
- if not os.path.exists(instrumented_dex_file):
- break
- instrumented_dex_files.append(instrumented_dex_file)
- instrumented_dex_index = instrumented_dex_index + 1
- assert len(instrumented_dex_files) > 0
- return instrumented_dex_files
+
+def instrument_dex_file(dex_file, include_instrumentation_server, options,
+ tmp_dir):
+ d8_cmd = [
+ 'java', '-cp', utils.R8_JAR,
+ '-Dcom.android.tools.r8.startup.instrumentation.instrument=1',
+ '-Dcom.android.tools.r8.startup.instrumentation.instrumentationtag=R8'
+ ]
+ if not include_instrumentation_server:
+ # We avoid injecting the InstrumentationServer by specifying it should only
+ # be added if foo.bar.Baz is in the program.
+ d8_cmd.append(
+ '-Dcom.android.tools.r8.startup.instrumentation.instrumentationserversyntheticcontext=foo.bar.Baz'
+ )
+ d8_cmd.extend([
+ 'com.android.tools.r8.D8', '--min-api',
+ str(apk_utils.get_min_api(options.apk)), '--output', tmp_dir,
+ '--release', dex_file
+ ])
+ subprocess.check_call(d8_cmd)
+ instrumented_dex_files = []
+ instrumented_dex_index = 1
+ while True:
+ instrumented_dex_name = get_dex_name(instrumented_dex_index)
+ instrumented_dex_file = os.path.join(tmp_dir, instrumented_dex_name)
+ if not os.path.exists(instrumented_dex_file):
+ break
+ instrumented_dex_files.append(instrumented_dex_file)
+ instrumented_dex_index = instrumented_dex_index + 1
+ assert len(instrumented_dex_files) > 0
+ return instrumented_dex_files
+
def should_discard_dex_file(dex_name, options):
- return options.discard is not None and dex_name in options.discard
+ return options.discard is not None and dex_name in options.discard
+
def should_instrument_dex_file(dex_name, options):
- return options.dex_files is not None and dex_name in options.dex_files
+ return options.dex_files is not None and dex_name in options.dex_files
+
def main(argv):
- options, args = parse_options(argv)
- with utils.TempDir() as tmp_dir:
- # Extract the dex files of the apk.
- uninstrumented_dir = os.path.join(tmp_dir, 'uninstrumented')
- os.mkdir(uninstrumented_dir)
+ options, args = parse_options(argv)
+ with utils.TempDir() as tmp_dir:
+ # Extract the dex files of the apk.
+ uninstrumented_dir = os.path.join(tmp_dir, 'uninstrumented')
+ os.mkdir(uninstrumented_dir)
- dex_predicate = \
- lambda name : name.startswith('classes') and name.endswith('.dex')
- zip_utils.extract_all_that_matches(
- options.apk, uninstrumented_dir, dex_predicate)
+ dex_predicate = \
+ lambda name : name.startswith('classes') and name.endswith('.dex')
+ zip_utils.extract_all_that_matches(options.apk, uninstrumented_dir,
+ dex_predicate)
- # Instrument each dex one by one.
- instrumented_dir = os.path.join(tmp_dir, 'instrumented')
- os.mkdir(instrumented_dir)
+ # Instrument each dex one by one.
+ instrumented_dir = os.path.join(tmp_dir, 'instrumented')
+ os.mkdir(instrumented_dir)
- include_instrumentation_server = True
- instrumented_dex_index = 1
- uninstrumented_dex_index = 1
- while True:
- dex_name = get_dex_name(uninstrumented_dex_index)
- dex_file = os.path.join(uninstrumented_dir, dex_name)
- if not os.path.exists(dex_file):
- break
- if not should_discard_dex_file(dex_name, options):
- if should_instrument_dex_file(dex_name, options):
- with utils.TempDir() as tmp_instrumentation_dir:
- instrumented_dex_files = \
- instrument_dex_file(
- dex_file,
- include_instrumentation_server,
- options,
- tmp_instrumentation_dir)
- for instrumented_dex_file in instrumented_dex_files:
- add_instrumented_dex(
- instrumented_dex_file, instrumented_dex_index, instrumented_dir)
- instrumented_dex_index = instrumented_dex_index + 1
- include_instrumentation_server = False
- else:
- add_instrumented_dex(dex_file, instrumented_dex_index, instrumented_dir)
- instrumented_dex_index = instrumented_dex_index + 1
- uninstrumented_dex_index = uninstrumented_dex_index + 1
+ include_instrumentation_server = True
+ instrumented_dex_index = 1
+ uninstrumented_dex_index = 1
+ while True:
+ dex_name = get_dex_name(uninstrumented_dex_index)
+ dex_file = os.path.join(uninstrumented_dir, dex_name)
+ if not os.path.exists(dex_file):
+ break
+ if not should_discard_dex_file(dex_name, options):
+ if should_instrument_dex_file(dex_name, options):
+ with utils.TempDir() as tmp_instrumentation_dir:
+ instrumented_dex_files = \
+ instrument_dex_file(
+ dex_file,
+ include_instrumentation_server,
+ options,
+ tmp_instrumentation_dir)
+ for instrumented_dex_file in instrumented_dex_files:
+ add_instrumented_dex(instrumented_dex_file,
+ instrumented_dex_index,
+ instrumented_dir)
+ instrumented_dex_index = instrumented_dex_index + 1
+ include_instrumentation_server = False
+ else:
+ add_instrumented_dex(dex_file, instrumented_dex_index,
+ instrumented_dir)
+ instrumented_dex_index = instrumented_dex_index + 1
+ uninstrumented_dex_index = uninstrumented_dex_index + 1
- assert instrumented_dex_index > 1
+ assert instrumented_dex_index > 1
- # Masseur APK.
- apk_masseur.masseur(options.apk, dex=instrumented_dir, out=options.out)
+ # Masseur APK.
+ apk_masseur.masseur(options.apk, dex=instrumented_dir, out=options.out)
+
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/measure_startup.py b/tools/startup/measure_startup.py
index 57f6cb4..7e97735 100755
--- a/tools/startup/measure_startup.py
+++ b/tools/startup/measure_startup.py
@@ -18,446 +18,461 @@
import perfetto_utils
import utils
+
def setup(options):
- # Increase screen off timeout to avoid device screen turns off.
- twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
- previous_screen_off_timeout = adb_utils.get_screen_off_timeout(
- options.device_id)
- adb_utils.set_screen_off_timeout(
- twenty_four_hours_in_millis, options.device_id)
+ # Increase screen off timeout to avoid device screen turns off.
+ twenty_four_hours_in_millis = 24 * 60 * 60 * 1000
+ previous_screen_off_timeout = adb_utils.get_screen_off_timeout(
+ options.device_id)
+ adb_utils.set_screen_off_timeout(twenty_four_hours_in_millis,
+ options.device_id)
- # Unlock device.
- adb_utils.unlock(options.device_id, options.device_pin)
+ # Unlock device.
+ adb_utils.unlock(options.device_id, options.device_pin)
- teardown_options = {
- 'previous_screen_off_timeout': previous_screen_off_timeout
- }
- return teardown_options
+ teardown_options = {
+ 'previous_screen_off_timeout': previous_screen_off_timeout
+ }
+ return teardown_options
+
def teardown(options, teardown_options):
- # Reset screen off timeout.
- adb_utils.set_screen_off_timeout(
- teardown_options['previous_screen_off_timeout'],
- options.device_id)
+ # Reset screen off timeout.
+ adb_utils.set_screen_off_timeout(
+ teardown_options['previous_screen_off_timeout'], options.device_id)
+
def run_all(apk_or_apks, options, tmp_dir):
- # Launch app while collecting information.
- data_total = {}
- for iteration in range(1, options.iterations + 1):
- print('Starting iteration %i' % iteration)
- out_dir = os.path.join(options.out_dir, str(iteration))
- teardown_options = setup_for_run(apk_or_apks, out_dir, options)
- data = run(out_dir, options, tmp_dir)
- teardown_for_run(out_dir, options, teardown_options)
- add_data(data_total, data)
- print('Result:')
- print(data)
- print(compute_data_summary(data_total))
- print('Done')
- print('Average result:')
- data_summary = compute_data_summary(data_total)
- print(data_summary)
- write_data_to_dir(options.out_dir, data_summary)
- if options.out:
- write_data_to_file(options.out, data_summary)
+ # Launch app while collecting information.
+ data_total = {}
+ for iteration in range(1, options.iterations + 1):
+ print('Starting iteration %i' % iteration)
+ out_dir = os.path.join(options.out_dir, str(iteration))
+ teardown_options = setup_for_run(apk_or_apks, out_dir, options)
+ data = run(out_dir, options, tmp_dir)
+ teardown_for_run(out_dir, options, teardown_options)
+ add_data(data_total, data)
+ print('Result:')
+ print(data)
+ print(compute_data_summary(data_total))
+ print('Done')
+ print('Average result:')
+ data_summary = compute_data_summary(data_total)
+ print(data_summary)
+ write_data_to_dir(options.out_dir, data_summary)
+ if options.out:
+ write_data_to_file(options.out, data_summary)
+
def compute_data_summary(data_total):
- data_summary = {}
- for key, value in data_total.items():
- if not isinstance(value, list):
- data_summary[key] = value
- continue
- data_summary['%s_avg' % key] = round(statistics.mean(value), 1)
- data_summary['%s_med' % key] = statistics.median(value)
- data_summary['%s_min' % key] = min(value)
- data_summary['%s_max' % key] = max(value)
- return data_summary
+ data_summary = {}
+ for key, value in data_total.items():
+ if not isinstance(value, list):
+ data_summary[key] = value
+ continue
+ data_summary['%s_avg' % key] = round(statistics.mean(value), 1)
+ data_summary['%s_med' % key] = statistics.median(value)
+ data_summary['%s_min' % key] = min(value)
+ data_summary['%s_max' % key] = max(value)
+ return data_summary
+
def setup_for_run(apk_or_apks, out_dir, options):
- adb_utils.root(options.device_id)
+ adb_utils.root(options.device_id)
- print('Installing')
- adb_utils.uninstall(options.app_id, options.device_id)
- if apk_or_apks['apk']:
- adb_utils.install(apk_or_apks['apk'], options.device_id)
- else:
- assert apk_or_apks['apks']
- adb_utils.install_apks(apk_or_apks['apks'], options.device_id)
-
- os.makedirs(out_dir, exist_ok=True)
-
- # Grant notifications.
- if options.grant_post_notification_permission:
- adb_utils.grant(
- options.app_id,
- 'android.permission.POST_NOTIFICATIONS',
- options.device_id)
-
- # AOT compile.
- if options.aot:
- print('AOT compiling')
- if options.baseline_profile:
- adb_utils.clear_profile_data(options.app_id, options.device_id)
- if options.baseline_profile_install == 'adb':
- adb_utils.install_profile_using_adb(
- options.app_id, options.baseline_profile, options.device_id)
- else:
- assert options.baseline_profile_install == 'profileinstaller'
- adb_utils.install_profile_using_profileinstaller(
- options.app_id, options.device_id)
+ print('Installing')
+ adb_utils.uninstall(options.app_id, options.device_id)
+ if apk_or_apks['apk']:
+ adb_utils.install(apk_or_apks['apk'], options.device_id)
else:
- adb_utils.force_compilation(options.app_id, options.device_id)
+ assert apk_or_apks['apks']
+ adb_utils.install_apks(apk_or_apks['apks'], options.device_id)
- # Cooldown and then unlock device.
- if options.cooldown > 0:
- print('Cooling down for %i seconds' % options.cooldown)
- assert adb_utils.get_screen_state(options.device_id).is_off()
- time.sleep(options.cooldown)
- teardown_options = adb_utils.prepare_for_interaction_with_device(
- options.device_id, options.device_pin)
- else:
- teardown_options = None
+ os.makedirs(out_dir, exist_ok=True)
- # Prelaunch for hot startup.
- if options.hot_startup:
- print('Prelaunching')
- adb_utils.launch_activity(
+ # Grant notifications.
+ if options.grant_post_notification_permission:
+ adb_utils.grant(options.app_id, 'android.permission.POST_NOTIFICATIONS',
+ options.device_id)
+
+ # AOT compile.
+ if options.aot:
+ print('AOT compiling')
+ if options.baseline_profile:
+ adb_utils.clear_profile_data(options.app_id, options.device_id)
+ if options.baseline_profile_install == 'adb':
+ adb_utils.install_profile_using_adb(options.app_id,
+ options.baseline_profile,
+ options.device_id)
+ else:
+ assert options.baseline_profile_install == 'profileinstaller'
+ adb_utils.install_profile_using_profileinstaller(
+ options.app_id, options.device_id)
+ else:
+ adb_utils.force_compilation(options.app_id, options.device_id)
+
+ # Cooldown and then unlock device.
+ if options.cooldown > 0:
+ print('Cooling down for %i seconds' % options.cooldown)
+ assert adb_utils.get_screen_state(options.device_id).is_off()
+ time.sleep(options.cooldown)
+ teardown_options = adb_utils.prepare_for_interaction_with_device(
+ options.device_id, options.device_pin)
+ else:
+ teardown_options = None
+
+ # Prelaunch for hot startup.
+ if options.hot_startup:
+ print('Prelaunching')
+ adb_utils.launch_activity(options.app_id,
+ options.main_activity,
+ options.device_id,
+ wait_for_activity_to_launch=False)
+ time.sleep(options.startup_duration)
+ adb_utils.navigate_to_home_screen(options.device_id)
+ time.sleep(1)
+
+ # Drop caches before run.
+ adb_utils.drop_caches(options.device_id)
+ return teardown_options
+
+
+def teardown_for_run(out_dir, options, teardown_options):
+ assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
+
+ if options.capture_screen:
+ target = os.path.join(out_dir, 'screen.png')
+ adb_utils.capture_screen(target, options.device_id)
+
+ if options.cooldown > 0:
+ adb_utils.teardown_after_interaction_with_device(
+ teardown_options, options.device_id)
+ adb_utils.ensure_screen_off(options.device_id)
+ else:
+ assert teardown_options is None
+
+
+def run(out_dir, options, tmp_dir):
+ assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
+
+ # Start logcat for time to fully drawn.
+ logcat_process = None
+ if options.fully_drawn_logcat_message:
+ adb_utils.clear_logcat(options.device_id)
+ logcat_process = adb_utils.start_logcat(
+ options.device_id,
+ format='time',
+ filter='%s ActivityTaskManager:I' %
+ options.fully_drawn_logcat_filter,
+ silent=True)
+
+ # Start perfetto trace collector.
+ perfetto_process = None
+ perfetto_trace_path = None
+ if options.perfetto:
+ perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
+ out_dir, tmp_dir, options.device_id)
+
+ # Launch main activity.
+ launch_activity_result = adb_utils.launch_activity(
options.app_id,
options.main_activity,
options.device_id,
- wait_for_activity_to_launch=False)
- time.sleep(options.startup_duration)
- adb_utils.navigate_to_home_screen(options.device_id)
- time.sleep(1)
+ intent_data_uri=options.intent_data_uri,
+ wait_for_activity_to_launch=True)
- # Drop caches before run.
- adb_utils.drop_caches(options.device_id)
- return teardown_options
+ # Wait for app to be fully drawn.
+ logcat = None
+ if logcat_process is not None:
+ wait_until_fully_drawn(logcat_process, options)
+ logcat = adb_utils.stop_logcat(logcat_process)
-def teardown_for_run(out_dir, options, teardown_options):
- assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
+ # Wait for perfetto trace collector to stop.
+ if options.perfetto:
+ perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
- if options.capture_screen:
- target = os.path.join(out_dir, 'screen.png')
- adb_utils.capture_screen(target, options.device_id)
+ # Get minor and major page faults from app process.
+ data = compute_data(launch_activity_result, logcat, perfetto_trace_path,
+ options)
+ write_data_to_dir(out_dir, data)
+ return data
- if options.cooldown > 0:
- adb_utils.teardown_after_interaction_with_device(
- teardown_options, options.device_id)
- adb_utils.ensure_screen_off(options.device_id)
- else:
- assert teardown_options is None
-
-def run(out_dir, options, tmp_dir):
- assert adb_utils.get_screen_state(options.device_id).is_on_and_unlocked()
-
- # Start logcat for time to fully drawn.
- logcat_process = None
- if options.fully_drawn_logcat_message:
- adb_utils.clear_logcat(options.device_id)
- logcat_process = adb_utils.start_logcat(
- options.device_id,
- format='time',
- filter='%s ActivityTaskManager:I' % options.fully_drawn_logcat_filter,
- silent=True)
-
- # Start perfetto trace collector.
- perfetto_process = None
- perfetto_trace_path = None
- if options.perfetto:
- perfetto_process, perfetto_trace_path = perfetto_utils.record_android_trace(
- out_dir, tmp_dir, options.device_id)
-
- # Launch main activity.
- launch_activity_result = adb_utils.launch_activity(
- options.app_id,
- options.main_activity,
- options.device_id,
- intent_data_uri=options.intent_data_uri,
- wait_for_activity_to_launch=True)
-
- # Wait for app to be fully drawn.
- logcat = None
- if logcat_process is not None:
- wait_until_fully_drawn(logcat_process, options)
- logcat = adb_utils.stop_logcat(logcat_process)
-
- # Wait for perfetto trace collector to stop.
- if options.perfetto:
- perfetto_utils.stop_record_android_trace(perfetto_process, out_dir)
-
- # Get minor and major page faults from app process.
- data = compute_data(
- launch_activity_result, logcat, perfetto_trace_path, options)
- write_data_to_dir(out_dir, data)
- return data
def wait_until_fully_drawn(logcat_process, options):
- print('Waiting until app is fully drawn')
- while True:
- is_fully_drawn = any(
- is_app_fully_drawn_logcat_message(line, options) \
- for line in logcat_process.lines)
- if is_fully_drawn:
- break
- time.sleep(1)
- print('Done')
+ print('Waiting until app is fully drawn')
+ while True:
+ is_fully_drawn = any(
+ is_app_fully_drawn_logcat_message(line, options) \
+ for line in logcat_process.lines)
+ if is_fully_drawn:
+ break
+ time.sleep(1)
+ print('Done')
+
def compute_time_to_fully_drawn_from_time_to_first_frame(logcat, options):
- displayed_time = None
- fully_drawn_time = None
- for line in logcat:
- if is_app_displayed_logcat_message(line, options):
- displayed_time = get_timestamp_from_logcat_message(line)
- elif is_app_fully_drawn_logcat_message(line, options):
- fully_drawn_time = get_timestamp_from_logcat_message(line)
- assert displayed_time is not None
- assert fully_drawn_time is not None
- assert fully_drawn_time >= displayed_time
- return fully_drawn_time - displayed_time
+ displayed_time = None
+ fully_drawn_time = None
+ for line in logcat:
+ if is_app_displayed_logcat_message(line, options):
+ displayed_time = get_timestamp_from_logcat_message(line)
+ elif is_app_fully_drawn_logcat_message(line, options):
+ fully_drawn_time = get_timestamp_from_logcat_message(line)
+ assert displayed_time is not None
+ assert fully_drawn_time is not None
+ assert fully_drawn_time >= displayed_time
+ return fully_drawn_time - displayed_time
+
def get_timestamp_from_logcat_message(line):
- time_end_index = len('00-00 00:00:00.000')
- time_format = '%m-%d %H:%M:%S.%f'
- time_str = line[0:time_end_index] + '000'
- time_seconds = datetime.datetime.strptime(time_str, time_format).timestamp()
- return int(time_seconds * 1000)
+ time_end_index = len('00-00 00:00:00.000')
+ time_format = '%m-%d %H:%M:%S.%f'
+ time_str = line[0:time_end_index] + '000'
+ time_seconds = datetime.datetime.strptime(time_str, time_format).timestamp()
+ return int(time_seconds * 1000)
+
def is_app_displayed_logcat_message(line, options):
- substring = 'Displayed %s' % adb_utils.get_component_name(
- options.app_id, options.main_activity)
- return substring in line
+ substring = 'Displayed %s' % adb_utils.get_component_name(
+ options.app_id, options.main_activity)
+ return substring in line
+
def is_app_fully_drawn_logcat_message(line, options):
- return re.search(options.fully_drawn_logcat_message, line)
+ return re.search(options.fully_drawn_logcat_message, line)
+
def add_data(data_total, data):
- for key, value in data.items():
- if key == 'app_id':
- assert data_total.get(key, value) == value
- data_total[key] = value
- if key == 'time':
- continue
- if key in data_total:
- if key == 'app_id':
- assert data_total[key] == value
- else:
- existing_value = data_total[key]
- assert isinstance(value, int)
- assert isinstance(existing_value, list)
- existing_value.append(value)
- else:
- assert isinstance(value, int), key
- data_total[key] = [value]
+ for key, value in data.items():
+ if key == 'app_id':
+ assert data_total.get(key, value) == value
+ data_total[key] = value
+ if key == 'time':
+ continue
+ if key in data_total:
+ if key == 'app_id':
+ assert data_total[key] == value
+ else:
+ existing_value = data_total[key]
+ assert isinstance(value, int)
+ assert isinstance(existing_value, list)
+ existing_value.append(value)
+ else:
+ assert isinstance(value, int), key
+ data_total[key] = [value]
+
def compute_data(launch_activity_result, logcat, perfetto_trace_path, options):
- minfl, majfl = adb_utils.get_minor_major_page_faults(
- options.app_id, options.device_id)
- meminfo = adb_utils.get_meminfo(options.app_id, options.device_id)
- data = {
- 'app_id': options.app_id,
- 'time': time.ctime(time.time()),
- 'minfl': minfl,
- 'majfl': majfl
- }
- data.update(meminfo)
- startup_data = compute_startup_data(
- launch_activity_result, logcat, perfetto_trace_path, options)
- return data | startup_data
+ minfl, majfl = adb_utils.get_minor_major_page_faults(
+ options.app_id, options.device_id)
+ meminfo = adb_utils.get_meminfo(options.app_id, options.device_id)
+ data = {
+ 'app_id': options.app_id,
+ 'time': time.ctime(time.time()),
+ 'minfl': minfl,
+ 'majfl': majfl
+ }
+ data.update(meminfo)
+ startup_data = compute_startup_data(launch_activity_result, logcat,
+ perfetto_trace_path, options)
+ return data | startup_data
-def compute_startup_data(
- launch_activity_result, logcat, perfetto_trace_path, options):
- time_to_first_frame = launch_activity_result.get('total_time')
- startup_data = {
- 'adb_startup': time_to_first_frame
- }
- # Time to fully drawn.
- if options.fully_drawn_logcat_message:
- startup_data['time_to_fully_drawn'] = \
- compute_time_to_fully_drawn_from_time_to_first_frame(logcat, options) \
- + time_to_first_frame
+def compute_startup_data(launch_activity_result, logcat, perfetto_trace_path,
+ options):
+ time_to_first_frame = launch_activity_result.get('total_time')
+ startup_data = {'adb_startup': time_to_first_frame}
- # Perfetto stats.
- perfetto_startup_data = {}
- if options.perfetto:
- TraceProcessor = perfetto_utils.get_trace_processor()
- trace_processor = TraceProcessor(file_path=perfetto_trace_path)
+ # Time to fully drawn.
+ if options.fully_drawn_logcat_message:
+ startup_data['time_to_fully_drawn'] = \
+ compute_time_to_fully_drawn_from_time_to_first_frame(logcat, options) \
+ + time_to_first_frame
- # Compute time to first frame according to the builtin android_startup
- # metric.
- startup_metric = trace_processor.metric(['android_startup'])
- time_to_first_frame_ms = \
- startup_metric.android_startup.startup[0].to_first_frame.dur_ms
- perfetto_startup_data['perfetto_startup'] = round(time_to_first_frame_ms)
+ # Perfetto stats.
+ perfetto_startup_data = {}
+ if options.perfetto:
+ TraceProcessor = perfetto_utils.get_trace_processor()
+ trace_processor = TraceProcessor(file_path=perfetto_trace_path)
- if not options.hot_startup:
- # Compute time to first and last doFrame event.
- bind_application_slice = perfetto_utils.find_unique_slice_by_name(
- 'bindApplication', options, trace_processor)
- activity_start_slice = perfetto_utils.find_unique_slice_by_name(
- 'activityStart', options, trace_processor)
- do_frame_slices = perfetto_utils.find_slices_by_name(
- 'Choreographer#doFrame', options, trace_processor)
- first_do_frame_slice = next(do_frame_slices)
- *_, last_do_frame_slice = do_frame_slices
+ # Compute time to first frame according to the builtin android_startup
+ # metric.
+ startup_metric = trace_processor.metric(['android_startup'])
+ time_to_first_frame_ms = \
+ startup_metric.android_startup.startup[0].to_first_frame.dur_ms
+ perfetto_startup_data['perfetto_startup'] = round(
+ time_to_first_frame_ms)
- perfetto_startup_data.update({
- 'time_to_first_choreographer_do_frame_ms':
- round(perfetto_utils.get_slice_end_since_start(
- first_do_frame_slice, bind_application_slice)),
- 'time_to_last_choreographer_do_frame_ms':
- round(perfetto_utils.get_slice_end_since_start(
- last_do_frame_slice, bind_application_slice))
- })
+ if not options.hot_startup:
+ # Compute time to first and last doFrame event.
+ bind_application_slice = perfetto_utils.find_unique_slice_by_name(
+ 'bindApplication', options, trace_processor)
+ activity_start_slice = perfetto_utils.find_unique_slice_by_name(
+ 'activityStart', options, trace_processor)
+ do_frame_slices = perfetto_utils.find_slices_by_name(
+ 'Choreographer#doFrame', options, trace_processor)
+ first_do_frame_slice = next(do_frame_slices)
+ *_, last_do_frame_slice = do_frame_slices
- # Return combined startup data.
- return startup_data | perfetto_startup_data
+ perfetto_startup_data.update({
+ 'time_to_first_choreographer_do_frame_ms':
+ round(
+ perfetto_utils.get_slice_end_since_start(
+ first_do_frame_slice, bind_application_slice)),
+ 'time_to_last_choreographer_do_frame_ms':
+ round(
+ perfetto_utils.get_slice_end_since_start(
+ last_do_frame_slice, bind_application_slice))
+ })
+
+ # Return combined startup data.
+ return startup_data | perfetto_startup_data
+
def write_data_to_dir(out_dir, data):
- data_path = os.path.join(out_dir, 'data.txt')
- write_data_to_file(data_path, data)
+ data_path = os.path.join(out_dir, 'data.txt')
+ write_data_to_file(data_path, data)
+
def write_data_to_file(out_file, data):
- with open(out_file, 'w') as f:
- for key, value in data.items():
- f.write('%s=%s\n' % (key, str(value)))
+ with open(out_file, 'w') as f:
+ for key, value in data.items():
+ f.write('%s=%s\n' % (key, str(value)))
+
def parse_options(argv):
- result = argparse.ArgumentParser(
- description='Generate a perfetto trace file.')
- result.add_argument('--app-id',
- help='The application ID of interest',
- required=True)
- result.add_argument('--aot',
- help='Enable force compilation',
- default=False,
- action='store_true')
- result.add_argument('--apk',
- help='Path to the .apk')
- result.add_argument('--apks',
- help='Path to the .apks')
- result.add_argument('--bundle',
- help='Path to the .aab')
- result.add_argument('--capture-screen',
- help='Take a screenshot after each test',
- default=False,
- action='store_true')
- result.add_argument('--cooldown',
- help='Seconds to wait before running each iteration',
- default=0,
- type=int)
- result.add_argument('--device-id',
- help='Device id (e.g., emulator-5554).')
- result.add_argument('--device-pin',
- help='Device pin code (e.g., 1234)')
- result.add_argument('--fully-drawn-logcat-filter',
- help='Logcat filter for the fully drawn message '
- '(e.g., "tag:I")')
- result.add_argument('--fully-drawn-logcat-message',
- help='Logcat message that indicates that the app is '
- 'fully drawn (regexp)')
- result.add_argument('--grant-post-notification-permission',
- help='Grants the android.permission.POST_NOTIFICATIONS '
- 'permission before launching the app',
- default=False,
- action='store_true')
- result.add_argument('--hot-startup',
- help='Measure hot startup instead of cold startup',
- default=False,
- action='store_true')
- result.add_argument('--intent-data-uri',
- help='Value to use for the -d argument to the intent '
- 'that is used to launch the app')
- result.add_argument('--iterations',
- help='Number of traces to generate',
- default=1,
- type=int)
- result.add_argument('--main-activity',
- help='Main activity class name',
- required=True)
- result.add_argument('--no-perfetto',
- help='Disables perfetto trace generation',
- action='store_true',
- default=False)
- result.add_argument('--out',
- help='File to store result in')
- result.add_argument('--out-dir',
- help='Directory to store trace files in',
- required=True)
- result.add_argument('--baseline-profile',
- help='Baseline profile (.prof) in binary format')
- result.add_argument('--baseline-profile-metadata',
- help='Baseline profile metadata (.profm) in binary '
- 'format')
- result.add_argument('--baseline-profile-install',
- help='Whether to install profile using adb or '
- 'profileinstaller',
- choices=['adb', 'profileinstaller'],
- default='profileinstaller')
- result.add_argument('--startup-duration',
- help='Duration in seconds before shutting down app',
- default=15,
- type=int)
- options, args = result.parse_known_args(argv)
- setattr(options, 'perfetto', not options.no_perfetto)
+ result = argparse.ArgumentParser(
+ description='Generate a perfetto trace file.')
+ result.add_argument('--app-id',
+ help='The application ID of interest',
+ required=True)
+ result.add_argument('--aot',
+ help='Enable force compilation',
+ default=False,
+ action='store_true')
+ result.add_argument('--apk', help='Path to the .apk')
+ result.add_argument('--apks', help='Path to the .apks')
+ result.add_argument('--bundle', help='Path to the .aab')
+ result.add_argument('--capture-screen',
+ help='Take a screenshot after each test',
+ default=False,
+ action='store_true')
+ result.add_argument('--cooldown',
+ help='Seconds to wait before running each iteration',
+ default=0,
+ type=int)
+ result.add_argument('--device-id', help='Device id (e.g., emulator-5554).')
+ result.add_argument('--device-pin', help='Device pin code (e.g., 1234)')
+ result.add_argument('--fully-drawn-logcat-filter',
+ help='Logcat filter for the fully drawn message '
+ '(e.g., "tag:I")')
+ result.add_argument('--fully-drawn-logcat-message',
+ help='Logcat message that indicates that the app is '
+ 'fully drawn (regexp)')
+ result.add_argument('--grant-post-notification-permission',
+ help='Grants the android.permission.POST_NOTIFICATIONS '
+ 'permission before launching the app',
+ default=False,
+ action='store_true')
+ result.add_argument('--hot-startup',
+ help='Measure hot startup instead of cold startup',
+ default=False,
+ action='store_true')
+ result.add_argument('--intent-data-uri',
+ help='Value to use for the -d argument to the intent '
+ 'that is used to launch the app')
+ result.add_argument('--iterations',
+ help='Number of traces to generate',
+ default=1,
+ type=int)
+ result.add_argument('--main-activity',
+ help='Main activity class name',
+ required=True)
+ result.add_argument('--no-perfetto',
+ help='Disables perfetto trace generation',
+ action='store_true',
+ default=False)
+ result.add_argument('--out', help='File to store result in')
+ result.add_argument('--out-dir',
+ help='Directory to store trace files in',
+ required=True)
+ result.add_argument('--baseline-profile',
+ help='Baseline profile (.prof) in binary format')
+ result.add_argument('--baseline-profile-metadata',
+ help='Baseline profile metadata (.profm) in binary '
+ 'format')
+ result.add_argument('--baseline-profile-install',
+ help='Whether to install profile using adb or '
+ 'profileinstaller',
+ choices=['adb', 'profileinstaller'],
+ default='profileinstaller')
+ result.add_argument('--startup-duration',
+ help='Duration in seconds before shutting down app',
+ default=15,
+ type=int)
+ options, args = result.parse_known_args(argv)
+ setattr(options, 'perfetto', not options.no_perfetto)
- paths = [
- path for path in [options.apk, options.apks, options.bundle]
- if path is not None]
- assert len(paths) == 1, 'Expected exactly one .apk, .apks, or .aab file.'
+ paths = [
+ path for path in [options.apk, options.apks, options.bundle]
+ if path is not None
+ ]
+ assert len(paths) == 1, 'Expected exactly one .apk, .apks, or .aab file.'
- # Build .apks file up front to avoid building the bundle upon each install.
- if options.bundle:
- os.makedirs(options.out_dir, exist_ok=True)
- options.apks = os.path.join(options.out_dir, 'Bundle.apks')
- adb_utils.build_apks_from_bundle(
- options.bundle, options.apks, overwrite=True)
- del options.bundle
+ # Build .apks file up front to avoid building the bundle upon each install.
+ if options.bundle:
+ os.makedirs(options.out_dir, exist_ok=True)
+ options.apks = os.path.join(options.out_dir, 'Bundle.apks')
+ adb_utils.build_apks_from_bundle(options.bundle,
+ options.apks,
+ overwrite=True)
+ del options.bundle
- # Profile is only used with --aot.
- assert options.aot or not options.baseline_profile
+ # Profile is only used with --aot.
+ assert options.aot or not options.baseline_profile
- # Fully drawn logcat filter and message is absent or both present.
- assert (options.fully_drawn_logcat_filter is None) == \
- (options.fully_drawn_logcat_message is None)
+ # Fully drawn logcat filter and message is absent or both present.
+ assert (options.fully_drawn_logcat_filter is None) == \
+ (options.fully_drawn_logcat_message is None)
- return options, args
+ return options, args
+
def global_setup(options):
- # If there is no cooldown then unlock the screen once. Otherwise we turn off
- # the screen during the cooldown and unlock the screen before each iteration.
- teardown_options = None
- if options.cooldown == 0:
- teardown_options = adb_utils.prepare_for_interaction_with_device(
- options.device_id, options.device_pin)
- assert adb_utils.get_screen_state(options.device_id).is_on()
- else:
- adb_utils.ensure_screen_off(options.device_id)
- return teardown_options
+ # If there is no cooldown then unlock the screen once. Otherwise we turn off
+ # the screen during the cooldown and unlock the screen before each iteration.
+ teardown_options = None
+ if options.cooldown == 0:
+ teardown_options = adb_utils.prepare_for_interaction_with_device(
+ options.device_id, options.device_pin)
+ assert adb_utils.get_screen_state(options.device_id).is_on()
+ else:
+ adb_utils.ensure_screen_off(options.device_id)
+ return teardown_options
+
def global_teardown(options, teardown_options):
- if options.cooldown == 0:
- adb_utils.teardown_after_interaction_with_device(
- teardown_options, options.device_id)
- else:
- assert teardown_options is None
+ if options.cooldown == 0:
+ adb_utils.teardown_after_interaction_with_device(
+ teardown_options, options.device_id)
+ else:
+ assert teardown_options is None
+
def main(argv):
- (options, args) = parse_options(argv)
- with utils.TempDir() as tmp_dir:
- apk_or_apks = { 'apk': options.apk, 'apks': options.apks }
- if options.baseline_profile \
- and options.baseline_profile_install == 'profileinstaller':
- assert not options.apks, 'Unimplemented'
- apk_or_apks['apk'] = apk_utils.add_baseline_profile_to_apk(
- options.apk,
- options.baseline_profile,
- options.baseline_profile_metadata,
- tmp_dir)
- teardown_options = global_setup(options)
- run_all(apk_or_apks, options, tmp_dir)
- global_teardown(options, teardown_options)
+ (options, args) = parse_options(argv)
+ with utils.TempDir() as tmp_dir:
+ apk_or_apks = {'apk': options.apk, 'apks': options.apks}
+ if options.baseline_profile \
+ and options.baseline_profile_install == 'profileinstaller':
+ assert not options.apks, 'Unimplemented'
+ apk_or_apks['apk'] = apk_utils.add_baseline_profile_to_apk(
+ options.apk, options.baseline_profile,
+ options.baseline_profile_metadata, tmp_dir)
+ teardown_options = global_setup(options)
+ run_all(apk_or_apks, options, tmp_dir)
+ global_teardown(options, teardown_options)
+
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
\ No newline at end of file
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/perfetto_utils.py b/tools/startup/perfetto_utils.py
index eb32722..4cf8613 100644
--- a/tools/startup/perfetto_utils.py
+++ b/tools/startup/perfetto_utils.py
@@ -7,85 +7,85 @@
import subprocess
import sys
+
def get_trace_processor():
- try:
- from perfetto.trace_processor import TraceProcessor
- except ImportError:
- sys.exit(
- 'Unable to analyze perfetto trace without the perfetto library. '
- 'Install instructions:\n'
- ' sudo apt install python3-pip\n'
- ' pip3 install perfetto')
- return TraceProcessor
+ try:
+ from perfetto.trace_processor import TraceProcessor
+ except ImportError:
+ sys.exit(
+ 'Unable to analyze perfetto trace without the perfetto library. '
+ 'Install instructions:\n'
+ ' sudo apt install python3-pip\n'
+ ' pip3 install perfetto')
+ return TraceProcessor
+
def ensure_record_android_trace(tmp_dir):
- record_android_trace_path = os.path.join(tmp_dir, 'record_android_trace')
- if not os.path.exists(record_android_trace_path):
- cmd = [
- 'curl',
- '--output',
- record_android_trace_path,
- '--silent',
- 'https://raw.githubusercontent.com/google/perfetto/master/tools/'
+ record_android_trace_path = os.path.join(tmp_dir, 'record_android_trace')
+ if not os.path.exists(record_android_trace_path):
+ cmd = [
+ 'curl', '--output', record_android_trace_path, '--silent',
+ 'https://raw.githubusercontent.com/google/perfetto/master/tools/'
'record_android_trace'
- ]
- subprocess.check_call(cmd)
- assert os.path.exists(record_android_trace_path)
- return record_android_trace_path
+ ]
+ subprocess.check_call(cmd)
+ assert os.path.exists(record_android_trace_path)
+ return record_android_trace_path
+
def record_android_trace(out_dir, tmp_dir, device_id=None):
- record_android_trace_path = ensure_record_android_trace(tmp_dir)
- config_path = os.path.join(os.path.dirname(__file__), 'config.pbtx')
- perfetto_trace_path = os.path.join(out_dir, 'trace.perfetto-trace')
- cmd = [
- sys.executable,
- record_android_trace_path,
- '--config',
- config_path,
- '--out',
- perfetto_trace_path,
- '--no-open']
- if device_id is not None:
- cmd.extend(['--serial', device_id])
- perfetto_process = subprocess.Popen(
- cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- lines = []
- for line in perfetto_process.stdout:
- line = line.decode('utf-8')
- lines.append(line)
- if 'enabled ftrace' in line.strip():
- return perfetto_process, perfetto_trace_path
- raise ValueError(
- 'Expected to find line containing: enabled ftrace, got: %s' % lines)
+ record_android_trace_path = ensure_record_android_trace(tmp_dir)
+ config_path = os.path.join(os.path.dirname(__file__), 'config.pbtx')
+ perfetto_trace_path = os.path.join(out_dir, 'trace.perfetto-trace')
+ cmd = [
+ sys.executable, record_android_trace_path, '--config', config_path,
+ '--out', perfetto_trace_path, '--no-open'
+ ]
+ if device_id is not None:
+ cmd.extend(['--serial', device_id])
+ perfetto_process = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ lines = []
+ for line in perfetto_process.stdout:
+ line = line.decode('utf-8')
+ lines.append(line)
+ if 'enabled ftrace' in line.strip():
+ return perfetto_process, perfetto_trace_path
+ raise ValueError(
+ 'Expected to find line containing: enabled ftrace, got: %s' % lines)
+
def stop_record_android_trace(perfetto_process, out_dir):
- if perfetto_process.poll() is not None:
- raise ValueError('Expected perfetto process to be running')
- # perfetto should terminate in at most 15 seconds,
- perfetto_config_duration=15
- stdout, stderr = perfetto_process.communicate(
- timeout=perfetto_config_duration*2)
- stdout = stdout.decode('utf-8')
- stderr = stderr.decode('utf-8')
- assert perfetto_process.returncode == 0
- assert os.path.exists(os.path.join(out_dir, 'trace.perfetto-trace'))
+ if perfetto_process.poll() is not None:
+ raise ValueError('Expected perfetto process to be running')
+ # perfetto should terminate in at most 15 seconds,
+ perfetto_config_duration = 15
+ stdout, stderr = perfetto_process.communicate(
+ timeout=perfetto_config_duration * 2)
+ stdout = stdout.decode('utf-8')
+ stderr = stderr.decode('utf-8')
+ assert perfetto_process.returncode == 0
+ assert os.path.exists(os.path.join(out_dir, 'trace.perfetto-trace'))
+
# https://perfetto.dev/docs/analysis/sql-tables
def find_slices_by_name(slice_name, options, trace_processor):
- return trace_processor.query(
- 'SELECT slice.dur, slice.ts FROM slice'
- ' INNER JOIN thread_track ON (slice.track_id = thread_track.id)'
- ' INNER JOIN thread using (utid)'
- ' INNER JOIN process using (upid)'
- ' WHERE slice.name = "%s"'
- ' AND process.name = "%s"'
- ' ORDER BY slice.ts ASC'
- % (slice_name, options.app_id))
+ return trace_processor.query(
+ 'SELECT slice.dur, slice.ts FROM slice'
+ ' INNER JOIN thread_track ON (slice.track_id = thread_track.id)'
+ ' INNER JOIN thread using (utid)'
+ ' INNER JOIN process using (upid)'
+ ' WHERE slice.name = "%s"'
+ ' AND process.name = "%s"'
+ ' ORDER BY slice.ts ASC' % (slice_name, options.app_id))
+
def find_unique_slice_by_name(slice_name, options, trace_processor):
- query_it = find_slices_by_name(slice_name, options, trace_processor)
- assert len(query_it) == 1
- return next(query_it)
+ query_it = find_slices_by_name(slice_name, options, trace_processor)
+ assert len(query_it) == 1
+ return next(query_it)
+
def get_slice_end_since_start(slice, initial_slice):
- return (slice.ts + slice.dur - initial_slice.ts) / 1000000
+ return (slice.ts + slice.dur - initial_slice.ts) / 1000000
diff --git a/tools/startup/profile_utils.py b/tools/startup/profile_utils.py
index 5c74a5c..7a8de4a 100755
--- a/tools/startup/profile_utils.py
+++ b/tools/startup/profile_utils.py
@@ -10,6 +10,7 @@
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).
#
@@ -24,86 +25,93 @@
#
# 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:]
- while line.startswith('['):
- line = line[1:]
- assert line.startswith('L'), line
- descriptor = line
- art_profile[descriptor] = flags
- return art_profile
+ 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:]
+ while line.startswith('['):
+ line = line[1:]
+ assert line.startswith('L'), line
+ descriptor = line
+ art_profile[descriptor] = flags
+ return art_profile
-def transform_art_profile_to_r8_startup_list(
- art_profile, generalize_synthetics=False):
- r8_startup_list = {}
- for startup_descriptor, flags in art_profile.items():
- transformed_startup_descriptor = transform_synthetic_descriptor(
- startup_descriptor) if generalize_synthetics else startup_descriptor
- r8_startup_list[transformed_startup_descriptor] = {
- 'conditional_startup': False,
- 'hot': flags['hot'],
- 'startup': flags['startup'],
- 'post_startup': flags['post_startup']
- }
- return r8_startup_list
+
+def transform_art_profile_to_r8_startup_list(art_profile,
+ generalize_synthetics=False):
+ r8_startup_list = {}
+ for startup_descriptor, flags in art_profile.items():
+ transformed_startup_descriptor = transform_synthetic_descriptor(
+ startup_descriptor) if generalize_synthetics else startup_descriptor
+ r8_startup_list[transformed_startup_descriptor] = {
+ 'conditional_startup': False,
+ 'hot': flags['hot'],
+ 'startup': flags['startup'],
+ 'post_startup': flags['post_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
+ 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
+ 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
+ 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)
+ (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:]))
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/startup/relayout.py b/tools/startup/relayout.py
index b0f8aac..3d0cd94 100755
--- a/tools/startup/relayout.py
+++ b/tools/startup/relayout.py
@@ -17,79 +17,85 @@
import utils
import zip_utils
-LOWEST_SUPPORTED_MIN_API = 21 # Android L (native multi dex)
+LOWEST_SUPPORTED_MIN_API = 21 # Android L (native multi dex)
+
def parse_options(argv):
- result = argparse.ArgumentParser(
- description='Relayout a given APK using a startup profile.')
- result.add_argument('--apk',
- help='Path to the .apk',
- required=True)
- result.add_argument('--desugared-library',
- choices=['auto', 'true', 'false'],
- default='auto',
- help='Whether the last dex file of the app is desugared '
- 'library')
- result.add_argument('--no-build',
- action='store_true',
- default=False,
- help='To disable building using gradle')
- result.add_argument('--out',
- help='Destination of resulting apk',
- required=True)
- result.add_argument('--profile',
- help='Path to the startup profile')
- options, args = result.parse_known_args(argv)
- return options, args
+ result = argparse.ArgumentParser(
+ description='Relayout a given APK using a startup profile.')
+ result.add_argument('--apk', help='Path to the .apk', required=True)
+ result.add_argument(
+ '--desugared-library',
+ choices=['auto', 'true', 'false'],
+ default='auto',
+ help='Whether the last dex file of the app is desugared '
+ 'library')
+ result.add_argument('--no-build',
+ action='store_true',
+ default=False,
+ help='To disable building using gradle')
+ result.add_argument('--out',
+ help='Destination of resulting apk',
+ required=True)
+ result.add_argument('--profile', help='Path to the startup profile')
+ options, args = result.parse_known_args(argv)
+ return options, args
+
def get_dex_to_relayout(options, temp):
- marker = extractmarker.extractmarker(options.apk, build=not options.no_build)
- if '~~L8' not in marker:
- return [options.apk], None
- dex_dir = os.path.join(temp, 'dex')
- dex_predicate = \
- lambda name : name.startswith('classes') and name.endswith('.dex')
- extracted_dex_files = \
- zip_utils.extract_all_that_matches(options.apk, dex_dir, dex_predicate)
- desugared_library_dex = 'classes%s.dex' % len(extracted_dex_files)
- assert desugared_library_dex in extracted_dex_files
- return [
- os.path.join(dex_dir, name) \
- for name in extracted_dex_files if name != desugared_library_dex], \
- os.path.join(dex_dir, desugared_library_dex)
+ marker = extractmarker.extractmarker(options.apk,
+ build=not options.no_build)
+ if '~~L8' not in marker:
+ return [options.apk], None
+ dex_dir = os.path.join(temp, 'dex')
+ dex_predicate = \
+ lambda name : name.startswith('classes') and name.endswith('.dex')
+ extracted_dex_files = \
+ zip_utils.extract_all_that_matches(options.apk, dex_dir, dex_predicate)
+ desugared_library_dex = 'classes%s.dex' % len(extracted_dex_files)
+ assert desugared_library_dex in extracted_dex_files
+ return [
+ os.path.join(dex_dir, name) \
+ for name in extracted_dex_files if name != desugared_library_dex], \
+ os.path.join(dex_dir, desugared_library_dex)
+
def has_desugared_library_dex(options):
- if options.desugared_library == 'auto':
- marker = extractmarker.extractmarker(
- options.apk, build=not options.no_build)
- return '~~L8' in marker
- return options.desugared_library == 'true'
+ if options.desugared_library == 'auto':
+ marker = extractmarker.extractmarker(options.apk,
+ build=not options.no_build)
+ return '~~L8' in marker
+ return options.desugared_library == 'true'
+
def main(argv):
- (options, args) = parse_options(argv)
- with utils.TempDir() as temp:
- dex = os.path.join(temp, 'dex.zip')
- d8_args = [
- '--min-api',
- str(max(apk_utils.get_min_api(options.apk), LOWEST_SUPPORTED_MIN_API)),
- '--output', dex,
- '--no-desugaring',
- '--release']
- if options.profile:
- d8_args.extend(['--startup-profile', options.profile])
- dex_to_relayout, desugared_library_dex = get_dex_to_relayout(options, temp)
- d8_args.extend(dex_to_relayout)
- toolhelper.run(
- 'd8',
- d8_args,
- build=not options.no_build,
- main='com.android.tools.r8.D8')
- if desugared_library_dex is not None:
- dex_files = [name for name in \
- zip_utils.get_names_that_matches(dex, lambda x : True)]
- zip_utils.add_file_to_zip(
- desugared_library_dex, 'classes%s.dex' % str(len(dex_files) + 1), dex)
- apk_masseur.masseur(options.apk, dex=dex, out=options.out)
+ (options, args) = parse_options(argv)
+ with utils.TempDir() as temp:
+ dex = os.path.join(temp, 'dex.zip')
+ d8_args = [
+ '--min-api',
+ str(
+ max(apk_utils.get_min_api(options.apk),
+ LOWEST_SUPPORTED_MIN_API)), '--output', dex,
+ '--no-desugaring', '--release'
+ ]
+ if options.profile:
+ d8_args.extend(['--startup-profile', options.profile])
+ dex_to_relayout, desugared_library_dex = get_dex_to_relayout(
+ options, temp)
+ d8_args.extend(dex_to_relayout)
+ toolhelper.run('d8',
+ d8_args,
+ build=not options.no_build,
+ main='com.android.tools.r8.D8')
+ if desugared_library_dex is not None:
+ dex_files = [name for name in \
+ zip_utils.get_names_that_matches(dex, lambda x : True)]
+ zip_utils.add_file_to_zip(desugared_library_dex,
+ 'classes%s.dex' % str(len(dex_files) + 1),
+ dex)
+ apk_masseur.masseur(options.apk, dex=dex, out=options.out)
+
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ sys.exit(main(sys.argv[1:]))