APM之:卡顿监控

方案一:参考了ANR-WatchDog机制

ANR-WatchDog机制原理不复杂,它内部启动了一个子线程,定时通过主线程Handler发送Message,然后定时去检查Message的处理结果。

通俗来说就是利用了Android系统MessageQueue队列的排队处理特性。

通过主线程Handler发送消息到MessageQueue队列,5秒去看下这个Message有没有被消费,如果消费了则代表没有卡顿,如果没有,则代表有卡顿,当然这个5秒是可调节的。

这种监控非常简单,接入成本较低,但是有弊端,比如

  • 1.它会漏报情况,举个例子,比如我们以5秒未响应作为卡顿阈值,如果我们发送监听Message的时间在上一个消息处理的第2-5秒之间,那这种就会产生漏报。
  • 2.监听间隔越小,系统开销越大。
  • 3.即便监听到了,不好区分卡顿链路,无法提供准确的卡顿堆栈。

方案二:参考BlockCanary开源库,利用Looper.loop() Printer打印机制

每次Message处理(也就是dispatchMessage(msg))都会在处理前,和处理后通过Looper.mLogging打印日志。

我们只需知道打印日志的时间差即可知道Message的处理耗时。当耗时超过我们的阈值时我们即可收集调用堆栈,然后根据堆栈进行针对性优化即可。

这种方案也有弊端,比如

  • 1.View的TouchEvent事件是在queue.next()中处理的,只统计dispatchMessage(msg)前后耗时,不会覆盖到View的TouchEvent耗时。
  • 2.IdleHandler.queueIdle()也在queue.next()中,当主线程空闲会调用IdleHandler,此时IdleHandler也是在主线程执行,当过于耗时时也可能出现卡顿,甚至到只ANR,这种场景也无法监控。
  • 3.SyncBarrier(同步屏障)的泄漏同样无法被监控到,这种情况比较少见,参考了微信的监控方案。

比如

