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