• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

手把手教你高效监控ANR

android 搞代码 3年前 (2022-03-01) 21次浏览 已收录 0个评论
文章目录[隐藏]

ANR监控是一个十分有年代感的话题了,然而市面上的ANR监控工具,或者并非真正意义上的ANR的监控(而是5秒卡顿监控);或者并不欠缺,监控不到到所有的ANR。而想要失去一个欠缺的ANR监控工具,必须要先理解零碎整个ANR的流程。本文剖析了ANR的次要流程,给出了一个欠缺的ANR监控计划。该计划曾经在Android微信客户端上通过全量验证,稳固地运行了一年多的工夫。

咱们晓得ANR流程根本都是在system_server零碎过程实现的,零碎过程的行为咱们很难监控和扭转,想要监控ANR就必须找到零碎过程跟咱们本人的利用过程是否有交互,如果有,两者交互的边界在哪里,边界上利用一端的行为,才是咱们比拟容易能监控到的,想要要找到这个边界,咱们就必须要理解ANR的流程。

一、ANR流程

无论ANR的起源是哪里,最终都会走到ProcessRecord中的appNotResponding,这个办法包含了ANR的次要流程,所以也比拟长,咱们找出一些要害的逻辑来剖析:frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java:

void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,

先是一长串if else,给出了几种比拟极其的状况,会间接return,而不会产生一个ANR,这些状况包含:过程正在处于正在敞开的状态,正在crash的状态,被kill的状态,或者雷同过程曾经处在ANR的流程中。

另外很重要的一个逻辑就是判断以后ANR是否是一个SilentAnr,所谓“缄默的ANR”,其实就是后盾ANR,后盾ANR跟前台ANR会有不同的体现:前台ANR会弹无响应的Dialog,后盾ANR会间接杀死过程。前后台ANR的判断的准则是:如果产生ANR的过程对用户来说是有感知的,就会被认为是前台ANR,否则是后盾ANR。另外,如果在开发者选项中勾选了“显示后盾ANR”,那么全副ANR都会被认为是前台ANR。

咱们持续剖析这个办法:

