blob: 2f1eff2c41f67a96420919e419bc58cfd97fce30 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2019, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runner of one test given a setting.
Run app and gather the measurement in a certain configuration.
Print the result to stdout.
See --help for more details.
Sample usage:
$> ./python run_app_with_prefetch.py -p com.android.settings -a
com.android.settings.Settings -r fadvise -i input
"""
import argparse
import os
import sys
import time
from typing import List, Tuple, Optional
# local imports
import lib.adb_utils as adb_utils
from lib.app_runner import AppRunner, AppRunnerListener
# global variables
DIR = os.path.abspath(os.path.dirname(__file__))
sys.path.append(os.path.dirname(DIR))
import lib.print_utils as print_utils
import lib.cmd_utils as cmd_utils
import iorap.lib.iorapd_utils as iorapd_utils
class PrefetchAppRunner(AppRunnerListener):
def __init__(self,
package: str,
activity: Optional[str],
readahead: str,
compiler_filter: Optional[str],
timeout: Optional[int],
simulate: bool,
debug: bool,
input:Optional[str],
**kwargs):
self.app_runner = AppRunner(package,
activity,
compiler_filter,
timeout,
simulate)
self.app_runner.add_callbacks(self)
self.simulate = simulate
self.readahead = readahead
self.debug = debug
self.input = input
print_utils.DEBUG = self.debug
cmd_utils.SIMULATE = self.simulate
def run(self) -> Optional[List[Tuple[str]]]:
"""Runs an app.
Returns:
A list of (metric, value) tuples.
"""
return self.app_runner.run()
def preprocess(self):
passed = self.validate_options()
if not passed:
return
# Sets up adb environment.
adb_utils.root()
adb_utils.disable_selinux()
time.sleep(1)
# Kill any existing process of this app
adb_utils.pkill(self.app_runner.package)
if self.readahead != 'warm':
print_utils.debug_print('Drop caches for non-warm start.')
# Drop all caches to get cold starts.
adb_utils.vm_drop_cache()
if self.readahead != 'warm' and self.readahead != 'cold':
iorapd_utils.enable_iorapd_readahead()
def postprocess(self, pre_launch_timestamp: str):
passed = self._perform_post_launch_cleanup(pre_launch_timestamp)
if not passed and not self.app_runner.simulate:
print_utils.error_print('Cannot perform post launch cleanup!')
return None
# Kill any existing process of this app
adb_utils.pkill(self.app_runner.package)
def _perform_post_launch_cleanup(self, logcat_timestamp: str) -> bool:
"""Performs cleanup at the end of each loop iteration.
Returns:
A bool indicates whether the cleanup succeeds or not.
"""
if self.readahead != 'warm' and self.readahead != 'cold':
passed = iorapd_utils.wait_for_iorapd_finish(self.app_runner.package,
self.app_runner.activity,
self.app_runner.timeout,
self.debug,
logcat_timestamp)
if not passed:
return passed
return iorapd_utils.disable_iorapd_readahead()
# Don't need to do anything for warm or cold.
return True
def metrics_selector(self, am_start_output: str,
pre_launch_timestamp: str) -> str:
"""Parses the metric after app startup by reading from logcat in a blocking
manner until all metrics have been found".
Returns:
the total time and displayed time of app startup.
For example: "TotalTime=123\nDisplayedTime=121
"""
total_time = AppRunner.parse_total_time(am_start_output)
displayed_time = adb_utils.blocking_wait_for_logcat_displayed_time(
pre_launch_timestamp, self.app_runner.package, self.app_runner.timeout)
return 'TotalTime={}\nDisplayedTime={}'.format(total_time, displayed_time)
def validate_options(self) -> bool:
"""Validates the activity and trace file if needed.
Returns:
A bool indicates whether the activity is valid.
"""
needs_trace_file = self.readahead != 'cold' and self.readahead != 'warm'
if needs_trace_file and (self.input is None or
not os.path.exists(self.input)):
print_utils.error_print('--input not specified!')
return False
# Install necessary trace file. This must be after the activity checking.
if needs_trace_file:
passed = iorapd_utils.iorapd_compiler_install_trace_file(
self.app_runner.package, self.app_runner.activity, self.input)
if not cmd_utils.SIMULATE and not passed:
print_utils.error_print('Failed to install compiled TraceFile.pb for '
'"{}/{}"'.
format(self.app_runner.package,
self.app_runner.activity))
return False
return True
def parse_options(argv: List[str] = None):
"""Parses command line arguments and return an argparse Namespace object."""
parser = argparse.ArgumentParser(
description='Run an Android application once and measure startup time.'
)
required_named = parser.add_argument_group('required named arguments')
required_named.add_argument('-p', '--package', action='store', dest='package',
help='package of the application', required=True)
# optional arguments
# use a group here to get the required arguments to appear 'above' the
# optional arguments in help.
optional_named = parser.add_argument_group('optional named arguments')
optional_named.add_argument('-a', '--activity', action='store',
dest='activity',
help='launch activity of the application')
optional_named.add_argument('-s', '--simulate', dest='simulate',
action='store_true',
help='simulate the process without executing '
'any shell commands')
optional_named.add_argument('-d', '--debug', dest='debug',
action='store_true',
help='Add extra debugging output')
optional_named.add_argument('-i', '--input', action='store', dest='input',
help='perfetto trace file protobuf',
default='TraceFile.pb')
optional_named.add_argument('-r', '--readahead', action='store',
dest='readahead',
help='which readahead mode to use',
default='cold',
choices=('warm', 'cold', 'mlock', 'fadvise'))
optional_named.add_argument('-t', '--timeout', dest='timeout', action='store',
type=int,
help='Timeout after this many seconds when '
'executing a single run.',
default=10)
optional_named.add_argument('--compiler-filter', dest='compiler_filter',
action='store',
help='Which compiler filter to use.',
default=None)
return parser.parse_args(argv)
def main():
opts = parse_options()
runner = PrefetchAppRunner(**vars(opts))
result = runner.run()
if result is None:
return 1
print(result)
return 0
if __name__ == '__main__':
sys.exit(main())