JankMan-极致的卡顿分析系统

 更新时间:2023年05月06日 08:31:08   作者:Dx  
这篇文章主要为大家介绍了JankMan-极致的卡顿分析系统使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

1.卡顿分析系统介绍

  • 此系统拥有了端上采集两个维度数据的能力

    • 方法运行数据:系统在编译期间基于ASM9+AGP7+自定义方法ID映射+自定义字节码指令集实现了方法运行数据的采集。
    • 帧性能数据:系统在运行期间基于FrameMatrix+自定义数据结构体实现了端上帧数据的采集。

当APP发生运行卡顿时,系统可自动分析堆栈,并且关联卡顿帧的方法调用链,并作出记录最终导出至文件。整体基于协程制作,性能损耗仅需1%。

基于Compose KMP独创了多端可用的线下还原器,将采集到的数据做格式效验,并作出二次还原解析,进行了perfetto的二次开发,实现了可视化展示整体信息的能力。

2.思路介绍

2.1方法运行数据采集

2.1.1方法ID映射

由于系统会在运行期间会采集大量数据,所以需要将庞大的方法名映射为指定ID的能力,如下

//方法ID`方法具体全路径和行参
24`com.d.hookplus.HookApplication$onCreate$1.onActivitySaveInstanceState[android.app.Activity,android.os.Bundle,]

此处的方法名包含了全路径和行参,如果通过字符串记录是十分庞大的,所以在编译期间使用ASM技术将其对应成ID,降低空间复杂度

2.2.2函数记录能力

在ASM轮训期间,在方法开始和结束位置各插入对应的指令用于实现标记功能

override fun onMethodEnter() {
    //方法进入
    methodVisitor?.perfettoVisitMethodDelegate(PerfettoCentre.const.METHOD_ENTER, methodId)
    super.onMethodEnter()
}
​
override fun onMethodExit(opcode: Int) {
    //方法退出
    methodVisitor?.perfettoVisitMethodDelegate(PerfettoCentre.const.METHOD_EXIT, methodId)
    super.onMethodExit(opcode)
}

处理退出指令时,catch和return指令也有对应的记录

在对应的时机插入对应的代码

this.let { methodVisitor ->
        methodVisitor.visitFieldInsn(
            GETSTATIC,
            "com/d/hookcore/perfetto/PerfettoCore",
            "Companion",
            "Lcom/d/hookcore/perfetto/PerfettoCore$Companion;"
        )
        methodVisitor.visitIntInsn(BIPUSH, enterOrExit)
        methodVisitor.visitIntInsn(SIPUSH, methodId)
        methodVisitor.visitMethodInsn(
            INVOKEVIRTUAL,
            "com/d/hookcore/perfetto/PerfettoCore$Companion",
            "getTraceLine",
            "(II)V",
            false
        )
    }
例:
private fun initRecycler() {
    //方法开始,第一个参数用于标记进入或者退出,第二个参数用于标记映射的方法ID
    PerfettoCore.getTraceLine(0,12)
    var recyclerView: RecyclerView = findViewById(R.id.rec)
    recyclerView.adapter = NodeAdapter(messageList)
    PerfettoCore.getTraceLine(1,12)
}
  • 插入完代码的例子如下:

2.2.3.运行方法记录内容

方法记录后的结果如下

如:main-1,942155.120954153,1,39,1

说明
当前线程Main
当前线程ID1
当前时间秒值942155.120954153
当前方法标记(进入或退出)1
当前方法ID39
当前帧位下标1

2.2帧数据采集

2.2.1于传统方式的区别

区别于Choreographer Hook的方式,系统采用了FrameMatrix实现了帧数据采集

Choreographer的Hook点是在Looper.loop之中的Printer.println下,

final Printer logging = me.mLogging;
if (logging != null) {
 //Hook点
 logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);
}

缺点如下:

每次都需要字符串匹配,性能损耗严重

在API31之中被封掉了

2.2.2FrameMatrix的能力

FrameMatrix通过addOnFrameMetricsAvailableListener实现了帧数据获取的能力

window.addOnFrameMetricsAvailableListener({ window, frameMetrics, dropCountSinceLastInvocation ->
//do something
}, frameMetricsHandler)

如果不对window进行addOnFrameMetricsAvailableListener,数据也会保留在平台层,所以没有性能损耗

FrameMatrix是Android平台层提供的帧数据采集,包含如下信息

类别参数说明
获取每帧性能数据FIRST_DRAW_FRAME表示当前帧是否是当前 Window 布局中绘制的第一帧
INTENDED_VSYNC_TIMESTAMP当前帧的预期开始时间, 如果此值与 VSYNC_TIMESTAMP 不同,则表示 UI 线程上发生了阻塞,阻止了 UI 线程及时响应vsync信号
TOTAL_DURATION表示此帧渲染并发布给显示子系统所花费的总时间, 等于所有其他具有时间价值的指标的值之和
VSYNC_TIMESTAMP在所有 vsync 监听器和帧绘制中使用的时间值(Choreographer 的帧回调, 动画, View#getDrawingTime等)
cpuDurationCOMMAND_ISSUE_DURATION表示向 GPU 发出绘制命令的耗时
SWAP_BUFFERS_DURATION表示将此帧的帧缓冲区发送给显示子系统所花的时间
uiDurationUNKNOWN_DELAY_DURATION表示等待 UI 线程响应并处理帧所经过的时间, 大多数情况下应为0
INPUT_HANDLING_DURATION表示处理输入事件回调的耗时
ANIMATION_DURATION表示执行动画回调的耗时
LAYOUT_MEASURE_DURATION表示对 View 树进行 measure 和 layout 所花的时间
DRAW_DURATION表示将 View 树转换为 DisplayList 的耗时
SYNC_DURATION表示将 DisplayList 与渲染线程同步所花的时间

2.3.同步算法

同步算法是将方法运行数据和帧性能数据自动分析,裁剪,合并出问题堆栈,并保存到指定位置的过程

在开发者自定义连续卡顿多少帧后进行Dump

如果每卡顿一帧就Dump,信息量太密集,并且意义不大,建议开发者连续卡顿5帧起步

2.3.1.同步算法细节

整个同步的过程是在单独的一个HandlerThread中进行的,所以面临了两个难题:

由于HandlerThread接受帧数据的时机是不确定的,即可能方法数据已经收集到很多帧以后了,但是帧数据才刚刚到来。如何精准定位到卡顿范围内的全部函数

如何尽可能减小性能损耗,降低时间复杂度和空间复杂度

所以我们整个同步的过程是围绕着这两个问题进行设计的

2.3.2.算法合并过程

如果卡顿帧连续个数到达了开发者定义的个数,那么开始还原

将首个卡顿帧的开始时间和连续卡顿帧后的第一个不卡顿帧的开始时间作为时间范围,与函数运行数据的时间点进行校准。匹配到函数运行的范围区域,并通过两个指针进行标记

具体匹配的过程是通过魔改版的二分查找实现

将标记出的函数运行范围进行导出

导出的能力是基于NIO实现的

导出完毕后将函数指针位移到开始位置,重复利用空间

上述过程均在子线程进行,对主线程无影响,现阶段损耗为3%左右

2.4.可视化展示

  • 将Dump出的数据进行二次改造,支持可视化展示

2.4.1.线下还原器

  • 基于perfetto的构造格式进行二次改造,用于支持可视化展示

2.4.2.支持多端的线下还原器

基于Compose KMP实现了多端的可视化还原器,效果如下:

参数说明如下:

  • MappingFile:函数ID映射文件
  • SourceFile:后缀为.zy_trace的文件,是系统自动采集的格式
  • OutputPath:输出为perfetto识别格式的文件目录

还原格式细节

如下:

com.d.hookplus|11116
main-1,942155.105176549,0,29,0
main-1,942155.105229674,1,29,0
main-1,942155.105313008,0,29,0
main-1,942155.105322383,1,29,0
main-1,942155.105492174,0,29,0
main-1,942155.105503112,1,29,0
main-1,942155.105524466,0,30,0
.......
# tracer: nop
#
# entries-in-buffer/entries-written: 30624/30624   #P:4
#
#                                  _-----=> irqs-off
#                                 / _----=> need-resched
#                                | / _---=> hardirq/softirq
#                                || / _--=> preempt-depth
#                                ||| /     delay
#       TASK-PID    TGID   CPU#  ||||    TIMESTAMP  FUNCTION
#          | |        |      |   ||||       |         |
com.d.hookplus-main-1 [000]... 942155.105177: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105230: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105313: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105322: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105492: tracing_mark_write: B|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105503: tracing_mark_write: E|11116|0com.d.hookplus.NodeAdapter.getItemCount.[]
com.d.hookplus-main-1 [000]... 942155.105524: tracing_mark_write: 
.....
  • 改造前:
  • 改造后:

2.4.3.可视化展示

  • 最终将还原器输出的文件直接导入至perfetto系统中,效果如下

以上就是JankMan-极致的卡顿分析系统的详细内容,更多关于JankMan卡顿分析系统的资料请关注脚本之家其它相关文章!

相关文章

  • Android开发中libs和jinLibs文件夹的作用详解

    Android开发中libs和jinLibs文件夹的作用详解

    这篇文章主要给大家介绍了关于Android开发中libs和jinLibs文件夹的作用的相关资料,文中通过图文及示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-09-09
  • Android RetainFragment状态保存的方法

    Android RetainFragment状态保存的方法

    本篇文章主要介绍了Android RetainFragment状态保存的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • Android编程实现对话框Dialog背景透明功能示例

    Android编程实现对话框Dialog背景透明功能示例

    这篇文章主要介绍了Android编程实现对话框Dialog背景透明功能,涉及Android对话框的布局、属性及事件处理相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • Android 线程thread的两种实现方法(必看)

    Android 线程thread的两种实现方法(必看)

    下面小编就为大家带来一篇Android 线程thread的两种实现方法(必看)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-02-02
  • Android Canvas之drawBitmap方法案例详解

    Android Canvas之drawBitmap方法案例详解

    这篇文章主要介绍了Android Canvas之drawBitmap方法案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Android 获取屏幕高度,标题高度,状态栏高度(实例代码)

    Android 获取屏幕高度,标题高度,状态栏高度(实例代码)

    getWindow().findViewById(Window.ID_ANDROID_CONTENT)这个方法获取到的view就是程序不包括标题栏的部分,然后就可以知道标题栏的高度了
    2013-11-11
  • android使用Messenger绑定Service的多种实现方法

    android使用Messenger绑定Service的多种实现方法

    android使用Messenger绑定Service的多种实现方法,需要的朋友可以参考一下
    2013-05-05
  • Android 使用Intent传递数据的实现思路与代码

    Android 使用Intent传递数据的实现思路与代码

    Intent是Android中一个非常重要的概念,跟这个词的本意(意图,目的)一样,这个类在Android中的作用就是要调用某个组建去做某一件事,接下来详细介绍,感兴趣的朋友可以参考下
    2013-01-01
  • android实现指纹识别功能

    android实现指纹识别功能

    这篇文章主要介绍了android指纹识别功能,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • Android路由框架ARouter的使用示例

    Android路由框架ARouter的使用示例

    组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护、更快捷的同步开发与更简单的单独调试,而ARouter的出现就是让组件间、模块间是实现完全的独立。ARouter主要解决组件间、模块间的界面跳转问题。
    2021-06-06

最新评论