/*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}...try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;}...if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}}}

每次Message处理(也就是dispatchMessage(msg))都会在处理前,和处理后通过Looper.mLogging打印日志.

我们只需知道打印日志的时间差即可知道Message的处理耗时。当耗时超过我们的阈值的时候就是发生卡顿的时机,我们触发收集调用堆栈,然后根据堆栈进行针对性优化即可。

替换Printer可以通过Looper提供的方法,然后自定义Printer进行计算统计即可。

/*** Control logging of messages as they are processed by this Looper.  If* enabled, a log message will be written to <var>printer</var>* at the beginning and ending of each message dispatch, identifying the* target Handler and message contents.** @param printer A Printer object that will receive log messages, or* null to disable message logging.*/public void setMessageLogging(@Nullable Printer printer) {mLogging = printer;}@Override
public void println(String x) {if (mStopWhenDebugging && Debug.isDebuggerConnected()) {return;}if (!mPrintingStarted) {//1、记录第一次执行时间,mStartTimestampmStartTimestamp = System.currentTimeMillis();mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();mPrintingStarted = true;startDump(); //2、开始dump堆栈信息} else {//3、第二次就进来这里了,调用isBlock 判断是否卡顿final long endTime = System.currentTimeMillis();mPrintingStarted = false;if (isBlock(endTime)) {notifyBlockEvent(endTime);}stopDump(); //4、结束dump堆栈信息}}//判断是否卡顿的代码很简单,跟上次处理消息时间比较,比如大于3秒,就认为卡顿了private boolean isBlock(long endTime) {return endTime - mStartTimestamp > mBlockThresholdMillis;}//获取堆栈数据protected void doSample() {StringBuilder stringBuilder = new StringBuilder();// 获取堆栈信息for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {stringBuilder.append(stackTraceElement.toString()).append(BlockInfo.SEPARATOR);}synchronized (sStackMap) {// LinkedHashMap中数据超过100个就remove掉链表最前面的if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {sStackMap.remove(sStackMap.keySet().iterator().next());}//放入LinkedHashMap,时间作为key,value是堆栈信息sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());}}

??怎么监控TouchEvent和IdelHander的卡顿 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/27475.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习grdecl文件格式之后的事情

学习了grdecl文件格式&#xff0c;搞地质的专业人士都知道&#xff0c;这是专门用在地质上的油藏软件&#xff08;个人感觉就是斯伦贝谢的Petrel的&#xff09;的一种文件格式&#xff0c;正好自己也在学习三维的开发&#xff0c;顺手写了一个简单的读取grdecl算法&#xff0c;…

Vue3【十九】自定义Hooks钩子 将数据和方法分组

Vue3【十九】自定义Hooks钩子 将数据和方法分组 Vue3【十九】自定义Hooks钩子 将数据和方法分组 每个分组都可以放置 各种生命周期钩子 分组和可以使用计算属性等 案例截图 目录结构 代码 person.vue <template><div class"person"><h2>Vue3自定…

Linux指令学习(4)

目录 0.普通用户和root用户之间的切换 1.head/tail指令 2.管道 3.date命令 4.三个查找相关的指令 5.文件过滤grep 6.打包和压缩 5.zip/unzip指令 0.普通用户和root用户之间的切换 &#xff08;1&#xff09;这个我们之前不是经常使用这个root用户吗&#xff0c;现在随着…

NLP--逻辑回归

1.定义 如何解决二元分类问题&#xff0c;除了上节我们谈到的贝叶斯分类器&#xff0c;我们可以通过计算数据属于不同类别的概率进行分类的逻辑回归。虽然有回归二字&#xff0c;但逻辑回归解决的是分类问题&#xff0c;也可以用于两类以上的多分类问题。 2.方法 概率是介于0到…

python操作jenkins

参考链接&#xff1a; python操作jenkins

数据结构笔记39-48

碎碎念&#xff1a;想了很久&#xff0c;不知道数据结构这个科目最终该以什么笔记方式呈现出来&#xff0c;是纸质版还是电子版&#xff1f;后来想了又想&#xff0c;还是电子版吧&#xff1f;毕竟和计算机有关~&#xff08;啊哈哈哈哈哈哈哈&#xff09; 概率论已经更新完了&…

代码随想录训练营第八天 151反转字符串中的单词 右旋字符串

第一题&#xff1a; 原题链接&#xff1a;151. 反转字符串中的单词 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 先把首尾的空格去掉&#xff1a;找到第一个不是空格的字符&#xff0c;找到最后一个不是空格的字符。substr字符串为新的字符串t。 使用双指针&…

【Python入门与进阶】Jupyter Notebook配置与优化

目录 1.Jupyter Notebook简介 2.Jupyter Notebook的安装 2.1 命令行安装 2.2 可视化界面安装 3.Jupyter Notebook的使用 3.1 启动 Jupyter Notebook 3.2 Jupyter Notebook 界面介绍 3.3 创建新的 Notebook 3.4 编写和运行代码单元 3.5 使用 Markdown 编写文档 3.6 保…

快慢指针在字符串中的应用-443. 压缩字符串

题目链接及描述 443. 压缩字符串 - 力扣&#xff08;LeetCode&#xff09; 题目分析 这个题目总体不算太难&#xff0c;如果之前接触过双指针&#xff08;快慢指针&#xff09;的话&#xff0c;比较好做。题目可以理解为计算数组中对应各个连续字符出现的次数&#xff0c;并将…

SAPUI5基础知识5 - 控件(control)的使用

1. 背景 在SAPUI5中&#xff0c;控件&#xff08;Control&#xff09;是构建用户界面的基本元素。控件是一个可重用的组件&#xff0c;它可以与用户进行交互或显示信息。 每个控件都有自己的特性&#xff0c;例如属性&#xff08;Properties&#xff09;、聚合&#xff08;Agg…

btrace:binder_transaction+eBPF+Golang实现通用的Android APP动态行为追踪工具

一、简介&#xff1a; 在进行Android恶意APP检测时&#xff0c;需要进行自动化的行为分析&#xff0c;一般至少包括行为采集和行为分析两个模块。其中&#xff0c;行为分析有基于规则、基于机器学习、基于深度学习甚至基于大模型的方案&#xff0c;各有各的优缺点&#xff0c;不…

CentOS 7基础操作14_Linux组账号管理

在5.1.2节学习了管理Linux操作系统中用户账号的相关命令&#xff0c;接下来继续学习组账号管理的相关命令。组账号管理命令的使用相对较少&#xff0c;主要包括groupadd、groupdel、gpasswd等。 对于用户账号来说&#xff0e;对应的组账号可分为基本组和附加组两种类型&#xf…

【AI开发】LangGraph基础

在LangGraph中有三个重要元素 StateGraphNodeEdge StateGraph 首先stategraph是用来描述整个图的&#xff0c;图中的状态会随着多个agent的工作不断的更新&#xff0c;节点node就是用来更新状态的 如何来定义一张图中的状态 每个应用的状态可能不同&#xff0c;所以我们需要…

kettle实时增量同步mysql数据

** 本文主要介绍运用kettle实时增量同步mysql数据 ** Debezium介绍 官网地址&#xff1a;https://debezium.io/documentation/ Debezium是一个开源项目&#xff0c;为捕获数据更改(Capture Data Change,CDC)提供了一个低延迟的流式处理平台&#xff0c;通过安装配置Debeziu…

Aigtek高压功率放大器在超声电机中的应用

超声电机是一种先进的电机技术&#xff0c;常用于各种应用&#xff0c;如医疗成像、工业自动化和汽车技术。这些电机在高速、高精度和低噪音要求的领域中表现出色。在驱动这些超声电机时&#xff0c;高压功率放大器发挥着关键作用。本文将介绍高压功率放大器如何驱动超声电机&a…

腾讯元宝APP:AIGC大模型的新篇章

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到我们生活的方方面面。腾讯作为国内科技巨头&#xff0c;近期推出的元宝APP更是为AIGC&#xff08;人工智能生成内容&#xff09;市场注入了新的活力。这款大模型产品的上线&#xff0c;不仅丰富…

【kyuubi-spark】从0-1部署kyuubi集成spark执行spark sql到k8s读取iceberg的minio数据

一、背景 团队在升级大数据架构 前端使用trino查询&#xff0c;对trino也进行了很多优化&#xff0c;目前测试来看&#xff0c;运行还算稳定&#xff0c;但是不可避免的trino的任务总会出现失败的情况。原来的架构是trino失败后去跑hive&#xff0c;而hive是跑mapreduce依赖于…

PostgreSQL:在CASE WHEN语句中使用SELECT语句

CASE WHEN语句是一种条件语句&#xff0c;用于多条件查询&#xff0c;相当于java的if/else。它允许我们根据不同的条件执行不同的操作。你甚至能在条件里面写子查询。而在一些情况下&#xff0c;我们可能需要在CASE WHEN语句中使用SELECT语句来检索数据或计算结果。下面是一些示…

c->c++(一):部分KeyWord

本文主要探讨c相关关键字的使用。 char char默认是unsigned/signed取决平台,wchar_t宽字符:用于Unicode编码(超过一个字节),用wcin和wcout输入输出,字符串为wstring char8_t(20),char16_t(11起),char32_t(11):指定占用字节数且是无符号,字符串类u8string,u16s…

Debian 安装 kubernets

Docker环境 添加 Docker 的官方 GPG 密钥 安装 apt 依赖包&#xff0c;用于通过 HTTPS 来获取仓库 sudo apt-get install \apt-transport-https \ca-certificates \curl \gnupg2 \software-properties-common -y添加秘钥 curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/li…