其他
百度App Android启动性能优化-工具篇
The following article is from 百度App技术 Author Wiseman,Tang
一、前言
GEEK TALK
高性能,保证自身高性能,以防带偏优化方向 多维度,能监控多维度信息,帮助全面发现问题 易用性,方便的可视化界面,方便分析
丨1.2 二次开发
Trace采集
Java/Kotlin:提供了android.os.Trace类,通过在方法开始和结束点成对添加Trace.beginSection和Trace.endSection; NDK:通过引入<trace.h>,通过ATrace_beginSection() / Atrace_endSection()添加Trace; Android系统进程:提供了ATRACE_*宏添加Trace,定义在libcutils/trace.h;
防劣化
二、Perfetto介绍
GEEK TALK
百度APP启动性能优化工具是基于Perfetto二次开发,下面对Perfetto的架构和原理做相应的介绍。
丨2.1 整体介绍
△Perfetto整体介绍
Record traces
Linux ftrace:支持收集内核事件,如cpu调度事件和系统调用等; /proc和/sys pollers:支持采样进程或者系统维度cpu和内存状态; heap profilers:支持采集java和native内存信息;
Analyze traces
Visualize traces
丨2.2 Perfetto采集
采集指令
./record_android_trace -c atrace.cfg -n -o trace.html
Trace config
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
原理简介
△Perfetto采集Trace原理
ftrace
// 创建文件,关联file_operations
struct 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数据流
△Trace Processor
Perfetto native protobuf format Linux ftrace Android systrace Chrome JSON (including JSON embedding Android systrace text) Fuchsia binary format Ninja logs (the build system)
三、自动插桩工具
GEEK TALK
丨3.1 自动插桩
class Test {
public void test() {
Trace.benginSection("test");
// 方法体
// ...
Trace.endSection();
}
}
丨3.2 Did Not Finish问题
class Test {
public void test() throws Exception {
Trace.benginSection("test");
// 方法体,代码出现异常,外部调用方法catch住
testThrowException();// 这个方法抛出异常,代码返回,endSection不会调用
// endSection可能存在不调用的情况
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();
}
}
}
public void testMethod(boolean, boolean);
descriptor: (ZZ)V
flags: ACC_PUBLIC
Code:
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块的指令。
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即可; 方法结束点会有多个,结束点存在两种情况,正常结束和异常结束,针对正常结束点(各类return指令)前添加Trace.endSection; 异常结束(主动抛出异常或者运行时异常),则用try-catch住整个方法体,catch异常类型为Throwable,在catch块中添加Trace.endSection,并且抛出捕获的异常。
丨3.3 监控系统类方法
boolean isMain = Looper.getMainLooper() == Looper.myLooper();
try {
if (isMain) {
Trace.beginSection("Main Thread Wait");
}
lock.wait(timeout, nanos);
} finally {
if (isMain) {
Trace.endSection();
}
}
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);
}
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,指定类名、方法名、方法签名,调用方法。
ALOAD 4 # 加载对象引用
LLOAD 1 # 加载long timeout
ILOAD 3 # 加载int nanos
INVOKEVIRTUAL java/lang/Object.wait (JI)V # 调用Object实例方法
ALOAD 4 # 加载对象引用
LLOAD 1 # 加载long timeout
ILOAD 3 # 加载int nanos
INVOKESTATIC com/baidu/systrace/SystraceInject.wait (Ljava/lang/Object;JI)V # 调用SystraceInject.wait的静态方法
丨3.4 包尺寸和性能问题
四、Trace自动分析工具
GEEK TALK
分析大于指定耗时阈值的方法列表; 对比分析版本耗时劣化、新增耗时问题; 支持统计TOP N异步线程CPU耗时; 支持分析主线程锁问题(monitor contention 前缀)。
丨4.1 核心表
△自动分析使用的表
丨4.2 方法耗时统计
Wall Duration统计
CPU Duration统计
丨4.3 问题分析
主线程锁
方法耗时劣化
方法CPU耗时劣化
新增方法耗时
五、最佳实践
GEEK TALK
百度APP基于自动插桩工具和Trace自动化分析工具,构建了一套线下防劣化监控流水线,流程如下:
六、小结
GEEK TALK
END