๐ค Recoding Flashlight
On Android, we already have many tools to measure the performance of apps. But can we measure the performance of production apps, without installing any SDKs in the app? As we'll see, Android is based on Linux, so that gives us a lot of power.
The power of ADB ๐งโ
On Android, you can use ADB (Android Debug Bridge) to control your Android device from your computer.
You can for instance install apps with adb install
, or even run some events: for instance I can use adb shell input text Bonjour
to directly type some text.
What's interesting in that last command is the use of adb shell
. adb shell
opens a new shell directly on your Android device.
This means that if you run echo Bonjour
in that shell, your Android device is actually running the prompt!
To double-check that I can run uname
on my computer (running macOS) and in the adb shell
:
Instead of running adb shell
and then running a command, we can directly type adb shell <command>
instead.
For instance adb shell echo Bonjour
.
What about performance?โ
Since Android is based on Linux, we can start to think about all the performance tools available out of the box on Linux systems.
How about top
? On my Mac, I can run top
in the terminal and I see a lot of performance stats about the apps I'm using.
For instance iTerm was the app consuming the more CPU power when I ran top
in the video above.
Does it work with Android? Let's find out by running adb shell top
.
Actually, let's add a -d 1
to display values every 1sec.
It works! ๐ฅณ At the top, I see that the app consuming the most CPU power is named com.twitter.and+
. Sounds like an app you know? Yup, I was scrolling in the Twitter app while running top
.
I don't have access to Twitter's source code, and yet quite easily, I'm able to measure its performance! ๐ฅณ
CPU consumption reported in top can exceed 100%, because each CPU core accounts for 100%. For instance, my Pixel 8 has 9 cores, so the max CPU consumption reported would be 900%.
Going thread by threadโ
To have more granularity, it'd be nice to have a thread by thread consumption breakdown.
This is particularly useful for React Native apps for instance, where you need to make sure the JS thread is not running at 100% to still be able to handle events.
We can have thread by thread info by passing the -H
option. Easy to remember, right? It's H like ... hum ... H like threads? ๐คท
I was puzzled by the naming, but it actually stands for Hardware threads.
Anyway, let's try running adb shell top -d 1 -H
:
This time, I tested with a Bluesky, a React Native app. We can now see a breakdown of all the threads on the device (not just Bluesky), but we can figure out what the top 6 threads are about on the image below:
mqt_js
: JS thread used by React Native apps.blueskyweb.app
: Android UI thread.
You'll see it in every Android app. It has the same name as the app id, except it's truncated to 16 characters since that's the max process name length in Linux.RenderThread
: also a thread you'll see in every app.
Simply put, it handles communication between the UI thread and the GPU.top
, yep that's thetop
command we were running. More on that below.mqt_native_modules
: a thread typical in RN app using the old RN architectureglide-disk-cach
: a thread related to Glide, an Android library to display image.
The threads used by an app can actually reveal a lot about how the technology they use!
Without knowing anything about the Bluesky app, you can guess it's a React Native using the old architecture, and using the Glide library to display images.
Limitationsโ
To measure CPU consumption, it would seem we have everything we need! We can measure thread by thread, even for production apps, without needing to install a particular SDK.
However a few caveats:
- โ ๏ธ
top
cannot display measures more often than 1s. We can't display every 500ms for instance. - ๐จ
top
in itself actually consumes a lot of CPU!
I was using a Pixel 8 for the previous videos (not a bad phone) and already needed ~25% CPU power to run it. My tests with lower end Android phones or even TVs had it reach almost 100% several times!
You need to be wary of the performance of your performance measuring tool itself! ๐คฏ
Performance tools have an impact on the performance of your device and sometimes on your app!
Android Studio profiling for instance can slow down your app or increase its memory footprint.
Let's see if we can go beyond top
and write a tool that has minimal impact on the device ๐ช
Recoding CPU measuresโ
Have you ever opened the Android source code?โ
Wanna know how top
is implemented on Android? Well we can just clone or search the Android source code from the source web site
I don't know if you've ever done this, but when I interact with a codebase I don't know, to search for the code of a specific feature, I like to search for some wording ๐
So here, let's search for some wording contained inside adb shell top --help
and let's see if we're lucky ๐ค, for instance Usage graphs instead of text
ps.c
sounds like a good candidate for a file containing the implementation for top
:
- it's a C file
- it's named
ps
which is another tool to get performance measures on Linux - it's in a
toybox
folder. A quick Google search lets me knowtoybox
is a set of open source implementation of utilities such asls
,mv
and ...top
!
The code is still quite massive and tricky to understand, especially if you have no C/C++ experience.
However, this is a good use case to get AI to explain what a complex code is doing!
Sadly I didn't have GenAI at the time of writing Flashlight, likely you could just ask "how to measure CPU consumption on Android with adb" and it would give you the solution directly ๐
But where's the fun in that? ๐
Still, we can apply a bit of guesswork. Likely, top_main
is the function that we want, and it calls top_common
directly:
top_common
seems to be doing a lot of things! However these 2 lines seem interesting! There's some mention of threads, and look: we find the -H
flag we were passing!
So with dirtree_flagread
, we're going through the /proc
folder, and with the -H
flag we're applying the get_threads
function.
So let's look at the get_threads
function:
For each subfolder of /proc
, we're going through the task
folder and applying the get_ps
function.
Searching for get_ps
in the file, we can cheat a little bit because it's well documented!
Basically it reads a lot of data from the /stat
file. A quick Google search about "proc stat" reveals from the Linux documentation that this file does hold CPU stats for every process!
Let's wrap up, what do we need to implement?โ
- in the
/proc
folder, we find several folder.
Basically it's one folder for each process, so each app will have its own folder there. They're numeral folders, based on the process id, or pid. - in
/proc/<PID>/task
, we seem to find all the threads for the app in subfolders, each have their own thread process id - in
/proc/<PID>/task/<Thread PID>/stat
, we have all the CPU stats for a given thread - based on the Linux documentation, we're interested in the 14th and 15th column
Let's code this!โ
This is demonstrated in this Kotlin repository.
More details will be added to this page in the near future!
Recoding FPSโ
This is demonstrated in this Kotlin repository.
More details will be added to this page in the near future!