• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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