| #!/bin/bash |
| # |
| # Copyright 2018, 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. |
| |
| usage() { |
| cat <<EOF |
| Usage: run_app_with_prefetch --package <name> [OPTIONS]... |
| |
| -p, --package <name> package of the app to test |
| -a, --activity <name> activity to use |
| -h, --help usage information (this) |
| -v, --verbose enable extra verbose printing |
| -i, --input <file> trace file protobuf (default 'TraceFile.pb') |
| -r, --readahead <mode> cold, warm, fadvise, mlock (default 'warm') |
| -w, --when <when> aot or jit (default 'aot') |
| -c, --count <count> how many times to run (default 1) |
| -s, --sleep <sec> how long to sleep after readahead |
| -t, --timeout <sec> how many seconds to timeout in between each app run (default 10) |
| -o, --output <file.csv> what file to write the performance results into as csv (default stdout) |
| EOF |
| } |
| |
| DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
| source "$DIR/lib/common" |
| |
| needs_trace_file="n" |
| input_file="" |
| package="" |
| mode='warm' |
| count=2 |
| sleep_time=2 |
| timeout=10 |
| output="" # stdout by default |
| when="aot" |
| parse_arguments() { |
| while [[ $# -gt 0 ]]; do |
| case "$1" in |
| -h|--help) |
| usage |
| exit 0 |
| ;; |
| -p|--package) |
| package="$2" |
| shift |
| ;; |
| -a|--activity) |
| activity="$2" |
| shift |
| ;; |
| -i|--input) |
| input_file="$2" |
| shift |
| ;; |
| -v|--verbose) |
| export verbose="y" |
| ;; |
| -r|--readahead) |
| mode="$2" |
| shift |
| ;; |
| -c|--count) |
| count="$2" |
| ((count+=1)) |
| shift |
| ;; |
| -s|--sleep) |
| sleep_time="$2" |
| shift |
| ;; |
| -t|--timeout) |
| timeout="$2" |
| shift |
| ;; |
| -o|--output) |
| output="$2" |
| shift |
| ;; |
| -w|--when) |
| when="$2" |
| shift |
| ;; |
| --compiler-filter) |
| compiler_filter="$2" |
| shift |
| ;; |
| *) |
| echo "Invalid argument: $1" >&2 |
| exit 1 |
| esac |
| shift |
| done |
| } |
| |
| echo_to_output_file() { |
| if [[ "x$output" != x ]]; then |
| echo "$@" >> $output |
| fi |
| # Always echo to stdout as well. |
| echo "$@" |
| } |
| |
| find_package_path() { |
| local pkg="$1" |
| |
| res="$(adb shell find "/data/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" |
| if [[ -z $res ]]; then |
| res="$(adb shell find "/system/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" |
| fi |
| echo "$res" |
| } |
| |
| # Main entry point |
| if [[ $# -eq 0 ]]; then |
| usage |
| exit 1 |
| else |
| parse_arguments "$@" |
| |
| # if we do not have have package exit early with an error |
| [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 1 |
| |
| if [[ $mode != "cold" && $mode != "warm" ]]; then |
| needs_trace_file="y" |
| if [[ -z "$input_file" ]] || ! [[ -f $input_file ]]; then |
| echo "--input not specified" 1>&2 |
| exit 1 |
| fi |
| fi |
| |
| if [[ "$activity" == "" ]]; then |
| activity="$(get_activity_name "$package")" |
| if [[ "$activity" == "" ]]; then |
| echo "Activity name could not be found, invalid package name?" 1>&2 |
| exit 1 |
| else |
| verbose_print "Activity name inferred: " "$activity" |
| fi |
| fi |
| fi |
| |
| adb root > /dev/null |
| |
| if [[ ($when == jit) || ($when == aot) ]] && [[ "$(adb shell getenforce)" != "Permissive" ]]; then |
| echo "Disable selinux permissions and restart framework." |
| adb shell setenforce 0 |
| adb shell stop |
| adb shell start |
| adb wait-for-device |
| fi |
| |
| # TODO: set performance governor etc, preferrably only once |
| # before every single app run. |
| |
| # Kill everything before running. |
| remote_pkill "$package" |
| sleep 1 |
| |
| timings_array=() |
| |
| package_path="$(find_package_path "$package")" |
| if [[ $? -ne 0 ]]; then |
| echo "Failed to detect package path for '$package'" >&2 |
| exit 1 |
| fi |
| verbose_print "Package was in path '$package_path'" |
| |
| keep_application_trace_file=n |
| application_trace_file_path="$package_path/TraceFile.pb" |
| trace_file_directory="$package_path" |
| if [[ $needs_trace_file == y ]]; then |
| # system server always passes down the package path in a hardcoded spot. |
| if [[ $when == "jit" ]]; then |
| verbose_print adb push "$input_file" "$application_trace_file_path" |
| adb push "$input_file" "$application_trace_file_path" |
| keep_application_trace_file="y" |
| else |
| # otherwise use a temporary directory to get normal non-jit behavior. |
| trace_file_directory="/data/local/tmp/prefetch/$package" |
| adb shell mkdir -p "$trace_file_directory" |
| verbose_print adb push "$input_file" "$trace_file_directory/TraceFile.pb" |
| adb push "$input_file" "$trace_file_directory/TraceFile.pb" |
| fi |
| fi |
| |
| # Everything other than JIT: remove the trace file, |
| # otherwise system server activity hints will kick in |
| # and the new just-in-time app pre-warmup will happen. |
| if [[ $keep_application_trace_file == "n" ]]; then |
| adb shell "[[ -f '$application_trace_file_path' ]] && rm '$application_trace_file_path'" |
| fi |
| |
| # Perform AOT readahead/pinning/etc when an application is about to be launched. |
| # For JIT readahead, we allow the system to handle it itself (this is a no-op). |
| # |
| # For warm, cold, etc modes which don't need readahead this is always a no-op. |
| perform_aot() { |
| local the_when="$1" # user: aot, jit |
| local the_mode="$2" # warm, cold, fadvise, mlock, etc. |
| |
| if [[ $the_when != "aot" ]]; then |
| # TODO: just in time implementation.. should probably use system server. |
| return 0 |
| fi |
| |
| # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. |
| if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then |
| |
| # TODO: add activity_hint_sender.exp |
| verbose_print "starting with package=$package package_path=$trace_file_directory" |
| coproc hint_sender_fd { $ANDROID_BUILD_TOP/system/iorap/src/sh/activity_hint_sender.exp "$package" "$trace_file_directory" "$the_mode"; } |
| hint_sender_pid=$! |
| verbose_print "Activity hint sender began" |
| |
| notification_success="n" |
| while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do |
| verbose_print "$hint_sender_output" |
| if [[ "$hint_sender_output" == "Press any key to send completed event..."* ]]; then |
| verbose_print "WE DID SEE NOTIFICATION SUCCESS." |
| notification_success='y' |
| # Give it some time to actually perform the readaheads. |
| sleep $sleep_time |
| break |
| fi |
| done |
| |
| if [[ $notification_success == 'n' ]]; then |
| echo "[FATAL] Activity hint notification failed." 1>&2 |
| exit 1 |
| fi |
| fi |
| } |
| |
| perform_aot_cleanup() { |
| local the_when="$1" # user: aot, jit |
| local the_mode="$2" # warm, cold, fadvise, mlock, etc. |
| |
| if [[ $the_when != "aot" ]]; then |
| # TODO: just in time implementation.. should probably use system server. |
| return 0 |
| fi |
| |
| # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. |
| if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then |
| # Clean up the hint sender by telling it that the launch was completed, |
| # and to shutdown the watcher. |
| echo "Done\n" >&"${hint_sender_fd[1]}" |
| |
| while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do |
| verbose_print "$hint_sender_output" |
| done |
| |
| wait $hint_sender_pid |
| fi |
| } |
| |
| configure_compiler_filter() { |
| local the_compiler_filter="$1" |
| local the_package="$2" |
| local the_activity="$3" |
| |
| if [[ -z $the_compiler_filter ]]; then |
| verbose_print "No --compiler-filter specified, don't need to force it." |
| return 0 |
| fi |
| |
| local current_compiler_filter_info="$("$DIR"/query_compiler_filter.py --package "$the_package")" |
| local res=$? |
| if [[ $res -ne 0 ]]; then |
| return $res |
| fi |
| |
| local current_compiler_filter |
| local current_reason |
| local current_isa |
| read current_compiler_filter current_reason current_isa <<< "$current_compiler_filter_info" |
| |
| verbose_print "Compiler Filter="$current_compiler_filter "Reason="$current_reason "Isa="$current_isa |
| |
| # Don't trust reasons that aren't 'unknown' because that means we didn't manually force the compilation filter. |
| # (e.g. if any automatic system-triggered compilations are not unknown). |
| if [[ $current_reason != "unknown" ]] || [[ $current_compiler_filter != $the_compiler_filter ]]; then |
| verbose_print "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" |
| "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" |
| res=$? |
| else |
| verbose_print "Queried compiler-filter matched requested compiler-filter, skip forcing." |
| res=0 |
| fi |
| |
| return $res |
| } |
| |
| # Ensure the APK is currently compiled with whatever we passed in via --compiler-filter. |
| # No-op if this option was not passed in. |
| configure_compiler_filter "$compiler_filter" "$package" "$activity" || exit 1 |
| |
| # TODO: This loop logic could probably be moved into app_startup_runner.py |
| for ((i=0;i<count;++i)) do |
| verbose_print "==========================================" |
| verbose_print "==== ITERATION $i ====" |
| verbose_print "==========================================" |
| if [[ $mode != "warm" ]]; then |
| verbose_print "Drop caches for non-warm start." |
| # Drop all caches to get cold starts. |
| adb shell "echo 3 > /proc/sys/vm/drop_caches" |
| fi |
| |
| perform_aot "$when" "$mode" |
| |
| verbose_print "Running with timeout $timeout" |
| |
| # TODO: multiple metrics output. |
| total_time="$(timeout $timeout $DIR/launch_application "$package" "$activity")" |
| |
| if [[ $? -ne 0 ]]; then |
| echo "WARNING: Skip bad result, try iteration again." >&2 |
| ((i=i-1)) |
| continue |
| fi |
| |
| perform_aot_cleanup "$when" "$mode" |
| |
| echo "Iteration $i. Total time was: $total_time" |
| |
| timings_array+=($total_time) |
| done |
| |
| # drop the first result which is usually garbage. |
| timings_array=("${timings_array[@]:1}") |
| |
| |
| # Print out interactive/debugging timings and averages. |
| # Other scripts should use the --output flag and parse the CSV. |
| for tim in "${timings_array[@]}"; do |
| echo_to_output_file -ne "$tim," |
| done |
| echo_to_output_file "" |
| |
| average_string=$(echo "${timings_array[@]}" | awk '{s+=$0}END{print "Average:",s/NR}' RS=" ") |
| echo -ne ${average_string}. |
| if [[ x$output != x ]]; then |
| echo " Saved results to '$output'" |
| fi |
| |
| # Temporary hack around multiple activities being launched with different package paths (for same app): |
| # Clean up all left-over TraceFile.pb |
| adb shell 'for i in $(find /data/app -name TraceFile.pb); do rm \$i; done' |
| |
| # Kill the process to ensure AM isn't keeping it around. |
| remote_pkill "$package" |
| |
| exit 0 |