1.日誌獲取html
Watchdog相關的問題甚至須要如下全部的日誌:java
logcat 經過adb logcat
命令輸出Android的一些當前運行日誌,能夠經過logcat的 -b 參數指定要輸出的日誌緩衝區,緩衝區對應着logcat的一種日誌類型。 高版本的logcat可使用 -b all 獲取到全部緩衝區的日誌android
dumpsys 經過adb dumpsys
命令輸出一些重要的系統服務信息,譬如內存、電源、磁盤等.git
traces 該文件記錄了一個時間段的函數調用棧信息,一般在應用發生ANR(Application Not Responding)時,會觸發打印各進程的函數調用棧。 站在Linux的角度,其實就是向進程發送SIGNAL_QUIT(3)請求,譬如,咱們能夠經過adb shell kill -3 <pid>
命令,打印指定進程的的trace。 SIGNAL_QUIT(3)表面意思有一點誤導,它其實並不會致使進程退出。輸出通常在 */data/anr/traces.txt* 文件中,固然,這是能夠靈活配置的, Android提供的系統屬性dalvik.vm.stack-trace-file能夠用來配置生成traces文件的位置。github
binder 經過Binder跨進程調用的日誌,能夠經過adb shell cat
命令從 /proc/binder 下取出對應的日誌shell
dropbox 爲了記錄歷史的logcat日誌,Android引入了Dropbox,將歷史日誌持久化到磁盤中(/data/system/dropbox)。 logcat的緩衝區大小畢竟是有限的,因此須要循環利用,這樣歷史的日誌信息就會被沖掉。在一些自動化測試的場景下,譬如Monkey須要長時間的運行, 就須要把歷史的日誌全都保存下來。app
tombstone tombstone錯誤通常由Dalvik錯誤、native層的代碼問題致使的。當系統發生tombstone時,內核會上報一個嚴重的警告信號, 上層收到後,把當前的調用棧信息持久化到磁盤中(/data/tombstone)函數
bugreport 經過adb bugreport
命令輸出,日誌內容多到爆,logcat, traces, dmesg, dumpsys, binder的日誌都包含在其中。 因爲輸出bugreport的時間很長,當系統發生錯誤時,咱們再執行bugreport每每就來不及了(此時,系統可能都已經重啓了),因此,要動用bugreport就須要結合一些其餘機制, 譬如在殺掉system_server進程以前,先讓bugreport運行完。工具
2.問題定位oop
Watchdog出現的日誌很明顯,logcat中的event, system中都會有體現,要定位問題,能夠從檢索日誌中的watchdog關鍵字開始。
發生Watchdog檢測超時這麼重要的系統事件,Android會打印一個EventLog:
watchdog: Blocked in handler XXX # 表示HandlerChecker超時了
watchdog: Blocked in monitor XXX # 表示MonitorChecker超時了
Watchdog是運行在system_server進程中,會打印一些System類型的日誌。在手機處於非調試狀態時,伴隨Watchdog出現的每每是system_server進程被殺,從而系統重啓。 當Watchdog要主動殺掉system_server進程時,如下關鍵字就會出如今SystemLog中:
Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: XXX
Watchdog: XXX
Watchdog: "*** GOODBYE!
當咱們在日誌中檢索到上述兩類關鍵信息時,說明「Watchdog顯靈」了,從另外一個角度來理解,就是「System Not Responding」了。 接下來,咱們須要進一步定位在watchdog出現以前,system_server進程在幹什麼,處於一個什麼狀態。 這與排除」Application Not Responding「問題差很少,咱們須要進程的traces信息、當前系統的CPU運行信息、IO信息。
找到Watchddog出現以前的traces.txt文件,這個時間差最好不要太大,由於Watchdog默認的超時時間是1分鐘,過久之前的traces並不能說明問題。 誘導Watchdong出現的直接緣由其實就是system_server中某個線程被阻塞了,這個信息在event和system的log中清晰可見。 咱們以一個systemLog爲例:
W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.wm.WindowManagerService on foreground thread (android.fg)
Watchdog告訴咱們Monitor Checker超時了,具體在哪呢? 名爲android.fg的線程在WindowManagerService的monitor()方法被阻塞了。這裏隱含了兩層意思:
WindowManagerService實現了Watchdog.Monitor這個接口,並將本身做爲Monitor Checker的對象加入到了Watchdog的監測集中
monitor()方法是運行在android.fg線程中的。Android將android.fg設計爲一個全局共享的線程,意味着它的消息隊列能夠被其餘線程共享, Watchdog的Monitor Checker就是使用的android.fg線程的消息隊列。所以,出現Monitor Checker的超時,確定是android.fg線程阻塞在monitor()方法上。
咱們打開system_server進程的traces,檢索 android.fg 能夠快速定位到該線程的函數調用棧:
"android.fg" prio=5 tid=25 Blocked
| group="main" sCount=1 dsCount=0 obj=0x12eef900 self=0x7f7a8b1000
| sysTid=973 nice=0 cgrp=default sched=0/0 handle=0x7f644e9000
| state=S schedstat=( 3181688530 2206454929 8991 ) utm=251 stm=67 core=1 HZ=100
| stack=0x7f643e7000-0x7f643e9000 stackSize=1036KB
| held mutexes=
at com.android.server.wm.WindowManagerService.monitor(WindowManagerService.java:13125)
- waiting to lock <0x126dccb8> (a java.util.HashMap) held by thread 91
at com.android.server.Watchdog$HandlerChecker.run(Watchdog.java:204)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
android.fg線程調用棧告訴咱們幾個關鍵的信息:
題外話:每個進程都會對本身所轄的線程編號,從1開始。1號線程一般就是咱們所說的主線程。 線程在Linux系統中還有一個全局的編號,由sysTid表示。咱們在logcat等日誌中看到的通常是線程的全局編號。 譬如,本例中android.fg線程在system_server進程中的編號是25,系統全局編號是973。
能夠在traces.txt文件中檢索 tid=91 來快速找到91號線程的函數調用棧信息:
"Binder_C" prio=5 tid=91 Native
| group="main" sCount=1 dsCount=0 obj=0x12e540a0 self=0x7f63289000
| sysTid=1736 nice=0 cgrp=default sched=0/0 handle=0x7f6127c000
| state=S schedstat=( 96931835222 49673449591 260122 ) utm=7046 stm=2647 core=2 HZ=100
| stack=0x7f5ffbc000-0x7f5ffbe000 stackSize=1008KB
| held mutexes=
at libcore.io.Posix.writeBytes(Native method)
at libcore.io.Posix.write(Posix.java:258)
at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313)
at libcore.io.IoBridge.write(IoBridge.java:537)
at java.io.FileOutputStream.write(FileOutputStream.java:186)
at com.android.internal.util.FastPrintWriter.flushBytesLocked(FastPrintWriter.java:334)
at com.android.internal.util.FastPrintWriter.flushLocked(FastPrintWriter.java:355)
at com.android.internal.util.FastPrintWriter.appendLocked(FastPrintWriter.java:303)
at com.android.internal.util.FastPrintWriter.print(FastPrintWriter.java:466)
- locked <@addr=0x134c4910> (a com.android.internal.util.FastPrintWriter$DummyWriter)
at com.android.server.wm.WindowState.dump(WindowState.java:1510)
at com.android.server.wm.WindowManagerService.dumpWindowsNoHeaderLocked(WindowManagerService.java:12279)
at com.android.server.wm.WindowManagerService.dumpWindowsLocked(WindowManagerService.java:12266)
at com.android.server.wm.WindowManagerService.dump(WindowManagerService.java:12654)
- locked <0x126dccb8> (a java.util.HashMap)
at android.os.Binder.dump(Binder.java:324)
at android.os.Binder.onTransact(Binder.java:290)
91號線程的名字是Binder_C,它的函數調用棧告訴咱們幾個關鍵信息:
題外話:關於Binder線程。當Android進程啓動時,就會建立一個線程池,專門處理Binder事務。線程池中會根據當前的binder線程計數器的值來構造新建立的binder線程, 線程名」Binder_%X」,X是十六進制。固然,線程池的線程數也有上限,默認狀況下爲16,因此,能夠看到 Binder_1 ~ Binder_F 這樣的線程命名。
聰明的你看到這或許已經可以想到解決辦法了,在這個IO寫操做上加一個超時機制,而且這個超時小於Watchdog的超時,不就可讓線程釋放它所佔有的鎖了嗎? 是的,這確實能夠做爲一個臨時解決方案(Workaround),或者說一個保護機制。但咱們能夠再往深處想想,這個IO寫操做爲何會阻塞:
這都須要咱們再進一步從日誌中去找緣由。若是已有的日誌不全,找不到論據,咱們還須要設計場景來驗證假設,解決問題的難度陡然上升。
3.場景還原
咱們經歷了兩個關鍵步驟:
這兩個過程基本就涵蓋了Watchdog的運行機制了,但這並無解決問題啊。咱們須要找到線程阻塞的緣由是什麼,然而,線程阻塞的緣由就千奇百怪了。 若是有問題出現的現場,而且問題能夠重現,那麼咱們能夠經過調試的手段來分析問題產生的緣由。 若是問題只是偶然出現,甚至只有一堆日誌,咱們就須要從日誌中來還原問題出現的場景,這一步纔是真正考驗你們Android/Linux功底的地方。
繼續以上述問題爲例,咱們來進一步還原問題出現的場景,從Java層的函數調用棧來看:
Binder_C線程要出現這種函數調用棧,咱們能夠初步肯定是Android接受了以下命令 (dumpsys原理請查閱dumpsys介紹一文):
$ adb shell dumpsys window
當經過命令行運行以上命令時,客戶端(PC)的adb server會向服務端(手機)的adbd發送指令, adbd進程會fork出一個叫作dumpsys的子進程,dumpsys進程再利用Binder機制和system_server通訊 (adb的實現原理能夠查閱adb介紹一文)。
僅憑這個仍是分析不出問題所在,咱們須要啓用內核的日誌了。當調用JNI方法libcore.io.Posix.writeBytes()時,會觸發系統調用, Linux會從用戶態切換到內核態,內核的函數調用棧也能夠從traces中找到:
kernel: __switch_to+0x74/0x8c
kernel: pipe_wait+0x60/0x9c
kernel: pipe_write+0x278/0x5cc
kernel: do_sync_write+0x90/0xcc
kernel: vfs_write+0xa4/0x194
kernel: SyS_write+0x40/0x8c
kernel: cpu_switch_to+0x48/0x4c
在Java層,明確指明要寫文件(FileOutputStream),正常狀況下,系統調用write()就完事了,但Kernel卻打開了一個管道,最終阻塞在了pipe_wait()方法。 什麼場景下會打開一個管道,並且管道會阻塞呢?一系列的猜測和驗證過程接踵而至。
這裏有必要先補充一些基礎知識了:
Linux的管道實現藉助了文件系統的file結構和VFS(Virtual File System),經過將兩個file結構指向同一個臨時的VFS索引節點,而這個VFS索引節點又指向一個物理頁面時, 實際上就創建了一個管道。
這就解釋了爲何發起系統調用write的時候,打開了一個管道。由於dumpsys和system_server進程,將本身的file結構指向了同一個VFS索引節點。
管道是一個生產者-消費者模型,當緩衝區滿時,則生產者不能往管道中再寫數據了,需等到消費者讀數據。若是消費者來不及處理緩衝區的數據,或者鎖定緩衝區,則生產者就掛起了。
結合到例子中的場景,system_server進程沒法往管道中寫數據,極可能是dumpsys進程一直忙碌來不及處理新的數據。
接下來,須要再從日誌中尋找dumpsys進程的運行狀態了:
接下來的分析過程已經偏離Watchdog機制愈來愈遠了,咱們點到爲止。
小夥伴們能夠看到,場景還原涉及到的知識點很是之寬泛,並且有必定的深度。在沒有現場的狀況下,伴隨一系列的假設和驗證過程,充滿了不肯定性和發現問題的喜悅。 正所謂,同問題作鬥爭,其樂無窮!
至此,咱們分析Watchdog問題的慣用方法,回答前面提出來的第二個問題:
經過event或system類型的logcat日誌,檢索Watchdog出現的關鍵信息;經過traces,分析出致使Watchdog檢查超時的直接緣由;經過其餘日誌,還原出問題出現的場景。
4.實例分析
從sys_log中,檢索到了Watchdog的出現關鍵信息
TIPS: 在sys_log中搜索關鍵字」WATCHDOG KILLING SYSTEM PROCESS」
10-14 17:10:51.548 892 1403 W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in handler on ActivityManager (ActivityManager)
這是一個Watchdog的Looper Checker超時,因爲ActivityManager這個線程一直處於忙碌狀態,致使Watchdog檢查超時。 Watchdog出現的時間是10-14 17:10:51.548左右,須要從traces.txt中找到這個時間段的system_server進程的函數調用棧信息, system_server的進程號是892。
從traces.txt中找到對應的函數調用棧
traces.txt包含不少進程在不一樣時間段的函數調用棧信息,爲了檢索的方便,首先能夠將traces.txt分塊。 筆者寫了一個工具,能夠從traces.txt文件中分割出指定進程號的函數調用棧信息。
TIPS: 在system_server的traces中(經過工具分割出的system_server_892_2015-10-14-17:09:06文件)搜索關鍵字」ActivityManager」
"ActivityManager" prio=5 tid=17 TimedWaiting
| group="main" sCount=1 dsCount=0 obj=0x12c0e6d0 self=0x7f84caf000
| sysTid=938 nice=-2 cgrp=default sched=0/0 handle=0x7f7d887000
| state=S schedstat=( 107864628645 628257779012 60356 ) utm=7799 stm=2987 core=2 HZ=100
| stack=0x7f6e68f000-0x7f6e691000 stackSize=1036KB
| held mutexes=
at java.lang.Object.wait!(Native method)
- waiting on <0x264ff09d> (a com.android.server.am.ActivityManagerService$5)
at java.lang.Object.wait(Object.java:422)
at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:5395)
at com.android.server.am.ActivityManagerService.dumpStackTraces(ActivityManagerService.java:5282)
at com.android.server.am.ActivityManagerService$AnrActivityManagerService.dumpStackTraces(ActivityManagerService.java:22676)
at com.mediatek.anrmanager.ANRManager$AnrDumpMgr.dumpAnrDebugInfoLocked(SourceFile:1023)
at com.mediatek.anrmanager.ANRManager$AnrDumpMgr.dumpAnrDebugInfo(SourceFile:881)
at com.android.server.am.ActivityManagerService.appNotResponding(ActivityManagerService.java:6122)
- locked <0x21c77912> (a com.mediatek.anrmanager.ANRManager$AnrDumpRecord)
at com.android.server.am.BroadcastQueue$AppNotResponding.run(BroadcastQueue.java:228)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:192)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
ActivityManager線程實際上運行着AMS的消息隊列,這個函數調用棧的關鍵信息:
observer.wait(200); // Wait for write-close, give up after 200msec
還原問題的場景
從ActivityManager這個線程的調用棧,咱們就會有一些疑惑:
帶着這些疑惑,咱們再回到日誌中:
從sys_log中,能夠檢索到Watchdog出現的時間點(17:10:51.548)以前,com.android.systemui發生了ANR,從而引起AMS打印函數調用棧:
TIPS: 在sys_log中檢索」ANR in」關鍵字或在event_log中檢索」anr」關鍵字
10-14 17:10:04.215 892 938 E ANRManager: ANR in com.android.systemui, time=27097912
10-14 17:10:04.215 892 938 E ANRManager: Reason: Broadcast of Intent { act=android.intent.action.TIME_TICK flg=0x50000114 (has extras) }
10-14 17:10:04.215 892 938 E ANRManager: Load: 89.22 / 288.15 / 201.91
10-14 17:10:04.215 892 938 E ANRManager: Android time :[2015-10-14 17:10:04.14] [27280.396]
10-14 17:10:04.215 892 938 E ANRManager: CPU usage from 17016ms to 0ms ago:
10-14 17:10:04.215 892 938 E ANRManager: 358% 23682/float_bessel: 358% user + 0% kernel
10-14 17:10:04.215 892 938 E ANRManager: 57% 23604/debuggerd64: 3.8% user + 53% kernel / faults: 11369 minor
10-14 17:10:04.215 892 938 E ANRManager: 2% 892/system_server: 0.9% user + 1% kernel / faults: 136 minor
從這個日誌信息中,咱們兩個疑惑就釋然了:
發生ANR以前的CPU負載遠高於正常狀況好幾倍(Load: 89.22 / 288.15 / 201.91),在這種CPU負載下,com.android.systemui進程發生處理廣播消息超時(Reason: Broadcast of Intent)再正常不過了。 在這以前CPU都被float_bessel這個進程給佔了,這貨僅憑一己之力就耗了358%的CPU資源。
observer.wait(200)在調用後,便進入排隊等待喚醒狀態(Waiting),在等待200毫秒後,便從新開始申請CPU資源,而此時,CPU資源一直被float_bessel佔着沒有釋放,因此該線程一直在等CPU資源。 等了1分鐘後,Watchdog跳出來講「不行,你已經等了1分鐘了,handler處理其餘消息了」。
在多核狀況下,CPU的使用率統計會累加多個核的使用率,因此會出現超過100%的狀況。那麼float_bessel到底是什麼呢?它是一個Linux的測試樣本,貝塞爾函數的計算,耗的就是CPU。
這樣,該問題的場景咱們就還原出來了:在壓力測試的環境下,CPU被float_bessel運算佔用,致使com.android.systemui進程發生ANR,從而引起AMS打印trace; 但因爲AMS一直等不到CPU資源,Watchdog檢測超時,殺掉system_server進程,系統重啓。
對於壓力測試而言,咱們通常會設定一個經過標準,在某些壓力狀況下,出現一些錯誤是容許的。對於Android實際用戶的使用場景而言,本例中的壓力一般是不存在的,因此在實際項目中,這種類型的Watchdog問題,咱們通常不解決。
Android中Watchdog用來看護system_server進程,system_server進程運行着系統最終要的服務,譬如AMS、PKMS、WMS等, 當這些服務不能正常運轉時,Watchdog可能會殺掉system_server,讓系統重啓。
Watchdog的實現利用了鎖和消息隊列機制。當system_server發生死鎖或消息隊列一直處於忙碌狀態時,則認爲系統已經沒有響應了(System Not Responding)。
在分析Watchdog問題的時候,首先要有詳盡的日誌,其次要能定位出致使Watchdog超時的直接緣由,最重要的是能還原出問題發生的場景。