jacoco-实战篇-多服务exec文件采集、合并、下载


 
背景:
  由于目前环境使用容器,对应的pod可能存在替换,直接干掉,挂掉等情况,已经ip的问题,所以使用tcpserver的方式不可行,故采用tcpclient方式,将数据进行收集到指定某个tcpserver端
 
架构设计图:
  待补充
 

jacoco-server:

1 代码覆盖率数据采集服务:
    负责和提供数据的目标服务交互,目标服务启动时,指定jacoco-server的相关参数,server端会为每个目标服务启动一个socket线程,持续接收数据流,并形成exec文件,保存至服务器硬盘。 2 jacoco文件下载服务:
    负责和jacoco-client交互,建立socket连接,将服务器硬盘中的所有exec文件合并并写入流中。

 

代码部分:

程序主函数:

 1 package org.jacoco.examples;
 2 
 3 import org.apache.commons.cli.CommandLine;
 4 import org.apache.commons.cli.CommandLineParser;
 5 import org.apache.commons.cli.DefaultParser;
 6 import org.apache.commons.cli.Options;
 7 import org.apache.commons.cli.ParseException;
 8 
 9 /**
10  * Jacoco-server
11  * @author yanfuchang 
12  */
13 public class JacocoServer {
14 
15     public static void main(String[] args) throws ParseException {
16         // 实际运行打成jar包方式
17         // 创建commons-option参数映射表
18         // -filepath E:\jacoco\ -address 127.0.0.1 -dataport 6300 -downloadport 6301 -filemaxnum 2000
19         // CommandLineParser commandLineParser = new DefaultParser();
20         // Options options = new Options();
21         // options.addOption("filepath", true, "jacoco文件存放路径");
22         // options.addOption("address", true, "socket主机地址");
23         // options.addOption("dataport", true, "数据采集服务端口号");
24         // options.addOption("downloadport", true, "文件下载服务端口号");
25         // options.addOption("filemaxnum", true, "数据采集服务文件数最大阈值");
26         // CommandLine commandLine = commandLineParser.parse(options, args);
27         // // 获取服务参数
28         // String filepath = commandLine.getOptionValue("filepath");
29         // String address = commandLine.getOptionValue("address");
30         // String dataPort = commandLine.getOptionValue("dataport");
31         // String downLoadPort = commandLine.getOptionValue("downloadport");
32         // String fileMaxNum = commandLine.getOptionValue("filemaxnum");
33 
34         // 本地测试部分
35         String filepath = "/Users/xxxx/Documents/Jacoco/";
36         String address = "127.0.0.1";
37         String dataPort = "9123";
38         String downLoadPort = "9124";
39         String fileMaxNum = "200";
40         // 代码覆盖率数据采集服务
41         new ExecuteDataServer(filepath, address, dataPort, fileMaxNum).execute();
42         // Jacoco-exec文件下载服务
43         new DownLoadDataServer(filepath, address, downLoadPort).execute();
44     }
45 }

 

