使用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是统计覆盖率最优解也是相对完美的解决方案了,本文就不做太多扩展。

相关推荐
程序员阿鹏34 分钟前
ArrayList 与 LinkedList 的区别?
java·开发语言·后端·eclipse·intellij-idea
18你磊哥36 分钟前
java重点学习-JVM类加载器+垃圾回收
java·jvm
聂 可 以38 分钟前
在SpringBoot项目中利用Redission实现布隆过滤器(布隆过滤器的应用场景、布隆过滤器误判的情况、与位图相关的操作)
java·spring boot·redis
长安初雪1 小时前
Java客户端SpringDataRedis(RedisTemplate使用)
java·redis
aloha_7891 小时前
B站宋红康JAVA基础视频教程(chapter14数据结构与集合源码)
java·数据结构·spring boot·算法·spring cloud·mybatis
尘浮生1 小时前
Java项目实战II基于Java+Spring Boot+MySQL的洗衣店订单管理系统(开发文档+源码+数据库)
java·开发语言·数据库·spring boot·mysql·maven·intellij-idea
猿饵块2 小时前
cmake--get_filename_component
java·前端·c++
编程小白煎堆2 小时前
C语言:枚举类型
java·开发语言
王哈哈嘻嘻噜噜2 小时前
c语言中“函数指针”
java·c语言·数据结构
qq_339191142 小时前
spring boot admin集成,springboot2.x集成监控
java·前端·spring boot