Trace工具主要是为了快速的分析java层方法耗时,在收集收集数据时,我们只收集了自己关注的数据,没有其他数据的干扰,可以很快的找到方法索引,分析方法耗时。
搜索方式:类名|方法名,或者方法名
方法1:com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|loadVCS
方法2:com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|preLoad
1 使用
1.1修改
android/amap_bundle_portal/build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| buildscript { repositories { maven { url 'https://maven.amap.com/nexus/content/repositories/libs/' } } dependencies { classpath 'com.amap.trace:trace-build:1.1.4' } }
apply plugin: 'com.amap.trace'
trace { packages = ['com.autonavi.vcs','com.autonavi.bundle.vui'] isUI = true }
|
1.2编译
如果编译有问题,删除如下目录
1
| android/amap_bundle_portal/build/intermediates/transforms/AmapTraceTransform
|
1.3 运行app
方法栈为超过500次调用,输出一次方法栈记录
输出目录为:/sdcard/Android/data/com.autonavi.minimap/files/trace/trace-1672751118743
1
| adb pull /sdcard/Android/data/com.autonavi.minimap/files/trace/
|
1.4 Perfetto查看
https://ui.perfetto.dev/
将导出的文件拖入perfetto网页
输入关注的方法:例如我想看VUICenter中的onPageLifeCreate方法
搜索:onPageLifeCreate 或者VUICenter|onPageLifeCreate
使用快捷键 w放大,s缩小,a左移,d右移
2 原理
我们这里使用了atrace的日志输出格式,我们给所有的关注的方法进行插桩,在方法开始和结束的位置进行打点,然后将打的点进行输出,输出格式是atrace的日志格式。
atrace的日志格式,我么可以使用systrace和perfetto进行解析。
2.1 atrace日志格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| capturing trace... done TRACE: # tracer: nop # # entries-in-buffer/entries-written: 251577/251577 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION # | | | | |||| | | <...>-14676 (14676) [004] .... 362012.007768: tracing_mark_write: B|14676|MainActivity:onCreate <...>-14676 (14676) [004] .... 362011.991722: tracing_mark_write: E|14676
|
格式如下:
1
| <线程名>-<线程id> (进程id) [cpu] .... <us时间戳>: tracing_mark_write: <B|E>|<进程id>|<tag>|<args>
|
那么我们只需要在方法开始和结束的地方进行插桩,将上方的信息写入到trace日志中,即可统计到方法耗时。
我们导出的日志如下
1 2 3 4 5 6 7 8 9
| TRACE: # tracer: nop #
main-30573 (30573) [001] ... 513231.528305: tracing_mark_write: B|30573|com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|loadVCS main-30573 (30573) [001] ... 513231.583242: tracing_mark_write: E|30573|com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|loadVCS TaskScheduler#3-30674 (30573) [001] ... 513231.729805: tracing_mark_write: B|30573|com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|preLoad TaskScheduler#3-30674 (30573) [001] ... 513231.732829: tracing_mark_write: E|30573|com.autonavi.bundle.vui.impl.VUIOuterServiceImpl|preLoad
|
2.2 插桩
这里插桩使用了tranform + asm;
AmapTrace 主要是将信息写入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| public class AmapTrace {
static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static List<String> traces = new ArrayList<>();
public static int pid = 0; public static boolean isFirst = true;
public static final String FILE_NAME = "trace-" + System.currentTimeMillis(); public static String FILE_PATH;
public static final Map<Long, Integer> threadMap = new HashMap<>();
public static String getFilePath() { return FILE_PATH + File.separator + "files" + File.separator + "trace" + File.separator + FILE_NAME; }
public static void startTrace(String methodId) { if (pid == 0) { pid = android.os.Process.myPid(); }
final long time = SystemClock.elapsedRealtimeNanos();
Integer tid = getTid();
String pkg = Thread.currentThread().getName();
executorService.execute(new Runnable() { @Override public void run() { realStartTrace(methodId, tid, pkg, time); } }); }
private static int getTid() { Integer aLong = threadMap.get(Thread.currentThread().getId()); if (aLong == null) { aLong = Process.myTid(); threadMap.put(Thread.currentThread().getId(), aLong); } return aLong; }
public static void endTrace(String methodId) { if (pid == 0) { pid = android.os.Process.myPid(); }
final long time = SystemClock.elapsedRealtimeNanos();
int tid = getTid();
String pkg = Thread.currentThread().getName();
executorService.execute(new Runnable() { @Override public void run() { realEndTrace(methodId, tid, pkg, time); } });
}
private static void realStartTrace(String methodId, int tid, String pkg, long time) { String us = String.valueOf(time / 1000); us = us.substring(0, 6) + "." + us.substring(6); traces.add(String.format("%s-%d (%d) [001] ... %s: tracing_mark_write: B|%d|%s", pkg, tid, pid, us, pid, methodId)); }
private static void realEndTrace(String methodId, int tid, String pkg, long time) {
String us = String.valueOf(time / 1000); us = us.substring(0, 6) + "." + us.substring(6); traces.add(String.format("%s-%d (%d) [001] ... %s: tracing_mark_write: E|%d|%s", pkg, tid, pid, us, pid, methodId)); if (traces.size() > 1000) { export(); } }
public static void export() { } }
|
文件中FILE_PATH 没有进行赋值,是在编译阶段进行的字节码修改
3 问题
如果一个包中同时存在大小写class,解压时被覆盖的问题,如下,r.class 的内容会被R.class的内容覆盖。
这个问题暂时还没有解决,目前处理是当遇到这个jar包时,直接过滤了。
4 未来
未来计划使用Trace.beginSection和Trace.endSection,主要需要解决2个问题。
- hook FD : /sys/kernel/debug/tracing/trace_marker
- atrace_enabled_tags 的值返回1
hook 使用kwai::linker