Mobile/Fennec/Android/Profiling

From MozillaWiki
< Mobile‎ | Fennec‎ | Android
Jump to: navigation, search

traceview

Java code can be effectively profiled with traceview, included in the Android SDK. See http://developer.android.com/guide/developing/tools/traceview.html

  • The Fennec build needs to be "debuggable". Nightlies are not, but local developer builds are.
  • For profiling pageload, you can generally use Monitor/DDMS. Just press the "start method tracing" button, load a page, wait, press the "stop method tracing" button.
  • For profiling startup time, you really need to add Debug.startMethodTracing(...) and Debug.stopMethodTracing(). Sample patch
  • Or use the "am start" -P option, e.g.
adb shell am start -n org.mozilla.fennec_$USER/.App -S -P /data/local/tmp/fennec.trace
# wait for Fennec launch
adb pull /data/local/tmp/fennec.trace
# in Monitor, File > Open File > fennec.trace

Built-in Profiler

See https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler#Profiling_Firefox_mobile

oprofile

Adding oprofile to CyanogenMod 7

CyanogenMod 7 is used here, but these steps will probably work similarly for other ROMs.

tl;dr:

  • CyanogenMod comes with the oprofile binaries opcontrol and oprofiled
  • However the CyanogenMod kernel doesn't include the oprofile module, so we need to recompile the kernel with oprofile
  • However oprofile module is kind of broken, so we need to patch it
  • opcontrol is broken too; we need to fix it
  • oprofile profiles happily there after

Recompile kernel with oprofile

Root and flash a standard CyanogenMod 7 ROM. Make sure the ROM runs.

Check the kernel version:

> adb shell uname -r
2.6.32.28-cyanogenmod-g4f4ee2e

Note the kernel branch (2.6.32) and revision (4f4ee2e) used to compile the kernel. Also find the right kernel repo for your phone at https://github.com/CyanogenMod (for HTC G2 it's 'htc-kernel-msm7x30').

Checkout the kernel source with the same revision:

git clone -b KERNEL_VER git://github.com/CyanogenMod/KERNEL_REPO.git ~/android-kernel
cd ~/android-kernel
git checkout KERNEL_REV

In this example, replace KERNEL_VER with 'android-msm-2.6.32', KERNEL_REPO with 'htc-kernel-msm7x30', and KERNEL_REV with '4f4ee2e'

Get a copy of the kernel config:

adb pull /proc/config.gz
gunzip -c config.gz > .config

Make sure .config contains these lines:

CONFIG_PROFILING=y
CONFIG_OPROFILE=y
CONFIG_HAVE_OPROFILE=y
CONFIG_OPROFILE_ARMV7=y

Currently oprofile on ARM only supports OMAP3 processors. If your processor is anything else (the HTC G2 has a Qualcomm MSM7230), you need to patch oprofile. For MSM7x30, apply this patch:

diff --git a/arch/arm/oprofile/op_model_v7.c b/arch/arm/oprofile/op_model_v7.c
index f20295f..b0fbc65 100644
--- a/arch/arm/oprofile/op_model_v7.c
+++ b/arch/arm/oprofile/op_model_v7.c
@@ -371,6 +371,9 @@ static int irqs[] = {
 #ifdef CONFIG_ARCH_OMAP3
        INT_34XX_BENCH_MPU_EMUL,
 #endif
+#if defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_QSD8X50)
+        INT_ARM11_PM,
+#endif
 };
 
 static void armv7_pmnc_stop(void)

If you will be getting callgraphs, apply the patch from bug 674986

Set the toolchain and compile: (the original kernel is built with 4.4.3 but NDK has 4.4.0; not sure if this matters...)

export CCOMPILER=/PATH/TO/NDK/build/prebuilt/linux-x86/arm-eabi-4.4.0/bin/arm-eabi-
make ARCH=arm CROSS_COMPILE=$CCOMPILER -j4

After the smoke settles, the kernel should be at arch/arm/boot/zImage

Flash kernel to phone

Extract a copy of of boot.img from the CyanogenMod zip used to flash the phone:

unzip update-cm-7.0.3-vision-signed.zip boot.img

Use the split_bootimg.pl [1] script to extract boot.img; note the output

> split_bootimg.pl boot.img
Page size: 2048 (0x00000800)
Kernel size: 2275692 (0x0022b96c)
Ramdisk size: 144106 (0x000232ea)
Second size: 0 (0x00000000)
Board name: 
Command line: no_console_suspend=1 console=null
Writing boot.img-kernel ... complete.
Writing boot.img-ramdisk.gz ... complete.

Now we need to make a new boot.img using the mkbootimg [2] tool included in the Android source repo.

mkbootimg --kernel ~/android-kernel/arch/arm/boot/zImage --ramdisk boot.img-ramdisk.gz --cmdline "CMDLINE" --board "BOARD" --base BASE -o new-boot.img

