使用Jacoco做线上代码覆盖率分析

背景

代码覆盖率,就是衡量代码分支被运行到的比例,所以被称作覆盖率。

大家都知道,jacoco可以分析单测分支覆盖率,例如用Idea可以统计单测覆盖率。 但是在线上运行的核心链路中,代码沉淀好几年,俗称shi山代码,又如何对线上代码影响最小的方式,统计线上覆盖率并重构。

调研

Jacoco插桩原理简介:利用Javaagent的修改字节码技术,每个类分配一个Boolean数组,在一些分支,方法中插入boolean修改值,这样走到该方法或者分支上的Boolean值就是true,通过这种方式可以分析出是否覆盖。

该插桩对代码影响可以说非常小,对接口rt也基本不会有影响,可以选择jacoco做覆盖率分析,对线上应用影响可以忽略。

理论

Jacoco agent有两种模式

  • 模式一:Offline模式,提前对jar包做修改,运行中生成覆盖率信息到文件中,适合单测使用
  • 模式二:On-the-fly,用javaagent进行动态修改字节码,在程序关闭后自动生成一个exec文件
  • 模式三:On-the-fly tcpClient模式,需要自己搭建一个jacocoServer,显然成本太高,并且不同环境隔离的问题比较麻烦
  • 模式四:On-the-fly tcpServer模式,agent会启动一个tcp服务,需要的时候用jacocoClient从该端口拉当前覆盖率

线上应用不能随便停机,而且现在很多应用都是容器化,停后文件会丢失,那么显而易见,模式四完美解决了这个问题和我们的需求,那么我们只要将代码放到线上跑7天,然后进入机器用jacocoClient dump下来jacoco.exec文件,实时分析。

实践

理论可行,实践开始。

下载jacoco,官网下载随便选择一个,我这里选择0.8.8,切记agent和client要版本一致就行。

解压后进入lib目录下,可以看见下图,我们会用到jacocoagent.jar和jacococli.jar,解压出来

我们先创建一个SpringBoot项目,普通Java项目也可以通过这种方式agent

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>org.example</groupId>
    <artifactId>JacocoSpringBoot</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.13</version>
        </dependency>
    </dependencies>
</project>

Application

java 复制代码
package com.zhusheng.jacoco;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

TestController

java 复制代码
package com.zhusheng.jacoco.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;

@RestController
public class TestController {
    
    private List<String> list = Collections.emptyList();
    
    @GetMapping("/test")
    public String getTest(@RequestParam("id") Long id) {
        if (id == 1) {
            System.out.println("走到这里");
        } else {
            System.out.println("走到下面");
        }
        return id.toString();
    }
    
    public String test() {
        List<String> list1 = list;
        System.out.println(1);
        return "没走到这里";
    }
    
}

Config

java 复制代码
package com.zhusheng.jacoco.config;


public class Config {
    
    public Config() {
        int a = 10;
        int b = 20;
        System.out.println("走到这里");
    }
}

TestConfiguration

java 复制代码
package com.zhusheng.jacoco.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class TestConfiguration {
    
    @Bean
    public Config config() {
        System.out.println("走到这里");
        return new Config();
    }
}

启动

添加agent参数

java 复制代码
-javaagent:D:\openSource\jacocoagent.jar=includes=com.zhusheng.jacoco.*,output=tcpserver,port=2024,address=127.0.0.1

参数含义

D:\openSource\jacocoagent.jar是你解压jacocoagent的绝对路径

includes=com.zhusheng.jacoco.*表示只对com.zhusheng.jacoco包下做插桩

output=tcpserver以tpcServer模式启动

port=2024表示tcp端口2024

address=127.0.0.1可访问ip

访问一下 http://localhost:8080/test?id=1 测试效果 用lsof可以看下2024端口是否被监听成功

导出exec并分析

来到jacococli的目录下

java 复制代码
java -jar jacococli.jar dump --address localhost --port 2024 --destfile ./jacoco.exec

可以测试一下是否agent成功

java 复制代码
java -jar jacococli.jar execinfo jacoco.exec

可以看到,jacoco已经成功插桩并且在项目稳定运行中可以实时统计出覆盖率。

分析

这里推荐直接使用idea进行分析
RUN->Show Coverage Data...选择刚刚dump下来的jacoco.exec

左侧为是否hit到该行 覆盖率 类覆盖率,方法覆盖率,行覆盖率 注意 :如果发现覆盖率都是0,则是jacoco.exec中的classId跟你目前的classId匹配不上,在idea中重新build一下并且确保本地代码跟线上代码是一致的,否则会出现覆盖率为0的问题。

拿到行覆盖率后,就可以愉快的删除shi山代码以及重构了,再也不怕因为不知道这行代码用不用的上而不敢删别人的垃圾代码了。

总结

jacoco能以一种侵入非常低的方式,并且不需要线上停机的方式统计出线上真实的代码覆盖率。有想更深入了解jacoco原理的可以去学习下,jacoco是统计覆盖率最优解也是相对完美的解决方案了,本文就不做太多扩展。

相关推荐
hummhumm1 分钟前
Oracle 第29章:Oracle数据库未来展望
java·开发语言·数据库·python·sql·oracle·database
wainyz10 分钟前
Java NIO操作
java·开发语言·nio
工业3D_大熊16 分钟前
【虚拟仿真】CEETRON SDK在船舶流体与结构仿真中的应用解读
java·python·科技·信息可视化·c#·制造·虚拟现实
lzb_kkk25 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
爬山算法1 小时前
Maven(28)如何使用Maven进行依赖解析?
java·maven
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java