天天看點

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

1 探針政策

  • 在一串位元組碼指令中插入這些探針,隻要該探針被執行了,說明其之前的指令都被執行了
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料
  • 注意方法結束了是在 return 指令前放置探針哦
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料
  • 跳轉語句的記錄
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料
  • 條件語句
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料
  • JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

2 探針特點

探測的唯一目的是記錄它至少執行過一次。探測器不記錄它被調用的次數或收集任何時間資訊。後者超出了代碼覆寫率分析的範圍,更多的是在性能分析工具的目标中

  • 最小的運作時間開銷
  • 對應用程式代碼無副作用
  • 線程安全
  • 記錄位元組碼的執行
  • 辨別不同類型探針

    使用的 boolean 數組記錄對應的指令是否被執行

3 為什麼最小的性能開銷?

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

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探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

通過将這種原理用于 jacoco,降低了性能開銷

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

4 如何實作代碼注入

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

JaCoCo通過ASM在位元組碼中插入Probe指針(探測指針),每個探測指針都是一個BOOL變量(true表示執行、false表示沒有執行),程式運作時通過改變指針的結果來檢測代碼的執行情況(不會改變原代碼的行為).

增量注入

JaCoCo預設全量注入.

源碼中注入的邏輯主要在ClassProbesAdapter

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

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指針取或:

JaCoCo探針政策原理及案例總結1 探針政策2 探針特點3 為什麼最小的性能開銷?4 如何實作代碼注入增量注入自動擷取運作時資料

繼續閱讀