Wednesday, October 19, 2016

Android logging

I was looking at building a framework for logging which is expected to be platform independent. However, truth is that it cannot be really platform independent. If I say that all processes send the log to the logging framework using IPC and stop there, I will not be doing justice to the job of defining the architecture. I need to see what type of IPC is better for our QAs. (Here the main quality attribute that we consider is performance).

Now comes another problem. If I evaluate the IPC for one of the platform (custom made Linux) against IPC that in Android, (two of the five or six platforms that I need to consider) it might so happen that IPC mechanism chosen for Linux is not available under Android. So, I eventually decided to go bit deeper and look at the details of IPC mechanisms available in Android.

It was not that easy to understand the whole framework in a short duration. However, the following page was really helpful.

http://elinux.org/Android_Logging_System

So, every application uses a library function and writes to some buffers in kernel. I checked the corresponding code. Lets start with Log.java

The file is present in frameworks/base/core/java/android/util/Log.java
No magic seems to be happening there. It just calls corresponding native function println_native
println_native is in frameworks/base/core/jni/android_util_Log.cpp. However, the real work happens in __android_log_buf_write which is inside system/core/liblog/logd_write.c (or logd_write_kern.c depending on compile time flag). It calls write_to_log which is a function pointer which very intelligently implements a singleton.

Let us look at logd_write.c first:
At first write_to_log is set to __write_to_log_init which calls __write_to_log_initialize. Which opens a file "/dev/pmsg0" and a Unix socket /dev/scoket/logdw. The pointer is then modified to point to __write_to_log_daemon and subsequent calls reach that function.

In this function (__write_to_log_daemon), the log is written to both the socket and the device. We will first see what happens to socket.

The listener is in system/core/logd/LogListener.cpp. However, this class extends SocketListener. The real work is done in SocketListener::runListener. The socket is read for each client here. It took me a while to find out how the generic SocketListener class passes data to LogListener. SocketListener class has a virtual function onDataAvailable(SocketClient *c) which is implemented by LogListener. When the SocketListener finds that there is data to be read from the socket, it calls the onDataAvailable function  which maps to corresponding function of LogListener class.

What happens in LogListener::onDataAvailable?
The message is read and it is put into a LogBuffer. It also informs the reader when a new message arrives. All the connected clients can now get the data from the reader.

Apps-->Log-->/dev/socket/logdw-->LogListener-->LogBuffer-->LogReader-->/dev/socket/logdr-->Clients of log reader!

Well, I will update the log what happens to /dev/pmsg0 later. Time to sleep. Anyway, my takeaway from this is that I can safely use unix sockets for IPC in both Android and Linux without bothering much about performance and my code might be portable across these two platforms if I take a little bit of care.

Note: I have looked at Anroid 6.0.1 source to get this information.