Replace CMDLINE and BOARD with values in the output from split_bootimg.pl. I found the value for BASE from a hex dump of boot.img:

> xxd -s 12 -l 4 -g 1 boot.img
000000c: 00 80 00 04                                      ....

00 80 00 04 is little-endian for 0x04008000 so BASE is 0x04000000. So in this example the command is:

mkbootimg --kernel ~/android-kernel/arch/arm/boot/zImage --ramdisk boot.img-ramdisk.gz --cmdline "no_console_suspend=1 console=null" --base 0x04000000 -o new-boot.img

As a sanity check, if for --kernel you specify 'boot.img-kernel', new-boot.img should be identical to boot.img

Now you can put the kernel on the phone. Boot into bootloader and use fastboot:

adb reboot bootloader
fastboot boot new-boot.img

This kernel update will be temporary. You can also do "fastboot flash boot new-boot.img" to make it permanent. If fastboot outputs error ("not allowed", etc), you might need a new bootloader that supports fastboot. On the HTC G2, I found a 0.76.2 ENG bootloader and flashed it with dd in adb shell.

If Wifi breaks, you have to replace the wlan kernel module. For the HTC Vision, the module is located at drivers/net/wireless/bcm4329/bcm4329.ko. Of course, since you replaced the module, you have to replace the kernel with fastboot flash rather than fastboot boot

adb shell mount -o remount,rw /system
adb push bcm4329.ko /system/lib/modules/
adb shell mount -o remount,ro /system

Patch and build opcontrol

There's a prebuilt opcontrol here [3], otherwise:

Because opcontrol is a part of CyanogenMod, we need to recompile the whole thing. (Or not, but I don't know any alternative way) Follow step here: http://wiki.cyanogenmod.com/wiki/Building_from_source. You might need to use a branch/revision that corresponds to your CyanogenMod version.

Before you start building, you want to edit external/oprofile/opcontrol/opcontrol.cpp and find calls to mkdir and add a 0 in front of the permissions.

Set up oprofile

adb shell mount -o remount,rw /system
adb push opcontrol /system/xbin
adb shell chmod +x /system/xbin/opcontrol
echo "#! /system/xbin/bash
touch /data/local/tmp/vmlinux
opcontrol --setup
opcontrol --vmlinux=/data/local/tmp/vmlinux \
--kernel-range=0x`adb shell cat /proc/kallsyms | grep ' _text' | head -c 8`,\
0x`adb shell cat /proc/kallsyms | grep ' _etext' | head -c 8` \
--event=CPU_CYCLES:125000" > opsetup
adb push opsetup /system/xbin
adb shell chmod +x /system/xbin/opsetup
echo "#! /bin/bash
rm -rf oprofile
mkdir oprofile
adb pull /data/oprofile oprofile/" > oppull
chmod +x oppull
adb shell mount -o remount,ro /system

The sample rate is (CPU freuqency in MHz) / (CPU_CYCLES count) / 64. For the HTC G2, it's 800MHz / 125000 / 64 = 100 samples per second.

Using oprofile on CyanogenMod 7

adb shell opsetup > /dev/null 2>&1 & # start up oprofile
adb shell opcontrol --start          # start profiling
adb shell opcontrol --status         # check status
adb shell opcontrol --stop           # stop profiling
adb shell opcontrol --shutdown       # shut down oprofile
./oppull                             # pull profile data from phone

Note that if you do start and stop, then start and stop again, the second set of data will be merged with the first set of data. I think you should be able use adb shell opcontrol --reset to start all over, but it didn't seem to work for me. Alternatively, you can always do a shut down and start up sequence to start over.

opreport is used to analyze the profile. The native x86 version seems to deal with the ARM data fine.

sudo apt-get install oprofile

You will see a lot of names in the form "/path/to/file.so (deleted)", and opreport can't find symbols because of the " (deleted)" part, so we create some symlinks:

rm -f -R oplibs
mkdir oplibs
for f in /PATH/TO/OBJDIR/dist/lib/*
do ln -s $f "oplibs/${f##*/} (deleted)"
done

Then you can see the report after the profile is pulled. e.g.:

opreport --session-dir=oprofile -l -t 1 -p ~+/oplibs 2>/dev/null

Custom kernel for Nexus One

Background information

oprofile is disabled in Nexus One kernel, so the kernel needs to be rebuilt to include it. These links have instructions on how to build a custom kernel:
http://androidboss.com/custom-linux-kernel-for-nexus-one/
http://atechyblog.blogspot.com/2010/08/enabling-serial-port-on-nexus-one.html
http://osdir.com/ml/android-platform/2010-07/msg00278.html

The key point is to add the following options to the .config to enable profiling:

CONFIG_OPROFILE_ARMV7=y
CONFIG_PROFILING=y
CONFIG_OPROFILE=y
CONFIG_HAVE_OPROFILE=y

Nexus One specifics

