Lately we want to understand the cpu utilization of an Android app we wrote. We tried a few things like android-ndk-profiler and easy-performance-analyzer but they did not provide a very detailed results in terms of system calls. They are easy to use but don't tell the details we want to see. Valgrind is a very powerful profiling tool in Linux and we wondered to use that in Android. There are few topics in compiling that using Android NDK (mostly here). The answer contains detailed scripts, which is great, but there are certain confusions during the compilation. In short:
- There are a few bugs in the script
- Android-21 has a bug (issue of redefinition), but android-19 certainly works
- In our case, we need to analyze standalone Android binary executions
Therefore, here is my entry.
Environment
My compiling environment is Ubuntu 12.04 64-bit. NDK version is r10d, and SDK is rev 22.x.
Preparation - The Script
Based on the stack overflow answer, you don't really need much preparation. The script will help you download the version you'd like and compile it for you. However, I do want to point out talk a little bit here.
The script somehow does not work for me in setting variables. Therefore, here is my modified version (comments in red).
#!/usr/bin/env bash #set -x function extract() { if [ -f "$1" ] ; then case "$1" in *.tar.bz2) tar xvjf "$1" ;; *.tar.gz) tar xvzf "$1" ;; *.bz2) bunzip2 "$1" ;; *.rar) unrar x "$1" ;; *.gz) gunzip "$1" ;; *.tar) tar xvf "$1" ;; *.tbz2) tar xvjf "$1" ;; *.tgz) tar xvzf "$1" ;; *.zip) unzip "$1" ;; *.Z) uncompress "$1" ;; *.7z) 7z x "$1" ;; *) echo "$1 cannot be extracted via >extract<" ;; esac else echo "'$1' is not a valid file" fi } VALGRIND_VERSION="3.10.1" VALGRIND_EXTENSION=".tar.bz2" VALGRIND_DIRECTORY="valgrind-${VALGRIND_VERSION}" VALGRIND_TARBALL="valgrind-${VALGRIND_VERSION}${VALGRIND_EXTENSION}" # Only download Valgrind tarball again if not already downloaded if [[ ! -f "${VALGRIND_TARBALL}" ]]; then wget -v -nc "http://valgrind.org/downloads/${VALGRIND_TARBALL}" fi # Only extract Valgrind tarball again if not already extracted if [[ ! -d "$VALGRIND_DIRECTORY" ]]; then extract "$VALGRIND_TARBALL" fi # Ensure ANDROID_NDK_HOME is set (the check does not work for me, remember to modify path for ANDROID_NDK_HOME) # if [[ ! -z "$ANDROID_NDK_HOME" ]]; then export ANDROID_NDK_HOME="$HOME/Software/Android/android-ndk-r10c" # fi # Ensure ANDOID_SDK_HOME is set (the check does not work for me, remember to modify path for ANDROID_SDK_HOME) # if [[ ! -z "$ANDROID_SDK_HOME" ]]; then export ANDROID_SDK_HOME="$HOME/Software/Android/android-sdk/" # fi if [[ ! -d "$VALGRIND_DIRECTORY" ]]; then echo "Problem with extracting Valgrind from $VALGRIND_TARBALL into $VALGRIND_DIRECTORY!!!" exit -1 fi # Move to extracted directory cd "$VALGRIND_DIRECTORY" # ARM Toolchain ARCH_ABI="arm-linux-androideabi-4.9" export AR="$ANDROID_NDK_HOME/toolchains/${ARCH_ABI}/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar" export LD="$ANDROID_NDK_HOME/toolchains/${ARCH_ABI}/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ld" export CC="$ANDROID_NDK_HOME/toolchains/${ARCH_ABI}/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc" export CXX="$ANDROID_NDK_HOME/toolchains/${ARCH_ABI}/prebuilt/linux-x86_64/bin/arm-linux-androideabi-g++" [[ ! -d "$ANDROID_NDK_HOME" || ! -f "$AR" || ! -f "$LD" || ! -f "$CC" || ! -f "$CXX" ]] && echo "Make sure AR, LD, CC, CXX variables are defined correctly. Ensure ANDROID_NDK_HOME is defined also" && exit -1 # Configure build ANDROID_PLATFORM=android-19 # android-21 has a bug so let's do it with 19 export CPPFLAGS="--sysroot=$ANDROID_NDK_HOME/platforms/${ANDROID_PLATFORM}/arch-arm" export CFLAGS="--sysroot=$ANDROID_NDK_HOME/platforms/${ANDROID_PLATFORM}/arch-arm" BUILD=true # don't care, let's just build it!
if [[ "$BUILD" = true ]]; then ./configure --prefix="/data/local/Inst" \ --host="armv7-unknown-linux" \ --target="armv7-unknown-linux" \ --with-tmpdir="/sdcard" # bug: there was a space here [[ $? -ne 0 ]] && echo "Can't configure!" && exit -1 # Determine the number of jobs (commands) to be run simultaneously by GNU Make NO_CPU_CORES=$(grep -c ^processor /proc/cpuinfo) if [ $NO_CPU_CORES -le 8 ]; then JOBS=$(($NO_CPU_CORES+1)) else JOBS=${NO_CPU_CORES} fi # Compile Valgrind make -j "${JOBS}" [[ $? -ne 0 ]] && echo "Can't compile!" && exit -1 # Install Valgrind locally make -j "${JOBS}" install DESTDIR="$(pwd)/Inst" [[ $? -ne 0 ]] && echo "Can't install!" && exit -1 fi # Push local Valgrind installtion to the phone (if it exists, just overwrite it) #if [[ $(adb shell ls -ld /data/local/Inst/bin/valgrind) = *"No such file or directory"* ]]; #then adb root adb remount adb shell "[ ! -d /data/local/Inst ] && mkdir /data/local/Inst" adb push Inst / adb shell "ls -l /data/local/Inst" # Ensure Valgrind on the phone is running adb shell "/data/local/Inst/bin/valgrind --version" # Add Valgrind executable to PATH (this might fail and indeed it fails..) adb shell "export PATH=$PATH:/data/local/Inst/bin/" #fi
Now we are ready. Save the code as build_valgrind.sh
. Then just run:
. build_valgrind.sh
If it gives something like the following in the end:
.... 287 files pushed. 0 files skipped. 3316 KB/s (242713648 bytes in 71.473s) drwxrwxr-x root root 2015-07-15 23:01 bin drwxrwxr-x root root 2015-07-14 17:23 include drwxrwxr-x root root 2015-07-14 17:22 lib drwxrwxr-x root root 2015-07-14 17:22 share
Then we are good to go.
Run Valgrind with Callgrind
Since we only run binary executions, it is relatively easy to do. Just perform:
adb shell "/data/local/Inst/bin/valgrind --callgrind-out-file=/sdcard/callgrind.out.%p --tool=callgrind <my_binary_file>"
and it should be good to go. If it says something like "cannot write tmp file," you can do the following to fix it:
- Add variable
TMPDIR
like following:adb shell "export TMPDIR=<other_writable_path> && /data/local/Inst/bin/valgrind --callgrind-out-file=/sdcard/callgrind.out.%p --tool=callgrind <my_binary_file>"
- The following requires root:
adb shell "su && mount -o remount rw /sdcard/"
See outputs
There are many ways to see outputs. Here I log the ways I do.
The first method is using KCachegrind
. Simply install it and run it. It is a GUI tool.
sudo apt-get install kcachegrind kcachegrind
The second method is to leverage a python script called gprof2dot
. Then we get a .dot
file and we can convert that into png. Assuming python
is installed already and also python-pip
, then
apt-get install graphviz pip install gprof2dot
Now suppose we get the output file named callgrind.out.1234
in path /sdcard/
. Then to get the png, just do
gprof2dot --format=callgrind --output=/sdcard/callgraph.out.1234.dot /sdcard/callgrind.out.1234 dot -Tpng /sdcard/callgraph.out.1234.dot -o callgraph.out.1234.png
And... booya! The following shows the output figure as an example. Good luck 😀