if (!isSilentAnr && !onlyDumpSelf) {

产生ANR后,为了能让开发者晓得ANR的起因,不便定位问题,会dump很多信息到ANR Trace文件里,下面的逻辑就是抉择须要dump的过程。ANR Trace文件是蕴含许多过程的Trace信息的,因为产生ANR的起因有可能是其余的过程抢占了太多资源,或者IPC到其余过程(尤其是零碎过程)的时候卡住导致的。

抉择须要dump的过程是一段挺有意思逻辑,咱们略微剖析下:须要被dump的过程被分为了firstPids、nativePids以及extraPids三类:

  • firstPIds:firstPids是须要首先dump的重要过程,产生ANR的过程无论如何是肯定要被dump的,也是首先被dump的,所以第一个被加到firstPids中。如果是SilentAnr(即后盾ANR),不必再退出任何其余的过程。如果不是,须要进一步增加其余的过程:如果产生ANR的过程不是system_server过程的话,须要增加system_server过程;接下来轮询AMS保护的一个LRU的过程List,如果最近拜访的过程蕴含了persistent的过程,或者带有BIND_TREAT_LIKE_ACTVITY标签的过程,都增加到firstPids中。
  • extraPids:LRU过程List中的其余过程,都会首先增加到lastPids中,而后lastPids会进一步被选出最近CPU使用率高的过程,进一步组成extraPids;
  • nativePids:nativePids最为简略,是一些固定的native的零碎过程,定义在WatchDog.java中。

拿到须要dump的所有过程的pid后,AMS开始依照firstPids、nativePids、extraPids的程序dump这些过程的堆栈:

File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,

这里也是咱们须要重点剖析的中央,咱们持续看这里做了什么,跟到AMS外面,

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,

咱们首先关注到remainingTime,这是一个重要的变量,规定了咱们dump所有过程的最长工夫,因为dump过程所有线程的堆栈,自身就是一个重操作,何况是要dump许多过程,所以规定了产生ANR之后,dump全副过程的总工夫不能超过20秒,如果超过了,马上返回,确保ANR弹窗能够及时的弹出(或者被kill掉)。咱们持续跟到dumpJavaTracesTombstoned

private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {

再一路追到native层负责dump堆栈的system/core/debuggerd/client/debuggerd_client.cpp

bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) {

来了来了!之前说的交互边界终于找到了!这里会通过sigqueue向须要dump堆栈的过程发送SIGQUIT信号,也就是signal 3信号,而产生ANR的过程是肯定会被dump的,也是第一个被dump的。这就意味着,只有咱们能监控到零碎发送的SIGQUIT信号,兴许就可能监控到产生了ANR。

每一个利用过程都会有一个SignalCatcher线程,专门解决SIGQUIT,来到art/runtime/signal_catcher.cc

void* SignalCatcher::Run(void* arg) {

WaitForSignal办法调用了sigwait办法,这是一个阻塞办法。这里的死循环,就会始终一直的期待监听SIGQUIT和SIGUSR1这两个信号的到来。

整顿一下ANR的过程:当利用产生ANR之后,零碎会收集许多过程,来dump堆栈,从而生成ANR Trace文件,收集的第一个,也是肯定会被收集到的过程,就是产生ANR的过程,接着零碎开始向这些利用过程发送SIGQUIT信号,利用过程收到SIGQUIT后开始dump堆栈。来简略画个示意图:

所以,事实上过程产生ANR的整个流程,也只有dump堆栈的行为会在产生ANR的过程中执行。这个过程从收到SIGQUIT开始(圈1),到应用socket写Trace(圈2)完结,而后再持续回到server过程实现残余的ANR流程。咱们就在这两个边界上做做文章。

首先咱们必定会想到,咱们是否监听到syste_server发送给咱们的SIGQUIT信号呢?如果能够,咱们就胜利了一半。

二、监控SIGQUIT信号

Linux零碎提供了两种监听信号的办法,一种是SignalCatcher线程应用的sigwait办法进行同步、阻塞地监听,另一种是应用sigaction办法注册signal handler进行异步监听,咱们都来试试。

2.1. sigwait

咱们首先尝试前一种办法,模拟SignalCatcher线程,做截然不同的事件,通过一个死循环sigwait,始终监听SIGQUIT:

static void *mySigQuitCatcher(void* args) {

这个时候就有了两个不同的线程sigwait同一个SIGQUIT,具体会走到哪个呢,咱们在sigwait的文档中找到了这样的形容(sigwait办法是由sigwaitinfo办法实现的):

原来当有两个线程通过sigwait办法监听同一个信号时,具体是哪一个线程收到信号时不能确定的。不确定可不行,当然不满足咱们的需要。

3.2. Signal Handler

那咱们再试下另一种办法是否可行,咱们通过能够sigaction办法,建设一个Signal Handler:

void signalHandler(int sig, siginfo_t* info, void* uc) {

建设了Signal Handler之后,咱们发现在同时有sigwait和signal handler的状况下,信号没有走到咱们的signal handler而是仍然被零碎的Signal Catcher线程捕捉到了,这是什么起因呢?

原来是Android默认把SIGQUIT设置成了BLOCKED,所以只会响应sigwait而不会进入到咱们设置的handler办法中。咱们通过pthread_sigmask或者sigprocmask把SIGQUIT设置为UNBLOCK,那么再次收到SIGQUIT时,就肯定会进入到咱们的handler办法中。须要这样设置:

sigset_t sigSet;

最初须要留神,咱们通过Signal Handler抢到了SIGQUIT后,本来的Signal Catcher线程中的sigwait就不再能收到SIGQUIT了,本来的dump堆栈的逻辑就无奈实现了,咱们为了ANR的整个逻辑和流程跟原来完全一致,须要在Signal Handler外面从新向Signal Catcher线程发送一个SIGQUIT

int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid

(如果短少了从新向SignalCatcher发送SIGQUIT的步骤,AMS就始终等不到ANR过程写堆栈,直到20秒超时后,才会被迫中断,而持续之后的流程。间接的体现就是ANR弹窗十分慢(20秒超时工夫),并且/data/anr目录下无奈失常生成残缺的 ANR Trace文件。)

以上就失去了一个不扭转零碎行为的前提下,比较完善的监控SIGQUIT信号的机制,这也是咱们监控ANR的根底。

三、欠缺的ANR监控计划

监控到SIGQUIT信号并不等于就监控到了ANR。

3.1. 误报

充沛非必要条件1:产生ANR的过程肯定会收到SIGQUIT信号;然而收到SIGQUIT信号的过程并不一定产生了ANR。

思考上面两种状况:

  • 其余过程的ANR:下面提到过,产生ANR之后,产生ANR的过程并不是惟一须要dump堆栈的过程,零碎会收集许多其余的过程进行dump,也就是说当一个利用产生ANR的时候,其余的利用也有可能收到SIGQUIT信号。进一步,咱们监控到SIGQUIT时,可能是监听到了其余过程产生的ANR,从而产生误报。
  • 非ANR发送SIGQUIT:发送SIGQUIT信号其实是很容易的一件事件,开发者和厂商都能够很容易的发送一个SIGQUIT(java层调用android.os.Process.sendSignal办法;Native层调用kill或者tgkill办法),所以咱们可能会收到非ANR流程发送的SIGQUIT信号,从而产生误报。

怎么解决这些误报的问题呢,我从新回到ANR流程开始的中央:

void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,

在ANR弹窗前,会执行到makeAppNotRespondingLocked办法中,在这里会给产生ANR过程标记一个NOT_RESPONDING的flag。而这个flag咱们能够通过ActivityManager来获取:

private static boolean checkErrorState() {

监控到SIGQUIT后,咱们在20秒内(20秒是ANR dump的timeout工夫)一直轮询本人是否有NOT_RESPONDING对flag,一旦发现有这个flag,那么马上就能够认定产生了一次ANR。

(你可能会想,有这么不便的办法,监控SIGQUIT信号不是多余的吗?间接一个死循环,一直轮训这个flag不就完事了?是的,实践上的确能这么做,然而这么做过于的低效、耗电和不环保外,更要害的是,上面漏报的问题仍然无奈解决)

另外,Signal Handler回调的第二个参数siginfo_t,也蕴含了一些有用的信息,该构造体的第三个字段si_code示意该信号被发送的办法,SI_USER示意信号是通过kill发送的,SI_QUEUE示意信号是通过sigqueue发送的。但在Android的ANR流程中,高版本应用的是sigqueue发送的信号,某些低版本应用的是kill发送的信号,并不对立。

而第五个字段(极少数机型上是第四个字段)si_pid示意的是发送该信号的过程的pid,这里实用简直所有Android版本和机型的一个条件是:如果发送信号的过程是本人的过程,那么肯定不是一个ANR。能够通过这个条件排除本人发送SIGQUIT,而导致误报的状况。

3.2. 漏报

充沛非必要条件2:过程处于NOT_RESPONDING的状态能够确认该过程产生了ANR。然而产生ANR的过程并不一定会被设置为NOT_RESPONDING状态。

思考上面两种状况:

  • 后盾ANR(SilentAnr):之前剖析ANR流程咱们能够晓得,如果ANR被标记为了后盾ANR(即SilentAnr),那么杀死过程后就会间接return,并不会走到产生过程谬误状态的逻辑。这就意味着,后盾ANR没方法捕捉到,而后盾ANR的量同样十分大,并且后盾ANR会间接杀死过程,对用户的体验也是十分负面的,这么大一部分ANR监控不到,当然是无奈承受的。
  • 闪退ANR:除此之外,咱们还发现相当一部分机型(例如OPPO、VIVO两家的高Android版本的机型)批改了ANR的流程,即便是产生在前台的ANR,也并不会弹窗,而是间接杀死过程,即闪退。这部分的机型笼罩的用户量也十分大。并且,确定两家今后的新设施会始终维持这个机制。

所以咱们须要一种办法,在收到SIGQUIT信号后,可能十分疾速的侦察出本人是不是已处于ANR的状态,进行疾速的dump和上报。很容易想到,咱们能够通过主线程是否处于卡顿状态来判断。那么怎么最疾速的晓得主线程是不是卡住了呢?上一篇文章中,剖析Sync Barrier透露问题时,咱们反射过主线程Looper的mMessage对象,该对象的when变量,示意的就是以后正在解决的音讯入队的工夫,咱们能够通过when变量减去以后工夫,失去的就是等待时间,如果等待时间过长,就阐明主线程是处于卡住的状态,这时候收到SIGQUIT信号基本上就能够认为确实产生了一次ANR:

private static boolean isMainThreadStuck(){

咱们通过下面几种机制来综合判断收到SIGQUIT信号后,是否真的产生了一次ANR,最大水平地缩小误报和漏报,才是一个比较完善的监控计划。

3.3. 额定播种:获取ANR Trace

回到之前画的ANR流程示意图,Signal Catcher线程写Trace(圈2)也是一个边界,并且是通过socket的write办法来写Trace的,如果咱们可能hook到这里的write,咱们甚至就能够拿到零碎dump的ANR Trace内容。这个内容十分全面,包含了所有线程的各种状态、锁和堆栈(包含native堆栈),对于咱们排查问题非常有用,尤其是一些native问题和死锁等问题。Native Hook咱们采纳PLT Hook 计划,这种计划在微信上曾经被验证了其稳定性是可控的。

int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);

其中有几点须要留神:

  • 只Hook ANR流程:有些状况下,根底库中的connect/open/write办法可能调用的比拟频繁,咱们须要把hook的影响降到最低。所以咱们只会在接管到SIGQUIT信号后(从新发送SIGQUIT信号给Signal Catcher前)进行hook,ANR流程完结后再unhook。
  • 只解决Signal Catcher线程open/connect后的第一次write:除了Signal Catcher线程中的dump trace的流程,其余中央调用的write办法咱们并不关怀,并不需要解决。例如,dump trace的流程会在在write办法前,零碎会先应用connet办法链接一个path为“/dev/socket/tombstoned_java_trace”的socket,咱们能够hook connect办法,拿到这个socket的name,咱们只解决connect这个socket后,雷同线程(即Signal Catcher线程)的第一次write,这次write的内容才是咱们惟一关怀的。
  • Hook点因API Level而不同:须要hook的write办法在不同的Android版本中,所在的so也不尽相同,不同API Level须要别离解决,hook不同的so和办法。目前这个计划在API 18以上都测试过可行。

这个Hook Trace的计划,不仅仅能够用来查ANR问题,任何时候咱们都能够手动向本人发送一个SIGQUIT信号,从而hook到过后的Trace。Trace的内容对于咱们排查线程死锁,线程异样,耗电等问题都十分有帮忙

这样咱们就失去了一个欠缺的ANR监控计划,这套计划在微信上安稳运行了很长一段时间,给咱们评估和优化微信Android客户端的品质提供了十分重要依据和方向。

关注我,每天分享常识干货


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:手把手教你高效监控ANR

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址