Using Valgrind and Callgrind on Android

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:

  1. There are a few bugs in the script
  2. Android-21 has a bug (issue of redefinition), but android-19 certainly works
  3. 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:

  1. 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>"
  2. 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 😀

gprof2dot2png_example

Leave a Reply

Your email address will not be published. Required fields are marked *