oprofile code included in the kernel needs to be fixed to work on Nexus One. The known fixes are for kernel version 2.6.32, branch "2.6.32-nexusonec". They are mentioned in the following posts:
Post 1, Post 2, The fix on Gerrit

Here's the combined patch:

--- a/arch/arm/oprofile/op_model_v7.c
+++ b/arch/arm/oprofile/op_model_v7.c
@@ -371,6 +371,11 @@
 #ifdef CONFIG_ARCH_OMAP3
 	INT_34XX_BENCH_MPU_EMUL,
 #endif
+#ifdef CONFIG_ARCH_MSM
+#ifdef CONFIG_ARCH_MSM_SCORPION
+        INT_ARM11_PM,
+#endif
+#endif
 };
 
 static void armv7_pmnc_stop(void)
@@ -386,12 +391,19 @@
 {
 	int ret;
 
+        if(ARRAY_SIZE(irqs) == 0) {
+            printk(KERN_ERR "oprofile: no interrupts for this CPU\n");
+            return -EINVAL;
+        }
+
 #ifdef DEBUG
 	armv7_pmnc_dump_regs();
 #endif
 	ret = armv7_request_interrupts(irqs, ARRAY_SIZE(irqs));
-	if (ret >= 0)
+	if (ret >= 0) {
+		armv7_setup_pmnc();
 		armv7_start_pmnc();
+	}
 
 	return ret;
 }

That specific branch is not available in the main repository at git://android.git.kernel.org/kernel/msm.git anymore, but still can be found here: git://github.com/Kali-/kernel_msm.git

Apparently this kernel was used in Android 2.2 Froyo. The latest official version of Android for Nexus One is 2.2.1. It is based on the kernel version 2.6.32.9-27240-gbca5320. This corresponds to the branch "android-msm-2.6.32" at git://android.git.kernel.org/kernel/msm.git

The patches above are still compatible with this branch, though enabling oprofile in this version of kernel has to be done carefully, as it's very easy to make the kernel unbootable.

Prerequisites

  • Linux (the steps below were used on Ubuntu 10.04)
  • Rooted Nexus One with Android 2.2.1, and unlocked bootloader
  • fastboot utility

You can find information on how to unlock and root your device here and on Google.

How to build a kernel with working oprofile for Nexus One

(These steps are based on the links mentioned above)

Get the kernel source:

$ git clone git://android.git.kernel.org/kernel/msm.git

The stock kernel version is 2.6.32.9. Once you get the source there are several branches in there. Check the branches and make a local branch of 2.6.32.9:

$ git branch -a
$ git checkout -b android-msm-2.6.32 origin/android-msm-2.6.32

Apply the op_model_v7.c fix from the section above.

Get the configuration from your device:

$ adb pull /proc/config.gz .
$ gzip -cd config.gz >.config

At this point you have to enable oprofile. The standard way would be to run

$ ARCH=arm CROSS_COMPILE=arm-eabi- make menuconfig

and enable it in General setup - Profiling support, but this may result in an unbootable kernel, so it's safer to enable it by adding the required options manually, i.e. by applying this patch:

--- a/msm/.config
+++ a/msm/.config
@@ -20,6 +20,7 @@
 CONFIG_GENERIC_HWEIGHT=y
 CONFIG_GENERIC_CALIBRATE_DELAY=y
 CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y
+CONFIG_OPROFILE_ARMV7=y
 CONFIG_VECTORS_BASE=0xffff0000
 CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config"
 CONFIG_CONSTRUCTORS=y
@@ -106,7 +107,9 @@
 CONFIG_SLAB=y
 # CONFIG_SLUB is not set
 # CONFIG_SLOB is not set
-# CONFIG_PROFILING is not set
+CONFIG_PROFILING=y
+CONFIG_TRACEPOINTS=y
+CONFIG_OPROFILE=y
 CONFIG_HAVE_OPROFILE=y
 # CONFIG_KPROBES is not set
 CONFIG_HAVE_KPROBES=y

Build the kernel (note: arm-eabi- tools from NDK have to be in the $PATH):

$ ARCH=arm CROSS_COMPILE=arm-eabi- make

This command will build a kernel image in arch/arm/boot/zImage. You can now boot with this image using fastboot without flashing.

First boot Nexus One to the FastBoot mode by turning power on with trackball pressed down, or, if it is already connected, use this command: adb reboot bootloader. When the device displays the screen saying "FASTBOOT USB", run the following command:

$ sudo fastboot boot /path/to/arch/arm/boot/zImage

This should boot your device with the custom kernel, which has oprofile enabled.

You'll need opcontrol and oprofiled, which you could download from here. These could be built manually, just opcontrol has to be patched as mentioned in the Post 1 above. The patch can be found here.

The stock WiFi driver does not work with the custom kernel, it needs to be replaced with the module built together with the new kernel. It is located here: drivers/net/wireless/bcm4329/bcm4329.ko. Copy it to the device:

$ adb push drivers/net/wireless/bcm4329/bcm4329.ko /data/local/tmp

Backup the stock module and copy the new one over:

$ adb shell
$ su
# mount -o rw,remount /dev/block/mtdblock3 /system
# cd /system/lib/modules
# mv bcm4329.ko bcm4329_stock.ko
# dd if=/data/local/tmp/bcm4329.ko of=./bcm4329.ko
# chmod 644 bcm4329.ko
# mount -o ro,remount /dev/block/mtdblock3 /system/
# exit
$ exit

Now if you reboot with your new kernel, WiFi should work.

Congratulations! You've got a working system with oprofile!

Ready to use ROM with oprofile

Due to the legal issues, the ROM is not available publicly. If you have Intranet access, you can find it here.


Adding oprofile to CyanogenMod ROM for Nexus One

Note: Currently it doesn't seem possible to use CyanogenMod ROMs, as they are based on the newer kernels, where oprofile code was changed, so the Nexus One specific patches cannot be applied "as is". But this approach could be still used if the solution could be found to that.

The approach with a custom kernel may not work as the rest of the ROM may not be compatible with it, so it might be safer to build the whole ROM.

Here are the instructions how to do this:
http://wiki.cyanogenmod.com/index.php?title=Compile_CyanogenMod_for_Passion

"device/htc/passion/extract-files.sh" may not find some libraries, as they are missing on the device, but they are required for the build. Those files could be extracted from the compiled CyanogenMod image downloaded from here:
http://wiki.cyanogenmod.com/index.php?title=Latest_Version#N1CM6

Also "vendor/cyanogen/get-google-files" script may not find Google applications, as the link it uses could be obsolete. A link to the latest package can be found on the same page:
http://wiki.cyanogenmod.com/index.php?title=Latest_Version#Google_Apps
You need HDPI version.

Kernel config has to be modified before the build to include the same OPROFILE options as above. The default config, which needs to be updated, is located here:

kernel-msm/arch/arm/configs/cyanogen_mahimahi_defconfig

(This needs to be verified. If it doesn't work, the actual .config used during the build could be found here: out/target/product/passion/obj/kernel/.config)

Issues

The main issue currently is that though the initial setup with "opcontrol --setup" seems to succeed, the oprofile counter configuration fails:

# opcontrol --event=CPU_CYCLES:64
Cannot open /dev/oprofile/0/user: No such file or directory
Cannot open /dev/oprofile/0/kernel: No such file or directory
Cannot open /dev/oprofile/0/unit_mask: No such file or directory
Cannot open /dev/oprofile/0/enabled: No such file or directory
Cannot open /dev/oprofile/0/count: No such file or directory
Cannot open /dev/oprofile/0/event: No such file or directory
Counter configuration failed for CPU_CYCLES
Did you do "opcontrol --setup" first?

This needs to be investigated, but it looks like oprofile compiled into the kernel does not have proper support for Nexus One hardware, and has to be fixed.

I encountered the same behavior when running opcontrol on the Galaxy Nexus and found that defining CONFIG_HW_PERF_EVENTS in the kernel .config file fixed the problem on the Galaxy Nexus.

Using oprofile on Galaxy Nexus

See also bug730900.

There is no oprofile support in the factory / production Android build on the Galaxy Nexus. The engineering build (ie. full_maguro-eng configuration) includes user-mode support for oprofile (opcontrol, etc.), but no kernel support for oprofile (/dev/oprofilefs is not present).

The approach used here is to build a custom kernel with oprofile support configured, patch the Android source tree with the new kernel and then build and flash an engineering build of Android.

Full instructions for building Android begin at http://source.android.com/source/initializing.html.

Here is the short version reflecting what I did, step-by-step:

# Get Android sources, initialize environment
mkdir <myandroid>
cd <myandroid>
curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > repo
chmod +x repo
./repo init -u https://android.googlesource.com/platform/manifest -b android-4.0.1_r1.2
./repo sync
source build/envsetup.sh
lunch full_maguro-eng
<get the ITL41F binaries from http://code.google.com/android/nexus/drivers.html#maguroitl41f and extract them>

# Get kernel source, customize for oprofile, build kernel
mkdir <mykernel>
cd <mykernel>
git clone https://android.googlesource.com/kernel/omap.git
cd omap
git branch -a
git checkout -t remotes/origin/android-omap-tuna-3.0-mr0
export ARCH=arm
export CROSS_COMPILE=<myandroid>/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi-
make tuna_defconfig
<edit .config, setting:
CONFIG_PROFILING=y
CONFIG_OPROFILE=y
CONFIG_HAVE_OPROFILE=y
CONFIG_HW_PERF_EVENTS=y
CONFIG_PERF_EVENTS=y
CONFIG_PERF_COUNTERS=y
/edit>
make
 

The opcontrol project is in <myandroid>/external/oprofile/opcontrol; it includes special support for ARM_V7_A, but there is a bug in the makefile, Android.mk: -DWITH_ARM_V7_A is added to LOCAL_CFLAGS, but then LOCAL_CFLAGS is clobbered further down -- fix this with something like:

LOCAL_C_INCLUDES := $(common_target_c_includes)
LOCAL_CFLAGS := $(common_target_cflags)
# Force ARM_V7_A
LOCAL_CFLAGS += -DWITH_ARM_V7_A
 
# Patch Android with my kernel, build, install images
cp arch/arm/boot/zImage <myandroid>/device/samsung/tuna/kernel
cd <myandroid>
make
<put phone into bootloader mode: power off, then hold volume up + volume down + power>
fastboot -w flashall
 

Once the phone reboots, verify the new kernel:

mylinux$ adb shell dmesg | grep oprofile
<6>[    2.772705] oprofile: using arm/armv7-ca9
 

Now everything appears to be working, but I cannot seem to collect samples!

    root@android:/data/local # opcontrol --reset --kernel-range=0xc004d000,0xc0752000 --event=CPU_CYCLES:1 --setup --status --verbose-log=all --verbose
    list_events = 0
    setup = 1
    Configure /dev/oprofile/0/user (1)
    Configure /dev/oprofile/0/kernel (1)
    Configure /dev/oprofile/0/unit_mask (0)
    Configure /dev/oprofile/0/enabled (1)
    Configure /dev/oprofile/0/count (1)
    Configure /dev/oprofile/0/event (255)
    Configure /dev/oprofile/1/enabled (0)
    Configure /dev/oprofile/2/enabled (0)
    Configure /dev/oprofile/3/enabled (0)
    Configure /dev/oprofile/4/enabled (0)
    Starting oprofiled...
    command: oprofiled --session-dir=/data/oprofile --events=CPU_CYCLES:255:0:1:0:1:1 --no-vmlinux -r 0xc004d000,0xc0752000 --verbose=all
    Events: CPU_CYCLES:255:0:1:0:1:1
    Using 2.6+ OProfile kernel interface.
    Using log file /data/oprofile/samples/oprofiled.log
    Ready
    Driver directory: /dev/oprofile
    Session directory: /data/oprofile
    Counter 0:
        name: CPU_CYCLES
        count: 1
    Counter 1 disabled
    Counter 2 disabled
    Counter 3 disabled
    Counter 4 disabled
    oprofiled pid: 1528
    profiler is not running
      cpu1         0 samples received
      cpu1         0 samples lost overflow
      cpu1         0 samples invalid eip
      cpu1         0 backtrace aborted
      cpu0         0 samples received
      cpu0         0 samples lost overflow
      cpu0         0 samples invalid eip
      cpu0         0 backtrace aborted
     
    root@android:/data/local #
    root@android:/data/local # opcontrol --start
    root@android:/data/local # opcontrol --status --verbose
    list_events = 0
    setup = 0
    Driver directory: /dev/oprofile
    Session directory: /data/oprofile
    Counter 0:
        name: CPU_CYCLES
        count: 1
    Counter 1 disabled
    Counter 2 disabled
    Counter 3 disabled
    Counter 4 disabled
    oprofiled pid: 1528
    profiler is running
      cpu1         0 samples received
      cpu1         0 samples lost overflow
      cpu1         0 samples invalid eip
      cpu1         0 backtrace aborted
      cpu0         0 samples received
      cpu0         0 samples lost overflow
      cpu0         0 samples invalid eip
      cpu0         0 backtrace aborted
     
    root@android:/data/local # opcontrol --stop
    root@android:/data/local # ls -lR /data/oprofile
     
    /data/oprofile:
    -rw-rw-rw- root     root          724 2012-03-07 16:43 abi
    -rw-rw-rw- root     root            2 2012-03-07 16:46 complete_dump
    -rw-rw-rw- root     root            4 2012-03-07 16:43 lock
    prw-rw-rw- root     root              2012-03-07 16:43 opd_pipe
    drwxr-xr-x root     root              2012-03-07 16:43 samples
     
    /data/oprofile/samples:
    -rw-r--r-- root     root        57424 2012-03-07 16:46 oprofiled.log
    root@android:/data/local # tail /data/oprofile/samples/oprofiled.log
    ...
    CPU_SWITCH to 1
    CPU_SWITCH to 0
    CPU_SWITCH to 1
    CPU_SWITCH to 0
    CPU_SWITCH to 1 
 

Using oprofile

After the new ROM is flashed to the device, oprofile can be controlled with opcontrol.

Manuals, examples, cheat sheet are here: OProfile documentation

Some Android-specific instructions can be found here:
http://www.omappedia.org/wiki/Android_Debugging#OProfile_on_OMAP3

PowerTOP

Compiling PowerTOP

Using NDK5, make a cross-compiling toolchain:

cd /PATH/TO/NDK
build/tools/make-standalone-toolchain.sh --platform=android-5 --install-dir=../ndk5-toolchain
export PATH=$PATH:~+/ndk5-toolchain/bin

Download and extract the latest version of ncurses (5.9 here) and PowerTOP (1.13 here):

cd ~
wget http://www.lesswatts.org/projects/powertop/download/powertop-1.13.tar.gz
tar -xzf powertop-1.13.tar.gz
wget http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.9.tar.gz
tar -xzf ncurses-5.9.tar.gz

Patch ncurses using this patch:

diff --git a/ncurses-5.9/configure b/ncurses-5.9/configure
index 639b790..d8b8c53 100755
--- a/ncurses-5.9/configure
+++ b/Downloads/ncurses-5.9/configure
@@ -13370,17 +13370,16 @@ EOF
 EOF
  ;;
 esac
 
 for ac_header in \
 fcntl.h \
 getopt.h \
 limits.h \
-locale.h \
 math.h \
 poll.h \
 sys/bsdtypes.h \
 sys/ioctl.h \
 sys/param.h \
 sys/poll.h \
 sys/select.h \
 sys/time.h \

Patch PowerTOP using this patch:

TODO: Investigate other changes to PowerTOP source to make it run better on Android

diff --git a/powertop-1.13/Makefile b/powertop-1.13/Makefile
index fe3bc94..c327105 100644
--- a/powertop-1.13/Makefile
+++ b/powertop-1.13/Makefile
@@ -13,23 +13,23 @@ CFLAGS+=-D VERSION=\"$(VERSION)\"
 # The w in -lncursesw is not a typo; it is the wide-character version
 # of the ncurses library, needed for multi-byte character languages
 # such as Japanese and Chinese etc.
 #
 # On Debian/Ubuntu distros, this can be found in the
 # libncursesw5-dev package. 
 #
 
-OBJS = powertop.o config.o process.o misctips.o bluetooth.o display.o suggestions.o wireless.o cpufreq.o \
-	sata.o xrandr.o ethernet.o cpufreqstats.o usb.o urbnum.o intelcstates.o wifi-new.o perf.o \
+OBJS = powertop.o config.o process.o misctips.o bluetooth.o display.o suggestions.o cpufreq.o \
+	sata.o xrandr.o cpufreqstats.o usb.o urbnum.o intelcstates.o wifi-new.o \
 	alsa-power.o ahci-alpm.o dmesg.o devicepm.o
 	
 
 powertop: $(OBJS) Makefile powertop.h
-	$(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -lncursesw -o powertop
+	$(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -lncurses -o powertop
 	@(cd po/ && $(MAKE))
 
 powertop.8.gz: powertop.8
 	gzip -c $< > $@
 
 install: powertop powertop.8.gz
 	mkdir -p ${DESTDIR}${BINDIR}
 	cp powertop ${DESTDIR}${BINDIR}
diff --git a/powertop-1.13/powertop.c b/powertop-1.13/powertop.c
index 74eb328..6370e0d 100644
--- a/powertop-1.13/powertop.c
+++ b/powertop-1.13/powertop.c
@@ -25,17 +25,16 @@
 #include <getopt.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdint.h>
 #include <sys/types.h>
 #include <dirent.h>
-#include <libintl.h>
 #include <ctype.h>
 #include <assert.h>
 #include <locale.h>
 #include <time.h>
 #include <limits.h>
 #include <sys/stat.h>
 
 #include "powertop.h"
@@ -846,22 +845,16 @@ void version()
 int main(int argc, char **argv)
 {
 	char line[1024];
 	int ncursesinited=0;
 	FILE *file = NULL;
 	uint64_t cur_usage[8], cur_duration[8];
 	double wakeups_per_second = 0;
 
-	setlocale (LC_ALL, "");
-	bindtextdomain ("powertop", "/usr/share/locale");
-	textdomain ("powertop");
-
-	start_data_dirty_capture();
-
 	while (1) {
 		static struct option opts[] = {
 			{ "dump", 0, NULL, 'd' },
 			{ "time", 1, NULL, 't' },
 			{ "pids", 0, NULL, 'p' },
 			{ "help", 0, NULL, 'h' },
 			{ "version", 0, NULL, 'v' },
 			{ 0, 0, NULL, 0 }
@@ -1089,18 +1082,16 @@ int main(int argc, char **argv)
 			push_line_pid(line2, cnt, 0, pid);
 		}
 
 		if (file)
 			pclose(file);
 
 		reset_suggestions();
 
-		parse_data_dirty_buffer();
-
 		if (strstr(line, "total events")) {
 			int d;
 			d = strtoull(line, NULL, 10) / sysconf(_SC_NPROCESSORS_ONLN);
 			if (totalevents == 0) { /* No c-state info available, use timerstats instead */
 				totalevents = d * sysconf(_SC_NPROCESSORS_ONLN) + total_interrupt;
 				if (d < interrupt_0)
 					totalevents += interrupt_0 - d;
 			}
@@ -1243,24 +1234,22 @@ int main(int argc, char **argv)
 			  10);
 
 		suggest_bluetooth_off();
 		suggest_nmi_watchdog();
 		if (maxsleep > 15.0)
 			suggest_hpet();
 		suggest_ac97_powersave();
 		suggest_hda_powersave();
-		suggest_wireless_powersave();
 		suggest_wifi_new_powersave();
 		suggest_ondemand_governor();
 		suggest_noatime();
 		suggest_sata_alpm();
 		suggest_powersched();
 		suggest_xrandr_TV_off();
-		suggest_WOL_off();
 		suggest_writeback_time();
 		suggest_usb_autosuspend();
 		suggest_runtime_suspend();
 
 		usb_activity_hint();
 		void devicepm_activity_hint();
 		alsa_activity_hint();
 		ahci_activity_hint();
diff --git a/powertop-1.13/powertop.h b/powertop-1.13/powertop.h
index d8f8182..3b5f4e7 100644
--- a/powertop-1.13/powertop.h
+++ b/powertop-1.13/powertop.h
@@ -21,18 +21,16 @@
  * Authors:
  *	Arjan van de Ven <arjan@linux.intel.com>
  */
 
 
 #ifndef __INCLUDE_GUARD_POWERTOP_H_
 #define __INCLUDE_GUARD_POWERTOP_H_
 
-#include <libintl.h>
-
 struct line {
 	char	*string;
 	int	count;
 	int	disk_count;
 	char	pid[12];
 };
 
 typedef void (suggestion_func)(void);
@@ -87,17 +85,17 @@ extern suggestion_func *suggestion_activate;
 /* min definition borrowed from the Linux kernel */
 #define min(x,y) ({ \
         typeof(x) _x = (x);     \
         typeof(y) _y = (y);     \
         (void) (&_x == &_y);            \
         _x < _y ? _x : _y; })
 
 
-#define _(STRING)    gettext(STRING)
+#define _(STRING)    (STRING)
 
 
 #define PT_COLOR_DEFAULT    1
 #define PT_COLOR_HEADER_BAR 2
 #define PT_COLOR_ERROR      3
 #define PT_COLOR_RED        4
 #define PT_COLOR_YELLOW     5
 #define PT_COLOR_GREEN      6

Configure, compile, and install ncurses:

cd ~/ncurses-5.9
CFLAGS='-march=armv7-a -mfloat-abi=softfp -mfpu=vfp -Os' \
 LDFLAGS='-Wl,--fix-cortex-a8' \
 CC=arm-linux-androideabi-gcc \
 ./configure --host=arm-linux-androideabi --prefix=${HOME}/ndk5-toolchain/sysroot/usr
make HOSTCC=gcc
make install

Compile PowerTOP

cd ~/powertop-1.13
CFLAGS='-march=armv7-a -mfloat-abi=softfp -mfpu=vfp -isystem =/usr/include/ncurses' \
 LDFLAGS='-Wl,--fix-cortex-a8' \
 CC=arm-linux-androideabi-gcc \
 make

Push PowerTOP to phone

adb push powertop /data/
chmod +x /data/powertop

Using PowerTOP

It seems necessary to set TERMINFO in order to run PowerTOP:

adb shell
TERMINFO=/system/etc/terminfo /data/powertop

systemtap

Compiling systemtap

Think of the children, get the prebuilt version. Extract it to your host machine, and to /data on your device.

Basically running systemtap consists of the host portion and the target portion. For the host portion you use a standard systemtap installation to cross compile your script into a kernel module. For the target portion you transfer the kernel module to your device and run it on the device.

The host portion you can get through apt-get. The target portion you have to compile (use the CodeSourcery ARM GNU/Linux toolchain):

LDFLAGS=-static \
  ac_cv_file__usr_include_avahi_common=no \
  ac_cv_file__usr_include_avahi_client=no \
  ac_cv_file__usr_include_nspr=no \
  ac_cv_file__usr_include_nspr4=no \
  ac_cv_file__usr_include_nss=no \
  ac_cv_file__usr_include_nss3=no \
  ./configure --prefix=/data/systemtap --host=arm-none-linux-gnueabi \
    --disable-translator --disable-nls
make install

Note that prefix has to be /data/systemtap (or another path that matches Android file system) so you need to make a /data directory on your host machine before you run make install.

You might need libelf. Get the latest elfutils release, then compile:

./configure --host=arm-none-linux-gnueabi --disable-nls \
  --prefix=/PATH/TO/TOOLCHAIN/arm-none-linux-gnueabi/libc/usr
cd libelf
make install

Where /PATH/TO/TOOLCHAIN is the path to your CodeSourcery toolchain.

Using systemtap

Before you start, you need the source of your device's kernel. See the oprofile section for one example way to get the kernel source.

To compile your script on the host, make sure you have the NDK toolchain (r5 or above), then run

stap -gv -a arm -B CROSS_COMPILE=arm-linux-androideabi- \
  -r /PATH/TO/KERNEL -R /PATH/TO/SYSTEMTAP/RUNTIME \
  -m NAME -B CFLAGS_MODULE="-DMODULE -fno-pic" NAME.stp

where NAME is the name of your script, /PATH/TO/KERNEL is the path to your kernel source, and /PATH/TO/SYSTEMTAP/RUNTIME is the path to your target systemtap runtime (for example if you're using the prebuilt version above, /PATH/TO/SYSTEMTAP/RUNTIME = systemtap/share/systemtap/runtime)

adb push your kernel module to your device, then run

staprun -x PID NAME.ko

where PID is the pid of fennec process

Sample systemtap scripts

glandium's I/O tracking script

global targetpid;
global file_path;

probe begin {
  targetpid = target();
}

probe kernel.function("__do_page_cache_readahead") {
  if (targetpid == pid())
    file_path[tid()] = d_path(&$filp->f_path);
}

probe kernel.function("do_mpage_readpage") {
  if (targetpid == pid() && (tid() in file_path)) {
    now = gettimeofday_us();
    printf("%d %s %d\n", now, file_path[tid()], $page->index*4096);
  }
}

probe kernel.function("__do_page_cache_readahead").return {
  if (targetpid == pid())
    delete file_path[tid()];
}

jchen's I/O tracking script

Note that this script accesses the hardware timer directly to overcome limitations in the systemtap gettimeofday() resolution (basically in systemtap, gettimeofday() is limited to jiffy resolution).

This also means to run it on a processor other than a MSM7x30, get_rawtime() will need to change.

%{
#include <arch/arm/include/asm/io.h>

#define MSM_CSR_BASE ((void*)0xF8001000)
#define MSM_TMR_BASE MSM_CSR_BASE
#define MSM_GPT_BASE (MSM_TMR_BASE + 0x04)
#define MSM_DGT_BASE (MSM_TMR_BASE + 0x24)
#define TIMER_COUNT_VAL 0x0004
%}

function get_rawtime:long () %{ /* pure */ /* unprivileged */

  cycles_t c = readl(MSM_DGT_BASE + TIMER_COUNT_VAL);
  THIS->__retvalue = ((int64_t) (int32_t) c) - global.s_origin_time;

%}

global parentid, childid;
global start_time, total_time;
global origin_time;
global creation_time;

probe begin {
  parentid = 0;
  childid = 0;
  origin_time = get_rawtime()
}

function is_parent:long () {
  if (pid() == parentid)
    return parentid;
  if (0 == parentid && isinstr(execname(), "fennec")) {
    parentid = pid();
    return parentid;
  }
  return 0;
}

function is_child:long () {
  if (pid() == childid)
    return childid;
  if (0 == childid && isinstr(execname(), "plugin")) {
    childid = pid();
    return childid;
  }
  return 0;
}

probe syscall.fork.return {
  if (is_parent() || is_child()) {
    if ($return != 0) {
      thdid = is_parent() ? $return : -$return;
      creation_time[thdid] = get_rawtime();
      total_time[thdid, "", creation_time[thdid] / 8192] = -1;
    }
  }
}

probe syscall.execve.return {
  if (is_parent() || is_child()) {
    thdid = is_parent() ? tid() : -tid();
    creation_time[thdid] = get_rawtime();
    total_time[thdid, "", creation_time[thdid] / 8192] = -1;
  }
}

probe syscall.exit {
  if (is_parent() || is_child()) {
    thdid = is_parent() ? tid() : -tid();
    total_time[thdid, "", creation_time[thdid] / 8192] =
        get_rawtime() - creation_time[thdid];
  }
}

probe vfs.read {
  if (bytes_to_read > 0 && (is_parent() || is_child())) {
    thdid = is_parent() ? tid() : -tid();
    start_time[thdid] = get_rawtime();
  }
}

probe vfs.read.return {
  if (bytes_to_read > 0 && (is_parent() || is_child())) {
    thdid = is_parent() ? tid() : -tid();
    fn = "rd:" . __file_filename(file);
    st = start_time[thdid];
    total_time[thdid, fn, st / 8192] += get_rawtime() - st;
  }
}

probe kernel.function("filemap_fault") {
  if (is_parent() || is_child()) {
    thdid = is_parent() ? tid() : -tid();
    start_time[thdid] = get_rawtime();
  }
}

probe kernel.function("filemap_fault").return {
  if (is_parent() || is_child()) {
    thdid = is_parent() ? tid() : -tid();
    fn = "mm:" . __file_filename($vma->vm_file);
    st = start_time[thdid];
    total_time[thdid, fn, st / 8192] += get_rawtime() - st;
  }
}

probe end {
  foreach ([t, fn, st] in total_time) {
    printf("%d, %s, %d, %d\n", t, fn, st, total_time[t, fn, st]);
  }
}