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