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,官网下载 (opens new window)随便选择一个,我这里选择0.8.8,切记agent和client要版本一致就行。
解压后进入lib目录下,可以看见下图,我们会用到jacocoagent.jar和jacococli.jar,解压出来
我们先创建一个SpringBoot项目,普通Java项目也可以通过这种方式agent
pom.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
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
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
package com.zhusheng.jacoco.config;
public class Config {
public Config() {
int a = 10;
int b = 20;
System.out.println("走到这里");
}
}
TestConfiguration
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参数
-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 -jar jacococli.jar dump --address localhost --port 2024 --destfile ./jacoco.exec
可以测试一下是否agent成功
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是统计覆盖率最优解也是相对完美的解决方案了,本文就不做太多扩展。