代码覆盖率数据采集服务核心代码部分:

  1 package org.jacoco.examples;
  2 
  3 import java.io.File;
  4 import java.io.FileOutputStream;
  5 import java.io.IOException;
  6 import java.net.InetAddress;
  7 import java.net.ServerSocket;
  8 import java.net.Socket;
  9 import java.util.UUID;
 10 
 11 import org.apache.commons.io.FileUtils;
 12 import org.jacoco.core.data.ExecutionData;
 13 import org.jacoco.core.data.ExecutionDataWriter;
 14 import org.jacoco.core.data.IExecutionDataVisitor;
 15 import org.jacoco.core.data.ISessionInfoVisitor;
 16 import org.jacoco.core.data.SessionInfo;
 17 import org.jacoco.core.runtime.RemoteControlReader;
 18 import org.jacoco.core.runtime.RemoteControlWriter;
 19 
 20 /**
 21  * 代码覆盖率数据采集服务
 22  * @author yanfuchang 
 23  */
 24 public class ExecuteDataServer extends AbstractServer {
 25 
 26     private static String FILEPATH = "/Users/yanfuchang/Documents/Jacoco/";
 27     private static String ADDRESS = "0.0.0.0";
 28     private static int PORT = 6300;
 29     private static int FILEMAXNUM = 2000;
 30     private static final String EXEC = ".exec";
 31 
 32     ExecuteDataServer(String filePath, String address, String port, String fileMaxNum) {
 33         if (filePath != null && !filePath.equals("")) {
 34             FILEPATH = filePath;
 35         }
 36         if (address != null && !address.equals("")) {
 37             ADDRESS = address;
 38         }
 39         if (port != null && !port.equals("")) {
 40             PORT = Integer.parseInt(port);
 41         }
 42         if (fileMaxNum != null && !fileMaxNum.equals("")) {
 43             FILEMAXNUM = Integer.parseInt(fileMaxNum);
 44         }
 45     }
 46 
 47     @Override
 48     ServerSocket getServer() throws IOException {
 49         return new ServerSocket(PORT, 0, InetAddress.getByName(ADDRESS));
 50     }
 51 
 52     @Override
 53     Runnable getHandler(Socket server) {
 54         try {
 55             return new Handler(server);
 56         } catch (IOException e) {
 57             e.printStackTrace();
 58         }
 59         return null;
 60     }
 61 
 62     private class Handler implements Runnable, ISessionInfoVisitor, IExecutionDataVisitor {
 63         private final Socket socket;
 64         private final RemoteControlReader reader;
 65         private ExecutionDataWriter fileWriter;
 66         private final RemoteControlWriter remoteWriter;
 67 
 68         Handler(final Socket socket) throws IOException {
 69             this.socket = socket;
 70             remoteWriter = new RemoteControlWriter(socket.getOutputStream());
 71             reader = new RemoteControlReader(socket.getInputStream());
 72             reader.setRemoteCommandVisitor(remoteWriter);
 73             reader.setSessionInfoVisitor(this);
 74             reader.setExecutionDataVisitor(this);
 75         }
 76 
 77         @Override
 78         public void run() {
 79             File file = null;
 80             try {
 81                 // while循环读取socket
 82                 while (true) {
 83                     // 获取文件名称集合
 84                     File[] files = new File(FILEPATH).listFiles();
 85                     // 判断文件数是否超过阈值
 86                     if (files != null && files.length > FILEMAXNUM) {
 87                         // 等待下一次循环
 88                         Thread.sleep(10000);
 89                         continue;
 90                     }
 91                     // 创建exec文件保存路径
 92                     String path = FILEPATH + UUID.randomUUID();
 93                     file = new File(path);
 94                     // 获取文件输出流
 95                     try (FileOutputStream outputStream = FileUtils.openOutputStream(file)) {
 96                         System.out.println("open" + path);
 97                         // 更新输出流到ExecutionDataWriter
 98                         fileWriter = new ExecutionDataWriter(outputStream);
 99                         remoteWriter.visitDumpCommand(true, false);
100                         // 判断是否读取完毕
101                         if (!reader.read()) {
102                             break;
103                         }
104                         // 写入输出流
105                         fileWriter.flush();
106                     }
107                     System.out.println("close" + path);
108                     // 修改文件名称,加.exec后缀名
109                     file.renameTo(new File(path + EXEC));
110                     // 等待下一次循环
111                     Thread.sleep(300000);
112                 }
113             } catch (final IOException | InterruptedException e) {
114                 if (file != null) {
115                     file.delete();
116                 }
117                 e.printStackTrace();
118             } finally {
119                 if (socket != null) {
120                     try {
121                         socket.close();
122                     } catch (IOException e) {
123                         e.printStackTrace();
124                     }
125                 }
126             }
127         }
128 
129         @Override
130         public void visitSessionInfo(final SessionInfo info) {
131             System.out.printf("Retrieving execution Data for session: %s%n", info.getId());
132             fileWriter.visitSessionInfo(info);
133         }
134 
135         @Override
136         public void visitClassExecution(final ExecutionData data) {
137             fileWriter.visitClassExecution(data);
138         }
139     }
140 }

 

Jacoco-exec文件下载服务:

 1 package org.jacoco.examples;
 2 
 3 import java.io.File;
 4 import java.io.IOException;
 5 import java.net.InetAddress;
 6 import java.net.ServerSocket;
 7 import java.net.Socket;
 8 import java.util.concurrent.locks.ReentrantLock;
 9 import java.util.stream.Stream;
