diff options
Diffstat (limited to 'third_party/libwebrtc/tools_webrtc/android')
9 files changed, 1089 insertions, 0 deletions
diff --git a/third_party/libwebrtc/tools_webrtc/android/OWNERS b/third_party/libwebrtc/tools_webrtc/android/OWNERS new file mode 100644 index 0000000000..cf092a316a --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/OWNERS @@ -0,0 +1 @@ +xalep@webrtc.org diff --git a/third_party/libwebrtc/tools_webrtc/android/__init__.py b/third_party/libwebrtc/tools_webrtc/android/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/__init__.py diff --git a/third_party/libwebrtc/tools_webrtc/android/adb_shell.sh b/third_party/libwebrtc/tools_webrtc/android/adb_shell.sh new file mode 100755 index 0000000000..e2d4f9187e --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/adb_shell.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +# 'adb shell' always returns "0" regardless of executable return code. +# This handy script will return executable return code to shell which +# can be used by buildbots. + +adb_shell () { + local RET ADB_LOG + ADB_LOG=$(mktemp "${TMPDIR:-/tmp}/adb-XXXXXXXX") + adb "$1" "$2" shell "$3" "$4" ";" echo \$? | tee "$ADB_LOG" + sed -i -e 's![[:cntrl:]]!!g' "$ADB_LOG" # Remove \r. + RET=$(sed -e '$!d' "$ADB_LOG") # Last line contains status code. + rm -f "$ADB_LOG" + return $RET +} diff --git a/third_party/libwebrtc/tools_webrtc/android/build_aar.py b/third_party/libwebrtc/tools_webrtc/android/build_aar.py new file mode 100755 index 0000000000..d910b39a7c --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/build_aar.py @@ -0,0 +1,269 @@ +#!/usr/bin/env vpython3 + +# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. +"""Script to generate libwebrtc.aar for distribution. + +The script has to be run from the root src folder. +./tools_webrtc/android/build_aar.py + +.aar-file is just a zip-archive containing the files of the library. The file +structure generated by this script looks like this: + - AndroidManifest.xml + - classes.jar + - libs/ + - armeabi-v7a/ + - libjingle_peerconnection_so.so + - x86/ + - libjingle_peerconnection_so.so +""" + +import argparse +import logging +import os +import shutil +import subprocess +import sys +import tempfile +import zipfile + +SCRIPT_DIR = os.path.dirname(os.path.realpath(sys.argv[0])) +SRC_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir)) +DEFAULT_ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] +NEEDED_SO_FILES = ['libjingle_peerconnection_so.so'] +JAR_FILE = 'lib.java/sdk/android/libwebrtc.jar' +MANIFEST_FILE = 'sdk/android/AndroidManifest.xml' +TARGETS = [ + 'sdk/android:libwebrtc', + 'sdk/android:libjingle_peerconnection_so', +] + +sys.path.append(os.path.join(SCRIPT_DIR, '..', 'libs')) +from generate_licenses import LicenseBuilder + +sys.path.append(os.path.join(SRC_DIR, 'build')) +import find_depot_tools + + +def _ParseArgs(): + parser = argparse.ArgumentParser(description='libwebrtc.aar generator.') + parser.add_argument( + '--build-dir', + type=os.path.abspath, + help='Build dir. By default will create and use temporary dir.') + parser.add_argument('--output', + default='libwebrtc.aar', + type=os.path.abspath, + help='Output file of the script.') + parser.add_argument('--arch', + default=DEFAULT_ARCHS, + nargs='*', + help='Architectures to build. Defaults to %(default)s.') + parser.add_argument('--use-goma', + action='store_true', + default=False, + help='Use goma.') + parser.add_argument('--use-remoteexec', + action='store_true', + default=False, + help='Use RBE.') + parser.add_argument('--use-unstripped-libs', + action='store_true', + default=False, + help='Use unstripped .so files within libwebrtc.aar') + parser.add_argument('--verbose', + action='store_true', + default=False, + help='Debug logging.') + parser.add_argument( + '--extra-gn-args', + default=[], + nargs='*', + help="""Additional GN arguments to be used during Ninja generation. + These are passed to gn inside `--args` switch and + applied after any other arguments and will + override any values defined by the script. + Example of building debug aar file: + build_aar.py --extra-gn-args='is_debug=true'""") + parser.add_argument( + '--extra-ninja-switches', + default=[], + nargs='*', + help="""Additional Ninja switches to be used during compilation. + These are applied after any other Ninja switches. + Example of enabling verbose Ninja output: + build_aar.py --extra-ninja-switches='-v'""") + parser.add_argument( + '--extra-gn-switches', + default=[], + nargs='*', + help="""Additional GN switches to be used during compilation. + These are applied after any other GN switches. + Example of enabling verbose GN output: + build_aar.py --extra-gn-switches='-v'""") + return parser.parse_args() + + +def _RunGN(args): + cmd = [ + sys.executable, + os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py') + ] + cmd.extend(args) + logging.debug('Running: %r', cmd) + subprocess.check_call(cmd) + + +def _RunNinja(output_directory, args): + cmd = [ + os.path.join(SRC_DIR, 'third_party', 'ninja', 'ninja'), '-C', + output_directory + ] + cmd.extend(args) + logging.debug('Running: %r', cmd) + subprocess.check_call(cmd) + + +def _EncodeForGN(value): + """Encodes value as a GN literal.""" + if isinstance(value, str): + return '"' + value + '"' + if isinstance(value, bool): + return repr(value).lower() + return repr(value) + + +def _GetOutputDirectory(build_dir, arch): + """Returns the GN output directory for the target architecture.""" + return os.path.join(build_dir, arch) + + +def _GetTargetCpu(arch): + """Returns target_cpu for the GN build with the given architecture.""" + if arch in ['armeabi', 'armeabi-v7a']: + return 'arm' + if arch == 'arm64-v8a': + return 'arm64' + if arch == 'x86': + return 'x86' + if arch == 'x86_64': + return 'x64' + raise Exception('Unknown arch: ' + arch) + + +def _GetArmVersion(arch): + """Returns arm_version for the GN build with the given architecture.""" + if arch == 'armeabi': + return 6 + if arch == 'armeabi-v7a': + return 7 + if arch in ['arm64-v8a', 'x86', 'x86_64']: + return None + raise Exception('Unknown arch: ' + arch) + + +def Build(build_dir, arch, use_goma, use_remoteexec, extra_gn_args, + extra_gn_switches, extra_ninja_switches): + """Generates target architecture using GN and builds it using ninja.""" + logging.info('Building: %s', arch) + output_directory = _GetOutputDirectory(build_dir, arch) + gn_args = { + 'target_os': 'android', + 'is_debug': False, + 'is_component_build': False, + 'rtc_include_tests': False, + 'target_cpu': _GetTargetCpu(arch), + 'use_goma': use_goma, + 'use_remoteexec': use_remoteexec, + } + arm_version = _GetArmVersion(arch) + if arm_version: + gn_args['arm_version'] = arm_version + gn_args_str = '--args=' + ' '.join( + [k + '=' + _EncodeForGN(v) for k, v in gn_args.items()] + extra_gn_args) + + gn_args_list = ['gen', output_directory, gn_args_str] + gn_args_list.extend(extra_gn_switches) + _RunGN(gn_args_list) + + ninja_args = TARGETS[:] + if use_goma or use_remoteexec: + ninja_args.extend(['-j', '200']) + ninja_args.extend(extra_ninja_switches) + _RunNinja(output_directory, ninja_args) + + +def CollectCommon(aar_file, build_dir, arch): + """Collects architecture independent files into the .aar-archive.""" + logging.info('Collecting common files.') + output_directory = _GetOutputDirectory(build_dir, arch) + aar_file.write(MANIFEST_FILE, 'AndroidManifest.xml') + aar_file.write(os.path.join(output_directory, JAR_FILE), 'classes.jar') + + +def Collect(aar_file, build_dir, arch, unstripped): + """Collects architecture specific files into the .aar-archive.""" + logging.info('Collecting: %s', arch) + output_directory = _GetOutputDirectory(build_dir, arch) + + abi_dir = os.path.join('jni', arch) + for so_file in NEEDED_SO_FILES: + source_so_file = os.path.join("lib.unstripped", + so_file) if unstripped else so_file + aar_file.write(os.path.join(output_directory, source_so_file), + os.path.join(abi_dir, so_file)) + + +def GenerateLicenses(output_dir, build_dir, archs): + builder = LicenseBuilder( + [_GetOutputDirectory(build_dir, arch) for arch in archs], TARGETS) + builder.GenerateLicenseText(output_dir) + + +def BuildAar(archs, + output_file, + use_goma=False, + use_remoteexec=False, + extra_gn_args=None, + ext_build_dir=None, + extra_gn_switches=None, + extra_ninja_switches=None, + unstripped=False): + extra_gn_args = extra_gn_args or [] + extra_gn_switches = extra_gn_switches or [] + extra_ninja_switches = extra_ninja_switches or [] + build_dir = ext_build_dir if ext_build_dir else tempfile.mkdtemp() + + for arch in archs: + Build(build_dir, arch, use_goma, use_remoteexec, extra_gn_args, + extra_gn_switches, extra_ninja_switches) + + with zipfile.ZipFile(output_file, 'w') as aar_file: + # Architecture doesn't matter here, arbitrarily using the first one. + CollectCommon(aar_file, build_dir, archs[0]) + for arch in archs: + Collect(aar_file, build_dir, arch, unstripped) + + license_dir = os.path.dirname(os.path.realpath(output_file)) + GenerateLicenses(license_dir, build_dir, archs) + + if not ext_build_dir: + shutil.rmtree(build_dir, True) + + +def main(): + args = _ParseArgs() + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + + BuildAar(args.arch, args.output, args.use_goma, args.use_remoteexec, + args.extra_gn_args, args.build_dir, args.extra_gn_switches, + args.extra_ninja_switches, args.use_unstripped_libs) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/third_party/libwebrtc/tools_webrtc/android/profiling/perf_setup.sh b/third_party/libwebrtc/tools_webrtc/android/profiling/perf_setup.sh new file mode 100755 index 0000000000..9c6b0f98ea --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/profiling/perf_setup.sh @@ -0,0 +1,470 @@ +#!/bin/bash + +# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. +# +# Usage: +# +# It is assumed that a release build of AppRTCMobile exists and has been +# installed on an Android device which supports USB debugging. +# +# Source this script once from the WebRTC src/ directory and resolve any +# reported issues. Add relative path to build directory as parameter. +# Required tools will be downloaded if they don't already exist. +# +# Once all tests are passed, a list of available functions will be given. +# Use these functions to do the actual profiling and visualization of the +# results. +# +# Note that, using a rooted device is recommended since it allows us to +# resolve kernel symbols (kallsyms) as well. +# +# Example usage: +# +# > . tools_webrtc/android/profiling/perf_setup.sh out/Release +# > perf_record 120 +# > flame_graph +# > plot_flame_graph +# > perf_cleanup + +if [ -n "$ZSH_VERSION" ]; then + # Running inside zsh. + SCRIPT_PATH="${(%):-%N}" +else + # Running inside something else (most likely bash). + SCRIPT_PATH="${BASH_SOURCE[0]}" +fi +SCRIPT_DIR="$(cd $(dirname "$SCRIPT_PATH") && pwd -P)" +source "${SCRIPT_DIR}/utilities.sh" + +# Root directory for local symbol cache. +SYMBOL_DIR="${TMPDIR:-/tmp}/android_symbols" +# Used as a temporary folder on the Android device for data storage. +DEV_TMP_DIR="/data/local/tmp" +# Relative path to native shared library containing symbols. +NATIVE_LIB_PATH="/lib.unstripped/libjingle_peerconnection_so.so" +# Name of application package for the AppRTCMobile demo. +APP_NAME="org.appspot.apprtc" + +# Make sure we're being sourced. +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" +} + +# Ensure that user includes name of build directory (e.g. out/Release) as +# input parameter. Store path in BUILD_DIR. +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 + +# Full (relative) path to the libjingle_peerconnection_so.so file. +function native_shared_lib_path() { + echo "${BUILD_DIR}${NATIVE_LIB_PATH}" +} + +# Target CPU architecture for the native shared library. +# Example: AArch64. +function native_shared_lib_arch() { + readelf -h $(native_shared_lib_path) | grep Machine | awk '{print $2}' +} + +# Returns true if the device architecture and the build target are the same. +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 +} + +# Copies the native shared library from the local host to the symbol cache +# which is used by simpleperf as base when searching for symbols. +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 +} + +# Copy kernel symbols from device to symbol cache in tmp. +function copy_kernel_symbols_from_device_to_symbol_cache() { + local symbol_cache="${SYMBOL_DIR}/kallsyms" + adb pull /proc/kallsyms "${symbol_cache}" +} 1> /dev/null + +# Download the correct version of 'simpleperf' to $DEV_TMP_DIR +# on the device and enable profiling. +function copy_simpleperf_to_device() { + local perf_binary + [[ $(dev_arch) == "aarch64" ]] \ + && perf_binary="/arm64/simpleperf" \ + || perf_binary="/arm/simpleperf" + # Copy the simpleperf binary from local host to temp folder on device. + adb push "${SCRIPT_DIR}/simpleperf/bin/android${perf_binary}" \ + "${DEV_TMP_DIR}" 1> /dev/null + # Copy simpleperf from temp folder to the application package. + adb shell run-as "${APP_NAME}" cp "${DEV_TMP_DIR}/simpleperf" . + adb shell run-as "${APP_NAME}" chmod a+x simpleperf + # Enable profiling on the device. + enable_profiling + # Allows usage of running report commands on the device. + if image_is_root; then + enable_report_symbols + fi +} + +# Copy the recorded 'perf.data' file from the device to the current directory. +# TODO(henrika): add support for specifying the destination. +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 + + +# Wraps calls to simpleperf report. Used by e.g. perf_report_threads. +# A valid profile input file must exist in the current folder. +# TODO(henrika): possibly add support to add path to alternative input file. +function perf_report() { + local perf_data="perf.data" + is_file "${perf_data}" \ + && simpleperf report \ + -n \ + -i "${perf_data}" \ + "$@" \ + || error "$(pwd)/${perf_data} is invalid" +} + +# Removes the folder specified as input parameter. Mainly intended for removal +# of simpleperf and Flame Graph tools. +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 +} + +# Utility method which deletes the downloaded simpleperf tool from the repo. +# It also removes the simpleperf root folder from PATH. +function rm_simpleperf() { + remove_tool "${SCRIPT_DIR}/simpleperf" +} + +# Utility method which deletes the downloaded Flame Graph tool from the repo. +# It also removes the Flame Graph root folder from PATH. +function rm_flame_graph() { + remove_tool "${SCRIPT_DIR}/flamegraph" +} + +# Lists the main available functions after sourcing this script. +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 +} + +# ----------------------------------------------------------------------------- +# Main methods to be used after sourcing the main script. +# ----------------------------------------------------------------------------- + +# Call this method after the application as been rebuilt and installed on the +# device to ensure that symbols are up-to-date. +function perf_update() { + copy_native_shared_library_to_symbol_cache + if image_is_root; then + copy_kernel_symbols_from_device_to_symbol_cache + fi +} + +# Record stack frame based call graphs while using the application. +# We use default events (cpu-cycles), and write records to 'perf.data' in the +# tmp folder on the device. Default duration is 60 seconds but it can be changed +# by adding one parameter. As soon as the recording is done, 'perf.data' is +# copied to the directory from which this method is called and a summary of +# the load distribution per thread is printed. +function perf_record() { + if app_is_running "${APP_NAME}"; then + # Ensure that the latest native shared library exists in the local cache. + 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 + # Copy profile results from device to current directory. + pull_perf_data_from_device + # Print out a summary report (load per thread). + perf_report_threads | tail -n +6 + else + # AppRTCMobile was not enabled. Start it up automatically and ask the user + # to start media and then call this method again. + warning "AppRTCMobile must be active" + app_start "${APP_NAME}" + echo "Start media and then call perf_record again..." + fi +} + +# Analyze the profile report and show samples per threads. +function perf_report_threads() { + perf_report --sort comm +} 2> /dev/null + +# Analyze the profile report and show samples per binary. +function perf_report_bins() { + perf_report --sort dso +} 2> /dev/null + +# Analyze the profile report and show samples per symbol. +function perf_report_symbols() { + perf_report --sort symbol --symfs "${SYMBOL_DIR}" +} + +# Print call graph showing how functions call others. +function perf_report_graph() { + perf_report -g caller --symfs "${SYMBOL_DIR}" +} + +# Print call graph showing how functions are called from others. +function perf_report_graph_callee() { + perf_report -g callee --symfs "${SYMBOL_DIR}" +} + +# Plots the default Flame Graph file if no parameter is provided. +# If a parameter is given, it will be used as file name instead of the default. +function plot_flame_graph() { + local file_name="flame_graph.svg" + if [[ "$#" -eq 1 ]]; then + file_name="$1" + fi + # Open up the SVG file in Chrome. Try unstable first and revert to stable + # if unstable fails. + google-chrome-unstable "${file_name}" \ + || google-chrome-stable "${file_name}" \ + || error "failed to find any Chrome instance" +} 2> /dev/null + +# Generate Flame Graph in interactive SVG format. +# First input parameter corresponds to output file name and second input +# parameter is the heading of the plot. +# Defaults will be utilized if parameters are not provided. +# See https://github.com/brendangregg/FlameGraph for details on Flame Graph. +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 +} + +# Remove all downloaded third-party tools. +function perf_cleanup () { + rm_simpleperf + rm_flame_graph +} + +main() { + printf "%s\n" "Preparing profiling of AppRTCMobile on Android:" + # Verify that this script is called from the root folder of WebRTC, + # i.e., the src folder one step below where the .gclient file exists. + 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}" + + # Verify that user has sourced envsetup.sh. + # TODO(henrika): might be possible to remove this check. + if [[ -z "$ENVSETUP_GYP_CHROME_SRC" ]]; then + error "must source envsetup script first" + return 1 + fi + ok "envsetup script has been sourced" + + # Given that envsetup is sourced, the adb tool should be accessible but + # do one extra check just in case. + 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" + + # Exactly one Android device must be connected. + if ! one_device_connected; then + error "one device must be connected" + return 1 + fi + ok "one device is connected via USB" + + # Restart adb with root permissions if needed. + if image_is_root && adb_has_no_root_permissions; then + adb root + ok "adb is running as root" + fi + + # Create an empty symbol cache in the tmp folder. + # TODO(henrika): it might not be required to start from a clean cache. + 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" + + # Ensure that path to the native library with symbols is valid. + 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}"" + + # Verify that the architechture of the device matches the architecture + # of the native library. + 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 after creating an + # application specific tree structure under ${SYMBOL_DIR}/data. + copy_native_shared_library_to_symbol_cache + ok "native library copied to ${SYMBOL_DIR}/data/app/${APP_NAME}" + + # Verify that the application is installed on the device. + 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" + + # Download simpleperf to <src>/tools_webrtc/android/profiling/simpleperf/. + # Cloning will only take place if the target does not already exist. + # The PATH variable will also be updated. + # TODO(henrika): would it be better to use a target outside the WebRTC repo? + 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 + + # Update the PATH variable with the path to the Linux version of simpleperf. + 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 + + # Copy correct version (arm or arm64) of simpleperf to the device + # and enable profiling at the same time. + if ! copy_simpleperf_to_device; then + error "failed to install simpleperf on the device" + return 1 + fi + ok "simpleperf is installed on the device" + + # Refresh the symbol cache and read kernal symbols from device if not + # already done. + perf_update + ok "symbol cache is updated" + + # Download Flame Graph to <src>/tools_webrtc/android/profiling/flamegraph/. + # Cloning will only take place if the target does not already exist. + # The PATH variable will also be updated. + # TODO(henrika): would it be better to use a target outside the WebRTC repo? + 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 +} + +# Only call main() if proper input parameter has been provided. +if is_set $BUILD_DIR; then + main "$@" +fi diff --git a/third_party/libwebrtc/tools_webrtc/android/profiling/utilities.sh b/third_party/libwebrtc/tools_webrtc/android/profiling/utilities.sh new file mode 100755 index 0000000000..46a97b8142 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/profiling/utilities.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +# Utility functions to be used by perf_setup.sh. +# Contains helper methods and functions that wraps usage of adb. + +function error() { + echo "[ERROR] "$@"" >&2 +} + +function warning() { + echo "[WARNING] "$@"" >&1 +} + +function ok() { + echo "[OK] "$@"" >&1 +} + +function abs_path { + (cd $1; pwd) +} + +function is_set() { + local var="$1" + [[ -n "${var}" ]] +} + +function is_file() { + local file="$1" + [[ -f "${file}" ]] +} + +function is_not_file() { + local file="$1" + [[ ! -f "${file}" ]] +} + +function is_dir() { + local dir="$1" + [[ -d "${dir}" ]] +} + +function is_not_dir() { + local dir="$1" + [[ ! -d "${dir}" ]] +} + +# Adds (prepends) the PATH environment variable while avoid duplicates. +function path_add() { + case ":${PATH:=$1}:" in + *:$1:*) ;; + *) PATH="$1:$PATH" ;; + esac +} + +# Removes a path from the PATH environment variable using search-and-replace +# parameter expansion. +function path_remove { + local path="$1" + # Substitute first occurrence of ":path" in PATH with an empty string. + # Deletes instances in the middle or at the end. + PATH=${PATH/":$path"/} + # Substitute first occurrence of "path:" in PATH with an empty string. + # Delete instances at the beginning. + PATH=${PATH/"$path:"/} +} + +# Returns the process ID (PID) of the process that corresponds to the +# application name given as input parameter. +function find_app_pid() { + local app_name="$1" + adb shell ps | grep "${app_name}" | awk '{print $2}' +} + +function app_is_installed() { + local app_name="$1" + local installed_app_name=$(adb shell pm list packages \ + | grep "${app_name}" | awk -F':' '{print $2}') + is_set "${installed_app_name}" \ + && [[ "${installed_app_name}" = "${app_name}" ]] +} + +function app_is_running() { + local app_name="$1" + local app_pid=$(find_app_pid "${app_name}") + is_set "${app_pid}" +} + +function app_start() { + local app_name="$1" + adb shell am start \ + -n "${app_name}/.ConnectActivity" \ + -a android.intent.action.MAIN +} + +function app_stop() { + local app_name="$1" + adb shell am force-stop "${app_name}" +} + +function app_uninstall() { + local app_name="$1" + adb uninstall "${app_name}" +} + +function dev_arch() { + adb shell uname -m +} + +function dev_ls() { + local dir="$1" + adb shell ls "${dir}" +} + +# Returns true if exactly on device is connected. +function one_device_connected() { + [[ $(adb devices | wc -l) = 3 ]] +} + +# Returns true if device is rooted. +function image_is_root() { + [[ $(adb shell getprop ro.build.type) = "userdebug" ]] +} + +# Returns true if device is not rooted. +function image_is_not_root() { + [[ $(adb shell getprop ro.build.type) = "user" ]] +} + +# Returns true if adb is not already running as root. +# Should only be called on rooted devices. +function adb_has_no_root_permissions() { + [[ $(adb shell getprop service.adb.root) = 0 ]] +} + +# Android devices may disable profiling by default. We must enable it. +function enable_profiling() { + adb shell setprop security.perf_harden 0 +} + +# To make the report of symbols on device successful, we need to execute +# `echo 0 >/proc/sys/kernel/kptr_restrict`. +# Only needed if we run report commands on the same machine as we run +# record commands. +function enable_report_symbols() { + adb shell "echo 0 > /proc/sys/kernel/kptr_restrict" +} diff --git a/third_party/libwebrtc/tools_webrtc/android/templates/maven-repository.jinja b/third_party/libwebrtc/tools_webrtc/android/templates/maven-repository.jinja new file mode 100644 index 0000000000..10a37f40d3 --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/templates/maven-repository.jinja @@ -0,0 +1,11 @@ +allprojects { + repositories { + maven { + url '{{ url }}' + credentials { + username '{{ username }}' + password '{{ password }}' + } + } + } +} diff --git a/third_party/libwebrtc/tools_webrtc/android/templates/pom.jinja b/third_party/libwebrtc/tools_webrtc/android/templates/pom.jinja new file mode 100644 index 0000000000..90ca51ca9c --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/templates/pom.jinja @@ -0,0 +1,18 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.webrtc</groupId> + <artifactId>google-webrtc</artifactId> + <version>{{ version }}</version> + <packaging>aar</packaging> + + <name>Google's WebRTC Android library</name> + <url>https://webrtc.org/</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.build.commitid>{{ commit }}</project.build.commitid> + </properties> + +</project> diff --git a/third_party/libwebrtc/tools_webrtc/android/test_aar.py b/third_party/libwebrtc/tools_webrtc/android/test_aar.py new file mode 100755 index 0000000000..7eb281aa9a --- /dev/null +++ b/third_party/libwebrtc/tools_webrtc/android/test_aar.py @@ -0,0 +1,143 @@ +#!/usr/bin/env vpython3 + +# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. +"""Script for building and testing WebRTC AAR.""" + +import argparse +import logging +import os +import re +import shutil +import subprocess +import sys +import tempfile + +SCRIPT_DIR = os.path.dirname(os.path.realpath(sys.argv[0])) +CHECKOUT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir)) + +sys.path.append(os.path.join(CHECKOUT_ROOT, 'tools_webrtc')) +from android.build_aar import BuildAar + +ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] +ARTIFACT_ID = 'google-webrtc' +COMMIT_POSITION_REGEX = r'^Cr-Commit-Position: refs/heads/master@{#(\d+)}$' +GRADLEW_BIN = os.path.join(CHECKOUT_ROOT, + 'examples/androidtests/third_party/gradle/gradlew') +ADB_BIN = os.path.join(CHECKOUT_ROOT, + 'third_party/android_sdk/public/platform-tools/adb') +AAR_PROJECT_DIR = os.path.join(CHECKOUT_ROOT, 'examples/aarproject') + + +def _ParseArgs(): + parser = argparse.ArgumentParser(description='Releases WebRTC on Bintray.') + parser.add_argument('--use-goma', + action='store_true', + default=False, + help='Use goma.') + parser.add_argument('--skip-tests', + action='store_true', + default=False, + help='Skips running the tests.') + parser.add_argument( + '--build-dir', + default=None, + help='Temporary directory to store the build files. If not specified, ' + 'a new directory will be created.') + parser.add_argument('--verbose', + action='store_true', + default=False, + help='Debug logging.') + return parser.parse_args() + + +def _GetCommitHash(): + commit_hash = subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], cwd=CHECKOUT_ROOT).decode('UTF-8').strip() + return commit_hash + + +def _GetCommitPos(): + commit_message = subprocess.check_output( + ['git', 'rev-list', '--format=%B', '--max-count=1', 'HEAD'], + cwd=CHECKOUT_ROOT).decode('UTF-8') + commit_pos_match = re.search(COMMIT_POSITION_REGEX, commit_message, + re.MULTILINE) + if not commit_pos_match: + raise Exception('Commit position not found in the commit message: %s' % + commit_message) + return commit_pos_match.group(1) + + +def _TestAAR(build_dir): + """Runs AppRTCMobile tests using the AAR. Returns true if the tests pass.""" + logging.info('Testing library.') + + # Uninstall any existing version of AppRTCMobile. + logging.info('Uninstalling previous AppRTCMobile versions. It is okay for ' + 'these commands to fail if AppRTCMobile is not installed.') + subprocess.call([ADB_BIN, 'uninstall', 'org.appspot.apprtc']) + subprocess.call([ADB_BIN, 'uninstall', 'org.appspot.apprtc.test']) + + # Run tests. + try: + # First clean the project. + subprocess.check_call([GRADLEW_BIN, 'clean'], cwd=AAR_PROJECT_DIR) + # Then run the tests. + subprocess.check_call([ + GRADLEW_BIN, 'connectedDebugAndroidTest', + '-PaarDir=' + os.path.abspath(build_dir) + ], + cwd=AAR_PROJECT_DIR) + except subprocess.CalledProcessError: + logging.exception('Test failure.') + return False # Clean or tests failed + + return True # Tests pass + + +def BuildAndTestAar(use_goma, skip_tests, build_dir): + version = '1.0.' + _GetCommitPos() + commit = _GetCommitHash() + logging.info('Building and Testing AAR version %s with hash %s', version, + commit) + + # If build directory is not specified, create a temporary directory. + use_tmp_dir = not build_dir + if use_tmp_dir: + build_dir = tempfile.mkdtemp() + + try: + base_name = ARTIFACT_ID + '-' + version + aar_file = os.path.join(build_dir, base_name + '.aar') + + logging.info('Building at %s', build_dir) + BuildAar(ARCHS, + aar_file, + use_goma=use_goma, + ext_build_dir=os.path.join(build_dir, 'aar-build')) + + tests_pass = skip_tests or _TestAAR(build_dir) + if not tests_pass: + raise Exception('Test failure.') + + logging.info('Test success.') + + finally: + if use_tmp_dir: + shutil.rmtree(build_dir, True) + + +def main(): + args = _ParseArgs() + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + BuildAndTestAar(args.use_goma, args.skip_tests, args.build_dir) + + +if __name__ == '__main__': + sys.exit(main()) |