1#!/bin/bash
2#
3# Copyright 2017, 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
17DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
18APP_STARTUP_DIR="$DIR/../app_startup/"
19source "$DIR/common"
20
21usage() {
22    cat <<EOF
23Usage: collector [OPTIONS]...
24
25Runs an application, causes an iorap trace to be collected for it, and then invokes the iorap
26compiler to generate a TraceFile.pb.
27
28    -p, --package               package of the app to test
29    -a, --activity              activity of the app to test
30    -h, --help                  usage information (this)
31    -v, --verbose               enable extra verbose printing
32    -i, --inodes                path to inodes file (system/extras/pagecache/pagecache.py -d inodes)
33    -b, --trace_buffer_size     how big to make trace buffer size (default 32768)
34    -w, --wait_time             how long to run systrace for (default 10) in seconds
35    -c, --compiler-filter       override the compilation filter if set (default none)
36    -o, --output                output trace file protobuf (default 'TraceFile.pb')
37EOF
38}
39
40
41DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
42
43trace_buffer_size=32768
44wait_time=10
45comp_filter=""
46output_dest="TraceFile.pb"
47
48parse_arguments() {
49  while [[ $# -gt 0 ]]; do
50    case "$1" in
51      -a|--activity)
52        activity="$2"
53        shift
54        ;;
55      -h|--help)
56        usage
57        exit 0
58        ;;
59      -p|--package)
60        package="$2"
61        shift
62        ;;
63      -i|--inodes)
64        inodes="$2"
65        shift
66        ;;
67      -b|--trace_buffer_size)
68        trace_buffer_size="$2"
69        shift
70        ;;
71      -w|--wait_time)
72        wait_time="$2"
73        shift
74        ;;
75      -c|--compiler-filter)
76        comp_filter="$2"
77        shift
78        ;;
79      -o|--output)
80        output_dest="$2"
81        shift
82        ;;
83      -v|--verbose)
84        verbose="y"
85        ;;
86    esac
87    shift
88  done
89}
90
91remote_pidof() {
92  local procname="$1"
93  adb shell ps | grep "$procname" | awk '{print $2;}'
94}
95
96remote_pkill() {
97  local procname="$1"
98  shift
99
100  local the_pids=$(remote_pidof "$procname")
101  local pid
102
103  for pid in $the_pids; do
104    verbose_print adb shell kill "$@" "$pid"
105    adb shell kill "$@" "$pid"
106  done
107}
108
109force_package_compilation() {
110  local arg_comp_filter="$1"
111  local arg_package="$2"
112
113  if [[ $arg_comp_filter == speed-profile ]]; then
114    # Force the running app to dump its profiles to disk.
115    remote_pkill "$arg_package" -SIGUSR1
116    sleep 1 # give some time for above to complete.
117  fi
118
119  adb shell cmd package compile -m "$arg_comp_filter" -f "$arg_package"
120}
121
122parse_package_dumpsys_line() {
123  local what_left="$1"
124  local what_right="$2"
125  local line="$3"
126
127  if [[ $line == *${what_left}*${what_right}* ]]; then
128    found="${line#*$what_left}"
129    found="${found%$what_right*}"
130    echo "$found"
131    return 0
132  fi
133
134  return 1
135}
136
137parse_package_dumpsys_section() {
138  local what_left="$1"
139  local what_right="$2"
140  shift
141  local lines="$@"
142
143  lines="${lines//$'\n'/}"
144
145  local new_lines=()
146
147  local current_line=""
148  local newline=n
149  local line
150  for line in "${lines[@]}"; do
151    if [[ $line == *: ]]; then
152      newline=y
153      current_line=""
154      new_lines+=("$current_line")
155
156      parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0
157    else
158      # strip all spaces from the start
159      line="${line//$' '/}"
160      current_line+="$line"
161      #prepend to current line
162    fi
163  done
164  [[ "$current_line" != "" ]] && new_lines+=("$current_line")
165
166  parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0
167
168  return 1
169}
170
171parse_package_compilation() {
172  local pkg="$1"
173#    [com.google.android.apps.maps]
174
175  local compilation_filter
176  local is_prebuilt
177  local isa
178  local etc
179
180  local ret_code
181
182  read compilation_filter is_prebuilt isa etc <<< "$("$APP_STARTUP_DIR"/query_compiler_filter.py --package "$pkg")"
183  ret_code=$?
184
185  if [[ $ret_code -eq 0 && x$compilation_filter != x ]]; then
186    verbose_print "Package compilation info for $pkg was '$compilation_filter'"
187    echo "$compilation_filter"
188    return 0
189  else
190    verbose_print "query failed ret code $ret_code filter=$compilation_filter"
191  fi
192
193  return $ret_code
194}
195
196# Main entry point
197if [[ $# -eq 0 ]]; then
198  usage
199  exit 1
200else
201  parse_arguments "$@"
202
203  # if we do not have have package exit early with an error
204  [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 1
205
206  if [[ -z "$inodes" ]] || ! [[ -f $inodes ]]; then
207    echo "--inodes not specified" 1>&2
208    exit 1
209  fi
210
211  if [[ "$activity" == "" ]]; then
212    activity="$(get_activity_name "$package")"
213    if [[ "$activity" == "" ]]; then
214      echo "Activity name could not be found, invalid package name?" 1>&2
215      exit 1
216    else
217      verbose_print "Activity name inferred: " "$activity"
218    fi
219  fi
220fi
221
222adb root > /dev/null
223
224if [[ "$(adb shell getenforce)" != "Permissive" ]]; then
225  adb shell setenforce 0
226  adb shell stop
227  adb shell start
228  adb wait-for-device
229fi
230
231compilation_was="$(parse_package_compilation "$package")"
232if [[ $? -ne 0 ]]; then
233  echo "Could not determine package compilation filter; was this package installed?" >&2
234  exit 1
235fi
236verbose_print "Package compilation: $compilation_was"
237
238# Cannot downgrade (e.g. from speed-profile to quicken) without forceful recompilation.
239# Forceful recompilation will recompile even if compilation filter was unchanged.
240# Therefore avoid recompiling unless the filter is actually different than what we asked for.
241if [[ "x$comp_filter" != "x" ]] && [[ "$compilation_was" != "$comp_filter" ]]; then
242  echo "Current compilation filter is '$compilation_was'; force recompile to '$comp_filter'" >&2
243  #TODO: this matching seems hopelessly broken, it will always recompile.
244
245  force_package_compilation "$comp_filter" "$package"
246fi
247
248# Drop all caches prior to beginning a systrace, otherwise we won't record anything already in pagecache.
249adb shell "echo 3 > /proc/sys/vm/drop_caches"
250
251trace_tmp_file="$(mktemp -t trace.XXXXXXXXX.html)"
252
253function finish {
254  [[ -f "$trace_tmp_file" ]] &&  rm "$trace_tmp_file"
255}
256trap finish EXIT
257
258launch_application_and_wait_for_trace() {
259  local package="$1"
260  local activity="$2"
261  local timeout=30 # seconds
262
263  # Ensure application isn't running already.
264  remote_pkill "$package"
265
266  # 5 second trace of Home screen causes
267  # a trace of the home screen.
268  # There is no way to abort the trace
269  # so just wait for it to complete instead.
270  sleep 30
271
272  local time_now="$(logcat_save_timestamp)"
273  local retcode=0
274
275  verbose_print "Drop caches for non-warm start."
276  # Drop all caches to get cold starts.
277  adb shell "echo 3 > /proc/sys/vm/drop_caches"
278
279  verbose_print "now launching application"
280  # Launch an application
281  "$APP_STARTUP_DIR"/launch_application "$package" "$activity"
282  retcode=$?
283  if [[ $retcode -ne 0 ]]; then
284    echo "FATAL: Application launch failed." >&2
285    return $retcode
286  fi
287
288  # This blocks until 'am start' returns at which point the application is
289  # already to be considered "started" as the first frame has been drawn.
290
291  # TODO: check for cold start w.r.t to activitymanager?
292
293  # Wait for application to start from the point of view of ActivityTaskManager.
294  local pattern="ActivityTaskManager: Displayed $package"
295  logcat_wait_for_pattern "$timeout" "$time_now" "$pattern"
296  retcode=$?
297  if [[ $retcode -ne 0 ]]; then
298    echo "FATAL: Could not find '$pattern' in logcat." >&2
299    return $retcode
300  fi
301
302  # Wait for iorapd to finish writing out the perfetto traces for this app.
303  iorapd_perfetto_wait_for_app_trace "$package" "$activity" "$timeout" "$time_now"
304  retcode=$?
305  if [[ $retcode -ne 0 ]]; then
306    echo "FATAL: Could not save perfetto app trace file." >&2
307    return $retcode
308  fi
309
310  verbose_print "iorapd has finished collecting app trace file for $package/$activity"
311}
312
313collector_main() {
314  # don't even bother trying to run anything until the screen is unlocked.
315  "$APP_STARTUP_DIR"/unlock_screen
316
317  # Don't mutate state while iorapd is running.
318  iorapd_stop || return $?
319
320  # Remove all existing metadata for a package/activity in iorapd.
321  iorapd_perfetto_purge_app_trace "$package" "$activity" || return $?
322  iorapd_compiler_purge_trace_file "$package" "$activity" || return $?
323
324  iorapd_perfetto_enable || return $?
325  iorapd_readahead_disable || return $?
326  iorapd_start || return $?
327
328  # Wait for perfetto trace to finished writing itself out.
329  launch_application_and_wait_for_trace "$package" "$activity" || return $?
330
331  # Pull the perfetto trace for manual inspection.
332  iorapd_perfetto_pull_trace_file "$package" "$activity" "perfetto_trace.pb"
333
334  # Compile the trace so that the next app run can use prefetching.
335  iorapd_compiler_for_app_trace "$package" "$activity" "$inodes" || return $?
336
337  # Save TraceFile.pb to local file.
338  iorapd_compiler_pull_trace_file "$package" "$activity" "$output_dest" || return $?
339  # Remove the TraceFile.pb from the device.
340  iorapd_compiler_purge_trace_file "$package" "$activity" || return $?
341
342  # TODO: better transactional support for restoring iorapd global properties
343  iorapd_perfetto_disable || return $?
344}
345
346collector_main "$@"
347
348verbose_print "Collector finished. Children: "
349if [[ $verbose == y ]]; then
350  jobs -p
351  ps f -g$$
352fi
353
354exit $?
355
356
357verbose_print "About to begin systrace"
358coproc systrace_fd {
359  # Disable stdout buffering since we need to know the output of systrace RIGHT AWAY.
360  stdbuf -oL "$ANDROID_BUILD_TOP"/external/chromium-trace/systrace.py --target=android -b "$trace_buffer_size" -t "$wait_time" am pagecache dalvik -o "$trace_tmp_file"
361}
362
363verbose_print "Systrace began"
364
365systrace_pid="$!"
366
367while read -r -u "${systrace_fd[0]}" systrace_output; do
368  verbose_print "$systrace_output"
369  if [[ "$systrace_output" == *"Starting tracing"* ]]; then
370    verbose_print "WE DID SEE STARTING TRACING."
371    break
372  fi
373done
374# Systrace has begun recording the tracing.
375# Run the application and collect the results.
376
377am_output="$(adb shell am start -S -W "$package"/"$activity")"
378if [[ $? -ne 0 ]]; then
379  echo "am start failed" >&2
380
381  exit 1
382fi
383
384verbose_print "$am_output"
385total_time="$(echo "$am_output" | grep 'TotalTime:' | sed 's/TotalTime: //g')"
386verbose_print "total time: $total_time"
387
388# Now wait for systrace to finish.
389
390wait "$systrace_pid" || { echo "Systrace finished before am start was finished, try a longer --wait_time"; exit 1; }
391verbose_print "Systrace has now finished"
392verbose_print "$(ls -la "$trace_tmp_file")"
393
394
395iorapd_perfetto_disable
396
397# Now that systrace has finished, convert the trace file html file to a protobuf.
398
399"$ANDROID_BUILD_TOP"/system/iorap/src/py/collector/trace_parser.py -i "$inodes" -t "$trace_tmp_file" -o "$output_dest" || exit 1
400
401echo "Trace file collection complete, trace file saved to \"$output_dest\"!" >&2
402
403finish
404