1#!/usr/bin/env python3 2# 3# Copyright 2019, The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Runner of one test given a setting. 18 19Run app and gather the measurement in a certain configuration. 20Print the result to stdout. 21See --help for more details. 22 23Sample usage: 24 $> ./python run_app_with_prefetch.py -p com.android.settings -a 25 com.android.settings.Settings -r fadvise -i input 26 27""" 28 29import argparse 30import os 31import sys 32import time 33from typing import List, Tuple, Optional 34 35# local imports 36import lib.adb_utils as adb_utils 37from lib.app_runner import AppRunner, AppRunnerListener 38 39# global variables 40DIR = os.path.abspath(os.path.dirname(__file__)) 41 42sys.path.append(os.path.dirname(DIR)) 43import lib.print_utils as print_utils 44import lib.cmd_utils as cmd_utils 45import iorap.lib.iorapd_utils as iorapd_utils 46 47class PrefetchAppRunner(AppRunnerListener): 48 def __init__(self, 49 package: str, 50 activity: Optional[str], 51 readahead: str, 52 compiler_filter: Optional[str], 53 timeout: Optional[int], 54 simulate: bool, 55 debug: bool, 56 input:Optional[str], 57 **kwargs): 58 self.app_runner = AppRunner(package, 59 activity, 60 compiler_filter, 61 timeout, 62 simulate) 63 self.app_runner.add_callbacks(self) 64 65 self.simulate = simulate 66 self.readahead = readahead 67 self.debug = debug 68 self.input = input 69 print_utils.DEBUG = self.debug 70 cmd_utils.SIMULATE = self.simulate 71 72 73 def run(self) -> Optional[List[Tuple[str]]]: 74 """Runs an app. 75 76 Returns: 77 A list of (metric, value) tuples. 78 """ 79 return self.app_runner.run() 80 81 def preprocess(self): 82 passed = self.validate_options() 83 if not passed: 84 return 85 86 # Sets up adb environment. 87 adb_utils.root() 88 adb_utils.disable_selinux() 89 time.sleep(1) 90 91 # Kill any existing process of this app 92 adb_utils.pkill(self.app_runner.package) 93 94 if self.readahead != 'warm': 95 print_utils.debug_print('Drop caches for non-warm start.') 96 # Drop all caches to get cold starts. 97 adb_utils.vm_drop_cache() 98 99 if self.readahead != 'warm' and self.readahead != 'cold': 100 iorapd_utils.enable_iorapd_readahead() 101 102 def postprocess(self, pre_launch_timestamp: str): 103 passed = self._perform_post_launch_cleanup(pre_launch_timestamp) 104 if not passed and not self.app_runner.simulate: 105 print_utils.error_print('Cannot perform post launch cleanup!') 106 return None 107 108 # Kill any existing process of this app 109 adb_utils.pkill(self.app_runner.package) 110 111 def _perform_post_launch_cleanup(self, logcat_timestamp: str) -> bool: 112 """Performs cleanup at the end of each loop iteration. 113 114 Returns: 115 A bool indicates whether the cleanup succeeds or not. 116 """ 117 if self.readahead != 'warm' and self.readahead != 'cold': 118 passed = iorapd_utils.wait_for_iorapd_finish(self.app_runner.package, 119 self.app_runner.activity, 120 self.app_runner.timeout, 121 self.debug, 122 logcat_timestamp) 123 124 if not passed: 125 return passed 126 127 return iorapd_utils.disable_iorapd_readahead() 128 129 # Don't need to do anything for warm or cold. 130 return True 131 132 def metrics_selector(self, am_start_output: str, 133 pre_launch_timestamp: str) -> str: 134 """Parses the metric after app startup by reading from logcat in a blocking 135 manner until all metrics have been found". 136 137 Returns: 138 the total time and displayed time of app startup. 139 For example: "TotalTime=123\nDisplayedTime=121 140 """ 141 total_time = AppRunner.parse_total_time(am_start_output) 142 displayed_time = adb_utils.blocking_wait_for_logcat_displayed_time( 143 pre_launch_timestamp, self.app_runner.package, self.app_runner.timeout) 144 145 return 'TotalTime={}\nDisplayedTime={}'.format(total_time, displayed_time) 146 147 def validate_options(self) -> bool: 148 """Validates the activity and trace file if needed. 149 150 Returns: 151 A bool indicates whether the activity is valid. 152 """ 153 needs_trace_file = self.readahead != 'cold' and self.readahead != 'warm' 154 if needs_trace_file and (self.input is None or 155 not os.path.exists(self.input)): 156 print_utils.error_print('--input not specified!') 157 return False 158 159 # Install necessary trace file. This must be after the activity checking. 160 if needs_trace_file: 161 passed = iorapd_utils.iorapd_compiler_install_trace_file( 162 self.app_runner.package, self.app_runner.activity, self.input) 163 if not cmd_utils.SIMULATE and not passed: 164 print_utils.error_print('Failed to install compiled TraceFile.pb for ' 165 '"{}/{}"'. 166 format(self.app_runner.package, 167 self.app_runner.activity)) 168 return False 169 170 return True 171 172 173 174def parse_options(argv: List[str] = None): 175 """Parses command line arguments and return an argparse Namespace object.""" 176 parser = argparse.ArgumentParser( 177 description='Run an Android application once and measure startup time.' 178 ) 179 180 required_named = parser.add_argument_group('required named arguments') 181 required_named.add_argument('-p', '--package', action='store', dest='package', 182 help='package of the application', required=True) 183 184 # optional arguments 185 # use a group here to get the required arguments to appear 'above' the 186 # optional arguments in help. 187 optional_named = parser.add_argument_group('optional named arguments') 188 optional_named.add_argument('-a', '--activity', action='store', 189 dest='activity', 190 help='launch activity of the application') 191 optional_named.add_argument('-s', '--simulate', dest='simulate', 192 action='store_true', 193 help='simulate the process without executing ' 194 'any shell commands') 195 optional_named.add_argument('-d', '--debug', dest='debug', 196 action='store_true', 197 help='Add extra debugging output') 198 optional_named.add_argument('-i', '--input', action='store', dest='input', 199 help='perfetto trace file protobuf', 200 default='TraceFile.pb') 201 optional_named.add_argument('-r', '--readahead', action='store', 202 dest='readahead', 203 help='which readahead mode to use', 204 default='cold', 205 choices=('warm', 'cold', 'mlock', 'fadvise')) 206 optional_named.add_argument('-t', '--timeout', dest='timeout', action='store', 207 type=int, 208 help='Timeout after this many seconds when ' 209 'executing a single run.', 210 default=10) 211 optional_named.add_argument('--compiler-filter', dest='compiler_filter', 212 action='store', 213 help='Which compiler filter to use.', 214 default=None) 215 216 return parser.parse_args(argv) 217 218def main(): 219 opts = parse_options() 220 runner = PrefetchAppRunner(**vars(opts)) 221 result = runner.run() 222 223 if result is None: 224 return 1 225 226 print(result) 227 return 0 228 229if __name__ == '__main__': 230 sys.exit(main()) 231