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