#!/bin/bash
if [ -n "$ZSH_VERSION" ]; then
SCRIPT_PATH="${(%):-%N}"
else
SCRIPT_PATH="${BASH_SOURCE[0]}"
fi
SCRIPT_DIR="$(cd $(dirname "$SCRIPT_PATH") && pwd -P)"
source "${SCRIPT_DIR}/utilities.sh"
SYMBOL_DIR="${TMPDIR:-/tmp}/android_symbols"
DEV_TMP_DIR="/data/local/tmp"
NATIVE_LIB_PATH="/lib.unstripped/libjingle_peerconnection_so.so"
APP_NAME="org.appspot.apprtc"
if [[ -n "${BASH_VERSION}" && "${BASH_SOURCE:-$0}" == "$0" ]]; then
error "perf_setup must be sourced"
exit 1
fi
function usage() {
printf "usage: . perf_setup.sh <build_dir>\n"
}
if [[ "$#" -eq 1 ]]; then
if is_not_dir "$1"; then
error "$1 is invalid"
return 1
fi
BUILD_DIR="$1"
else
error "Missing required parameter".
usage
return 1
fi
function native_shared_lib_path() {
echo "${BUILD_DIR}${NATIVE_LIB_PATH}"
}
function native_shared_lib_arch() {
readelf -h $(native_shared_lib_path) | grep Machine | awk '{print $2}'
}
function arch_is_ok() {
if [[ "$(dev_arch)" == "aarch64" ]] \
&& [[ "$(native_shared_lib_arch)" == "AArch64" ]]; then
return 0
elif [[ "$(dev_arch)" == "aarch32" ]] \
&& [[ "$(native_shared_lib_arch)" == "AArch32" ]]; then
return 0
else
return 1
fi
}
function copy_native_shared_library_to_symbol_cache() {
local arm_lib="arm"
if [[ "$(native_shared_lib_arch)" == "AArch64" ]]; then
arm_lib="arm64"
fi
for num in 1 2; do
local dir="${SYMBOL_DIR}/data/app/${APP_NAME}-${num}/lib/${arm_lib}"
mkdir -p "${dir}"
cp -u $(native_shared_lib_path) "${dir}"
done
}
function copy_kernel_symbols_from_device_to_symbol_cache() {
local symbol_cache="${SYMBOL_DIR}/kallsyms"
adb pull /proc/kallsyms "${symbol_cache}"
} 1> /dev/null
function copy_simpleperf_to_device() {
local perf_binary
[[ $(dev_arch) == "aarch64" ]] \
&& perf_binary="/arm64/simpleperf" \
|| perf_binary="/arm/simpleperf"
adb push "${SCRIPT_DIR}/simpleperf/bin/android${perf_binary}" \
"${DEV_TMP_DIR}" 1> /dev/null
adb shell run-as "${APP_NAME}" cp "${DEV_TMP_DIR}/simpleperf" .
adb shell run-as "${APP_NAME}" chmod a+x simpleperf
enable_profiling
if image_is_root; then
enable_report_symbols
fi
}
function pull_perf_data_from_device() {
adb shell run-as "${APP_NAME}" cp perf.data /sdcard/perf.data
adb pull sdcard/perf.data .
} 1> /dev/null
function perf_report() {
local perf_data="perf.data"
is_file "${perf_data}" \
&& simpleperf report \
-n \
-i "${perf_data}" \
"$@" \
|| error "$(pwd)/${perf_data} is invalid"
}
function remove_tool() {
local tool_dir="$1"
if is_dir "${tool_dir}"; then
echo "Removing ${tool_dir}..."
rm -rf "${tool_dir}"
path_remove "${tool_dir}"
fi
}
function rm_simpleperf() {
remove_tool "${SCRIPT_DIR}/simpleperf"
}
function rm_flame_graph() {
remove_tool "${SCRIPT_DIR}/flamegraph"
}
function print_function_help() {
printf "\nAvailable functions in this shell:\n"
printf " perf_record [duration, default=60sec]\n"
printf " perf_report_threads\n"
printf " perf_report_bins\n"
printf " perf_report_symbols\n"
printf " perf_report_graph\n"
printf " perf_report_graph_callee\n"
printf " perf_update\n"
printf " perf_cleanup\n"
printf " flame_graph\n"
printf " plot_flame_graph\n"
}
function cleanup() {
unset -f main
}
function perf_update() {
copy_native_shared_library_to_symbol_cache
if image_is_root; then
copy_kernel_symbols_from_device_to_symbol_cache
fi
}
function perf_record() {
if app_is_running "${APP_NAME}"; then
copy_native_shared_library_to_symbol_cache
local duration=60
if [ "$#" -eq 1 ]; then
duration="$1"
fi
local pid=$(find_app_pid "${APP_NAME}")
echo "Profiling PID $pid for $duration seconds (media must be is active)..."
adb shell run-as "${APP_NAME}" ./simpleperf record \
--call-graph fp \
-p "${pid}" \
-f 1000 \
--duration "${duration}" \
--log error
pull_perf_data_from_device
perf_report_threads | tail -n +6
else
warning "AppRTCMobile must be active"
app_start "${APP_NAME}"
echo "Start media and then call perf_record again..."
fi
}
function perf_report_threads() {
perf_report --sort comm
} 2> /dev/null
function perf_report_bins() {
perf_report --sort dso
} 2> /dev/null
function perf_report_symbols() {
perf_report --sort symbol --symfs "${SYMBOL_DIR}"
}
function perf_report_graph() {
perf_report -g caller --symfs "${SYMBOL_DIR}"
}
function perf_report_graph_callee() {
perf_report -g callee --symfs "${SYMBOL_DIR}"
}
function plot_flame_graph() {
local file_name="flame_graph.svg"
if [[ "$#" -eq 1 ]]; then
file_name="$1"
fi
google-chrome-unstable "${file_name}" \
|| google-chrome-stable "${file_name}" \
|| error "failed to find any Chrome instance"
} 2> /dev/null
function flame_graph() {
local perf_data="perf.data"
if is_not_file $perf_data; then
error "$(pwd)/${perf_data} is invalid"
return 1
fi
local file_name="flame_graph.svg"
local title="WebRTC Flame Graph"
if [[ "$#" -eq 1 ]]; then
file_name="$1"
fi
if [[ "$#" -eq 2 ]]; then
file_name="$1"
title="$2"
fi
if image_is_not_root; then
report_sample.py \
--symfs "${SYMBOL_DIR}" \
perf.data >out.perf
else
report_sample.py \
--symfs "${SYMBOL_DIR}" \
--kallsyms "${SYMBOL_DIR}/kallsyms" \
perf.data >out.perf
fi
stackcollapse-perf.pl out.perf >out.folded
flamegraph.pl --title="${title}" out.folded >"${file_name}"
rm out.perf
rm out.folded
}
function perf_cleanup () {
rm_simpleperf
rm_flame_graph
}
main() {
printf "%s\n" "Preparing profiling of AppRTCMobile on Android:"
local -r project_root_dir=$(pwd)
local dir=${project_root_dir##*/}
if [[ "${dir}" != "src" ]]; then
error "script must be called from the WebRTC project root (src) folder"
return 1
fi
ok "project root: ${project_root_dir}"
if [[ -z "$ENVSETUP_GYP_CHROME_SRC" ]]; then
error "must source envsetup script first"
return 1
fi
ok "envsetup script has been sourced"
local adb_full_path=$(which adb);
if [[ ! -x "${adb_full_path}" ]]; then
error "unable to find the Android Debug Bridge (adb) tool"
return 1
fi
ok "adb tool is working"
if ! one_device_connected; then
error "one device must be connected"
return 1
fi
ok "one device is connected via USB"
if image_is_root && adb_has_no_root_permissions; then
adb root
ok "adb is running as root"
fi
is_dir "${SYMBOL_DIR}" && rm -rf "${SYMBOL_DIR}"
mkdir "${SYMBOL_DIR}" \
&& ok "empty symbol cache created at ${SYMBOL_DIR}" \
|| error "failed to create symbol cache"
local native_lib=$(native_shared_lib_path)
if is_not_file ${native_lib}; then
error "${native_lib} is not a valid file"
return 1
fi
ok "native library: "${native_lib}""
if ! arch_is_ok; then
error "device is $(dev_arch) and lib is $(native_shared_lib_arch)"
return 1
fi
ok "device is $(dev_arch) and lib is $(native_shared_lib_arch)"
copy_native_shared_library_to_symbol_cache
ok "native library copied to ${SYMBOL_DIR}/data/app/${APP_NAME}"
if ! app_is_installed "${APP_NAME}"; then
error "${APP_NAME} is not installed on the device"
return 1
fi
ok "${APP_NAME} is installed on the device"
local simpleperf_dir="${SCRIPT_DIR}/simpleperf"
if is_not_dir "${simpleperf_dir}"; then
echo "Dowloading simpleperf..."
git clone https://android.googlesource.com/platform/prebuilts/simpleperf \
"${simpleperf_dir}"
chmod u+x "${simpleperf_dir}/report_sample.py"
fi
path_add "${simpleperf_dir}"
ok "${simpleperf_dir}" is added to PATH
local simpleperf_linux_dir="${SCRIPT_DIR}/simpleperf/bin/linux/x86_64/"
if is_not_dir "${simpleperf_linux_dir}"; then
error "${simpleperf_linux_dir} is invalid"
return 1
fi
path_add "${simpleperf_linux_dir}"
ok "${simpleperf_linux_dir}" is added to PATH
if ! copy_simpleperf_to_device; then
error "failed to install simpleperf on the device"
return 1
fi
ok "simpleperf is installed on the device"
perf_update
ok "symbol cache is updated"
local flamegraph_dir="${SCRIPT_DIR}/flamegraph"
if is_not_dir "${flamegraph_dir}"; then
echo "Dowloading Flame Graph visualization tool..."
git clone https://github.com/brendangregg/FlameGraph.git "${flamegraph_dir}"
fi
path_add "${flamegraph_dir}"
ok "${flamegraph_dir}" is added to PATH
print_function_help
cleanup
return 0
}
if is_set $BUILD_DIR; then
main "$@"
fi