一、前言

启动性能是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
  1. 其实实质就是一个try-catch块,catch块捕捉的异样类型为Throwable;
  2. 在失常完结点(各类return指令)前,把finally块的指令冗余的增加到各类return指令之前,保障失常退出;
  3. 异样完结点解决,被动抛出异样或者运行时异样,都对立由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,完满的插桩计划如下:

  1. 办法开始点只有一个,在办法开始点增加Trace.beginSection即可;
  2. 办法完结点会有多个,完结点存在两种状况,失常完结和异样完结,针对失常完结点(各类return指令)前增加Trace.endSection;
  3. 异样完结(被动抛出异样或者运行时异样),则用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。

办法调用次要有两步:

  1. 参数加载,依照参数程序从左到右加载办法指令的依赖的参数到操作数栈;
  2. 办法调用,执行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 低端机优化-启动性能优化(概述篇)

面向大规模数据的云端治理,百度桑田存储产品解析

加强剖析在百度统计的实际