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>
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,对于服务器上根据指定目录、文件名称的文件下载和报告生成,待完善脚本