1#!/bin/bash 2# 3# Copyright 2018, 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 17usage() { 18 cat <<EOF 19Usage: run_app_with_prefetch --package <name> [OPTIONS]... 20 21 -p, --package <name> package of the app to test 22 -a, --activity <name> activity to use 23 -h, --help usage information (this) 24 -v, --verbose enable extra verbose printing 25 -i, --input <file> trace file protobuf (default 'TraceFile.pb') 26 -r, --readahead <mode> cold, warm, fadvise, mlock (default 'warm') 27 -w, --when <when> aot or jit (default 'jit') 28 -c, --count <count> how many times to run (default 1) 29 -s, --sleep <sec> how long to sleep after readahead 30 -t, --timeout <sec> how many seconds to timeout in between each app run (default 10) 31 -o, --output <file.csv> what file to write the performance results into as csv (default stdout) 32EOF 33} 34 35DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 36source "$DIR/../iorap/common" 37 38report_fully_drawn="n" 39needs_trace_file="n" 40input_file="" 41package="" 42mode='warm' 43count=2 44sleep_time=2 45timeout=10 46output="" # stdout by default 47when="jit" 48parse_arguments() { 49 while [[ $# -gt 0 ]]; do 50 case "$1" in 51 -h|--help) 52 usage 53 exit 0 54 ;; 55 -p|--package) 56 package="$2" 57 shift 58 ;; 59 -a|--activity) 60 activity="$2" 61 shift 62 ;; 63 -i|--input) 64 input_file="$2" 65 shift 66 ;; 67 -v|--verbose) 68 export verbose="y" 69 ;; 70 -r|--readahead) 71 mode="$2" 72 shift 73 ;; 74 -rfd|--reportfullydrawn) 75 report_fully_drawn="y" 76 shift 77 ;; 78 -c|--count) 79 count="$2" 80 ((count+=1)) 81 shift 82 ;; 83 -s|--sleep) 84 sleep_time="$2" 85 shift 86 ;; 87 -t|--timeout) 88 timeout="$2" 89 shift 90 ;; 91 -o|--output) 92 output="$2" 93 shift 94 ;; 95 -w|--when) 96 when="$2" 97 shift 98 ;; 99 --compiler-filter) 100 compiler_filter="$2" 101 shift 102 ;; 103 *) 104 echo "Invalid argument: $1" >&2 105 exit 1 106 esac 107 shift 108 done 109 110 if [[ $when == "aot" ]]; then 111 # TODO: re-implement aot later for experimenting. 112 echo "Error: --when $when is unsupported" >&2 113 exit 1 114 elif [[ $when != "jit" ]]; then 115 echo "Error: --when must be one of (aot jit)." >&2 116 exit 1 117 fi 118} 119 120echo_to_output_file() { 121 if [[ "x$output" != x ]]; then 122 echo "$@" >> $output 123 fi 124 # Always echo to stdout as well. 125 echo "$@" 126} 127 128find_package_path() { 129 local pkg="$1" 130 131 res="$(adb shell find "/data/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" 132 if [[ -z $res ]]; then 133 res="$(adb shell find "/system/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" 134 fi 135 echo "$res" 136} 137 138# Main entry point 139if [[ $# -eq 0 ]]; then 140 usage 141 exit 1 142else 143 parse_arguments "$@" 144 145 # if we do not have have package exit early with an error 146 [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 1 147 148 if [[ $mode != "cold" && $mode != "warm" ]]; then 149 needs_trace_file="y" 150 if [[ -z "$input_file" ]] || ! [[ -f $input_file ]]; then 151 echo "--input not specified" 1>&2 152 exit 1 153 fi 154 fi 155 156 if [[ "$activity" == "" ]]; then 157 activity="$(get_activity_name "$package")" 158 if [[ "$activity" == "" ]]; then 159 echo "Activity name could not be found, invalid package name?" 1>&2 160 exit 1 161 else 162 verbose_print "Activity name inferred: " "$activity" 163 fi 164 fi 165fi 166 167adb root > /dev/null 168 169if [[ ($when == jit) || ($when == aot) ]] && [[ "$(adb shell getenforce)" != "Permissive" ]]; then 170 echo "Disable selinux permissions and restart framework." 171 adb shell setenforce 0 172 adb shell stop 173 adb shell start 174 adb wait-for-device 175fi 176 177# TODO: set performance governor etc, preferrably only once 178# before every single app run. 179 180# Kill everything before running. 181remote_pkill "$package" 182sleep 1 183 184timings_array=() 185 186package_path="$(find_package_path "$package")" 187if [[ $? -ne 0 ]]; then 188 echo "Failed to detect package path for '$package'" >&2 189 exit 1 190fi 191verbose_print "Package was in path '$package_path'" 192 193application_trace_file_path="$package_path/TraceFile.pb" 194trace_file_directory="$package_path" 195if [[ $needs_trace_file == y ]]; then 196 # system server always passes down the package path in a hardcoded spot. 197 if [[ $when == "jit" ]]; then 198 if ! iorapd_compiler_install_trace_file "$package" "$activity" "$input_file"; then 199 echo "Error: Failed to install compiled TraceFile.pb for '$package/$activity'" >&2 200 exit 1 201 fi 202 keep_application_trace_file="y" 203 else 204 echo "TODO: --readahead=aot is non-functional and needs to be fixed." >&2 205 exit 1 206 # otherwise use a temporary directory to get normal non-jit behavior. 207 trace_file_directory="/data/local/tmp/prefetch/$package" 208 adb shell mkdir -p "$trace_file_directory" 209 verbose_print adb push "$input_file" "$trace_file_directory/TraceFile.pb" 210 adb push "$input_file" "$trace_file_directory/TraceFile.pb" 211 fi 212fi 213 214# Everything other than JIT: remove the trace file, 215# otherwise system server activity hints will kick in 216# and the new just-in-time app pre-warmup will happen. 217if [[ $keep_application_trace_file == "n" ]]; then 218 iorapd_compiler_purge_trace_file "$package" "$activity" 219fi 220 221# Perform AOT readahead/pinning/etc when an application is about to be launched. 222# For JIT readahead, we allow the system to handle it itself (this is a no-op). 223# 224# For warm, cold, etc modes which don't need readahead this is always a no-op. 225perform_aot() { 226 local the_when="$1" # user: aot, jit 227 local the_mode="$2" # warm, cold, fadvise, mlock, etc. 228 229 # iorapd readahead for jit+(mlock/fadvise) 230 if [[ $the_when == "jit" && $the_mode != 'warm' && $the_mode != 'cold' ]]; then 231 iorapd_readahead_enable 232 return 0 233 fi 234 235 if [[ $the_when != "aot" ]]; then 236 # TODO: just in time implementation.. should probably use system server. 237 return 0 238 fi 239 240 # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. 241 if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then 242 243 # TODO: add activity_hint_sender.exp 244 verbose_print "starting with package=$package package_path=$trace_file_directory" 245 coproc hint_sender_fd { $ANDROID_BUILD_TOP/system/iorap/src/sh/activity_hint_sender.exp "$package" "$trace_file_directory" "$the_mode"; } 246 hint_sender_pid=$! 247 verbose_print "Activity hint sender began" 248 249 notification_success="n" 250 while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do 251 verbose_print "$hint_sender_output" 252 if [[ "$hint_sender_output" == "Press any key to send completed event..."* ]]; then 253 verbose_print "WE DID SEE NOTIFICATION SUCCESS." 254 notification_success='y' 255 # Give it some time to actually perform the readaheads. 256 sleep $sleep_time 257 break 258 fi 259 done 260 261 if [[ $notification_success == 'n' ]]; then 262 echo "[FATAL] Activity hint notification failed." 1>&2 263 exit 1 264 fi 265 fi 266} 267 268# Perform cleanup at the end of each loop iteration. 269perform_post_launch_cleanup() { 270 local the_when="$1" # user: aot, jit 271 local the_mode="$2" # warm, cold, fadvise, mlock, etc. 272 local logcat_timestamp="$3" # timestamp from before am start. 273 local res 274 275 if [[ $the_when != "aot" ]]; then 276 if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then 277 # Validate that readahead completes. 278 # If this fails for some reason, then this will also discard the timing of the run. 279 iorapd_readahead_wait_until_finished "$package" "$activity" "$logcat_timestamp" "$timeout" 280 res=$? 281 282 iorapd_readahead_disable 283 284 return $res 285 fi 286 # Don't need to do anything for warm or cold. 287 return 0 288 fi 289 290 # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. 291 if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then 292 # Clean up the hint sender by telling it that the launch was completed, 293 # and to shutdown the watcher. 294 echo "Done\n" >&"${hint_sender_fd[1]}" 295 296 while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do 297 verbose_print "$hint_sender_output" 298 done 299 300 wait $hint_sender_pid 301 fi 302} 303 304configure_compiler_filter() { 305 local the_compiler_filter="$1" 306 local the_package="$2" 307 local the_activity="$3" 308 309 if [[ -z $the_compiler_filter ]]; then 310 verbose_print "No --compiler-filter specified, don't need to force it." 311 return 0 312 fi 313 314 local current_compiler_filter_info="$("$DIR"/query_compiler_filter.py --package "$the_package")" 315 local res=$? 316 if [[ $res -ne 0 ]]; then 317 return $res 318 fi 319 320 local current_compiler_filter 321 local current_reason 322 local current_isa 323 read current_compiler_filter current_reason current_isa <<< "$current_compiler_filter_info" 324 325 verbose_print "Compiler Filter="$current_compiler_filter "Reason="$current_reason "Isa="$current_isa 326 327 # Don't trust reasons that aren't 'unknown' because that means we didn't manually force the compilation filter. 328 # (e.g. if any automatic system-triggered compilations are not unknown). 329 if [[ $current_reason != "unknown" ]] || [[ $current_compiler_filter != $the_compiler_filter ]]; then 330 verbose_print "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" 331 "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" 332 res=$? 333 else 334 verbose_print "Queried compiler-filter matched requested compiler-filter, skip forcing." 335 res=0 336 fi 337 338 return $res 339} 340 341# Ensure the APK is currently compiled with whatever we passed in via --compiler-filter. 342# No-op if this option was not passed in. 343configure_compiler_filter "$compiler_filter" "$package" "$activity" || exit 1 344 345# convert 'a=b\nc=d\ne=f\n...' into 'b,d,f,...' 346parse_metrics_output_string() { 347 # single string with newlines in it. 348 local input="$1" 349 350 local metric_name 351 local metric_value 352 local rest 353 354 local all_metrics=() 355 356 # (n1=v1 n2=v2 n3=v3 ...) 357 readarray -t all_metrics <<< "$input" 358 359 local kv_pair=() 360 local i 361 362 for i in "${all_metrics[@]}" 363 do 364 verbose_print "parse_metrics_output: element '$i'" 365 # name=value 366 367 IFS='=' read -r metric_name metric_value rest <<< "$i" 368 369 verbose_print "parse_metrics_output: metric_value '$metric_value'" 370 371 # (value1 value2 value3 ...) 372 all_metrics+=(${metric_value}) 373 done 374 375 # "value1,value2,value3,..." 376 join_by ',' "${all_metrics[@]}" 377} 378 379# convert 'a=b\nc=d\ne=f\n... into b,d,f,...' 380parse_metrics_output() { 381 local metric_name 382 local metric_value 383 local rest 384 385 local all_metrics=() 386 387 while IFS='=' read -r metric_name metric_value rest; do 388 verbose_print "metric: $metric_name, value: $metric_value; rest: $rest" 389 all_metrics+=($metric_value) 390 done 391 392 join_by ',' "${all_metrics[@]}" 393} 394 395# convert 'a=b\nc=d\ne=f\n... into b,d,f,...' 396parse_metrics_header() { 397 local metric_name 398 local metric_value 399 local rest 400 401 local all_metrics=() 402 403 while IFS='=' read -r metric_name metric_value rest; do 404 verbose_print "metric: $metric_name, value: $metric_value; rest: $rest" 405 all_metrics+=($metric_name) 406 done 407 408 join_by ',' "${all_metrics[@]}" 409} 410 411if [[ $report_fully_drawn == y ]]; then 412 metrics_header="$("$DIR/parse_metrics" --package "$package" --activity "$activity" --simulate --reportfullydrawn | parse_metrics_header)" 413else 414 metrics_header="$("$DIR/parse_metrics" --package "$package" --activity "$activity" --simulate | parse_metrics_header)" 415fi 416 417# TODO: This loop logic could probably be moved into app_startup_runner.py 418for ((i=0;i<count;++i)) do 419 verbose_print "==========================================" 420 verbose_print "==== ITERATION $i ====" 421 verbose_print "==========================================" 422 if [[ $mode != "warm" ]]; then 423 # The package must be killed **before** we drop caches, otherwise pages will stay resident. 424 verbose_print "Kill package for non-warm start." 425 remote_pkill "$package" 426 verbose_print "Drop caches for non-warm start." 427 # Drop all caches to get cold starts. 428 adb shell "echo 3 > /proc/sys/vm/drop_caches" 429 fi 430 431 perform_aot "$when" "$mode" 432 433 verbose_print "Running with timeout $timeout" 434 435 pre_launch_timestamp="$(logcat_save_timestamp)" 436 437 # TODO: multiple metrics output. 438 439if [[ $report_fully_drawn == y ]]; then 440 total_time="$(timeout $timeout "$DIR/launch_application" "$package" "$activity" | "$DIR/parse_metrics" --package "$package" --activity "$activity" --timestamp "$pre_launch_timestamp" --reportfullydrawn | parse_metrics_output)" 441else 442 total_time="$(timeout $timeout "$DIR/launch_application" "$package" "$activity" | "$DIR/parse_metrics" --package "$package" --activity "$activity" --timestamp "$pre_launch_timestamp" | parse_metrics_output)" 443fi 444 445 if [[ $? -ne 0 ]]; then 446 echo "WARNING: Skip bad result, try iteration again." >&2 447 ((i=i-1)) 448 continue 449 fi 450 451 perform_post_launch_cleanup "$when" "$mode" "$pre_launch_timestamp" 452 453 if [[ $? -ne 0 ]]; then 454 echo "WARNING: Skip bad cleanup, try iteration again." >&2 455 ((i=i-1)) 456 continue 457 fi 458 459 echo "Iteration $i. Total time was: $total_time" 460 461 timings_array+=("$total_time") 462done 463 464# drop the first result which is usually garbage. 465timings_array=("${timings_array[@]:1}") 466 467# Print the CSV header first. 468echo_to_output_file "$metrics_header" 469 470# Print out interactive/debugging timings and averages. 471# Other scripts should use the --output flag and parse the CSV. 472for tim in "${timings_array[@]}"; do 473 echo_to_output_file "$tim" 474done 475 476if [[ x$output != x ]]; then 477 echo " Saved results to '$output'" 478fi 479 480if [[ $needs_trace_file == y ]] ; then 481 iorapd_compiler_purge_trace_file "$package" "$activity" 482fi 483 484# Kill the process to ensure AM isn't keeping it around. 485remote_pkill "$package" 486 487exit 0 488