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