#!/bin/bash
#
# Copyright 2017, 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.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
APP_STARTUP_DIR="$DIR/../app_startup/"
source "$DIR/common"

usage() {
    cat <<EOF
Usage: collector [OPTIONS]...

Runs an application, causes an iorap trace to be collected for it, and then invokes the iorap
compiler to generate a TraceFile.pb.

    -p, --package               package of the app to test
    -a, --activity              activity of the app to test
    -h, --help                  usage information (this)
    -v, --verbose               enable extra verbose printing
    -i, --inodes                path to inodes file (system/extras/pagecache/pagecache.py -d inodes)
    -b, --trace_buffer_size     how big to make trace buffer size (default 32768)
    -w, --wait_time             how long to run systrace for (default 10) in seconds
    -c, --compiler-filter       override the compilation filter if set (default none)
    -o, --output                output trace file protobuf (default 'TraceFile.pb')
EOF
}


DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

trace_buffer_size=32768
wait_time=10
comp_filter=""
output_dest="TraceFile.pb"

parse_arguments() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -a|--activity)
        activity="$2"
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      -p|--package)
        package="$2"
        shift
        ;;
      -i|--inodes)
        inodes="$2"
        shift
        ;;
      -b|--trace_buffer_size)
        trace_buffer_size="$2"
        shift
        ;;
      -w|--wait_time)
        wait_time="$2"
        shift
        ;;
      -c|--compiler-filter)
        comp_filter="$2"
        shift
        ;;
      -o|--output)
        output_dest="$2"
        shift
        ;;
      -v|--verbose)
        verbose="y"
        ;;
    esac
    shift
  done
}

remote_pidof() {
  local procname="$1"
  adb shell ps | grep "$procname" | awk '{print $2;}'
}

remote_pkill() {
  local procname="$1"
  shift

  local the_pids=$(remote_pidof "$procname")
  local pid

  for pid in $the_pids; do
    verbose_print adb shell kill "$@" "$pid"
    adb shell kill "$@" "$pid"
  done
}

force_package_compilation() {
  local arg_comp_filter="$1"
  local arg_package="$2"

  if [[ $arg_comp_filter == speed-profile ]]; then
    # Force the running app to dump its profiles to disk.
    remote_pkill "$arg_package" -SIGUSR1
    sleep 1 # give some time for above to complete.
  fi

  adb shell cmd package compile -m "$arg_comp_filter" -f "$arg_package"
}

parse_package_dumpsys_line() {
  local what_left="$1"
  local what_right="$2"
  local line="$3"

  if [[ $line == *${what_left}*${what_right}* ]]; then
    found="${line#*$what_left}"
    found="${found%$what_right*}"
    echo "$found"
    return 0
  fi

  return 1
}

parse_package_dumpsys_section() {
  local what_left="$1"
  local what_right="$2"
  shift
  local lines="$@"

  lines="${lines//$'\n'/}"

  local new_lines=()

  local current_line=""
  local newline=n
  local line
  for line in "${lines[@]}"; do
    if [[ $line == *: ]]; then
      newline=y
      current_line=""
      new_lines+=("$current_line")

      parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0
    else
      # strip all spaces from the start
      line="${line//$' '/}"
      current_line+="$line"
      #prepend to current line
    fi
  done
  [[ "$current_line" != "" ]] && new_lines+=("$current_line")

  parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0

  return 1
}

parse_package_compilation() {
  local pkg="$1"
#    [com.google.android.apps.maps]

  local compilation_filter
  local is_prebuilt
  local isa
  local etc

  local ret_code

  read compilation_filter is_prebuilt isa etc <<< "$("$APP_STARTUP_DIR"/query_compiler_filter.py --package "$pkg")"
  ret_code=$?

  if [[ $ret_code -eq 0 && x$compilation_filter != x ]]; then
    verbose_print "Package compilation info for $pkg was '$compilation_filter'"
    echo "$compilation_filter"
    return 0
  else
    verbose_print "query failed ret code $ret_code filter=$compilation_filter"
  fi

  return $ret_code
}

# 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 [[ -z "$inodes" ]] || ! [[ -f $inodes ]]; then
    echo "--inodes not specified" 1>&2
    exit 1
  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 [[ "$(adb shell getenforce)" != "Permissive" ]]; then
  adb shell setenforce 0
  adb shell stop
  adb shell start
  adb wait-for-device
fi

compilation_was="$(parse_package_compilation "$package")"
if [[ $? -ne 0 ]]; then
  echo "Could not determine package compilation filter; was this package installed?" >&2
  exit 1
fi
verbose_print "Package compilation: $compilation_was"

# Cannot downgrade (e.g. from speed-profile to quicken) without forceful recompilation.
# Forceful recompilation will recompile even if compilation filter was unchanged.
# Therefore avoid recompiling unless the filter is actually different than what we asked for.
if [[ "x$comp_filter" != "x" ]] && [[ "$compilation_was" != "$comp_filter" ]]; then
  echo "Current compilation filter is '$compilation_was'; force recompile to '$comp_filter'" >&2
  #TODO: this matching seems hopelessly broken, it will always recompile.

  force_package_compilation "$comp_filter" "$package"
fi

