1 探針策略
- 在一系列字節碼指令中插入這些屬性呢,只要該屬性被執行了,說明其之前的指令都被執行了
- 注意方法結束了是在 return 指令前放置探針哦
- 跳轉語句的記錄
- 條件語句
2 探針特點
探測的唯一目的是記錄它至少執行過一次。探測器不記錄它被調用的次數或收集任何時間信息。後者超出了代碼覆蓋率分析的範圍,更多的是在性能分析工具的目標中
- 最小的運行時間開銷
- 對應用程序代碼無副作用
- 線程安全
- 記錄字節碼的執行
- 標識不同類型探針
使用的 boolean 數組記錄對應的指令是否被執行
3 爲什麼最小的性能開銷?
javap -c Fun
...
0: getstatic #2 // Field $assertionsDisabled:Z
3: ifne 18
6: iload_1
7: ifne 18
10: new #3 // class java/lang/AssertionError
13: dup
14: invokespecial #4 // Method java/lang/AssertionError."<init>":()V
17: athrow
18: return
...
- 其實翻譯過來代碼就是這樣子
- 通過將這種原理用於 jacoco,降低了性能開銷
4 如何實現代碼注入
JaCoCo通過ASM在字節碼中插入Probe指針(探測指針),每個探測指針都是一個BOOL變量(true表示執行、false表示沒有執行),程序運行時通過改變指針的結果來檢測代碼的執行情況(不會改變原代碼的行爲).
增量注入
JaCoCo默認全量注入.
源碼中注入的邏輯主要在ClassProbesAdapter
ASM在遍歷字節碼時,每次訪問一個方法定義,都會回調這個類的visitMethod
方法 ,在visitMethod
方法中再調用ClassProbeVisitor
的visitMethod
方法,並最終調用MethodInstrumenter
完成注入。部分代碼片段如下:
@Override
public final MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature,
final String[] exceptions) {
final MethodProbesVisitor methodProbes;
final MethodProbesVisitor mv = cv.visitMethod(access, name, desc,
signature, exceptions);
if (mv == null) {
// 無論如何,我們都需要訪問該方法,否則探針的ID無法重現
methodProbes = EMPTY_METHOD_PROBES_VISITOR;
} else {
methodProbes = mv;
}
return new MethodSanitizer(null, access, name, desc, signature,
exceptions) {
@Override
public void visitEnd() {
super.visitEnd();
LabelFlowAnalyzer.markLabels(this);
final MethodProbesAdapter probesAdapter = new MethodProbesAdapter(
methodProbes, ClassProbesAdapter.this);
if (trackFrames) {
final AnalyzerAdapter analyzer = new AnalyzerAdapter(
ClassProbesAdapter.this.name, access, name, desc,
probesAdapter);
probesAdapter.setAnalyzer(analyzer);
methodProbes.accept(this, analyzer);
} else {
methodProbes.accept(this, probesAdapter);
}
}
};
}
自動獲取運行時數據
代碼中通過反射執行下面的函數來獲取運行時數據,並保存到當前執行代碼的設備中:
org.jacoco.agent.rt.RT.getAgent().getExecutionData(false)
生成報告時需要用到運行時數據,爲了生成的覆蓋率報告更準確、開發同學用起來更方便,分別在如下時機把運行時數據保存到當前設備中:
- 每個頁面執行onDestory時
- 程序發生崩潰時
- 收到特定廣播(一個自定義的廣播,在執行生成覆蓋率報告的task前發送)時
並在生成覆蓋率報告之前把設備中的運行時數據同步到本地開發環境中。
上面可以看到,因爲獲取時機比較多,可能會得到多份運行時數據,對於這些數據,可以通過JaCoCo的mergeTask把ClassId相同的運行時數據進行merge。如下圖所示,JaCoCo會對ClassId相同的運行時數據進行merge,並對相同位置的probe指針取或: