| OLD | NEW |
| (Empty) |
| 1 #!/bin/bash | |
| 2 | |
| 3 # Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. | |
| 4 # | |
| 5 # Use of this source code is governed by a BSD-style license | |
| 6 # that can be found in the LICENSE file in the root of the source | |
| 7 # tree. An additional intellectual property rights grant can be found | |
| 8 # in the file PATENTS. All contributing project authors may | |
| 9 # be found in the AUTHORS file in the root of the source tree. | |
| 10 # | |
| 11 # Usage: | |
| 12 # | |
| 13 # It is assumed that a release build of AppRTCMobile exists and has been | |
| 14 # installed on an Android device which supports USB debugging. | |
| 15 # | |
| 16 # Source this script once from the WebRTC src/ directory and resolve any | |
| 17 # reported issues. Add relative path to build directory as parameter. | |
| 18 # Required tools will be downloaded if they don't already exist. | |
| 19 # | |
| 20 # Once all tests are passed, a list of available functions will be given. | |
| 21 # Use these functions to do the actual profiling and visualization of the | |
| 22 # results. | |
| 23 # | |
| 24 # Note that, using a rooted device is recommended since it allows us to | |
| 25 # resolve kernel symbols (kallsyms) as well. | |
| 26 # | |
| 27 # Example usage: | |
| 28 # | |
| 29 # > . tools-webrtc/android/profiling/perf_setup.sh out/Release | |
| 30 # > perf_record 120 | |
| 31 # > flame_graph | |
| 32 # > plot_flame_graph | |
| 33 # > perf_cleanup | |
| 34 | |
| 35 if [ -n "$ZSH_VERSION" ]; then | |
| 36 # Running inside zsh. | |
| 37 SCRIPT_PATH="${(%):-%N}" | |
| 38 else | |
| 39 # Running inside something else (most likely bash). | |
| 40 SCRIPT_PATH="${BASH_SOURCE[0]}" | |
| 41 fi | |
| 42 SCRIPT_DIR="$(cd $(dirname "$SCRIPT_PATH") && pwd -P)" | |
| 43 source "${SCRIPT_DIR}/utilities.sh" | |
| 44 | |
| 45 # Root directory for local symbol cache. | |
| 46 SYMBOL_DIR="${TMPDIR:-/tmp}/android_symbols" | |
| 47 # Used as a temporary folder on the Android device for data storage. | |
| 48 DEV_TMP_DIR="/data/local/tmp" | |
| 49 # Relative path to native shared library containing symbols. | |
| 50 NATIVE_LIB_PATH="/lib.unstripped/libjingle_peerconnection_so.so" | |
| 51 # Name of application package for the AppRTCMobile demo. | |
| 52 APP_NAME="org.appspot.apprtc" | |
| 53 | |
| 54 # Make sure we're being sourced. | |
| 55 if [[ -n "${BASH_VERSION}" && "${BASH_SOURCE:-$0}" == "$0" ]]; then | |
| 56 error "perf_setup must be sourced" | |
| 57 exit 1 | |
| 58 fi | |
| 59 | |
| 60 function usage() { | |
| 61 printf "usage: . perf_setup.sh <build_dir>\n" | |
| 62 } | |
| 63 | |
| 64 # Ensure that user includes name of build directory (e.g. out/Release) as | |
| 65 # input parameter. Store path in BUILD_DIR. | |
| 66 if [[ "$#" -eq 1 ]]; then | |
| 67 if is_not_dir "$1"; then | |
| 68 error "$1 is invalid" | |
| 69 return 1 | |
| 70 fi | |
| 71 BUILD_DIR="$1" | |
| 72 else | |
| 73 error "Missing required parameter". | |
| 74 usage | |
| 75 return 1 | |
| 76 fi | |
| 77 | |
| 78 # Full (relative) path to the libjingle_peerconnection_so.so file. | |
| 79 function native_shared_lib_path() { | |
| 80 echo "${BUILD_DIR}${NATIVE_LIB_PATH}" | |
| 81 } | |
| 82 | |
| 83 # Target CPU architecture for the native shared library. | |
| 84 # Example: AArch64. | |
| 85 function native_shared_lib_arch() { | |
| 86 readelf -h $(native_shared_lib_path) | grep Machine | awk '{print $2}' | |
| 87 } | |
| 88 | |
| 89 # Returns true if the device architecture and the build target are the same. | |
| 90 function arch_is_ok() { | |
| 91 if [[ "$(dev_arch)" == "aarch64" ]] \ | |
| 92 && [[ "$(native_shared_lib_arch)" == "AArch64" ]]; then | |
| 93 return 0 | |
| 94 elif [[ "$(dev_arch)" == "aarch32" ]] \ | |
| 95 && [[ "$(native_shared_lib_arch)" == "AArch32" ]]; then | |
| 96 return 0 | |
| 97 else | |
| 98 return 1 | |
| 99 fi | |
| 100 } | |
| 101 | |
| 102 # Copies the native shared library from the local host to the symbol cache | |
| 103 # which is used by simpleperf as base when searching for symbols. | |
| 104 function copy_native_shared_library_to_symbol_cache() { | |
| 105 local arm_lib="arm" | |
| 106 if [[ "$(native_shared_lib_arch)" == "AArch64" ]]; then | |
| 107 arm_lib="arm64" | |
| 108 fi | |
| 109 for num in 1 2; do | |
| 110 local dir="${SYMBOL_DIR}/data/app/${APP_NAME}-${num}/lib/${arm_lib}" | |
| 111 mkdir -p "${dir}" | |
| 112 cp -u $(native_shared_lib_path) "${dir}" | |
| 113 done | |
| 114 } | |
| 115 | |
| 116 # Copy kernel symbols from device to symbol cache in tmp. | |
| 117 function copy_kernel_symbols_from_device_to_symbol_cache() { | |
| 118 local symbol_cache="${SYMBOL_DIR}/kallsyms" | |
| 119 adb pull /proc/kallsyms "${symbol_cache}" | |
| 120 } 1> /dev/null | |
| 121 | |
| 122 # Download the correct version of 'simpleperf' to $DEV_TMP_DIR | |
| 123 # on the device and enable profiling. | |
| 124 function copy_simpleperf_to_device() { | |
| 125 local perf_binary | |
| 126 [[ $(dev_arch) == "aarch64" ]] \ | |
| 127 && perf_binary="/arm64/simpleperf" \ | |
| 128 || perf_binary="/arm/simpleperf" | |
| 129 # Copy the simpleperf binary from local host to temp folder on device. | |
| 130 adb push "${SCRIPT_DIR}/simpleperf/bin/android${perf_binary}" \ | |
| 131 "${DEV_TMP_DIR}" 1> /dev/null | |
| 132 # Copy simpleperf from temp folder to the application package. | |
| 133 adb shell run-as "${APP_NAME}" cp "${DEV_TMP_DIR}/simpleperf" . | |
| 134 adb shell run-as "${APP_NAME}" chmod a+x simpleperf | |
| 135 # Enable profiling on the device. | |
| 136 enable_profiling | |
| 137 # Allows usage of running report commands on the device. | |
| 138 if image_is_root; then | |
| 139 enable_report_symbols | |
| 140 fi | |
| 141 } | |
| 142 | |
| 143 # Copy the recorded 'perf.data' file from the device to the current directory. | |
| 144 # TODO(henrika): add support for specifying the destination. | |
| 145 function pull_perf_data_from_device() { | |
| 146 adb shell run-as "${APP_NAME}" cp perf.data /sdcard/perf.data | |
| 147 adb pull sdcard/perf.data . | |
| 148 } 1> /dev/null | |
| 149 | |
| 150 | |
| 151 # Wraps calls to simpleperf report. Used by e.g. perf_report_threads. | |
| 152 # A valid profile input file must exist in the current folder. | |
| 153 # TODO(henrika): possibly add support to add path to alternative input file. | |
| 154 function perf_report() { | |
| 155 local perf_data="perf.data" | |
| 156 is_file "${perf_data}" \ | |
| 157 && simpleperf report \ | |
| 158 -n \ | |
| 159 -i "${perf_data}" \ | |
| 160 "$@" \ | |
| 161 || error "$(pwd)/${perf_data} is invalid" | |
| 162 } | |
| 163 | |
| 164 # Removes the folder specified as input parameter. Mainly intended for removal | |
| 165 # of simpleperf and Flame Graph tools. | |
| 166 function remove_tool() { | |
| 167 local tool_dir="$1" | |
| 168 if is_dir "${tool_dir}"; then | |
| 169 echo "Removing ${tool_dir}..." | |
| 170 rm -rf "${tool_dir}" | |
| 171 path_remove "${tool_dir}" | |
| 172 fi | |
| 173 } | |
| 174 | |
| 175 # Utility method which deletes the downloaded simpleperf tool from the repo. | |
| 176 # It also removes the simpleperf root folder from PATH. | |
| 177 function rm_simpleperf() { | |
| 178 remove_tool "${SCRIPT_DIR}/simpleperf" | |
| 179 } | |
| 180 | |
| 181 # Utility method which deletes the downloaded Flame Graph tool from the repo. | |
| 182 # It also removes the Flame Graph root folder from PATH. | |
| 183 function rm_flame_graph() { | |
| 184 remove_tool "${SCRIPT_DIR}/flamegraph" | |
| 185 } | |
| 186 | |
| 187 # Lists the main available functions after sourcing this script. | |
| 188 function print_function_help() { | |
| 189 printf "\nAvailable functions in this shell:\n" | |
| 190 printf " perf_record [duration, default=60sec]\n" | |
| 191 printf " perf_report_threads\n" | |
| 192 printf " perf_report_bins\n" | |
| 193 printf " perf_report_symbols\n" | |
| 194 printf " perf_report_graph\n" | |
| 195 printf " perf_report_graph_callee\n" | |
| 196 printf " perf_update\n" | |
| 197 printf " perf_cleanup\n" | |
| 198 printf " flame_graph\n" | |
| 199 printf " plot_flame_graph\n" | |
| 200 } | |
| 201 | |
| 202 function cleanup() { | |
| 203 unset -f main | |
| 204 } | |
| 205 | |
| 206 # ----------------------------------------------------------------------------- | |
| 207 # Main methods to be used after sourcing the main script. | |
| 208 # ----------------------------------------------------------------------------- | |
| 209 | |
| 210 # Call this method after the application as been rebuilt and installed on the | |
| 211 # device to ensure that symbols are up-to-date. | |
| 212 function perf_update() { | |
| 213 copy_native_shared_library_to_symbol_cache | |
| 214 if image_is_root; then | |
| 215 copy_kernel_symbols_from_device_to_symbol_cache | |
| 216 fi | |
| 217 } | |
| 218 | |
| 219 # Record stack frame based call graphs while using the application. | |
| 220 # We use default events (cpu-cycles), and write records to 'perf.data' in the | |
| 221 # tmp folder on the device. Default duration is 60 seconds but it can be changed | |
| 222 # by adding one parameter. As soon as the recording is done, 'perf.data' is | |
| 223 # copied to the directory from which this method is called and a summary of | |
| 224 # the load distribution per thread is printed. | |
| 225 function perf_record() { | |
| 226 if app_is_running "${APP_NAME}"; then | |
| 227 # Ensure that the latest native shared library exists in the local cache. | |
| 228 copy_native_shared_library_to_symbol_cache | |
| 229 local duration=60 | |
| 230 if [ "$#" -eq 1 ]; then | |
| 231 duration="$1" | |
| 232 fi | |
| 233 local pid=$(find_app_pid "${APP_NAME}") | |
| 234 echo "Profiling PID $pid for $duration seconds (media must be is active)..." | |
| 235 adb shell run-as "${APP_NAME}" ./simpleperf record \ | |
| 236 --call-graph fp \ | |
| 237 -p "${pid}" \ | |
| 238 -f 1000 \ | |
| 239 --duration "${duration}" \ | |
| 240 --log error | |
| 241 # Copy profile results from device to current directory. | |
| 242 pull_perf_data_from_device | |
| 243 # Print out a summary report (load per thread). | |
| 244 perf_report_threads | tail -n +6 | |
| 245 else | |
| 246 # AppRTCMobile was not enabled. Start it up automatically and ask the user | |
| 247 # to start media and then call this method again. | |
| 248 warning "AppRTCMobile must be active" | |
| 249 app_start "${APP_NAME}" | |
| 250 echo "Start media and then call perf_record again..." | |
| 251 fi | |
| 252 } | |
| 253 | |
| 254 # Analyze the profile report and show samples per threads. | |
| 255 function perf_report_threads() { | |
| 256 perf_report --sort comm | |
| 257 } 2> /dev/null | |
| 258 | |
| 259 # Analyze the profile report and show samples per binary. | |
| 260 function perf_report_bins() { | |
| 261 perf_report --sort dso | |
| 262 } 2> /dev/null | |
| 263 | |
| 264 # Analyze the profile report and show samples per symbol. | |
| 265 function perf_report_symbols() { | |
| 266 perf_report --sort symbol --symfs "${SYMBOL_DIR}" | |
| 267 } | |
| 268 | |
| 269 # Print call graph showing how functions call others. | |
| 270 function perf_report_graph() { | |
| 271 perf_report -g caller --symfs "${SYMBOL_DIR}" | |
| 272 } | |
| 273 | |
| 274 # Print call graph showing how functions are called from others. | |
| 275 function perf_report_graph_callee() { | |
| 276 perf_report -g callee --symfs "${SYMBOL_DIR}" | |
| 277 } | |
| 278 | |
| 279 # Plots the default Flame Graph file if no parameter is provided. | |
| 280 # If a parameter is given, it will be used as file name instead of the default. | |
| 281 function plot_flame_graph() { | |
| 282 local file_name="flame_graph.svg" | |
| 283 if [[ "$#" -eq 1 ]]; then | |
| 284 file_name="$1" | |
| 285 fi | |
| 286 # Open up the SVG file in Chrome. Try unstable first and revert to stable | |
| 287 # if unstable fails. | |
| 288 google-chrome-unstable "${file_name}" \ | |
| 289 || google-chrome-stable "${file_name}" \ | |
| 290 || error "failed to find any Chrome instance" | |
| 291 } 2> /dev/null | |
| 292 | |
| 293 # Generate Flame Graph in interactive SVG format. | |
| 294 # First input parameter corresponds to output file name and second input | |
| 295 # parameter is the heading of the plot. | |
| 296 # Defaults will be utilized if parameters are not provided. | |
| 297 # See https://github.com/brendangregg/FlameGraph for details on Flame Graph. | |
| 298 function flame_graph() { | |
| 299 local perf_data="perf.data" | |
| 300 if is_not_file $perf_data; then | |
| 301 error "$(pwd)/${perf_data} is invalid" | |
| 302 return 1 | |
| 303 fi | |
| 304 local file_name="flame_graph.svg" | |
| 305 local title="WebRTC Flame Graph" | |
| 306 if [[ "$#" -eq 1 ]]; then | |
| 307 file_name="$1" | |
| 308 fi | |
| 309 if [[ "$#" -eq 2 ]]; then | |
| 310 file_name="$1" | |
| 311 title="$2" | |
| 312 fi | |
| 313 if image_is_not_root; then | |
| 314 report_sample.py \ | |
| 315 --symfs "${SYMBOL_DIR}" \ | |
| 316 perf.data >out.perf | |
| 317 else | |
| 318 report_sample.py \ | |
| 319 --symfs "${SYMBOL_DIR}" \ | |
| 320 --kallsyms "${SYMBOL_DIR}/kallsyms" \ | |
| 321 perf.data >out.perf | |
| 322 fi | |
| 323 stackcollapse-perf.pl out.perf >out.folded | |
| 324 flamegraph.pl --title="${title}" out.folded >"${file_name}" | |
| 325 rm out.perf | |
| 326 rm out.folded | |
| 327 } | |
| 328 | |
| 329 # Remove all downloaded third-party tools. | |
| 330 function perf_cleanup () { | |
| 331 rm_simpleperf | |
| 332 rm_flame_graph | |
| 333 } | |
| 334 | |
| 335 main() { | |
| 336 printf "%s\n" "Preparing profiling of AppRTCMobile on Android:" | |
| 337 # Verify that this script is called from the root folder of WebRTC, | |
| 338 # i.e., the src folder one step below where the .gclient file exists. | |
| 339 local -r project_root_dir=$(pwd) | |
| 340 local dir=${project_root_dir##*/} | |
| 341 if [[ "${dir}" != "src" ]]; then | |
| 342 error "script must be called from the WebRTC project root (src) folder" | |
| 343 return 1 | |
| 344 fi | |
| 345 ok "project root: ${project_root_dir}" | |
| 346 | |
| 347 # Verify that user has sourced envsetup.sh. | |
| 348 # TODO(henrika): might be possible to remove this check. | |
| 349 if [[ -z "$ENVSETUP_GYP_CHROME_SRC" ]]; then | |
| 350 error "must source envsetup script first" | |
| 351 return 1 | |
| 352 fi | |
| 353 ok "envsetup script has been sourced" | |
| 354 | |
| 355 # Given that envsetup is sourced, the adb tool should be accessible but | |
| 356 # do one extra check just in case. | |
| 357 local adb_full_path=$(which adb); | |
| 358 if [[ ! -x "${adb_full_path}" ]]; then | |
| 359 error "unable to find the Android Debug Bridge (adb) tool" | |
| 360 return 1 | |
| 361 fi | |
| 362 ok "adb tool is working" | |
| 363 | |
| 364 # Exactly one Android device must be connected. | |
| 365 if ! one_device_connected; then | |
| 366 error "one device must be connected" | |
| 367 return 1 | |
| 368 fi | |
| 369 ok "one device is connected via USB" | |
| 370 | |
| 371 # Restart adb with root permissions if needed. | |
| 372 if image_is_root && adb_has_no_root_permissions; then | |
| 373 adb root | |
| 374 ok "adb is running as root" | |
| 375 fi | |
| 376 | |
| 377 # Create an empty symbol cache in the tmp folder. | |
| 378 # TODO(henrika): it might not be required to start from a clean cache. | |
| 379 is_dir "${SYMBOL_DIR}" && rm -rf "${SYMBOL_DIR}" | |
| 380 mkdir "${SYMBOL_DIR}" \ | |
| 381 && ok "empty symbol cache created at ${SYMBOL_DIR}" \ | |
| 382 || error "failed to create symbol cache" | |
| 383 | |
| 384 # Ensure that path to the native library with symbols is valid. | |
| 385 local native_lib=$(native_shared_lib_path) | |
| 386 if is_not_file ${native_lib}; then | |
| 387 error "${native_lib} is not a valid file" | |
| 388 return 1 | |
| 389 fi | |
| 390 ok "native library: "${native_lib}"" | |
| 391 | |
| 392 # Verify that the architechture of the device matches the architecture | |
| 393 # of the native library. | |
| 394 if ! arch_is_ok; then | |
| 395 error "device is $(dev_arch) and lib is $(native_shared_lib_arch)" | |
| 396 return 1 | |
| 397 fi | |
| 398 ok "device is $(dev_arch) and lib is $(native_shared_lib_arch)" | |
| 399 | |
| 400 # Copy native shared library to symbol cache after creating an | |
| 401 # application specific tree structure under ${SYMBOL_DIR}/data. | |
| 402 copy_native_shared_library_to_symbol_cache | |
| 403 ok "native library copied to ${SYMBOL_DIR}/data/app/${APP_NAME}" | |
| 404 | |
| 405 # Verify that the application is installed on the device. | |
| 406 if ! app_is_installed "${APP_NAME}"; then | |
| 407 error "${APP_NAME} is not installed on the device" | |
| 408 return 1 | |
| 409 fi | |
| 410 ok "${APP_NAME} is installed on the device" | |
| 411 | |
| 412 # Download simpleperf to <src>/tools-webrtc/android/profiling/simpleperf/. | |
| 413 # Cloning will only take place if the target does not already exist. | |
| 414 # The PATH variable will also be updated. | |
| 415 # TODO(henrika): would it be better to use a target outside the WebRTC repo? | |
| 416 local simpleperf_dir="${SCRIPT_DIR}/simpleperf" | |
| 417 if is_not_dir "${simpleperf_dir}"; then | |
| 418 echo "Dowloading simpleperf..." | |
| 419 git clone https://android.googlesource.com/platform/prebuilts/simpleperf \ | |
| 420 "${simpleperf_dir}" | |
| 421 chmod u+x "${simpleperf_dir}/report_sample.py" | |
| 422 fi | |
| 423 path_add "${simpleperf_dir}" | |
| 424 ok "${simpleperf_dir}" is added to PATH | |
| 425 | |
| 426 # Update the PATH variable with the path to the Linux version of simpleperf. | |
| 427 local simpleperf_linux_dir="${SCRIPT_DIR}/simpleperf/bin/linux/x86_64/" | |
| 428 if is_not_dir "${simpleperf_linux_dir}"; then | |
| 429 error "${simpleperf_linux_dir} is invalid" | |
| 430 return 1 | |
| 431 fi | |
| 432 path_add "${simpleperf_linux_dir}" | |
| 433 ok "${simpleperf_linux_dir}" is added to PATH | |
| 434 | |
| 435 # Copy correct version (arm or arm64) of simpleperf to the device | |
| 436 # and enable profiling at the same time. | |
| 437 if ! copy_simpleperf_to_device; then | |
| 438 error "failed to install simpleperf on the device" | |
| 439 return 1 | |
| 440 fi | |
| 441 ok "simpleperf is installed on the device" | |
| 442 | |
| 443 # Refresh the symbol cache and read kernal symbols from device if not | |
| 444 # already done. | |
| 445 perf_update | |
| 446 ok "symbol cache is updated" | |
| 447 | |
| 448 # Download Flame Graph to <src>/tools-webrtc/android/profiling/flamegraph/. | |
| 449 # Cloning will only take place if the target does not already exist. | |
| 450 # The PATH variable will also be updated. | |
| 451 # TODO(henrika): would it be better to use a target outside the WebRTC repo? | |
| 452 local flamegraph_dir="${SCRIPT_DIR}/flamegraph" | |
| 453 if is_not_dir "${flamegraph_dir}"; then | |
| 454 echo "Dowloading Flame Graph visualization tool..." | |
| 455 git clone https://github.com/brendangregg/FlameGraph.git "${flamegraph_dir}" | |
| 456 fi | |
| 457 path_add "${flamegraph_dir}" | |
| 458 ok "${flamegraph_dir}" is added to PATH | |
| 459 | |
| 460 print_function_help | |
| 461 | |
| 462 cleanup | |
| 463 | |
| 464 return 0 | |
| 465 } | |
| 466 | |
| 467 # Only call main() if proper input parameter has been provided. | |
| 468 if is_set $BUILD_DIR; then | |
| 469 main "$@" | |
| 470 fi | |
| OLD | NEW |