Android內存優化(二)系統進程之logd的native memory優化

最近的測試發現,C1 logd進程內存佔用比Mate9 logd進程內存佔用大很多,詳細數據如下:
內存最大值(KB) 內存最小值(KB) 內存平均值(KB)
C1 39794 21985 39353
Mate9 2804 2565 2719

懷疑有native內存泄露,或者內存老化問題出現,於是做了針對性的Native內存泄露測試,發現跑兩個小時的固定場景,很容易出現下面這段trace

Begin trace
size 4111590 count:49857 parentcnt:51505
0x00008c0c debug_malloc bionic/libc/malloc_debug/malloc_debug.cpp:310 /system/lib64/libc_malloc_debug.so
0x00083070 operator new(unsigned long) external/libcxxabi/src/cxa_new_delete.cpp:44 (discriminator 1) /system/lib64/libc++.so 
0x0000ac4c LogBufferElement system/core/logd/LogBufferElement.cpp:47 /system/bin/logd 
0x00007498 LogBuffer::log(log_id, log_time, unsigned int, int, int, char const*, unsigned short) system/core/logd/LogBuffer.cpp:209 (discriminator 3) /system/bin/logd 
0x00006168 LogListener::onDataAvailable(SocketClient*) system/core/logd/LogListener.cpp:112 (discriminator 1) /system/bin/logd 
0x00004200 SocketListener::runListener() system/core/libsysutils/src/SocketListener.cpp:245 /system/lib64/libsysutils.so 
0x00003cf8 SocketListener::threadStart(void*) system/core/libsysutils/src/SocketListener.cpp:147 /system/lib64/libsysutils.so 
0x00075c54 __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:198 (discriminator 1) /system/lib64/libc.so 
0x0001e0fc __start_thread bionic/libc/bionic/clone.cpp:41 (discriminator 1) /system/lib64/libc.so 
0xfffffffffffffffc ??? ??? ??? 
End Trace
Begin trace
size 2920176 count:52146 parentcnt:53794
0x00008c0c debug_malloc bionic/libc/malloc_debug/malloc_debug.cpp:310 /system/lib64/libc_malloc_debug.so
0x00083070 operator new(unsigned long) external/libcxxabi/src/cxa_new_delete.cpp:44 (discriminator 1) /system/lib64/libc++.so 
0x00007474 LogBuffer::log(log_id, log_time, unsigned int, int, int, char const*, unsigned short) system/core/logd/LogBuffer.cpp:209 (discriminator 1) /system/bin/logd 
0x00006168 LogListener::onDataAvailable(SocketClient*) system/core/logd/LogListener.cpp:112 (discriminator 1) /system/bin/logd 
0x00004200 SocketListener::runListener() system/core/libsysutils/src/SocketListener.cpp:245 /system/lib64/libsysutils.so 
0x00003cf8 SocketListener::threadStart(void*) system/core/libsysutils/src/SocketListener.cpp:147 /system/lib64/libsysutils.so 
0x00075c54 __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:198 (discriminator 1) /system/lib64/libc.so 
0x0001e0fc __start_thread bionic/libc/bionic/clone.cpp:41 (discriminator 1) /system/lib64/libc.so 
0xfffffffffffffffc ??? ??? ??? 
End Trace
Begin trace
size 1249464 count:52061 parentcnt:53709
0x00008c0c debug_malloc bionic/libc/malloc_debug/malloc_debug.cpp:310 /system/lib64/libc_malloc_debug.so
0x00083070 operator new(unsigned long) external/libcxxabi/src/cxa_new_delete.cpp:44 (discriminator 1) /system/lib64/libc++.so 
0x000075f4 std::_1::_allocate(unsigned long) external/libcxx/include/new:168 /system/bin/logd 
0x00006168 LogListener::onDataAvailable(SocketClient*) system/core/logd/LogListener.cpp:112 (discriminator 1) /system/bin/logd 
0x00004200 SocketListener::runListener() system/core/libsysutils/src/SocketListener.cpp:245 /system/lib64/libsysutils.so 
0x00003cf8 SocketListener::threadStart(void*) system/core/libsysutils/src/SocketListener.cpp:147 /system/lib64/libsysutils.so 
0x00075c54 __pthread_start(void*) bionic/libc/bionic/pthread_create.cpp:198 (discriminator 1) /system/lib64/libc.so 
0x0001e0fc __start_thread bionic/libc/bionic/clone.cpp:41 (discriminator 1) /system/lib64/libc.so 
0xfffffffffffffffc ??? ??? ??? 
End Trace