# Drop all caches prior to beginning a systrace, otherwise we won't record anything already in pagecache.
adb shell "echo 3 > /proc/sys/vm/drop_caches"

trace_tmp_file="$(mktemp -t trace.XXXXXXXXX.html)"

function finish {
  [[ -f "$trace_tmp_file" ]] &&  rm "$trace_tmp_file"
}
trap finish EXIT

launch_application_and_wait_for_trace() {
  local package="$1"
  local activity="$2"
  local timeout=30 # seconds

  # Ensure application isn't running already.
  remote_pkill "$package"

  # 5 second trace of Home screen causes
  # a trace of the home screen.
  # There is no way to abort the trace
  # so just wait for it to complete instead.
  sleep 30

  local time_now="$(logcat_save_timestamp)"
  local retcode=0

  verbose_print "Drop caches for non-warm start."
  # Drop all caches to get cold starts.
  adb shell "echo 3 > /proc/sys/vm/drop_caches"

  verbose_print "now launching application"
  # Launch an application
  "$APP_STARTUP_DIR"/launch_application "$package" "$activity"
  retcode=$?
  if [[ $retcode -ne 0 ]]; then
    echo "FATAL: Application launch failed." >&2
    return $retcode
  fi

  # This blocks until 'am start' returns at which point the application is
  # already to be considered "started" as the first frame has been drawn.

  # TODO: check for cold start w.r.t to activitymanager?

  # Wait for application to start from the point of view of ActivityTaskManager.
  local pattern="ActivityTaskManager: Displayed $package"
  logcat_wait_for_pattern "$timeout" "$time_now" "$pattern"
  retcode=$?
  if [[ $retcode -ne 0 ]]; then
    echo "FATAL: Could not find '$pattern' in logcat." >&2
    return $retcode
  fi

  # Wait for iorapd to finish writing out the perfetto traces for this app.
  iorapd_perfetto_wait_for_app_trace "$package" "$activity" "$timeout" "$time_now"
  retcode=$?
  if [[ $retcode -ne 0 ]]; then
    echo "FATAL: Could not save perfetto app trace file." >&2
    return $retcode
  fi

  verbose_print "iorapd has finished collecting app trace file for $package/$activity"
}

collector_main() {
  # don't even bother trying to run anything until the screen is unlocked.
  "$APP_STARTUP_DIR"/unlock_screen

  # Don't mutate state while iorapd is running.
  iorapd_stop || return $?

  # Remove all existing metadata for a package/activity in iorapd.
  iorapd_perfetto_purge_app_trace "$package" "$activity" || return $?
  iorapd_compiler_purge_trace_file "$package" "$activity" || return $?

  iorapd_perfetto_enable || return $?
  iorapd_readahead_disable || return $?
  iorapd_start || return $?

  # Wait for perfetto trace to finished writing itself out.
  launch_application_and_wait_for_trace "$package" "$activity" || return $?

  # Pull the perfetto trace for manual inspection.
  iorapd_perfetto_pull_trace_file "$package" "$activity" "perfetto_trace.pb"

  # Compile the trace so that the next app run can use prefetching.
  iorapd_compiler_for_app_trace "$package" "$activity" "$inodes" || return $?

  # Save TraceFile.pb to local file.
  iorapd_compiler_pull_trace_file "$package" "$activity" "$output_dest" || return $?
  # Remove the TraceFile.pb from the device.
  iorapd_compiler_purge_trace_file "$package" "$activity" || return $?

  # TODO: better transactional support for restoring iorapd global properties
  iorapd_perfetto_disable || return $?
}

collector_main "$@"

verbose_print "Collector finished. Children: "
if [[ $verbose == y ]]; then
  jobs -p
  ps f -g$$
fi

exit $?


verbose_print "About to begin systrace"
coproc systrace_fd {
  # Disable stdout buffering since we need to know the output of systrace RIGHT AWAY.
  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"
}

verbose_print "Systrace began"

systrace_pid="$!"

while read -r -u "${systrace_fd[0]}" systrace_output; do
  verbose_print "$systrace_output"
  if [[ "$systrace_output" == *"Starting tracing"* ]]; then
    verbose_print "WE DID SEE STARTING TRACING."
    break
  fi
done
# Systrace has begun recording the tracing.
# Run the application and collect the results.

am_output="$(adb shell am start -S -W "$package"/"$activity")"
if [[ $? -ne 0 ]]; then
  echo "am start failed" >&2

  exit 1
fi

verbose_print "$am_output"
total_time="$(echo "$am_output" | grep 'TotalTime:' | sed 's/TotalTime: //g')"
verbose_print "total time: $total_time"

# Now wait for systrace to finish.

wait "$systrace_pid" || { echo "Systrace finished before am start was finished, try a longer --wait_time"; exit 1; }
verbose_print "Systrace has now finished"
verbose_print "$(ls -la "$trace_tmp_file")"


iorapd_perfetto_disable

# Now that systrace has finished, convert the trace file html file to a protobuf.

"$ANDROID_BUILD_TOP"/system/iorap/src/py/collector/trace_parser.py -i "$inodes" -t "$trace_tmp_file" -o "$output_dest" || exit 1

echo "Trace file collection complete, trace file saved to \"$output_dest\"!" >&2

finish