10 
11 import org.jacoco.core.tools.ExecFileLoader;
12 import org.jacoco.utils.AutoUnlockUtils;
13 
14 /**
15  * jacoco文件下载服务
16  * @author yanfuchang
17  */
18 public class DownLoadDataServer extends AbstractServer {
19 
20     private static String FILEPATH = "/Users/yanfuchang/Documents/Jacoco";
21     private static String ADDRESS = "0.0.0.0";
22     private static int PORT = 6301;
23     private static final String EXEC = ".exec";
24 
25     DownLoadDataServer(String filePath, String address, String port) {
26         if (filePath != null && !filePath.equals("")) {
27             FILEPATH = filePath;
28         }
29         if (address != null && !address.equals("")) {
30             ADDRESS = address;
31         }
32         if (port != null && !port.equals("")) {
33             PORT = Integer.parseInt(port);
34         }
35     }
36 
37     @Override
38     ServerSocket getServer() throws IOException {
39         return new ServerSocket(PORT, 0, InetAddress.getByName(ADDRESS));
40     }
41 
42     @Override
43     Runnable getHandler(Socket server) {
44         return new Handler(server);
45     }
46 
47     private static class Handler implements Runnable {
48 
49         private final Socket socket;
50         private static final ReentrantLock lock = new ReentrantLock();
51 
52         Handler(final Socket socket) {
53             this.socket = socket;
54         }
55 
56         @Override
57         public void run() {
58             try (AutoUnlockUtils autoUnlockUtils = new AutoUnlockUtils(lock)) {
59                 autoUnlockUtils.lock();
60                 // 获取文件夹
61                 File mergeDir = new File(FILEPATH);
62                 // 获取文件名称集合
63                 File[] files = mergeDir.listFiles();
64                 // 判断是否有文件
65                 if (files == null || files.length == 0) {
66                     return;
67                 }
68                 // 取指定后缀名为.exec的文件
69                 files = Stream.of(files).filter(file -> file.getAbsolutePath().endsWith(EXEC)).toArray(File[]::new);
70                 // create loader
71                 ExecFileLoader loader = new ExecFileLoader();
72                 // 循环读取文件并写入到输出流
73                 for (File file : files) {
74                     loader.load(file);
75                     loader.save(socket.getOutputStream());
76                 }
77                 // 删除文件
78                 for (File file : files) {
79                     file.delete();
80                 }
81             } catch (final IOException e) {
82                 e.printStackTrace();
83             } finally {
84                 if (socket != null) {
85                     try {
86                         socket.close();
87                     } catch (IOException e) {
88                         e.printStackTrace();
89                     }
90                 }
91             }
92         }
93     }
94 }

 

对外提供服务抽象类:

 1 package org.jacoco.examples;
 2 
 3 import java.io.IOException;
 4 import java.net.ServerSocket;
 5 import java.net.Socket;
 6 
 7 /**
 8  * Jacoco服务抽象类
 9  * @author yanfuchang 
10  */
11 public abstract class AbstractServer {
12 
13     /**
14      * 获取ServerSocket
15      */
16     abstract ServerSocket getServer() throws IOException;
17 
18     /**
19      * 获取Handler
20      */
21     abstract Runnable getHandler(Socket server);
22 
23     /**
24      * 监听server端口逻辑
25      */
26     private void doHandler() throws IOException {
27         ServerSocket server = getServer();
28         // 开启监听
29         while (true) {
30             Runnable handler = getHandler(server.accept());
31             new Thread(handler).start();
32         }
33     }
34 
35     /**
36      * 创建新线程执行doHandler方法,防止单个server中accept方法阻塞
37      */
38     public void execute() {
39         new Thread(() -> {
40             try {
41                 doHandler();
42             } catch (IOException e) {
43                 e.printStackTrace();
44             }
45         }).start();
46     }
47 }

 

锁工具类:

 1 package org.jacoco.utils;
 2 
 3 import java.io.Closeable;
 4 import java.util.concurrent.locks.Lock;
 5 
 6 /**
 7  * 自动锁工具类
 8  *
 9  * @author yanfuchang
10  */
11 public class AutoUnlockUtils implements Closeable {
12 
13     private final Lock lock;
14 
15     public AutoUnlockUtils(Lock lock) {
16         this.lock = lock;
17     }
18 
19     public void lock() {
20         lock.lock();
21     }
22 
23     @Override
24     public void close() {
25         lock.unlock();
26     }
27 }

 

pom.xml文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>org.jacoco.example</groupId>
 8     <artifactId>jacoco-server</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10     <properties>
11         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12         <java.version>1.8</java.version>
13     </properties>
14 
15     <build>
16         <plugins>
17             <plugin>
18                 <groupId>org.apache.maven.plugins</groupId>
19                 <artifactId>maven-shade-plugin</artifactId>
20                 <version>3.2.1</version>
21                 <executions>
22                     <execution>
23                         <phase>package</phase>
24                         <goals>
25                             <goal>shade</goal>
26                         </goals>
27                         <configuration>
28                             <transformers>
29                                 <transformer
30                                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
31                                     <mainClass>org.jacoco.examples.JacocoServer</mainClass>
32                                 </transformer>
33                             </transformers>
34                         </configuration>
35                     </execution>
36                 </executions>
37             </plugin>
38             <plugin>
39                 <groupId>org.apache.maven.plugins</groupId>
40                 <artifactId>maven-compiler-plugin</artifactId>
41                 <configuration>
42                     <source>8</source>
43                     <target>8</target>
44                 </configuration>
45             </plugin>
46         </plugins>
47     </build>
48 
49     <dependencies>
50         <dependency>
51             <groupId>org.jacoco</groupId>
52             <artifactId>org.jacoco.core</artifactId>
53             <version>0.7.9</version>
54         </dependency>
55         <dependency>
56             <groupId>commons-io</groupId>
57             <artifactId>commons-io</artifactId>
58             <version>2.6</version>
59             <scope>compile</scope>
60         </dependency>
61         <dependency>
62             <groupId>commons-cli</groupId>
63             <artifactId>commons-cli</artifactId>
64             <version>1.4</version>
65         </dependency>
66     </dependencies>
67 </project>

 

jacoco-client:
  通过连接jacoco-server进行数据下载
 
 
核心代码部分:
 1 package org.jacoco.examples;
 2 
 3 import org.apache.commons.cli.CommandLine;
 4 import org.apache.commons.cli.CommandLineParser;
 5 import org.apache.commons.cli.DefaultParser;
 6 import org.apache.commons.cli.Options;
 7 import org.apache.commons.cli.ParseException;
 8 import org.apache.commons.io.FileUtils;
 9 
10 import java.io.File;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.net.Socket;
14 import java.text.SimpleDateFormat;
15 import java.util.Date;
16 
17 /**
18  * Jacoco-client
19  */
20 public class JacocoClient {
21 
22     private static String ADDRESS = "0.0.0.0";
23     private static int PORT = 6301;
24     private static String SAVE_PATH = "/Users/yanfuchang/Documents/Jacoco/jacoco-down/";
25     private static final String EXEC = ".exec";
26     private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyyMMddHHmmssSSS");
27 
28     public static void main(String[] args) throws ParseException, IOException {
29         // 初始化参数
30         // -savepath E:\jacoco-down\ -address 127.0.0.1 -port 6301
31         init(args);
32         System.out.println("jacoco-client init");
33         //创建保存的文件
34         File file = new File(SAVE_PATH + FORMATTER.format(new Date()) + EXEC);
35         // 连接server并获取输入流
36         try (Socket socket = new Socket(ADDRESS, PORT); InputStream is = socket.getInputStream()) {
37             // 流复制到文件
38             FileUtils.copyInputStreamToFile(is, file);
39             //文件大小为空,删除
40             if (FileUtils.sizeOf(file) == 0) {
41                 FileUtils.forceDeleteOnExit(file);
42             }
43             System.out.println("jacoco-client end");
44         } catch (Exception e) {
45             FileUtils.forceDeleteOnExit(file);
46             e.printStackTrace();
47         }
48     }
49 
50     /**
51      * 初始化参数
52      */
53     private static void init(String[] args) throws ParseException {
54         // 实际运行打成jar包方式
55         // // 创建commons-option参数映射表
56         // CommandLineParser commandLineParser = new DefaultParser();
57         // Options options = new Options();
58         // options.addOption("savepath", true, "jacoco文件下载路径");
59         // options.addOption("address", true, "下载服务-socket主机地址");
60         // options.addOption("port", true, "下载服务-socket端口号");
61         // CommandLine commandLine = commandLineParser.parse(options, args);
62         // // 获取服务参数
63         // String savePath = commandLine.getOptionValue("savepath");
64         // String address = commandLine.getOptionValue("address");
65         // String port = commandLine.getOptionValue("port");
66 
67         // 本地测试使用
68         String savePath = "/Users/yanfuchang/Documents/Jacoco/temp";
69         String address = "127.0.0.1";
70         String port = "9124";
71 
72         if (savePath != null && !savePath.equals("")) {
73             SAVE_PATH = savePath;
74         }
75         if (address != null && !address.equals("")) {
76             ADDRESS = address;
77         }
78         if (port != null && !port.equals("")) {
79             PORT = Integer.parseInt(port);
80         }
81     }
82 }

 