從上面的backtrace看,主要描述的是Logd如何處理客戶端的log過程,以Java層的Log.d(smg)舉例,主要執行流程:

Log.java d()​->Log.java println_native()->android_util_Log.cpp android_util_Log_println_native()->logger_write.c __android_log_buf_write​()->logger_write.c write_to_log() -> logger_write.c __write_to_log_init​() -> logger_write.c​ __write_to_log_initialize()​ -> logger_write.c __write_to_log_daemon​() 從邏輯看,就是往socket不斷的寫log,寫給誰呢?在__write_to_log_initialize()​ ​方法中會調用 config_write.c __android_log_config_write​(),初始化logdLoggerWrite​,從logd_write.c中可以看出,logdLoggerWrite​是個socket,
strcpy(un.sun_path, “/dev/socket/logdw”);
往socket裏面寫了,誰會去處理呢?
logd進程啓動的源碼中可知,LogListener​ listens on /dev/socket/logdw for client​,可見下面源碼
// LogListener listens on /dev/socket/logdw for client
// initiated log messages. New log entries are added to LogBuffer
// and LogReader is notified to send updates to connected clients.

LogListener *swl = new LogListener(logBuf, reader);
LogListener是SocketListener類型,初始化完成後會runListener​,當socket中有信息時,執行流:
SocketListener::runListener​() -> LogListener::onDataAvailable​() -> LogBuffer::log()
在LogBuffer::log()方法中,new LogBufferElement​,(這部分就跟上面的backtrace對應上了),並將其添加至LogBufferElementCollection​中,在LogBufferElement​構造方法中,會根據msg信息new char[],將信息存在LogBuffer~
存起來的Log,什麼時候會消費呢?
在代碼中,調用LogBuffer::erase()會釋放,從單詞意思也能猜到,這個方法什麼時候調用呢?
LogBuffer::maybePrune() -> LogBuffer::prune() -> LogBuffer::erase()​
而LogBuffer::maybePrune()​調用,只有在 LogBuffer::log()​,也就是說,上面的Java層調用Log.d()就可以觸發這個邏輯,觸發的條件在LogBuffer::maybePrune()​方法中

// Prune at most 10% of the log entries or maxPrune, whichever is less.
//
// mLogElementsLock must be held when this function is called.
void LogBuffer::maybePrune(log_id_t id) {
    size_t sizes = stats.sizes(id);
    unsigned long maxSize = log_buffer_size(id);
    if (sizes > maxSize) {
        size_t sizeOver = sizes - ((maxSize * 9) / 10);
        size_t elements = stats.realElements(id);
        size_t minElements = elements / 100;
        if (minElements < minPrune) {
            minElements = minPrune;
        }
        unsigned long pruneRows = elements * sizeOver / sizes;
        if (pruneRows < minElements) {
            pruneRows = minElements;
        }
        if (pruneRows > maxPrune) {
            pruneRows = maxPrune;
        }
        prune(id, pruneRows);
    }
}

從代碼可知,當對應的LogBuffer size超過了maxSize​,進行裁剪log,釋放一部分LogBufferElement​~其實到這可以看出,logd是log池,只有池子滿了,纔會主動釋放一部分內存
系統中有多少LogBuffer呢?最大值又是多少呢?
LogBuffer::init()方法中,會對LogBuffer初始化,從初始化的過程看,有7個LogBuffer,分別是:

static const char *LOG_NAME[LOG_ID_MAX] = {
    [LOG_ID_MAIN] = "main",
    [LOG_ID_RADIO] = "radio",
    [LOG_ID_EVENTS] = "events",
    [LOG_ID_SYSTEM] = "system",
    [LOG_ID_CRASH] = "crash",
    [LOG_ID_SECURITY] = "security",
    [LOG_ID_KERNEL] = "kernel",
};

根據系統屬性設置maxsize,從C1上看下跟logd相關的屬性:
設置邏輯貼代碼比較多,直接看一個說明文檔的註釋:
persist.logd.size number ro Global default size of the buffer for
all log ids at initial startup, at
runtime use: logcat -b all -G
ro.logd.size number svelte default for persist.logd.size. Larger
platform default sizes than 256KB are
known to not scale well under log spam
pressure. Address the spam first,
resist increasing the log buffer.
persist.logd.size. number ro Size of the buffer for log
ro.logd.size. number svelte default for persist.logd.size.
初始化過程執行完,每個LogBuffer的最大值如下:
main:2M
radio:4M
events:2M
system:4M
crash:1M
security:2M
kernel:2M

如果每個LogBuffer都填滿,所有LogBuffer內存佔用近20MB,再加上分配內存的中間開銷,佔用內存可能會非常大~

再來看下Mate9的logd相關設置:
Mate9沒有設置LogBuffer的大小,如果不設置,LogBuffer大小使用默認值,即256KB
如果將C1的logd相關屬性和Mate9保持一致後,測試結果如下:
內存最大值(KB) 內存最小值(KB) 內存平均值(KB)
C1 修改版 7894 4877 6610
Mate9 2804 2565 2719
​修改LogBuffer後的效果比較明顯,內存值會下降很多,但同樣會帶來log丟失的問題。也就是 LogBuffer::prune()方法帶來的問題
裁剪的主要邏輯:先刪黑名單和Log數最多的UID對應的Log,而且先刪oldest的log,如果仍不滿足,再根據白名單的配置,白名單的不刪除,其他都刪除,直到滿足條件

LogBuffer最大值意味着存放log信息的多少,比如我們生成bugreport時,會dumpstate,取system log,event log,radio log等信息,
LogBuffer比較小的話,取出來的信息會相對少一些,可能會對開發解問題造成影響。
也就說,改動前會看到2個小時前的event log,system log,改動配置後可能就會看到近20分鐘的event log,system log

看一下實際情況,跑了幾個小時的場景後,一臺C1是16號開發版包,logd內存:46614K​,一個C1是修改logd後的開發版包,logd內存6794K,
抓取兩個C1的bugreport
11-16開發版第一個bugreport
system log起始時間 event log起始時間 radio起始時間
15:00-17:52 15:20-17:52 10:27-17:5​2
11-16開發版第一個bugreport
system log起始時間 event log起始時間 radio起始時間
15:00-18:07 15:21-18:07 10:27-18:07
修改logd屬性的11-16號開發版第一個bugreport
system log起始時間 event log起始時間 radio起始時間
17:26-17:53 17:33-17:53 15:48-17:53
修改logd屬性的11-16號開發版第二個bugreport
system log起始時間 event log起始時間 radio起始時間
17:26-18:07 17:41-18:07 16:03-18:07
從數據可以看出,未做修改的版本system log會記錄4個小時左右,event log會記錄3個半小時左右,radio會記錄7個半小時,而修改logd屬性的版本system log會記錄30分鐘左右,
event log會記錄20分鐘左右,radio會記錄2個小時,整體比未做修改的版本時間要短,符合預期

開發版上log比較多,如果是穩定版,由於log少情況會好一些~從數據看,也是可以接受

因此,建議將RAM較小的外發版機型,將LogBuffer size調小,或者直接調至256KB,將內存影響降至最低~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章