一、前言
启动性能是APP的极为重要的一环,启动阶段呈现卡顿、黑屏问题,会影响用户体验,导致用户散失。百度APP在一些比拟低端的机器上也有相似启动性能问题,为保留存,须要对启动流程做深刻优化。现有的性能工具,无奈高效的发现、定位性能问题,归因剖析和防劣化老本很高,须要对现有工具进行二次开发,晋升效率。
丨1.1 工具选型
做好性能优化,不仅须要趁手的工具,而且对工具的要求还很高,具体来说,必须满足要求:
- 高性能,保障本身高性能,以防带偏优化方向
- 多维度,能监控多维度信息,帮忙全面发现问题
- 易用性,不便的可视化界面,不便剖析
目前业界支流的APP性能探测工具有TraceView、CPU Profiler、Systrace、Perfetto。
Perfetto提供了弱小的Trace剖析模块:Trace Processor,能够把多种类型的日志文件(Android systrace、Perfetto、linux ftrace)通过解析、提取其中的数据,结构化为SQLite数据库,并且提供基于SQL查问的Python API,可通过python实现自动化剖析;同时有良好的可视化页面,可通过可视化页面查看火焰图和写SQL进行Trace剖析。
从性能、监控维度的丰盛水平和提供的配套的剖析和可视化工具来抉择,Perfetto是最好的抉择,但后期因为Perfetto是9.0当前默认内置服务然而默认不可用,Android 11服务才默认可用,对低版本零碎反对不够,所以咱们抉择了Systrace+Perfetto工具联合,可笼罩所有Android零碎。随着Perfetto继续迭代,减少了对低版本Android零碎反对,百度APP也全面切换到了Perfetto为根底采集和剖析性能工具。
丨1.2 二次开发
Trace采集
Perfetto收集App的Trace是通过Android零碎的atrace收集,须要本人手动增加Trace收集代码,增加Trace采集形式如下:
- Java/Kotlin:提供了android.os.Trace类,通过在办法开始和完结点成对增加Trace.beginSection和Trace.endSection;
- NDK:通过引入<trace.h>,通过ATrace\_beginSection() / Atrace\_endSection()增加Trace;
- Android零碎过程:提供了ATRACE\_*宏增加Trace,定义在libcutils/trace.h;
在Android Framework和虚拟机外部会默认增加一些要害Trace,APP层须要手动增加,监控APP启动流程,有海量的办法,手动增加耗时耗力。百度APP大部分逻辑都是Java/Kotlin编写,Java/Kotlin代码会编译成字节码,在编译期间,可通过gradle transform批改字节码,咱们须要开发一套主动插桩的gradle插件,在编译时主动增加APP层Trace收集代码,实现监控APP层所有办法。
防劣化
随着优化继续上线,对性能指标会有肯定的正向收益,然而随着版本继续迭代,会有各种劣化问题,为保住优化成绩,咱们在线下每个版本公布之前都须要做真机启动性能测试,测试流程:
打包:须要打出主动插桩的包,须要一个基准包(上次公布版本的release分支的插桩包)和一个测试包(master分支的插桩包),用来做真机测试。
真机测试:用基准包和测试包手动跑启动相干case,启动Perfetto Trace抓取脚本,抓取Trace日志,会输入基准包Trace日志和测试包Trace日志,用作比照剖析。
比照剖析:Trace日志通过https://ui.perfetto.dev/ 关上可生成的火焰图,通过火焰图进行比照剖析,找到存在的劣化问题,这个流程是最耗时的,须要比照剖析的调用栈十分繁冗。
散发问题:梳理相干劣化问题,散发跟进对应业务负责同学。
这一整套流程实现,须要2人天,而比照剖析工作量最大,须要实现自动化剖析Trace日志性能,主动发现新增耗时、耗时劣化、锁期待等问题。
Perfetto提供了弱小的Trace剖析模块:Trace Processor,把多种类型的日志文件(Android systrace、Perfetto、linux ftrace)通过解析、提取其中的数据,结构化为SQLite数据库,并且提供基于SQL查问的python API,可通过python实现自动化剖析。
为提高效率,需基于Trace Processor的python API,开发一套Trace主动剖析工具集,实现疾速高效剖析版本启动劣化问题。
二、Perfetto介绍
百度APP启动性能优化工具是基于Perfetto二次开发,上面对Perfetto的架构和原理做相应的介绍。
丨2.1 整体介绍
Perfetto是Google开源的一套性能检测和剖析框架。依照性能可分成3大块,Record traces(采集)、Analyze traces(剖析)、Visualize traces(可视化)。
Record traces
Trace采集能力,反对采集多种类型的数据源,反对内核空间和用户空间数据源。
内核空间数据源是Perfetto内置的,须要零碎权限,次要的数据源包含:
- Linux ftrace:反对收集内核事件,如cpu调度事件和零碎调用等;
- /proc和/sys pollers:反对采样过程或者零碎维度cpu和内存状态;
- heap profilers:反对采集java和native内存信息;
用户空间数据采集,Perfetto 提供了对立的Tracing C++库,反对用户空间数据性能数据收集,也可用atrace在用户层增加Trace收集代码采集用户空间Trace。
Analyze traces
Trace剖析能力,提供Trace Processor模块能够把反对的Trace文件解析成一个内存数据库,数据库实现基于SQLite,提供SQL查问性能,同时提供了python API,百度APP也是基于Trace Processor开发了一套Trace自动化剖析工具集。
Visualize traces
Perfetto还提供了一个全新的Trace可视化工具,工具是一个网站:https://ui.perfetto.dev/ 。在可视化工具中可导入Trace文件,并且可应用Trace Processor和SQLite的查问和剖析能力。
丨2.2 Perfetto采集
采集指令
./record_android_trace -c atrace.cfg -n -o trace.html
record\_android\_trace:Perfetto提供的Trace采集帮忙脚本,对低版本Trace采集做了兼容,Android 9以上会通过adb调用默认内置Perfetto执行文件,Android 9以下会依据不同的CPU架构下载外置的Perfetto可执行文件,把可执行文件push到 /data/local/tmp/tracebox,最初通过adb指令启动Perfetto Trace采集,通过这个脚本可能反对所有机型的Trace采集。
-c path:指定trace config配置文件,配置Trace采集时长、buffer\_size、buffer policy、data source配置等;
-o path:指定Trace文件输入门路。
Trace config
Trace config配置当次采集的一些外围配置,采集时长、trace buffer size、buffer policy和data source配置等;
示例:
buffers: { size_kb: 522240 fill_policy: DISCARD}data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" atrace_categories: "dalvik" atrace_categories: "view" atrace_apps: "com.xx.xx" } }}duration_ms: 30000
buffers:设置当次采集的内存trace buffer配置,size\_kb,配置当次 trace buffer大小,单位kb;fill\_policy,配置trace buffer的策略,RING\_BUFFER,trace buffer满了后,新的内容会把最老的内容笼罩,DISCARD,trace buffer满了当前,新的trace会间接抛弃。
duration\_ms:trace采集时长,单位ms,达到指定时长后,会进行收集Trace。
data\_sources:name,以后data source名称,如linux.ftrace示意ftrace的配置;ftrace\_config,ftrace的配置;ftrace\_events,配置须要抓取的ftrace事件,内核空间trace;atrace\_categories,配置须要收集的atrace category,用户空间Trace;atrace\_apps,配置须要采集trace的利用过程包名。
原理简介
启动性能重点关注办法耗时,Perfetto采集办法耗时trace依赖atrace和ftrace实现。相干实现如下:
Perfetto通过atrace设置用户空间category(数据类型),包含APP自定义 Trace 事件、零碎view Trace、零碎层 gfx 渲染相干 Trace等,其最终都是通过调用 Android SDK 提供 Trace.beginSection 或者 ATrace宏记录到同一个文件 /sys/kernel/debug/tracing/trace\_marker 中,ftrace 会记录该写入操作工夫戳。其中Android Framework外面一些重要的模块都加了Trace收集,用户APP代码须要手动退出;
内核空间数据次要一些和零碎内核相干数据,如sched(CPU调度信息)、binder(binder驱动)、freq(CPU频率)等信息,Perfetto通过管制一些文件节点实现关上和敞开;
最终两种类型数据会写入ftrace RingBuffer中,Perfetto通过读取ftrace RingBuffer数据,实现Trace收集。
ftrace
ftrace是trace采集的外围实现,ftrace其实也是Perfetto的反对的一个data source,通过ftrace可实现收集用户空间和零碎空间trace数据。
ftrace是linux零碎内核的trace工具,其中RingBuffer是ftrace的根底,所有的trace原始数据都是通过RingBuffer记录的;
ftrace应用tracefs file system用来管制ftrace的配置和Trace日志输入,ftrace目录:/sys/kernel/debug/tracing(内核4.1之前) 或者 /sys/kernel/tracing(内核4.1之后)。
局部文件阐明:
ftrace如何通过在相应的文件节点写入信息和读取,实现ftrace的配置和Trace日志的输入?
ftrace应用了tracefs文件系统注册file\_operations构造体,对文件进行零碎调用会关联对应的函数指针,实现ftrace配置和ftrace Trace日志读取性能,相干代码实现:
// 创立文件,关联file_operationsstruct dentry *trace_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops){ struct dentry *ret; ret = tracefs_create_file(name, mode, parent, data, fops); if (!ret) pr_warn("Could not create tracefs '%s' entry\n", name); return ret;}// 定义操作trace文件系统调用对应的函数指针static const struct file_operations tracing_fops = { .open = tracing_open, .read = seq_read, .write = tracing_write_stub, .llseek = tracing_lseek, .release = tracing_release,};trace_create_file("trace", TRACE_MODE_WRITE, d_tracer, tr, &tracing_fops);
Perfetto采集ftrace数据
上面介绍一下残缺采集流程:
- 通过adb的形式启动执行perfetto,指定Trace config,配置buffer\_size、buffer policy、data source ftrace配置;
- Perfetto读取Trace config配置,写入ftrace文件节点,配置收集的数据类型和设置ftrace每个cpu的ringbuffer size,并且定期读取per\_cpu/cpu0/trace\_pipe\_raw内容,即定期读取每个cpu的ringbuffer数据,解析转换成对应的probuf格局,写入Producer和tracing service的共享内存中,tracing service会把共享内存的trace数据拷贝到trace buffer。
- 采集完结,进行trace收集,把tracing service的trace buffer数据读取进去,生成文件,通过Perfetto web ui查看。
相干数据流如下图:
△Perfetto采集ftrace数据流
丨2.3 Perfetto剖析**
Perfetto剖析模块,其外围是Trace Processor,其性能如下:
△Trace Processor
解析Trace文件、提取其中的数据,结构化为SQLite的内存数据库,并且提供基于SQL查问的API,通过写SQL的形式,查问对应的办法耗时,同时提供Python API。
反对的trace数据格式:
- Perfetto native protobuf format
- Linux ftrace
- Android systrace
- Chrome JSON (including JSON embedding Android systrace text)
- Fuchsia binary format
- Ninja logs (the build system)
三、主动插桩工具**
主动插桩工具是一个gradle编译插件,全办法Trace插桩,保障Trace闭合,反对监控零碎类,同时须要思考包体积和性能问题。
丨3.1 主动插桩
Android零碎会内置一些Trace,在APP代码须要手动增加,耗时耗力,须要实现一个主动插桩工具,主动在APP的办法增加Trace代码。
插桩代码:
class Test { public void test() { Trace.benginSection("test"); // 办法体 // ... Trace.endSection(); }}
主动插桩工具是利用Gradle Transform(Gradle Transform是Android官网提供给开发者在我的项目构建阶段中由class到dex转换之前批改class文件的一套api),开发的一个Gradle编译插件。利用ASM字节码操作框架,遍历所有的类的办法,在办法开始和完结点插入收集Trace的代码,实现APP全办法监控。
丨**3.2 Did Not Finish问题
主动插桩工具投入使用后,遇到了Did Not Finish的问题,如果呈现这种问题,整个Trace都错乱了,如下图所示:
Did Not Finish,示意办法没有完结,通过定位,是因为Trace.benginSection和Trace.endSection没有成对调用。为什么会呈现这种问题呢?
示例问题代码:
class Test { public void test() throws Exception { Trace.benginSection("test"); // 办法体,代码出现异常,内部调用办法catch住 testThrowException();// 这个办法抛出异样,代码返回,endSection不会调用 // endSection可能存在不调用的状况 Trace.endSection(); }}
运行期间,办法可能存在被动抛出异样和运行时异样的状况,如存在这种状况,Trace.endSection就得不到调用,就会存在问题。
如何保障Trace.benginSection和Trace.endSection的成对调用?
现实的解决方案是应用try-finally块整体包裹整个办法体,在办法开始点插入Trace.benginSection在finally块插入Trace.endSection,Java虚构机会保障finally块的代码在try块代码完结前都会调用,能够保障Trace.benginSection和Trace.endSection的成对调用。
示例代码:
class Test { public void testMethod(boolean a, boolean b) { try { Trace.beginSection("com.sample.systrace.TestNewClass.testMethod.()V"); if (!a) { throw new RuntimeException("test throw"); } Log.e("testa", "com.sample.systrace.TestNewClass.testMethod.()V"); if (b) { return; } Log.e("testb", "com.sample.systrace.TestNewClass.testMethod.()V"); } finally { Trace.endSection(); } }}
在字节码层面是没有finally关键字对应的字节码指令,为了搞明确finally的具体实现逻辑,对编译的字节码反编译:
public void testMethod(boolean, boolean);descriptor: (ZZ)Vflags: ACC_PUBLICCode: stack=3, locals=4, args_size=3 0: ldc #15 // String com.sample.systrace.TestNewClass.testMethod.(ZZ)V 2: invokestatic #21 // Method android/os/Trace.beginSection:(Ljava/lang/String;)V 5: iload_1 6: ifne 19 9: new #23 // class java/lang/RuntimeException 12: dup 13: ldc #25 // String test throw 15: invokespecial #27 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V 18: athrow // 手动抛出异样,没有增加finally块的字节码指令 19: ldc #29 // String testa 21: ldc #31 // String com.sample.systrace.TestNewClass.testMethod.()V 23: invokestatic #37 // Method android/util/Log.e:(Ljava/lang/String;Ljava/lang/String;)I 26: pop 27: iload_2 28: ifeq 35 31: invokestatic #40 // Method android/os/Trace.endSection:()V 34: return // if(b)如果b为true的一个return指令,上一个指令增加了invokestatic,即减少了Trace.endSection调用 35: ldc #42 // String testb 37: ldc #31 // String com.sample.systrace.TestNewClass.testMethod.()V 39: invokestatic #37 // Method android/util/Log.e:(Ljava/lang/String;Ljava/lang/String;)I 42: pop 43: invokestatic #40 // Method android/os/Trace.endSection:()V 46: return // 代码失常完结点,也插入了invokestatic,即减少了Trace.endSection调用 47: astore_3 // 开始异样解决,抛出异样之前也插入了invokestatic,即减少了Trace.endSection调用 48: invokestatic #40 // Method android/os/Trace.endSection:()V 51: aload_3 52: athrow Exception table: // 异样表,只有行号,from-to之间字节码指令产生异样,则跳转到target行进行解决 from to target type 0 46 47 Class java/lang/Throwable // 解决的异样类型 LocalVariableTable: Start Length Slot Name Signature 5 42 0 this Lcom/sample/systrace/TestNewClass; 5 42 1 a Z 5 42 2 b
- 其实实质就是一个try-catch块,catch块捕捉的异样类型为Throwable;
- 在失常完结点(各类return指令)前,把finally块的指令冗余的增加到各类return指令之前,保障失常退出;
- 异样完结点解决,被动抛出异样或者运行时异样,都对立由catch块解决,会在抛出异样之前插入finally块的指令。
对应的Java代码实现:
classTest { public void testMethod(boolean a, boolean b) { try { Trace.beginSection("com.sample.systrace.TestNewClass.testMethod.()V"); if (!a) { throw new RuntimeException("test throw"); } Log.e("testa", "com.sample.systrace.TestNewClass.testMethod.()V"); if (b) { Trace.endSection(); return; } Log.e("testb", "com.sample.systrace.TestNewClass.testMethod.()V"); Trace.endSection(); } catch(throwable e) { Trace.endSection(); throw e; }}
综上,为了保障Trace.beginSection和Trace.endSection成对调用,参考了虚拟机实现try-finally,完满的插桩计划如下:
- 办法开始点只有一个,在办法开始点增加Trace.beginSection即可;
- 办法完结点会有多个,完结点存在两种状况,失常完结和异样完结,针对失常完结点(各类return指令)前增加Trace.endSection;
- 异样完结(被动抛出异样或者运行时异样),则用try-catch住整个办法体,catch异样类型为Throwable,在catch块中增加Trace.endSection,并且抛出捕捉的异样。
丨3.3 监控零碎类办法
主动插桩计划,只能对APP的代码编译的字节码进行插桩,因为Android零碎和Java提供的零碎类的字节码不参加打包,不能进行插桩,但还是想监控零碎相干的类的一些不合理的调用。比方在主线程调用Object.wait,强制主线程进行期待,放弃CPU的使用权,线程进入sleep状态,期待其余线程notify或者wait的超时,可能会导致重大的性能问题。
为了监控此零碎类问题,须要把调用零碎Object.wait的代码,前后进行插桩,如下所示:
boolean isMain = Looper.getMainLooper() == Looper.myLooper(); try { if (isMain) { Trace.beginSection("Main Thread Wait"); } lock.wait(timeout, nanos); } finally { if (isMain) { Trace.endSection(); } }
间接在每个办法里调用了Object.wait的办法调用处进行以上的插桩逻辑,插桩实现异样简单,容易出错,而且这种实现会在每个Object.wait调用处进行雷同逻辑的插桩,会减少指令数量,导致包体积减少。
为了实现Object.wait办法监控,同时缩小插桩简单读,最终决定采纳字节码指令替换的计划,即在字节码层面把调用Object.wait办法指令,替换成自定义的wait办法,性能和零碎的wait一样,只是增加了自定义的Trace。
Object类定义的wait办法有三个:
public final native void wait(long timeout, int nanos) throws InterruptedException; public final void wait(long timeout) throws InterruptedException { wait(timeout, 0);}public final void wait() throws InterruptedException { wait(0);}
重写后的自定义的减少监控的wait办法,减少Trace监控代码,最终还是调用零碎的Object.wait办法:
public static void wait(Object lock, long timeout, int nanos) throws InterruptedException { // 监控主线程wait boolean isMain = Looper.getMainLooper() == Looper.myLooper(); try { if (isMain) { Trace.beginSection("Main Thread Wait"); } lock.wait(timeout, nanos); } finally { if (isMain) { Trace.endSection(); } }}public static void wait(Object lock) throws InterruptedException { wait(lock, 0L, 0);}public static void wait(Object lock, long timeout) throws InterruptedException { wait(lock, timeout, 0);}
在字节码里调用类办法指令有:INVOKEVIRTUAL(调用类实例办法)、INVOKESTATIC(调用静态方法)、INVOKESPECIAL(调用构造函数),这里咱们次要关注下INVOKEVIRTUAL和INVOKESTATIC。
办法调用次要有两步:
- 参数加载,依照参数程序从左到右加载办法指令的依赖的参数到操作数栈;
- 办法调用,执行INVOKEVIRTUAL或者INVOKESTATIC,指定类名、办法名、办法签名,调用办法。
其中INVOKEVIRTUAL是类实例办法调用,须要依赖对象援用,最先入操作数栈的是类对象援用,而后才是办法参数。
调用Object.wait(long timeout, int nanos)的字节码指令:
ALOAD 4 # 加载对象援用LLOAD 1 # 加载long timeoutILOAD 3 # 加载int nanosINVOKEVIRTUAL java/lang/Object.wait (JI)V # 调用Object实例办法
重写的wait办法是静态方法,有个细节,第一个入参必须是一个Object对象,不能换地位,对应字节码:
ALOAD 4 # 加载对象援用LLOAD 1 # 加载long timeoutILOAD 3 # 加载int nanosINVOKESTATIC com/baidu/systrace/SystraceInject.wait (Ljava/lang/Object;JI)V # 调用SystraceInject.wait的静态方法
从下面的字节码剖析,自定义办法SystraceInject.wait参数和零碎办法Object.wait参数程序保持一致,保障操作数栈入栈程序统一,参数加载流程统一,所以,咱们只须要替换办法调用指令即可实现替换,遍历APP所有办法的字节码指令,替换办法指标wait办法调用的指令,INVOKEVIRTUAL java/lang/Object.wait (JI)V 替换为 INVOKESTATIC com/baidu/systrace/SystraceInject (Ljava/lang/Object;JI)V,即可实现监控主线程wait问题。
同理,其余须要动静替换的零碎类也可用雷同的形式进行替换,也可实现对系统办法调用的监控。
丨3.4 包尺寸和性能问题**
主动插桩工具会对百度APP所有办法进行插桩,会导致包尺寸减少10M左右大小,为了缩小包尺寸,须要对插桩的办法进行一些过滤,如一些确定不耗时的办法,比方简略的get、set办法、空办法。
在剖析的过程中,还发现一些插桩导致的性能问题,如下图所示:
EventBus组件应用rxjava实现,调用层级十分深,在剖析的过程中会认为EventBus组件十分耗时,然而通过优化EventBus组件,自定义实现了一套高性能的EventBus组件,通过AB试验查看整个启动流程只快了50ms,收益没有预期的大。
通过源码剖析,收集App的trace,java/kotlin应用android.os.Trace,把trace信息最终会写入/sys/kernel/tracing/trace\_marker中,写入ftrace RingBuffer。这种形式有肯定的性能损耗,这是因为每个事件都波及到一个字符串化、一个JNI调用,以及一个用户空间<->内核空间的写入trace\_marker的零碎调用(最耗时的局部)。
为解决此类问题,在主动插桩工具减少黑名单机制,可通过配置文件,配置类名或者包名,指定类或者包下的所有类不进行插桩,达到缩小性能损耗和包体积的成果。
四、Trace主动剖析工具
Trace主动剖析工具次要是为了晋升剖析效率,基于基准版本自动化剖析耗时劣化和锁问题。工具基于Trace Processor提供的Python API,可本人写SQL脚本查问内存数据库表中的Trace数据。
百度APP基于Trace Processor开发了一系列的主动剖析工具集:
- 剖析大于指定耗时阈值的办法列表;
- 比照剖析版本耗时劣化、新增耗时问题;
- 反对统计TOP N异步线程CPU耗时;
- 反对剖析主线程锁问题(monitor contention 前缀)。
丨4.1 外围表
自动化剖析是基于内存数据库表,其应用的外围表如下:
process:过程信息表,通过过程名,可拿到内存表中过程惟一upid;
thread:线程信息表,通过upid能够查问到过程下的所有线程,同时线程惟一示意应用utid示意;
thread\_track:线程上下文,和utid绑定,能够通过track\_id关联slice表,示意指定线程下的工夫片事件;
sched\_slice:cpu调度线程表,一条记录示意cpu调度一个线程的工夫片,可用于计算线程被cpu调度时长,表构造:
slice:线程工夫片表,和线程关联,关联一个track\_id,记录用户空间的线程工夫片事件,可用统计办法耗时,表构造:
丨4.2 办法耗时统计
剖析性能问题,最重要的是统计办法耗时,自动化剖析工具统计办法耗时有两种口径:
Wall Duration:办法整体耗时,蕴含期待CPU调度(sleep、期待IO、工夫片耗尽)和CPU执行办法指令耗时,统计办法理论运行时长;
CPU Duration:CPU执行办法指令耗时,不蕴含期待调度的工夫,统计办法本身指令执行的实在耗时;
Wall Duration = CPU Duration + 期待调度时长,通过分析方法的Wall Duration和CPU Duration能够剖析出办法耗时是因为办法本身逻辑耗时,还是因为执行过程中存在锁、IO或者线程抢占的问题。
Wall Duration统计
Wall Duration是依据slice表中的dur字段统计办法整体耗时。
CPU Duration统计
CPU Duration统计须要联合slice表和sched\_slice表动静计算,CPU调度的最小单位是线程,办法运行在线程,所以计算方法CPU耗时的思路就是统计在办法运行这段时间,所有CPU调度办法所在线程的累积时长,即为办法的CPU执行耗时。slice表,统计了办法开始工夫戳、时长和track\_id(可通过thread\_track表找到对应的线程Id),可确定线程Id、开始和完结工夫戳;sched\_slice表蕴含了CPU调度线程信息,包含调度的CPU编号、线程id、时长和开始工夫戳,通过线程Id、开始和完结工夫戳,能够把这段时间内调度指定线程Id的记录,累加即可,须要留神解决一些边际条件。如下图所示,CPU duration须要把sched\_slice1和sched\_slice2累加。
丨4.3 问题剖析
百度APP目前自动化trace剖析次要剖析主线程耗时劣化,分析方法是基于一个基准版本(如线上版本release分支包)做为参照,与测试版本的每个主线程调用进行比照剖析。主动剖析反对剖析以下几类问题:
主线程锁
次要剖析synchronize关键字导致的锁问题,虚构机会通过atrace增加Trace信息,Trace信息有固定前缀monitor contention,并且会阐明占用锁的线程ID,间接剖析slice表name字段前缀为monitor contention。
办法耗时劣化
此类问题关注的是主线程的办法耗时劣化,通过比照基准版本和测试版本,耗时劣化是指测试的版本比照基准版本耗时有减少,到了肯定阈值(以后阈值10ms),会认为是耗时劣化问题。
办法CPU耗时劣化
此类问题劣化问题和办法耗时劣化相似,统计的是办法的CPU耗时。
新增办法耗时
此类问题关注的是主线程的新增办法耗时,测试版本新增办法的耗时达到肯定阈值(目前是5ms),会认为是新增耗时问题。
五、最佳实际
百度APP基于主动插桩工具和Trace自动化剖析工具,构建了一套线下防劣化监控流水线,流程如下:
其中的打包流程应用的是主动插桩工具,Trace主动剖析用的是Trace主动剖析工具。流水线主动打包,主动启动测试抓取trace,自动化剖析和依据堆栈主动散发问题,无需人工染指,只需投入很少人力解决一些须要豁免的问题(办法改名、零碎锁、线程调度问题等),比照之前单次性能人工测试和人工剖析须要2人天,极大晋升了效率。
性能测试报告:
报告中的指标计算和问题剖析都是有Trace自动化剖析工具产出,同时问题详情会有具体的劣化数据和堆栈,能疾速定位劣化问题。
六、小结
百度APP启动性能工具基于perfetto联合主动插桩和自动化剖析能力,反对采集APP全Java/kotlin办法Trace日志,同步反对自动化剖析劣化问题,能极大晋升效率。因为是全Java/kotlin办法插桩还存在影响包体积问题,同时采集trace也存在肯定性能损耗,后续还须要继续优化(持续缩小不必要插桩、管制采集层级、接入Perfetto SDK采集等)。
——END——
参考资料:
[1] Perfetto官网文档:
https://perfetto.dev/docs/
[2] Perfetto源码:
https://github.com/google/per...
[3] Ftrace原理解析:
https://blog.csdn.net/u012489...
[4] Ftrace官网文档:
https://www.kernel.org/doc/ht...
[5] Linux内核源码:
https://elixir.bootlin.com/li...
举荐浏览:
数字人技术在直播场景下的利用
百度工程师教你玩转设计模式(工厂模式)
超大模型工程化实际打磨,百度智能云公布云原生 AI 2.0 计划
前后端数据接口合作提效实际
前端的状态治理与工夫旅行:San实际篇
百度App 低端机优化-启动性能优化(概述篇)
面向大规模数据的云端治理,百度桑田存储产品解析
加强剖析在百度统计的实际