pom.xml文件:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>org.jacoco.example</groupId>
 8     <artifactId>jacoco-client</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10     <properties>
11         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12         <maven.compiler.source>8</maven.compiler.source>
13         <maven.compiler.target>8</maven.compiler.target>
14     </properties>
15 
16     <build>
17         <plugins>
18             <plugin>
19                 <groupId>org.apache.maven.plugins</groupId>
20                 <artifactId>maven-shade-plugin</artifactId>
21                 <version>3.2.1</version>
22                 <executions>
23                     <execution>
24                         <phase>package</phase>
25                         <goals>
26                             <goal>shade</goal>
27                         </goals>
28                         <configuration>
29                             <transformers>
30                                 <transformer
31                                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
32                                     <mainClass>org.jacoco.examples.JacocoClient</mainClass>
33                                 </transformer>
34                             </transformers>
35                         </configuration>
36                     </execution>
37                 </executions>
38             </plugin>
39         </plugins>
40     </build>
41 
42     <dependencies>
43         <dependency>
44             <groupId>commons-io</groupId>
45             <artifactId>commons-io</artifactId>
46             <version>2.6</version>
47             <scope>compile</scope>
48         </dependency>
49         <dependency>
50             <groupId>commons-cli</groupId>
51             <artifactId>commons-cli</artifactId>
52             <version>1.4</version>
53         </dependency>
54     </dependencies>
55 
56 </project>

 

 
 

 
实际使用说明:
1、先启动jacoco-server覆盖率采集和下载服务:java -jar jacoco-server.jar  -filepath /User/yanfuchang/Documents/Jacoco/ -address 127.0.01   -dataport  9123 -downloadport 9124 -filemaxnum 2000

 2、然后启动公司订单服务:java -javaagent:jacocoagent.jar=includes=*,output=tcpclient,port=9123,address=127.0.0.1,sessionid=fs-order,append=true -jar fs-order-0.0.1-SNAPSHOT.jar 

 3、当fs-order订单服务有请求时,jacoco-server会每个一段时间进行一次覆盖率采集,并根据fs-order服务启动时传的sessionId作为依据命名文件名称,然后将数据写入到该名称对应的文件中。

  4、当需要整体生成报告进行查看的时候,运行jacoco-client:java -jar jacoco-client.jar -filepath /User/yanfuchang/Documents/Jacoco/Jacoco-dump/ -address 127.0.01 -port 9124   ,此时会将server整理的所有exec文件,整体写到一个exec文件中,如果公司项目比较多,产生的覆盖率报告的文件也比较多时,第一种方式:修改jacoco-client和jacoco-server,根据jacoco-client传的内容进行指定文件下载。第二种方式,在jacoco-server所在服务器,单独写一个下载服务,提供api或者页面,根据指定路径、文件名称,进行覆盖率exec文件的下载,并执行生成报告的命令,然后将报告移动到nginx指定目录下,进行代理访问即可。

 

上述目前部署只用到了jacoco-server,对于服务器上根据指定目录、文件名称的文件下载和报告生成,待完善脚本

 

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM