From 9b458f37319cf7cb7ba65acd08b3271b8fb6a07e Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 1 May 2020 10:00:04 +0800 Subject: [PATCH 001/157] fit2cloud -> metersphere --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6c51bae..c45cedf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=node-controller -logging.file.path=/opt/fit2cloud/logs/${spring.application.name} +logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 From 40e5212837e65401165ef1b878f80bcaabfdcd21 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 13 May 2020 11:07:22 +0800 Subject: [PATCH 002/157] get set --- .../metersphere/controller/request/DockerLoginRequest.java | 6 ++++-- .../java/io/metersphere/controller/request/TestRequest.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java b/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java index fa01205..9767401 100644 --- a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java +++ b/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java @@ -1,8 +1,10 @@ package io.metersphere.controller.request; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; -@Data +@Getter +@Setter public class DockerLoginRequest { private String username; private String password; diff --git a/src/main/java/io/metersphere/controller/request/TestRequest.java b/src/main/java/io/metersphere/controller/request/TestRequest.java index f790f43..3ec0d3f 100644 --- a/src/main/java/io/metersphere/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/controller/request/TestRequest.java @@ -1,11 +1,13 @@ package io.metersphere.controller.request; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; import java.util.HashMap; import java.util.Map; -@Data +@Getter +@Setter public class TestRequest extends DockerLoginRequest { private int size; From 81a2574764bffda3dc3a6d3625833b193ecde735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=8C=AF?= Date: Thu, 4 Jun 2020 10:53:14 +0800 Subject: [PATCH 003/157] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 644a0c6..19f885b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD +ARG MS_VERSION=dev + RUN mkdir -p /opt/apps ADD target/node-controller-1.0.jar /opt/apps @@ -10,6 +12,8 @@ ENV JAVA_APP_JAR=/opt/apps/node-controller-1.0.jar ENV AB_OFF=true +ENV MS_VERSION=${MS_VERSION} + ENV JAVA_OPTIONS=-Dfile.encoding=utf-8 CMD ["/deployments/run-java.sh"] From 86507eca3c623401e014b8c2d4de8b2fc369f5ae Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 15 Jun 2020 14:40:44 +0800 Subject: [PATCH 004/157] =?UTF-8?q?=E5=81=9C=E6=AD=A2=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/service/JmeterOperateService.java | 2 +- src/main/java/io/metersphere/util/DockerClientService.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index 743a623..e9505fd 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -129,7 +129,7 @@ public void stopContainer(String testId) { .withNameFilter(Collections.singletonList(testId)) .exec(); // container stop - list.forEach(container -> DockerClientService.stopContainer(dockerClient, container.getId())); + list.forEach(container -> DockerClientService.removeContainer(dockerClient, container.getId())); } public List taskStatus(String testId) { diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index 7222232..c429534 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -83,7 +83,10 @@ public static void stopContainer(DockerClient client, String containerId) { * @param containerId */ public static void removeContainer(DockerClient client, String containerId) { - client.removeContainerCmd(containerId).exec(); + client.removeContainerCmd(containerId) + .withForce(true) + .withRemoveVolumes(true) + .exec(); } } From b11bf2dc298125f5f48dfda069083712289d1446 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Tue, 16 Jun 2020 17:20:49 +0800 Subject: [PATCH 005/157] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=9F=A5=E5=AE=B9=E5=99=A8=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=AD=98=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/JmeterOperateService.java | 4 +++- .../metersphere/util/DockerClientService.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index e9505fd..de1e3c5 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -77,7 +77,9 @@ public void onComplete() { try { FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); - DockerClientService.removeContainer(dockerClient, containerId); + if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + DockerClientService.removeContainer(dockerClient, containerId); + } LogUtil.info("Remove container completed: " + containerId); } catch (IOException e) { LogUtil.error("Remove dir error: ", e); diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index c429534..79b0449 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -2,6 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Info; import com.github.dockerjava.core.DefaultDockerClientConfig; @@ -9,6 +10,9 @@ import io.metersphere.controller.request.DockerLoginRequest; import org.apache.commons.lang.StringUtils; +import java.util.Collections; +import java.util.List; + public class DockerClientService { /** @@ -89,4 +93,17 @@ public static void removeContainer(DockerClient client, String containerId) { .exec(); } + /** + * 容器是否存在 + * @param client + * @param containerId + */ + public static int existContainer(DockerClient client, String containerId) { + List list = client.listContainersCmd() + .withShowAll(true) + .withIdFilter(Collections.singleton(containerId)) + .exec(); + return list.size(); + } + } From 61a06bb02d6a012c763b7910919075d9b621062d Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Wed, 17 Jun 2020 11:01:50 +0800 Subject: [PATCH 006/157] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=9F=A5kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/JmeterOperateService.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index de1e3c5..8238282 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -17,6 +17,9 @@ import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; @@ -26,6 +29,9 @@ public class JmeterOperateService { public void startContainer(TestRequest testRequest) throws IOException { + String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); + checkKafka(bootstrapServers); + LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); int size = testRequest.getSize(); @@ -90,6 +96,30 @@ public void onComplete() { }); } + private void checkKafka(String bootstrapServers) { + String[] servers = StringUtils.split(bootstrapServers, ","); + try { + for (String s : servers) { + String[] ipAndPort = s.split(":"); + //1,建立tcp + String ip = ipAndPort[0]; + int port = Integer.parseInt(ipAndPort[1]); + Socket soc = new Socket(); + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + //2.输入内容 + String content = "1010"; + byte[] bs = content.getBytes(); + OutputStream os = soc.getOutputStream(); + os.write(bs); + //3.关闭 + soc.close(); + } + } catch (Exception e) { + LogUtil.error(e); + throw new RuntimeException("Failed to connect to Kafka"); + } + } + private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); From f2e20db159710a8c6da3a76d1275fa5d4d315253 Mon Sep 17 00:00:00 2001 From: wangzhen-fit2cloud Date: Wed, 22 Jul 2020 19:41:37 +0800 Subject: [PATCH 007/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E4=B8=BA=201.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 19f885b..3f39b71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.0.jar /opt/apps +ADD target/node-controller-1.1.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.0.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.1.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index eaabcaf..a60ed5f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.0 + 1.1 node-controller node-controller From 464747c5a073e7ebc4d41adc71ffe948ad01462e Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 11 Aug 2020 15:38:43 +0800 Subject: [PATCH 008/157] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/util/DockerClientService.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java index 79b0449..2b25f78 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/util/DockerClientService.java @@ -4,7 +4,6 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Info; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.controller.request.DockerLoginRequest; @@ -21,11 +20,7 @@ public class DockerClientService { * @return */ public static DockerClient connectDocker() { - DockerClient dockerClient = DockerClientBuilder.getInstance().build(); - Info info = dockerClient.infoCmd().exec(); - LogUtil.info("docker的环境信息如下:================="); - LogUtil.info(info); - return dockerClient; + return DockerClientBuilder.getInstance().build(); } public static DockerClient connectDocker(DockerLoginRequest request) { @@ -37,11 +32,7 @@ public static DockerClient connectDocker(DockerLoginRequest request) { .withRegistryUsername(request.getUsername()) .withRegistryPassword(request.getPassword()) .build(); - DockerClient dockerClient = DockerClientBuilder.getInstance(config).build(); - Info info = dockerClient.infoCmd().exec(); - LogUtil.info("docker的环境信息如下:================="); - LogUtil.info(info); - return dockerClient; + return DockerClientBuilder.getInstance(config).build(); } /** @@ -95,6 +86,7 @@ public static void removeContainer(DockerClient client, String containerId) { /** * 容器是否存在 + * * @param client * @param containerId */ From 96bba101f670fc7f47fb604ceed0d78b43d1b2e4 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 21 Aug 2020 14:37:19 +0800 Subject: [PATCH 009/157] =?UTF-8?q?refactor:=20=E8=BE=93=E5=87=BA=20jmeter?= =?UTF-8?q?=20console=20=E7=9A=84=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/service/JmeterOperateService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java index 8238282..05c08bb 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/service/JmeterOperateService.java @@ -94,6 +94,21 @@ public void onComplete() { } }); }); + + containerIdList.forEach(containerId -> { + dockerClient.logContainerCmd(containerId) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + LogUtil.info(new String(item.getPayload()).trim()); + } + }); + + }); } private void checkKafka(String bootstrapServers) { From 644299df6efbc55f604e915b1fab7dd3534db11b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 Aug 2020 12:22:01 +0800 Subject: [PATCH 010/157] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E8=B0=83=E6=95=B4=EF=BC=8C=E6=B7=BB=E5=8A=A0=20jmeter?= =?UTF-8?q?=20HEAP=20=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 13 ------------- .../java/io/metersphere/node/Application.java | 19 +++++++++++++++++++ .../node/config/JmeterProperties.java | 13 +++++++++++++ .../controller/DockerClientController.java | 2 +- .../controller/JmeterOperateController.java | 6 +++--- .../request/DockerLoginRequest.java | 2 +- .../controller/request/TestRequest.java | 2 +- .../service/JmeterOperateService.java | 14 ++++++++++---- .../{ => node}/util/DockerClientService.java | 4 ++-- .../metersphere/{ => node}/util/LogUtil.java | 2 +- src/main/resources/logback.xml | 4 ++-- 11 files changed, 53 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/io/metersphere/Application.java create mode 100644 src/main/java/io/metersphere/node/Application.java create mode 100644 src/main/java/io/metersphere/node/config/JmeterProperties.java rename src/main/java/io/metersphere/{ => node}/controller/DockerClientController.java (86%) rename src/main/java/io/metersphere/{ => node}/controller/JmeterOperateController.java (93%) rename src/main/java/io/metersphere/{ => node}/controller/request/DockerLoginRequest.java (79%) rename src/main/java/io/metersphere/{ => node}/controller/request/TestRequest.java (89%) rename src/main/java/io/metersphere/{ => node}/service/JmeterOperateService.java (95%) rename src/main/java/io/metersphere/{ => node}/util/DockerClientService.java (96%) rename src/main/java/io/metersphere/{ => node}/util/LogUtil.java (99%) diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java deleted file mode 100644 index 010e94c..0000000 --- a/src/main/java/io/metersphere/Application.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.metersphere; - -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); - } - -} diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java new file mode 100644 index 0000000..702a592 --- /dev/null +++ b/src/main/java/io/metersphere/node/Application.java @@ -0,0 +1,19 @@ +package io.metersphere.node; + +import io.metersphere.node.config.JmeterProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; + +@EnableConfigurationProperties({ + JmeterProperties.class +}) +@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/io/metersphere/node/config/JmeterProperties.java b/src/main/java/io/metersphere/node/config/JmeterProperties.java new file mode 100644 index 0000000..38d9e4e --- /dev/null +++ b/src/main/java/io/metersphere/node/config/JmeterProperties.java @@ -0,0 +1,13 @@ +package io.metersphere.node.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) +@Setter +@Getter +public class JmeterProperties { + public static final String JMETER_PREFIX = "jmeter"; + private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; +} diff --git a/src/main/java/io/metersphere/controller/DockerClientController.java b/src/main/java/io/metersphere/node/controller/DockerClientController.java similarity index 86% rename from src/main/java/io/metersphere/controller/DockerClientController.java rename to src/main/java/io/metersphere/node/controller/DockerClientController.java index 516f498..7dea07c 100644 --- a/src/main/java/io/metersphere/controller/DockerClientController.java +++ b/src/main/java/io/metersphere/node/controller/DockerClientController.java @@ -1,4 +1,4 @@ -package io.metersphere.controller; +package io.metersphere.node.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/io/metersphere/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java similarity index 93% rename from src/main/java/io/metersphere/controller/JmeterOperateController.java rename to src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 7c8d24c..5a89f47 100644 --- a/src/main/java/io/metersphere/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -1,9 +1,9 @@ -package io.metersphere.controller; +package io.metersphere.node.controller; import com.github.dockerjava.api.model.Container; -import io.metersphere.controller.request.TestRequest; -import io.metersphere.service.JmeterOperateService; +import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.service.JmeterOperateService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; diff --git a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java b/src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java similarity index 79% rename from src/main/java/io/metersphere/controller/request/DockerLoginRequest.java rename to src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java index 9767401..52c854b 100644 --- a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java @@ -1,4 +1,4 @@ -package io.metersphere.controller.request; +package io.metersphere.node.controller.request; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/io/metersphere/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java similarity index 89% rename from src/main/java/io/metersphere/controller/request/TestRequest.java rename to src/main/java/io/metersphere/node/controller/request/TestRequest.java index 3ec0d3f..c063c36 100644 --- a/src/main/java/io/metersphere/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -1,4 +1,4 @@ -package io.metersphere.controller.request; +package io.metersphere.node.controller.request; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java similarity index 95% rename from src/main/java/io/metersphere/service/JmeterOperateService.java rename to src/main/java/io/metersphere/node/service/JmeterOperateService.java index 05c08bb..bba3126 100644 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -1,4 +1,4 @@ -package io.metersphere.service; +package io.metersphere.node.service; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; @@ -7,14 +7,16 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; -import io.metersphere.controller.request.TestRequest; -import io.metersphere.util.DockerClientService; -import io.metersphere.util.LogUtil; +import io.metersphere.node.config.JmeterProperties; +import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.util.DockerClientService; +import io.metersphere.node.util.LogUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import javax.annotation.Resource; import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -27,6 +29,8 @@ @Service public class JmeterOperateService { + @Resource + private JmeterProperties jmeterProperties; public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -137,6 +141,8 @@ private void checkKafka(String bootstrapServers) { private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); + // HEAP + env.put("HEAP", jmeterProperties.getHeap()); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); } diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java similarity index 96% rename from src/main/java/io/metersphere/util/DockerClientService.java rename to src/main/java/io/metersphere/node/util/DockerClientService.java index 2b25f78..ff7f083 100644 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -1,4 +1,4 @@ -package io.metersphere.util; +package io.metersphere.node.util; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; @@ -6,7 +6,7 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; -import io.metersphere.controller.request.DockerLoginRequest; +import io.metersphere.node.controller.request.DockerLoginRequest; import org.apache.commons.lang.StringUtils; import java.util.Collections; diff --git a/src/main/java/io/metersphere/util/LogUtil.java b/src/main/java/io/metersphere/node/util/LogUtil.java similarity index 99% rename from src/main/java/io/metersphere/util/LogUtil.java rename to src/main/java/io/metersphere/node/util/LogUtil.java index c955137..df77159 100644 --- a/src/main/java/io/metersphere/util/LogUtil.java +++ b/src/main/java/io/metersphere/node/util/LogUtil.java @@ -1,4 +1,4 @@ -package io.metersphere.util; +package io.metersphere.node.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index d5eae80..455a999 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -153,8 +153,8 @@ - - + + \ No newline at end of file From 0610b68d249e92d082c3f20714eb576a1eba3115 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 1 Sep 2020 16:03:35 +0800 Subject: [PATCH 011/157] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=EF=BC=8C=E5=90=AF=E5=8A=A8=E6=B5=8B=E8=AF=95=E6=97=B6?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=AE=B9=E5=99=A8=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/controller/request/TestRequest.java | 1 - .../node/service/JmeterOperateService.java | 111 ++++++++++-------- 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index c063c36..19f6ade 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -10,7 +10,6 @@ @Setter public class TestRequest extends DockerLoginRequest { - private int size; private String fileString; private String testId; private String image; diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index bba3126..ee8916b 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -23,7 +23,10 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -38,7 +41,6 @@ public void startContainer(TestRequest testRequest) throws IOException { LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); - int size = testRequest.getSize(); String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); @@ -59,60 +61,67 @@ public void startContainer(TestRequest testRequest) throws IOException { // 查找镜像 searchImage(dockerClient, testRequest.getImage()); + // 检查容器是否存在 + checkContainerExists(dockerClient, testId); + // 启动测试 + startContainer(testRequest, dockerClient, testId, containerImage, filePath); + } - ArrayList containerIdList = new ArrayList<>(); - for (int i = 0; i < size; i++) { - String containerName = testId + "-" + i; - // 创建 hostConfig - HostConfig hostConfig = HostConfig.newHostConfig(); - String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, containerName, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); - containerIdList.add(containerId); - } + private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + // 创建 hostConfig + HostConfig hostConfig = HostConfig.newHostConfig(); + String[] envs = getEnvs(testRequest); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); + // 从主机复制文件到容器 + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(filePath) + .withDirChildrenOnly(true) + .withRemotePath("/test") + .exec(); - containerIdList.forEach(containerId -> { - DockerClientService.startContainer(dockerClient, containerId); - LogUtil.info("Container create started containerId: " + containerId); - dockerClient.waitContainerCmd(containerId) - .exec(new WaitContainerResultCallback() { - @Override - public void onComplete() { - // 清理文件夹 - try { - FileUtils.forceDelete(new File(filePath)); - LogUtil.info("Remove dir completed."); - if (DockerClientService.existContainer(dockerClient, containerId) > 0) { - DockerClientService.removeContainer(dockerClient, containerId); - } - LogUtil.info("Remove container completed: " + containerId); - } catch (IOException e) { - LogUtil.error("Remove dir error: ", e); + DockerClientService.startContainer(dockerClient, containerId); + LogUtil.info("Container create started containerId: " + containerId); + dockerClient.waitContainerCmd(containerId) + .exec(new WaitContainerResultCallback() { + @Override + public void onComplete() { + // 清理文件夹 + try { + FileUtils.forceDelete(new File(filePath)); + LogUtil.info("Remove dir completed."); + if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + DockerClientService.removeContainer(dockerClient, containerId); } - LogUtil.info("completed...."); + LogUtil.info("Remove container completed: " + containerId); + } catch (IOException e) { + LogUtil.error("Remove dir error: ", e); } - }); - }); - - containerIdList.forEach(containerId -> { - dockerClient.logContainerCmd(containerId) - .withFollowStream(true) - .withStdOut(true) - .withStdErr(true) - .withTailAll() - .exec(new InvocationBuilder.AsyncResultCallback() { - @Override - public void onNext(Frame item) { - LogUtil.info(new String(item.getPayload()).trim()); - } - }); + LogUtil.info("completed...."); + } + }); + + dockerClient.logContainerCmd(containerId) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + LogUtil.info(new String(item.getPayload()).trim()); + } + }); + } - }); + private void checkContainerExists(DockerClient dockerClient, String testId) { + List list = dockerClient.listContainersCmd() + .withShowAll(true) + .withStatusFilter(Arrays.asList("created", "restarting", "running", "paused", "exited")) + .withNameFilter(Collections.singletonList(testId)) + .exec(); + if (!CollectionUtils.isEmpty(list)) { + list.forEach(container -> DockerClientService.removeContainer(dockerClient, container.getId())); + } } private void checkKafka(String bootstrapServers) { From fb4a23620c2a172abc0977020dceaa55fd731dec Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 2 Sep 2020 13:42:43 +0800 Subject: [PATCH 012/157] =?UTF-8?q?refactor:=20=E6=B7=BB=E5=8A=A0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index a60ed5f..cd0bbe3 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,10 @@ + + org.springframework.boot + spring-boot-configuration-processor + org.springframework.boot spring-boot-starter-web From ccb0e9a8f82c71af27d9a2ca7b8712a9be0f13d5 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 14 Sep 2020 16:06:43 +0800 Subject: [PATCH 013/157] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E5=AE=B9=E5=99=A8=E7=9A=84=E9=94=99=E8=AF=AF=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 +- .../controller/JmeterOperateController.java | 22 +----- .../RestControllerExceptionHandler.java | 18 +++++ .../node/controller/handler/ResultHolder.java | 73 +++++++++++++++++++ .../handler/ResultResponseBodyAdvice.java | 50 +++++++++++++ .../node/service/JmeterOperateService.java | 2 +- 6 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java create mode 100644 src/main/java/io/metersphere/node/controller/handler/ResultHolder.java create mode 100644 src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java diff --git a/pom.xml b/pom.xml index cd0bbe3..f746ac3 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,10 @@ org.projectlombok lombok - + + org.apache.commons + commons-lang3 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 5a89f47..e8f16e1 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -20,32 +20,18 @@ public class JmeterOperateController { * 初始化测试任务,根据需求启动若干个 JMeter Engine 容器 */ @PostMapping("/container/start") - public void startContainer(@RequestBody TestRequest testRequest) throws IOException { + public String startContainer(@RequestBody TestRequest testRequest) throws IOException { jmeterOperateService.startContainer(testRequest); - } - - // 上传测试相关文件,将请求传过来的脚本、测试数据等文件,拷贝到上述容器中 - @PostMapping("/upload/task") - public void uploadFile(String jmxString) { - // 挂载数据到启动的容器 - // FileUtil.saveFile(jmxString, "/User/liyuhao/test", "ceshi2.jmx"); - } - - // 启动测试任务,控制上述容器执行 jmeter 相关命令开始进行测试 - @PostMapping("/exec/task") - public void taskPerform() { - // 执行 jmx - // docker run -it -v /Users/liyuhao/test:/test justb4/jmeter:latest -n -t /test/ceshi3.jmx - // dockerClient.startContainerCmd("18894c7755b1").exec(); - // dockerClient.execCreateCmd("18894c7755b1").withAttachStdout(true).withCmd("jmeter", "-n", "-t", "/test/*.jmx"); + return "OK"; } /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ @GetMapping("container/stop/{testId}") - public void stopContainer(@PathVariable String testId) { + public String stopContainer(@PathVariable String testId) { jmeterOperateService.stopContainer(testId); + return "OK"; } /** diff --git a/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java b/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java new file mode 100644 index 0000000..de54f00 --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/RestControllerExceptionHandler.java @@ -0,0 +1,18 @@ +package io.metersphere.node.controller.handler; + + +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@RestControllerAdvice +public class RestControllerExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResultHolder msExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { + return ResultHolder.error(e.getMessage()); + } +} diff --git a/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java b/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java new file mode 100644 index 0000000..e3c1d2a --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/ResultHolder.java @@ -0,0 +1,73 @@ +package io.metersphere.node.controller.handler; + +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class ResultHolder { + public ResultHolder() { + this.success = true; + } + + private ResultHolder(Object data) { + this.data = data; + this.success = true; + } + + private ResultHolder(boolean success, String msg) { + this.success = success; + this.message = msg; + } + + private ResultHolder(boolean success, String msg, Object data) { + this.success = success; + this.message = msg; + this.data = data; + } + + // 请求是否成功 + private boolean success = false; + // 描述信息 + private String message; + // 返回数据 + private Object data = ""; + + public boolean isSuccess() { + return this.success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public static ResultHolder success(Object obj) { + return new ResultHolder(obj); + } + + public static ResultHolder error(String message) { + return new ResultHolder(false, message, null); + } + + public static ResultHolder error(String message, Object object) { + return new ResultHolder(false, message, object); + } + + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } +} diff --git a/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java new file mode 100644 index 0000000..e784219 --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java @@ -0,0 +1,50 @@ +package io.metersphere.node.controller.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.metersphere.node.util.LogUtil; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 统一处理返回结果集 + */ +@RestControllerAdvice(value = {"io.metersphere.node"}) +public class ResultResponseBodyAdvice implements ResponseBodyAdvice { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public boolean supports(MethodParameter methodParameter, Class> converterType) { + return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType) || StringHttpMessageConverter.class.isAssignableFrom(converterType); + } + + @Override + public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class> converterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + // 处理空值 + if (o == null && StringHttpMessageConverter.class.isAssignableFrom(converterType)) { + return null; + } + + if (!(o instanceof ResultHolder)) { + if (o instanceof String) { + ResultHolder success = ResultHolder.success(o); + try { + return objectMapper.writeValueAsString(success); + } catch (JsonProcessingException e) { + LogUtil.error(e); + return o; + } + } + return ResultHolder.success(o); + } + return o; + } + +} diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index ee8916b..48ef252 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -175,7 +175,7 @@ private void searchImage(DockerClient dockerClient, String imageName) { }).collect(Collectors.toList()); if (collect.size() == 0) { - throw new RuntimeException("Image Not Found."); + throw new RuntimeException("Image Not Found: " + imageName); } } From 6b614cdd2b9846d156f3d8aee67be997dec68925 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Sep 2020 10:32:24 +0800 Subject: [PATCH 014/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f39b71..e779aa1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.1.jar /opt/apps +ADD target/node-controller-1.3.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.1.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.3.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index a60ed5f..23bcc23 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.1 + 1.3 node-controller node-controller From 7f41d73e9f9ebbe9d1e9207b2159c5a8abc05eef Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 5 Nov 2020 16:01:24 +0800 Subject: [PATCH 015/157] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=94=AF=E6=8C=81=E4=B8=8A=E4=BC=A0jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/node/controller/request/TestRequest.java | 1 + .../metersphere/node/service/JmeterOperateService.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index 19f6ade..74c7518 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -15,4 +15,5 @@ public class TestRequest extends DockerLoginRequest { private String image; private Map testData = new HashMap<>(); private Map env = new HashMap<>(); + private Map testJars = new HashMap<>(); } diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 48ef252..124a0a2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -59,6 +59,15 @@ public void startContainer(TestRequest testRequest) throws IOException { } } + // 保存 byte[] jar + Map jarFiles = testRequest.getTestJars(); + if (!CollectionUtils.isEmpty(jarFiles)) { + for (String k : jarFiles.keySet()) { + byte[] v = jarFiles.get(k); + FileUtils.writeByteArrayToFile(new File(filePath + File.separator + k), v); + } + } + // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 From c9bf63ae90952eac5c9dd043278e14633d85aff0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 11 Nov 2020 17:57:08 +0800 Subject: [PATCH 016/157] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterOperateController.java | 17 +++++++++ .../node/controller/request/TestRequest.java | 1 + .../node/service/JmeterOperateService.java | 37 ++++++++++++++++--- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index e8f16e1..3ca6ed1 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -4,6 +4,9 @@ import com.github.dockerjava.api.model.Container; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.service.JmeterOperateService; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -34,6 +37,20 @@ public String stopContainer(@PathVariable String testId) { return "OK"; } + @GetMapping("download/jtl/{reportId}") + public ResponseEntity downloadJtl(@PathVariable String reportId) { + byte[] bytes = jmeterOperateService.downloadJtl(reportId); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType("application/octet-stream")) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl" + "\"") + .body(bytes); + } + + @GetMapping("delete/jtl/{reportId}") + public boolean deleteJtl(@PathVariable String reportId) { + return jmeterOperateService.deleteJtl(reportId); + } + /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index 74c7518..eb14292 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -12,6 +12,7 @@ public class TestRequest extends DockerLoginRequest { private String fileString; private String testId; + private String reportId; private String image; private Map testData = new HashMap<>(); private Map env = new HashMap<>(); diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 124a0a2..c4e7b3d 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -12,14 +12,13 @@ import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -34,6 +33,7 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; + private static final String rootPath = StringUtils.join(new String[]{"", "opt", "node-data"}, File.separator); public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -44,7 +44,7 @@ public void startContainer(TestRequest testRequest) throws IOException { String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{"", "opt", "node-data", testId}, File.separator); + String filePath = StringUtils.join(new String[]{rootPath, testId}, File.separator); String fileName = testId + ".jmx"; @@ -96,13 +96,19 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { + String jtlFileName = testRequest.getReportId() + ".jtl"; + InputStream input = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) + .exec(); + + IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } LogUtil.info("Remove container completed: " + containerId); - } catch (IOException e) { + } catch (Exception e) { LogUtil.error("Remove dir error: ", e); } LogUtil.info("completed...."); @@ -244,4 +250,25 @@ public void onNext(Frame item) { } return sb.toString(); } + + public byte[] downloadJtl(String reportId) { + try { + String jtlFileName = reportId + ".jtl"; + return IOUtils.toByteArray(new FileInputStream(rootPath + File.separator + jtlFileName)); + } catch (IOException e) { + LogUtil.error(e); + } + return new byte[0]; + } + + public boolean deleteJtl(String reportId) { + String jtlFileName = reportId + ".jtl"; + try { + FileUtils.forceDelete(new File(rootPath + File.separator + jtlFileName)); + return true; + } catch (IOException e) { + LogUtil.error(e); + } + return false; + } } From d725bae6f9cb8ebea1cfe761cea1d5a50bc8ac8c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 10:22:17 +0800 Subject: [PATCH 017/157] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../node/service/JmeterOperateService.java | 39 +++++++++---------- .../node/util/DockerClientService.java | 5 ++- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 876c73e..2e0f4df 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ com.github.docker-java docker-java - 3.2.0 + 3.2.5 org.projectlombok diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index c4e7b3d..228c0ec 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,10 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.Container; -import com.github.dockerjava.api.model.Frame; -import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.*; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -18,14 +15,14 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -77,16 +74,18 @@ public void startContainer(TestRequest testRequest) throws IOException { } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + List volumes = new ArrayList<>(); + List binds = new ArrayList<>(); + final Volume volume = new Volume("/test"); + volumes.add(volume); + final Bind bind = new Bind(filePath, volume); + binds.add(bind); // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); + hostConfig.withBinds(binds); + String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, volumes, envs).getId(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -97,11 +96,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; - InputStream input = dockerClient - .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) - .exec(); - IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); + File srcFile = new File(filePath + File.separator + jtlFileName); + File destDir = new File(rootPath + File.separator + jtlFileName); + + FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { diff --git a/src/main/java/io/metersphere/node/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java index ff7f083..c977795 100644 --- a/src/main/java/io/metersphere/node/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -4,6 +4,7 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.node.controller.request.DockerLoginRequest; @@ -41,11 +42,13 @@ public static DockerClient connectDocker(DockerLoginRequest request) { * @param client * @return */ - public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, HostConfig hostConfig, String... env) { + public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, + HostConfig hostConfig, List volumes, String... env) { CreateContainerResponse container = client.createContainerCmd(imageName) .withName(containerName) .withHostConfig(hostConfig) .withEnv(env) + .withVolumes(volumes) .exec(); return container; } From a74754ad8aa0f926e1e18c310812f7456f4fa07b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 10:44:09 +0800 Subject: [PATCH 018/157] =?UTF-8?q?feat:=20=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E5=AD=98jtl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/node/service/JmeterOperateService.java | 2 +- .../java/io/metersphere/node/util/DockerClientService.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 228c0ec..f34cf89 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -85,7 +85,7 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, hostConfig.withBinds(binds); String[] envs = getEnvs(testRequest); - String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, volumes, envs).getId(); + String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); diff --git a/src/main/java/io/metersphere/node/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java index c977795..9d0766a 100644 --- a/src/main/java/io/metersphere/node/util/DockerClientService.java +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -4,7 +4,6 @@ import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import io.metersphere.node.controller.request.DockerLoginRequest; @@ -43,12 +42,11 @@ public static DockerClient connectDocker(DockerLoginRequest request) { * @return */ public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, - HostConfig hostConfig, List volumes, String... env) { + HostConfig hostConfig, String... env) { CreateContainerResponse container = client.createContainerCmd(imageName) .withName(containerName) .withHostConfig(hostConfig) .withEnv(env) - .withVolumes(volumes) .exec(); return container; } From 77e04657f8a0c503a3585994a61ece13153859a2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 11:02:17 +0800 Subject: [PATCH 019/157] =?UTF-8?q?refactor:=20=E5=9B=9E=E9=80=80=E5=88=B0?= =?UTF-8?q?=E4=BB=A5=E5=89=8D=E7=9A=84=E5=A4=8D=E5=88=B6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index f34cf89..c4e7b3d 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,7 +2,10 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.*; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -15,14 +18,14 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -74,18 +77,16 @@ public void startContainer(TestRequest testRequest) throws IOException { } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { - List volumes = new ArrayList<>(); - List binds = new ArrayList<>(); - final Volume volume = new Volume("/test"); - volumes.add(volume); - final Bind bind = new Bind(filePath, volume); - binds.add(bind); // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - hostConfig.withBinds(binds); - String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); + // 从主机复制文件到容器 + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(filePath) + .withDirChildrenOnly(true) + .withRemotePath("/test") + .exec(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -96,11 +97,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; + InputStream input = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) + .exec(); - File srcFile = new File(filePath + File.separator + jtlFileName); - File destDir = new File(rootPath + File.separator + jtlFileName); - - FileUtils.copyFile(srcFile, destDir); + IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { From 215dc6416c1cb4cce920a73b7d81e7ece40c5cc8 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 12 Nov 2020 12:23:44 +0800 Subject: [PATCH 020/157] =?UTF-8?q?refactor:=20=E9=87=87=E7=94=A8=E6=8C=82?= =?UTF-8?q?=E5=8D=B7=E7=9A=84=E6=96=B9=E5=BC=8F=E5=A4=8D=E5=88=B6=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index c4e7b3d..569eed5 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,10 +2,7 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.Container; -import com.github.dockerjava.api.model.Frame; -import com.github.dockerjava.api.model.HostConfig; -import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.*; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; @@ -18,7 +15,10 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; @@ -33,7 +33,15 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; - private static final String rootPath = StringUtils.join(new String[]{"", "opt", "node-data"}, File.separator); + private static String ROOT_PATH; + + static { + ROOT_PATH = System.getenv("JMETER_DATA_PATH"); + LogUtil.info("JMETER_DATA_PATH: " + ROOT_PATH); + if (StringUtils.isBlank(ROOT_PATH)) { + ROOT_PATH = "/opt/metersphere/data/jmeter/"; + } + } public void startContainer(TestRequest testRequest) throws IOException { String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); @@ -44,7 +52,7 @@ public void startContainer(TestRequest testRequest) throws IOException { String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{rootPath, testId}, File.separator); + String filePath = StringUtils.join(new String[]{ROOT_PATH, testId}, File.separator); String fileName = testId + ".jmx"; @@ -79,14 +87,10 @@ public void startContainer(TestRequest testRequest) throws IOException { private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); + hostConfig.withBinds(Bind.parse(filePath + ":/test")); + String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); - // 从主机复制文件到容器 - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(filePath) - .withDirChildrenOnly(true) - .withRemotePath("/test") - .exec(); DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); @@ -97,11 +101,11 @@ public void onComplete() { // 清理文件夹 try { String jtlFileName = testRequest.getReportId() + ".jtl"; - InputStream input = dockerClient - .copyArchiveFromContainerCmd(containerId, "/test/" + jtlFileName) - .exec(); - IOUtils.copyLarge(input, new FileOutputStream(rootPath + File.separator + jtlFileName)); + File srcFile = new File(filePath + File.separator + jtlFileName); + File destDir = new File(ROOT_PATH + File.separator + jtlFileName); + + FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { @@ -254,7 +258,7 @@ public void onNext(Frame item) { public byte[] downloadJtl(String reportId) { try { String jtlFileName = reportId + ".jtl"; - return IOUtils.toByteArray(new FileInputStream(rootPath + File.separator + jtlFileName)); + return IOUtils.toByteArray(new FileInputStream(ROOT_PATH + File.separator + jtlFileName)); } catch (IOException e) { LogUtil.error(e); } @@ -264,7 +268,7 @@ public byte[] downloadJtl(String reportId) { public boolean deleteJtl(String reportId) { String jtlFileName = reportId + ".jtl"; try { - FileUtils.forceDelete(new File(rootPath + File.separator + jtlFileName)); + FileUtils.forceDelete(new File(ROOT_PATH + File.separator + jtlFileName)); return true; } catch (IOException e) { LogUtil.error(e); From 3149e9f767a6e343db8c93547cfc9352da8488ad Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 25 Nov 2020 11:16:31 +0800 Subject: [PATCH 021/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e779aa1..27f2f7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.3.jar /opt/apps +ADD target/node-controller-1.5.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.3.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.5.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 2e0f4df..1a08c4f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.3 + 1.5 node-controller node-controller From b4deb946a3cf2e150268b117ffc400fa5b9ea12d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 26 Nov 2020 11:20:50 +0800 Subject: [PATCH 022/157] =?UTF-8?q?refactor:=20=E6=89=A7=E8=A1=8C=E7=BB=93?= =?UTF-8?q?=E6=9D=9F=E5=90=8E=E6=B8=85=E7=90=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/node/service/JmeterOperateService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 569eed5..18c85c2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -100,12 +100,6 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { - String jtlFileName = testRequest.getReportId() + ".jtl"; - - File srcFile = new File(filePath + File.separator + jtlFileName); - File destDir = new File(ROOT_PATH + File.separator + jtlFileName); - - FileUtils.copyFile(srcFile, destDir); FileUtils.forceDelete(new File(filePath)); LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { From c668ad0b7807f230cbc5a1e514334c37d244c3c2 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:24:36 +0800 Subject: [PATCH 023/157] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build-push.yml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 0000000..a292545 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,58 @@ +name: Build Docker Image and Push + +on: + push: + branches: + - main + - v1* + pull_request: + branches: + - main + - v1* + + workflow_dispatch: + +jobs: + build_push: + runs-on: ubuntu-latest + name: Build Docker Image and Push + steps: + - uses: actions/checkout@v2 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v3.x + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Push to Docker Hub + uses: docker/build-push-action@v1 + with: + username: metersphere + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: metersphere/node-controller + tag_with_ref: true + build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From ee139124c156713ac0b45d6b360cb85b91c66f09 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:29:17 +0800 Subject: [PATCH 024/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Dockerfil?= =?UTF-8?q?e=20=E5=9F=BA=E7=A1=80=E9=95=9C=E5=83=8F=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e779aa1..835c858 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre +FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD From 7c8a97b3da406cecc712bf34da0cf4e73e73464b Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:34:24 +0800 Subject: [PATCH 025/157] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index a292545..57f0698 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -3,11 +3,11 @@ name: Build Docker Image and Push on: push: branches: - - main + - master - v1* pull_request: branches: - - main + - master - v1* workflow_dispatch: From f8849952f66458529248e4f5799363c5da7832d9 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:24:36 +0800 Subject: [PATCH 026/157] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/build-push.yml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 0000000..a292545 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,58 @@ +name: Build Docker Image and Push + +on: + push: + branches: + - main + - v1* + pull_request: + branches: + - main + - v1* + + workflow_dispatch: + +jobs: + build_push: + runs-on: ubuntu-latest + name: Build Docker Image and Push + steps: + - uses: actions/checkout@v2 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v3.x + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }} # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + + - name: Push to Docker Hub + uses: docker/build-push-action@v1 + with: + username: metersphere + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: metersphere/node-controller + tag_with_ref: true + build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From 0cd2e6cc911fd25c09291a2cd0b9ae04814fcb7c Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 3 Dec 2020 19:29:17 +0800 Subject: [PATCH 027/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Dockerfil?= =?UTF-8?q?e=20=E5=9F=BA=E7=A1=80=E9=95=9C=E5=83=8F=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 27f2f7d..b0e2a1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre +FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD From d3e0453b9e0893bfa00e6b87f704a16ba4992f95 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 10 Dec 2020 13:15:02 +0800 Subject: [PATCH 028/157] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=20docker=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F=20tag=20=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 57f0698..2a07cb3 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -54,5 +54,5 @@ jobs: username: metersphere password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: metersphere/node-controller - tag_with_ref: true + tags: ${{ env.GITHUB_REF_SLUG }} build_args: MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} From 5b626e4b98fb96e728b5506990db059a24dc9721 Mon Sep 17 00:00:00 2001 From: BugKing Date: Thu, 17 Dec 2020 12:58:06 +0800 Subject: [PATCH 029/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b0e2a1b..9fd33f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.5.jar /opt/apps +ADD target/node-controller-1.6.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.5.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.6.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 1a08c4f..5fd1ce9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.5 + 1.6 node-controller node-controller From c6b0ffc51f95f8a08bcb366b7abf41593a7afda4 Mon Sep 17 00:00:00 2001 From: BugKing Date: Fri, 18 Dec 2020 13:47:30 +0800 Subject: [PATCH 030/157] =?UTF-8?q?build:=20=E5=A2=9E=E5=8A=A0=20Jenkinsfi?= =?UTF-8?q?le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..0daf2cb --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,28 @@ +pipeline { + agent { + node { + label 'master' + } + } + options { quietPeriod(2400) } + parameters { + string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') + string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + } + stages { + stage('Build/Test') { + steps { + configFileProvider([configFile(fileId: 'metersphere-maven', targetLocation: 'settings.xml')]) { + sh "mvn clean package --settings ./settings.xml" + } + } + } + stage('Docker build & push') { + steps { + sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-b\${BUILD_NUMBER} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." + sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + } + } + } +} From 775a9d982f5afa79f662f4b015dd5c2dbff163b1 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 10:45:36 +0800 Subject: [PATCH 031/157] =?UTF-8?q?build:=20Jenkinsfile=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 0daf2cb..aafe65f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,5 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } + stage('Notification') { + steps { + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' + } + } + } } } From c41ee0a96c5d58c91188622101713a447d4f75b1 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 10:46:11 +0800 Subject: [PATCH 032/157] =?UTF-8?q?build:=20Jenkinsfile=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 0daf2cb..aafe65f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,5 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } + stage('Notification') { + steps { + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' + } + } + } } } From 8715a5cc6101f60aa149a86e71667e3ce53e7198 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 14:07:12 +0800 Subject: [PATCH 033/157] =?UTF-8?q?build:=20Jenkinsfile=20=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=BD=BF=E7=94=A8=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index aafe65f..f9e7194 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,11 +24,12 @@ pipeline { sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } - stage('Notification') { - steps { - withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { - qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: '${WEBHOOK}' - } + } + post('Notification') { + always { + sh "echo \$WEBHOOK\n" + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: "$WEBHOOK" } } } From 666b840fce91add04ca6b028eed35e984f3844ee Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 21 Dec 2020 14:17:51 +0800 Subject: [PATCH 034/157] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=B7?= =?UTF-8?q?=E5=80=BC=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterOperateController.java | 20 +---- .../node/controller/request/TestRequest.java | 6 -- .../node/service/JmeterOperateService.java | 83 +++---------------- 3 files changed, 13 insertions(+), 96 deletions(-) diff --git a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 3ca6ed1..00359a0 100644 --- a/src/main/java/io/metersphere/node/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -4,13 +4,9 @@ import com.github.dockerjava.api.model.Container; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.service.JmeterOperateService; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; -import java.io.IOException; import java.util.List; @RestController @@ -23,7 +19,7 @@ public class JmeterOperateController { * 初始化测试任务,根据需求启动若干个 JMeter Engine 容器 */ @PostMapping("/container/start") - public String startContainer(@RequestBody TestRequest testRequest) throws IOException { + public String startContainer(@RequestBody TestRequest testRequest) { jmeterOperateService.startContainer(testRequest); return "OK"; } @@ -37,20 +33,6 @@ public String stopContainer(@PathVariable String testId) { return "OK"; } - @GetMapping("download/jtl/{reportId}") - public ResponseEntity downloadJtl(@PathVariable String reportId) { - byte[] bytes = jmeterOperateService.downloadJtl(reportId); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType("application/octet-stream")) - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + reportId + ".jtl" + "\"") - .body(bytes); - } - - @GetMapping("delete/jtl/{reportId}") - public boolean deleteJtl(@PathVariable String reportId) { - return jmeterOperateService.deleteJtl(reportId); - } - /** * 停止指定测试任务,控制上述容器停止指定的 JMeter 测试 */ diff --git a/src/main/java/io/metersphere/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java index eb14292..ccb1729 100644 --- a/src/main/java/io/metersphere/node/controller/request/TestRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -9,12 +9,6 @@ @Getter @Setter public class TestRequest extends DockerLoginRequest { - - private String fileString; - private String testId; - private String reportId; private String image; - private Map testData = new HashMap<>(); private Map env = new HashMap<>(); - private Map testJars = new HashMap<>(); } diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 18c85c2..7565e56 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -2,26 +2,23 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.WaitContainerResultCallback; -import com.github.dockerjava.api.model.*; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,61 +30,28 @@ public class JmeterOperateService { @Resource private JmeterProperties jmeterProperties; - private static String ROOT_PATH; - static { - ROOT_PATH = System.getenv("JMETER_DATA_PATH"); - LogUtil.info("JMETER_DATA_PATH: " + ROOT_PATH); - if (StringUtils.isBlank(ROOT_PATH)) { - ROOT_PATH = "/opt/metersphere/data/jmeter/"; - } - } - - public void startContainer(TestRequest testRequest) throws IOException { - String bootstrapServers = testRequest.getEnv().get("BOOTSTRAP_SERVERS"); + public void startContainer(TestRequest testRequest) { + Map env = testRequest.getEnv(); + String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); checkKafka(bootstrapServers); - LogUtil.info("Receive start container request, test id: {}", testRequest.getTestId()); + LogUtil.info("Receive start container request, test id: {}", env.get("TEST_ID")); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); - String testId = testRequest.getTestId(); String containerImage = testRequest.getImage(); - String filePath = StringUtils.join(new String[]{ROOT_PATH, testId}, File.separator); - String fileName = testId + ".jmx"; - - - // 每个测试生成一个文件夹 - FileUtils.writeStringToFile(new File(filePath + File.separator + fileName), testRequest.getFileString(), StandardCharsets.UTF_8); - // 保存测试数据文件 - Map testData = testRequest.getTestData(); - if (!CollectionUtils.isEmpty(testData)) { - for (String k : testData.keySet()) { - String v = testData.get(k); - FileUtils.writeStringToFile(new File(filePath + File.separator + k), v, StandardCharsets.UTF_8); - } - } - - // 保存 byte[] jar - Map jarFiles = testRequest.getTestJars(); - if (!CollectionUtils.isEmpty(jarFiles)) { - for (String k : jarFiles.keySet()) { - byte[] v = jarFiles.get(k); - FileUtils.writeByteArrayToFile(new File(filePath + File.separator + k), v); - } - } // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 - checkContainerExists(dockerClient, testId); + checkContainerExists(dockerClient, env.get("TEST_ID")); // 启动测试 - startContainer(testRequest, dockerClient, testId, containerImage, filePath); + startContainer(testRequest, dockerClient, env.get("TEST_ID"), containerImage); } - private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage, String filePath) { + private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - hostConfig.withBinds(Bind.parse(filePath + ":/test")); String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); @@ -100,14 +64,12 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, public void onComplete() { // 清理文件夹 try { - FileUtils.forceDelete(new File(filePath)); - LogUtil.info("Remove dir completed."); if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { - LogUtil.error("Remove dir error: ", e); + LogUtil.error("Remove container error: ", e); } LogUtil.info("completed...."); } @@ -248,25 +210,4 @@ public void onNext(Frame item) { } return sb.toString(); } - - public byte[] downloadJtl(String reportId) { - try { - String jtlFileName = reportId + ".jtl"; - return IOUtils.toByteArray(new FileInputStream(ROOT_PATH + File.separator + jtlFileName)); - } catch (IOException e) { - LogUtil.error(e); - } - return new byte[0]; - } - - public boolean deleteJtl(String reportId) { - String jtlFileName = reportId + ".jtl"; - try { - FileUtils.forceDelete(new File(ROOT_PATH + File.separator + jtlFileName)); - return true; - } catch (IOException e) { - LogUtil.error(e); - } - return false; - } } From 43a2f41f2e6d1a09f2d4b867d008fab3fb15ea73 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 14:33:58 +0800 Subject: [PATCH 035/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f9e7194..7f01aaf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'master' } } - options { quietPeriod(2400) } + options { quietPeriod(1200) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 0c79f27002814e491245eab45d86dd4f75dea245 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 21 Dec 2020 15:25:53 +0800 Subject: [PATCH 036/157] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=B7?= =?UTF-8?q?=E5=80=BC=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 7565e56..8b989f2 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -125,8 +125,6 @@ private void checkKafka(String bootstrapServers) { private String[] getEnvs(TestRequest testRequest) { Map env = testRequest.getEnv(); - // HEAP - env.put("HEAP", jmeterProperties.getHeap()); return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); } From 5ec119acee3dcc4ef7b1166e6ba73a8f068d97fe Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:52:51 +0800 Subject: [PATCH 037/157] xx --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b2f6ac8..1d31971 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { node { - label 'master' + label 'metersphere' } } options { quietPeriod(1200) } From 34cc77f80ba3040f59ed6dd3fad8f844766a5eef Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:53:52 +0800 Subject: [PATCH 038/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Jenkinsfi?= =?UTF-8?q?le=20=E6=9E=84=E5=BB=BA=E8=8A=82=E7=82=B9=20label?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index b2f6ac8..1d31971 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ pipeline { agent { node { - label 'master' + label 'metersphere' } } options { quietPeriod(1200) } From 4fc618c21c7d2eaa7b9e2e58599921d3879ce526 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:55:18 +0800 Subject: [PATCH 039/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=E9=9D=99?= =?UTF-8?q?=E9=BB=98=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1d31971..755b724 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'metersphere' } } - options { quietPeriod(1200) } + options { quietPeriod(60) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 00c2cf250c091e5efb758d4d249941d8056913f7 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 21 Dec 2020 16:56:05 +0800 Subject: [PATCH 040/157] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=9D=99=E9=BB=98?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 755b724..b967dba 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { label 'metersphere' } } - options { quietPeriod(60) } + options { quietPeriod(600) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') From 246c8183a8d72a8ca9b3d4ea28945df273523df5 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 23 Dec 2020 09:59:48 +0800 Subject: [PATCH 041/157] =?UTF-8?q?build:=20=E9=95=9C=E5=83=8F=E5=86=85?= =?UTF-8?q?=E7=9A=84=E7=89=88=E6=9C=AC=E5=8F=B7=E4=BD=BF=E7=94=A8=20commit?= =?UTF-8?q?=20ID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b967dba..fa8deb4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -7,7 +7,7 @@ pipeline { options { quietPeriod(600) } parameters { string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') - string(name: 'IMAGE_FREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + string(name: 'IMAGE_PREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') } stages { stage('Build/Test') { @@ -19,9 +19,9 @@ pipeline { } stage('Docker build & push') { steps { - sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-b\${BUILD_NUMBER} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." - sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" - sh "docker push ${IMAGE_FREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-\${GIT_COMMIT:0:8} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." + sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker push ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } stage('Notification') { From 740dc32c186d9816e64204a8bef1239ea2ab8cf6 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 6 Jan 2021 13:38:45 +0800 Subject: [PATCH 042/157] =?UTF-8?q?build:=20Jenkinsfile=20=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index fa8deb4..3b07ce5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,9 +5,9 @@ pipeline { } } options { quietPeriod(600) } - parameters { - string(name: 'IMAGE_NAME', defaultValue: 'ms-node-controller', description: '构建后的 Docker 镜像名称') - string(name: 'IMAGE_PREFIX', defaultValue: 'registry.cn-qingdao.aliyuncs.com/metersphere', description: '构建后的 Docker 镜像带仓库名的前缀') + environment { + IMAGE_NAME = 'metersphere' + IMAGE_PREFIX = 'registry.cn-qingdao.aliyuncs.com/metersphere' } stages { stage('Build/Test') { From 9e9455447aaf7394c27f36cba83b4d35a2fbd0c9 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 6 Jan 2021 14:01:46 +0800 Subject: [PATCH 043/157] =?UTF-8?q?build:=20=E4=BF=AE=E6=94=B9=20Jenkinsfi?= =?UTF-8?q?le=20=E9=95=9C=E5=83=8F=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3b07ce5..1106dc0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { } options { quietPeriod(600) } environment { - IMAGE_NAME = 'metersphere' + IMAGE_NAME = 'ms-node-controller' IMAGE_PREFIX = 'registry.cn-qingdao.aliyuncs.com/metersphere' } stages { From cda41a930718163c4cd2c5d197b57de0b4c7d7fa Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 27 Jan 2021 15:05:58 +0800 Subject: [PATCH 044/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fd33f5..8582b1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.6.jar /opt/apps +ADD target/node-controller-1.7.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.6.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.7.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 5fd1ce9..4032cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.6 + 1.7 node-controller node-controller From 164591f05242a01cd686ee6565f9cf04d76855d2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 22 Feb 2021 10:10:12 +0800 Subject: [PATCH 045/157] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/node/Application.java | 5 ----- .../metersphere/node/config/JmeterProperties.java | 13 ------------- .../node/service/JmeterOperateService.java | 12 +++++------- 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 src/main/java/io/metersphere/node/config/JmeterProperties.java diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java index 702a592..35b186f 100644 --- a/src/main/java/io/metersphere/node/Application.java +++ b/src/main/java/io/metersphere/node/Application.java @@ -1,14 +1,9 @@ package io.metersphere.node; -import io.metersphere.node.config.JmeterProperties; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.PropertySource; -@EnableConfigurationProperties({ - JmeterProperties.class -}) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) @SpringBootApplication public class Application { diff --git a/src/main/java/io/metersphere/node/config/JmeterProperties.java b/src/main/java/io/metersphere/node/config/JmeterProperties.java deleted file mode 100644 index 38d9e4e..0000000 --- a/src/main/java/io/metersphere/node/config/JmeterProperties.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.metersphere.node.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) -@Setter -@Getter -public class JmeterProperties { - public static final String JMETER_PREFIX = "jmeter"; - private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; -} diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 8b989f2..7a0c7c4 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.HostConfig; import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; -import io.metersphere.node.config.JmeterProperties; import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; @@ -15,7 +14,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import javax.annotation.Resource; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -28,15 +26,15 @@ @Service public class JmeterOperateService { - @Resource - private JmeterProperties jmeterProperties; public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); + String testId = env.get("TEST_ID"); + LogUtil.info("Receive start container request, test id: {}", testId); String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); + // 检查kafka连通性 checkKafka(bootstrapServers); - LogUtil.info("Receive start container request, test id: {}", env.get("TEST_ID")); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); String containerImage = testRequest.getImage(); @@ -44,9 +42,9 @@ public void startContainer(TestRequest testRequest) { // 查找镜像 searchImage(dockerClient, testRequest.getImage()); // 检查容器是否存在 - checkContainerExists(dockerClient, env.get("TEST_ID")); + checkContainerExists(dockerClient, testId); // 启动测试 - startContainer(testRequest, dockerClient, env.get("TEST_ID"), containerImage); + startContainer(testRequest, dockerClient, testId, containerImage); } private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { From a92cf980336e51998f5ffffaf84d129648190212 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 18 Mar 2021 13:14:26 +0800 Subject: [PATCH 046/157] =?UTF-8?q?refactor:=20=E4=BD=BF=E7=94=A8host?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E5=90=AF=E5=8A=A8=E5=AE=B9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 7a0c7c4..b3e6668 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -50,7 +50,7 @@ public void startContainer(TestRequest testRequest) { private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { // 创建 hostConfig HostConfig hostConfig = HostConfig.newHostConfig(); - + hostConfig.withNetworkMode("host"); String[] envs = getEnvs(testRequest); String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); From 08f682132b213ab22f0a58a4a74618650c21521a Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 24 Mar 2021 19:16:24 +0800 Subject: [PATCH 047/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8582b1c..cea8bc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,9 @@ ARG MS_VERSION=dev RUN mkdir -p /opt/apps -ADD target/node-controller-1.7.jar /opt/apps +ADD target/node-controller-1.8.jar /opt/apps -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.7.jar +ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar ENV AB_OFF=true diff --git a/pom.xml b/pom.xml index 4032cd9..8505672 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.7 + 1.8 node-controller node-controller From d72681108ec0c3ba3939f87f8ce3d2c55084c59c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 16 Apr 2021 17:56:04 +0800 Subject: [PATCH 048/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=8E=A5=E5=8F=A3=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 333 ++++ src/main/java/io/metersphere/Application.java | 31 + .../api/config/AppStartListener.java | 65 + .../metersphere/api/config/KafkaConfig.java | 17 + .../api/config/KafkaProperties.java | 51 + .../controller/JmeterExecuteController.java | 31 + .../api/controller/request/RunRequest.java | 14 + .../api/jmeter/APIBackendListenerClient.java | 246 +++ .../metersphere/api/jmeter/JMeterService.java | 86 + .../io/metersphere/api/jmeter/JMeterVars.java | 115 ++ .../metersphere/api/jmeter/LocalRunner.java | 24 + .../api/jmeter/constants/ApiRunMode.java | 5 + .../api/jmeter/constants/RequestType.java | 12 + .../api/jmeter/module/RequestResult.java | 47 + .../module/ResponseAssertionResult.java | 13 + .../api/jmeter/module/ResponseResult.java | 32 + .../api/jmeter/module/ScenarioResult.java | 50 + .../api/jmeter/module/TestResult.java | 67 + .../api/jmeter/utils/CommonBeanFactory.java | 34 + .../api/jmeter/utils/FileUtils.java | 32 + .../api/jmeter/utils/JmeterProperties.java | 21 + .../api/jmeter/utils/MSException.java | 24 + .../api/jmeter/utils/RunModeConfig.java | 11 + .../api/service/JmeterExecuteService.java | 73 + .../api/service/LoadTestProducer.java | 53 + .../java/io/metersphere/node/Application.java | 14 - .../java/org/apache/jmeter/NewDriver.java | 378 ++++ .../jmeter/assertions/JSONPathAssertion.java | 229 +++ .../apache/jmeter/reporters/ResultAction.java | 98 + .../apache/jmeter/samplers/SampleResult.java | 1623 +++++++++++++++++ .../jmeter/util/JSR223BeanInfoSupport.java | 93 + .../apache/jmeter/util/JSR223TestElement.java | 387 ++++ .../apache/jorphan/collections/HashTree.java | 1095 +++++++++++ src/main/resources/application.properties | 27 + .../resources/jmeter/bin/jmeter.properties | 1346 ++++++++++++++ .../jmeter/bin/saveservice.properties | 420 +++++ .../resources/jmeter/bin/upgrade.properties | 123 ++ src/main/resources/jmeter/bin/user.properties | 147 ++ 38 files changed, 7453 insertions(+), 14 deletions(-) create mode 100644 src/main/java/io/metersphere/Application.java create mode 100644 src/main/java/io/metersphere/api/config/AppStartListener.java create mode 100644 src/main/java/io/metersphere/api/config/KafkaConfig.java create mode 100644 src/main/java/io/metersphere/api/config/KafkaProperties.java create mode 100644 src/main/java/io/metersphere/api/controller/JmeterExecuteController.java create mode 100644 src/main/java/io/metersphere/api/controller/request/RunRequest.java create mode 100644 src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java create mode 100644 src/main/java/io/metersphere/api/jmeter/JMeterService.java create mode 100644 src/main/java/io/metersphere/api/jmeter/JMeterVars.java create mode 100644 src/main/java/io/metersphere/api/jmeter/LocalRunner.java create mode 100644 src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java create mode 100644 src/main/java/io/metersphere/api/jmeter/constants/RequestType.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/RequestResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/module/TestResult.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/MSException.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java create mode 100644 src/main/java/io/metersphere/api/service/JmeterExecuteService.java create mode 100644 src/main/java/io/metersphere/api/service/LoadTestProducer.java delete mode 100644 src/main/java/io/metersphere/node/Application.java create mode 100644 src/main/java/org/apache/jmeter/NewDriver.java create mode 100644 src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java create mode 100644 src/main/java/org/apache/jmeter/reporters/ResultAction.java create mode 100644 src/main/java/org/apache/jmeter/samplers/SampleResult.java create mode 100644 src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java create mode 100644 src/main/java/org/apache/jmeter/util/JSR223TestElement.java create mode 100644 src/main/java/org/apache/jorphan/collections/HashTree.java create mode 100644 src/main/resources/jmeter/bin/jmeter.properties create mode 100644 src/main/resources/jmeter/bin/saveservice.properties create mode 100644 src/main/resources/jmeter/bin/upgrade.properties create mode 100644 src/main/resources/jmeter/bin/user.properties diff --git a/pom.xml b/pom.xml index 8505672..127e7fa 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,9 @@ 1.8 + 5.4.1 + 1.1.3 + 2.7.8 @@ -62,6 +65,336 @@ + + + + org.flywaydb + flyway-core + + + mysql + mysql-connector-java + runtime + + + com.github.pagehelper + pagehelper + 5.0.3 + + + + org.apache.commons + commons-collections4 + 4.1 + + + org.apache.commons + commons-text + 1.8 + + + commons-codec + commons-codec + + + + com.alibaba + fastjson + 1.2.72 + + + + + org.springdoc + springdoc-openapi-ui + 1.5.6 + + + + org.apache.jmeter + ApacheJMeter_http + ${jmeter.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + + + xstream + com.thoughtworks.xstream + + + + + + com.thoughtworks.xstream + xstream + 1.4.16 + + + + org.python + jython-standalone + 2.7.0 + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_jdbc + ${jmeter.version} + + + + org.apache.jmeter + ApacheJMeter_tcp + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_java + ${jmeter.version} + + + + com.microsoft.sqlserver + mssql-jdbc + 7.4.1.jre8 + + + + org.postgresql + postgresql + 42.2.14 + + + + com.oracle.database.jdbc + ojdbc8 + 19.7.0.0 + + + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + org.apache.zookeeper + zookeeper + 3.4.14 + + + slf4j-log4j12 + org.slf4j + + + + + org.apache.curator + curator-framework + 4.0.1 + + + org.apache.curator + curator-recipes + 4.0.1 + + + + + org.apache.dubbo + dubbo-registry-nacos + ${dubbo.version} + + + com.alibaba.nacos + nacos-api + ${nacos.version} + + + com.alibaba.nacos + nacos-client + ${nacos.version} + + + + com.alibaba + easyexcel + 2.1.7 + + + + io.metersphere + jmeter-plugins-dubbo + 2.7.12 + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + io.swagger.parser.v3 + swagger-parser + 2.0.24 + + + + + org.codehaus.groovy + groovy-jsr223 + 2.5.10 + + + + + org.mozilla + rhino-engine + 1.7.13 + + + + org.jsoup + jsoup + 1.10.3 + + + + com.atlassian.commonmark + commonmark + 0.15.2 + + + + org.apache.commons + commons-compress + 1.20 + + + + org.dom4j + dom4j + 2.1.3 + + + + jaxen + jaxen + 1.2.0 + + + net.sourceforge.htmlcleaner + htmlcleaner + 2.24 + + + + org.json + json + 20171018 + + + + com.aliyun + alibaba-dingtalk-service-sdk + 1.0.1 + + + org.apache.httpcomponents + httpclient + 4.5.6 + + + + net.oneandone.reflections8 + reflections8 + 0.11.7 + + + + io.fabric8 + kubernetes-client + 4.13.0 + + + com.github.fge + json-schema-validator + 2.2.6 + + + + org.apache.jmeter + ApacheJMeter_bolt + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_jms + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ftp + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_junit + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_ldap + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_mail + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_components + ${jmeter.version} + + + org.apache.jmeter + ApacheJMeter_native + ${jmeter.version} + + + + com.jayway.jsonpath + json-path + 2.5.0 + + + org.aspectj + aspectjweaver + 1.9.6 + + + commons-collections + commons-collections + 3.2.2 + + + org.springframework.kafka + spring-kafka + + + + diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java new file mode 100644 index 0000000..d304b57 --- /dev/null +++ b/src/main/java/io/metersphere/Application.java @@ -0,0 +1,31 @@ +package io.metersphere; + +import io.metersphere.api.config.KafkaProperties; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.JmeterProperties; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.PropertySource; + +@ServletComponentScan +@EnableConfigurationProperties({ + KafkaProperties.class, + JmeterProperties.class, +}) +@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + @ConditionalOnMissingBean + public CommonBeanFactory commonBeanFactory() { + return new CommonBeanFactory(); + } +} diff --git a/src/main/java/io/metersphere/api/config/AppStartListener.java b/src/main/java/io/metersphere/api/config/AppStartListener.java new file mode 100644 index 0000000..fd4b171 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/AppStartListener.java @@ -0,0 +1,65 @@ +package io.metersphere.api.config; + +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.jmeter.NewDriver; +import org.python.core.Options; +import org.python.util.PythonInterpreter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.net.MalformedURLException; + +@Component +public class AppStartListener implements ApplicationListener { + + @Resource + private JMeterService jMeterService; + + @Value("${jmeter.home}") + private String jmeterHome; + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + System.out.println("================= NODE 应用启动 ================="); + System.setProperty("jmeter.home", jmeterHome); + loadJars(); + initPythonEnv(); + } + + /** + * 解决接口测试-无法导入内置python包 + */ + private void initPythonEnv() { + //解决无法加载 PyScriptEngineFactory + Options.importSite = false; + try { + PythonInterpreter interp = new PythonInterpreter(); + String path = jMeterService.getJmeterHome(); + System.out.println("sys.path: " + path); + path += "/lib/ext/jython-standalone.jar/Lib"; + interp.exec("import sys"); + interp.exec("sys.path.append(\"" + path + "\")"); + } catch (Exception e) { + e.printStackTrace(); + LogUtil.error(e.getMessage(), e); + } + } + + /** + * 加载jar包 + */ + private void loadJars() { + try { + NewDriver.addPath(FileUtils.JAR_FILE_DIR); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } +} diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java new file mode 100644 index 0000000..80ce8a6 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -0,0 +1,17 @@ +package io.metersphere.api.config; + +import org.apache.kafka.clients.admin.NewTopic; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.config.TopicBuilder; + +@Configuration +public class KafkaConfig { + private final String testTopic = "ms-api-exec-topic"; + + @Bean + public NewTopic apiExecTopic() { + return TopicBuilder.name(testTopic) + .build(); + } +} diff --git a/src/main/java/io/metersphere/api/config/KafkaProperties.java b/src/main/java/io/metersphere/api/config/KafkaProperties.java new file mode 100644 index 0000000..e52b95a --- /dev/null +++ b/src/main/java/io/metersphere/api/config/KafkaProperties.java @@ -0,0 +1,51 @@ +package io.metersphere.api.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = KafkaProperties.KAFKA_PREFIX) +@Getter +@Setter +public class KafkaProperties { + public static final String KAFKA_PREFIX = "kafka"; + + private String acks = "0"; // 不要设置all + private String expectedDelayEndTime = "30000"; // 30s + private String topic; + private String fields; + private String timestamp; + private String bootstrapServers; + private String sampleFilter; + private String testMode; + private String parseAllReqHeaders; + private String parseAllResHeaders; + private String compressionType; + private String batchSize; + private String clientId; + private String connectionsMaxIdleMs; + private KafkaProperties.Ssl ssl = new KafkaProperties.Ssl(); + private KafkaProperties.Log log = new KafkaProperties.Log(); + + @Getter + @Setter + public static class Ssl { + private String enabled = "false"; + private String keyPassword; + private String keystoreLocation; + private String keystorePassword; + private String keystoreType; + private String truststoreLocation; + private String truststorePassword; + private String truststoreType; + private String protocol; + private String enabledProtocols; + private String provider; + } + + @Getter + @Setter + public static class Log { + private String topic; + } +} diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java new file mode 100644 index 0000000..03a9c1b --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -0,0 +1,31 @@ +package io.metersphere.api.controller; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.node.util.LogUtil; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/jmeter") +public class JmeterExecuteController { + + @Resource + private JmeterExecuteService jmeterExecuteService; + + @PostMapping(value = "/api/run", consumes = {"multipart/form-data"}) + public String apiRun(@RequestParam(value = "files") MultipartFile[] bodyFiles, @RequestParam(value = "jarFiles") MultipartFile[] jarFiles, String request) { + LogUtil.info("接收到测试请求 start "); + RunRequest runRequest = JSON.parseObject(request, RunRequest.class); + return jmeterExecuteService.run(runRequest, bodyFiles, jarFiles); + } + + @GetMapping("/status") + public String getStatus() { + return "OK"; + } + +} diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java new file mode 100644 index 0000000..f0e6408 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.api.controller.request; + +import io.metersphere.api.jmeter.utils.RunModeConfig; +import lombok.Data; + +@Data +public class RunRequest { + private String testId; + private String userId; + private boolean isDebug; + private String runMode; + private String jmx; + private RunModeConfig config; +} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java new file mode 100644 index 0000000..c78488a --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -0,0 +1,246 @@ +package io.metersphere.api.jmeter; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.service.LoadTestProducer; +import io.metersphere.api.jmeter.constants.ApiRunMode; +import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.module.*; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerContext; +import org.springframework.http.HttpMethod; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.util.*; + +/** + * JMeter BackendListener扩展, jmx脚本中使用 + */ +public class APIBackendListenerClient extends AbstractBackendListenerClient implements Serializable { + + public final static String TEST_ID = "ms.test.id"; + + public final static String TEST_REPORT_NAME = "ms.test.report.name"; + + private final static String THREAD_SPLIT = " "; + + private final static String ID_SPLIT = "-"; + + private final List queue = new ArrayList<>(); + + private String runMode = ApiRunMode.RUN.name(); + + private String userId; + + private boolean isDebug; + + private LoadTestProducer loadTestProducer; + + /** + * 测试ID + */ + private String testId; + + /** + * 只有合并报告是这个有值 + */ + private String reportName; + + /** + * 获得控制台内容 + */ + private PrintStream oldPrintStream = System.out; + private ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + private void setConsole() { + // 设置新的out + System.setOut(new PrintStream(bos)); + } + + private String getConsole() { + System.setOut(oldPrintStream); + return bos.toString(); + } + + @Override + public void setupTest(BackendListenerContext context) throws Exception { + setConsole(); + setParam(context); + super.setupTest(context); + } + + + @Override + public void handleSampleResults(List sampleResults, BackendListenerContext context) { + queue.addAll(sampleResults); + } + + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + TestResult testResult = new TestResult(); + testResult.setTestId(testId); + testResult.setTotal(queue.size()); + testResult.setReportName(this.reportName); + testResult.setDebug(this.isDebug); + testResult.setUserId(this.userId); + loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); + // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id + final Map scenarios = new LinkedHashMap<>(); + queue.forEach(result -> { + // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); + } + + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } + + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); + + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); + + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + }); + testResult.getScenarios().addAll(scenarios.values()); + testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); + testResult.setRunMode(this.runMode); + // 推送执行结果 + loadTestProducer.sendMessage(JSON.toJSONString(testResult)); + queue.clear(); + super.teardownTest(context); + } + + private RequestResult getRequestResult(SampleResult result) { + RequestResult requestResult = new RequestResult(); + requestResult.setId(result.getSamplerId()); + requestResult.setName(result.getSampleLabel()); + requestResult.setUrl(result.getUrlAsString()); + requestResult.setMethod(getMethod(result)); + requestResult.setBody(result.getSamplerData()); + requestResult.setHeaders(result.getRequestHeaders()); + requestResult.setRequestSize(result.getSentBytes()); + requestResult.setStartTime(result.getStartTime()); + requestResult.setEndTime(result.getEndTime()); + requestResult.setTotalAssertions(result.getAssertionResults().length); + requestResult.setSuccess(result.isSuccessful()); + requestResult.setError(result.getErrorCount()); + if (result instanceof HTTPSampleResult) { + HTTPSampleResult res = (HTTPSampleResult) result; + requestResult.setCookies(res.getCookies()); + } + + for (SampleResult subResult : result.getSubResults()) { + requestResult.getSubRequestResults().add(getRequestResult(subResult)); + } + ResponseResult responseResult = requestResult.getResponseResult(); + responseResult.setBody(result.getResponseDataAsString()); + responseResult.setHeaders(result.getResponseHeaders()); + responseResult.setLatency(result.getLatency()); + responseResult.setResponseCode(result.getResponseCode()); + responseResult.setResponseSize(result.getResponseData().length); + responseResult.setResponseTime(result.getTime()); + responseResult.setResponseMessage(result.getResponseMessage()); + if (JMeterVars.get(result.hashCode()) != null && CollectionUtils.isNotEmpty(JMeterVars.get(result.hashCode()).entrySet())) { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : JMeterVars.get(result.hashCode()).entrySet()) { + builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n"); + } + if (StringUtils.isNotEmpty(builder)) { + responseResult.setVars(builder.toString()); + } + JMeterVars.remove(result.hashCode()); + } + for (AssertionResult assertionResult : result.getAssertionResults()) { + ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult); + if (responseAssertionResult.isPass()) { + requestResult.addPassAssertions(); + } + //xpath 提取错误会添加断言错误 + if (StringUtils.isBlank(responseAssertionResult.getMessage()) || + (StringUtils.isNotBlank(responseAssertionResult.getName()) && !responseAssertionResult.getName().endsWith("XPath2Extractor"))) { + responseResult.getAssertions().add(responseAssertionResult); + } + } + responseResult.setConsole(getConsole()); + + return requestResult; + } + + private String getMethod(SampleResult result) { + String body = result.getSamplerData(); + // Dubbo Protocol + String start = "RPC Protocol: "; + String end = "://"; + if (StringUtils.contains(body, start)) { + String protocol = StringUtils.substringBetween(body, start, end); + if (StringUtils.isNotEmpty(protocol)) { + return protocol.toUpperCase(); + } + return RequestType.DUBBO; + } else if (StringUtils.contains(result.getResponseHeaders(), "url:jdbc")) { + return "SQL"; + } else { + // Http Method + String method = StringUtils.substringBefore(body, " "); + for (HttpMethod value : HttpMethod.values()) { + if (StringUtils.equals(method, value.name())) { + return method; + } + } + return "Request"; + } + } + + private void setParam(BackendListenerContext context) { + this.testId = context.getParameter(TEST_ID); + this.reportName = context.getParameter(TEST_REPORT_NAME); + this.runMode = context.getParameter("runMode"); + this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; + this.userId = context.getParameter("USER_ID"); + if (StringUtils.isBlank(this.runMode)) { + this.runMode = ApiRunMode.RUN.name(); + } + } + + private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { + ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult(); + responseAssertionResult.setName(assertionResult.getName()); + responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError()); + if (!responseAssertionResult.isPass()) { + responseAssertionResult.setMessage(assertionResult.getFailureMessage()); + } + return responseAssertionResult; + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java new file mode 100644 index 0000000..670023d --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -0,0 +1,86 @@ +package io.metersphere.api.jmeter; + +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.utils.JmeterProperties; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.visualizers.backend.BackendListener; +import org.apache.jorphan.collections.HashTree; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.io.File; +import java.lang.reflect.Field; + +@Service +public class JMeterService { + + @Resource + private JmeterProperties jmeterProperties; + + @PostConstruct + public void init() { + String JMETER_HOME = getJmeterHome(); + + String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties"; + JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES); + JMeterUtils.setJMeterHome(JMETER_HOME); + JMeterUtils.setLocale(LocaleContextHolder.getLocale()); + } + + public String getJmeterHome() { + String home = getClass().getResource("/").getPath() + "jmeter"; + try { + File file = new File(home); + if (file.exists()) { + return home; + } else { + return jmeterProperties.getHome(); + } + } catch (Exception e) { + return jmeterProperties.getHome(); + } + } + + public static HashTree getHashTree(Object scriptWrapper) throws Exception { + Field field = scriptWrapper.getClass().getDeclaredField("testPlan"); + field.setAccessible(true); + return (HashTree) field.get(scriptWrapper); + } + + + private void addBackendListener(HashTree testPlan, RunRequest request) { + BackendListener backendListener = new BackendListener(); + backendListener.setName(request.getTestId()); + Arguments arguments = new Arguments(); + if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { + arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, request.getConfig().getReportName()); + } + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + if (StringUtils.isNotBlank(request.getRunMode())) { + arguments.addArgument("runMode", request.getRunMode()); + } + arguments.addArgument("DEBUG", request.isDebug() ? "DEBUG" : "RUN"); + arguments.addArgument("USER_ID", request.getUserId()); + backendListener.setArguments(arguments); + backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); + testPlan.add(testPlan.getArray()[0], backendListener); + } + + public void runSerial(RunRequest request, HashTree testPlan) { + try { + init(); + addBackendListener(testPlan, request); + LocalRunner runner = new LocalRunner(testPlan); + runner.run(); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException("读取脚本失败"); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/src/main/java/io/metersphere/api/jmeter/JMeterVars.java new file mode 100644 index 0000000..ae468d3 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterVars.java @@ -0,0 +1,115 @@ +package io.metersphere.api.jmeter; + +import com.alibaba.fastjson.JSON; +import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.extractor.JSR223PostProcessor; +import org.apache.jmeter.extractor.RegexExtractor; +import org.apache.jmeter.extractor.XPath2Extractor; +import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.collections.HashTree; +import org.springframework.util.StringUtils; + +import java.util.*; + +public class JMeterVars { + + private JMeterVars() { + } + + /** + * 数据和线程变量保持一致 + */ + private static Map variables = new HashMap<>(); + + /** + * 线程执行过程调用提取变量值 + * + * @param testId + * @param vars + * @param extract + */ + public static void addVars(Integer testId, JMeterVariables vars, String extract) { + JMeterVariables vs = variables.get(testId); + if (vs == null) { + vs = new JMeterVariables(); + } + if (!StringUtils.isEmpty(extract) && vars != null) { + List extracts = Arrays.asList(extract.split(";")); + if (CollectionUtils.isNotEmpty(extracts)) { + for (String item : extracts) { + String nrKey = item + "_matchNr"; + Object nr = vars.get(nrKey); + if (nr != null) { + int nrv = 0; + try { + nrv = Integer.valueOf(String.valueOf(nr)); + } catch (Exception e) { + } + if (nrv > 0) { + List data = new ArrayList<>(); + for (int i = 1; i < nrv + 1; i++) { + data.add(vars.get(item + "_" + i)); + } + String array = JSON.toJSONString(data); + vars.put(item, array); + } + } + vs.put(item, vars.get(item) == null ? "" : vars.get(item)); + } + vs.remove("TESTSTART.MS"); // 标示变量移除 + } + } + + variables.put(testId, vs); + } + + /** + * 处理所有请求,有提取变量的请求增加后置脚本提取变量值 + * + * @param tree + */ + public static void addJSR223PostProcessor(HashTree tree) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy || key instanceof DubboSample || key instanceof JDBCSampler) { + StringJoiner extract = new StringJoiner(";"); + for (Object child : node.keySet()) { + if (child instanceof RegexExtractor) { + RegexExtractor regexExtractor = (RegexExtractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof XPath2Extractor) { + XPath2Extractor regexExtractor = (XPath2Extractor) child; + extract.add(regexExtractor.getRefName()); + } else if (child instanceof JSONPostProcessor) { + JSONPostProcessor regexExtractor = (JSONPostProcessor) child; + extract.add(regexExtractor.getRefNames()); + } + } + + if (Optional.ofNullable(extract).orElse(extract).length() > 0) { + JSR223PostProcessor shell = new JSR223PostProcessor(); + shell.setEnabled(true); + shell.setProperty("script", "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");"); + node.add(shell); + } + } + + if (node != null) { + addJSR223PostProcessor(node); + } + } + } + + public static JMeterVariables get(Integer key) { + return variables.get(key); + } + + public static void remove(Integer key) { + variables.remove(key); + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java new file mode 100644 index 0000000..3db1bb7 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java @@ -0,0 +1,24 @@ +package io.metersphere.api.jmeter; + +import org.apache.jmeter.engine.JMeterEngine; +import org.apache.jmeter.engine.JMeterEngineException; +import org.apache.jmeter.engine.StandardJMeterEngine; +import org.apache.jorphan.collections.HashTree; + +public class LocalRunner { + private final HashTree jmxTree; + + public LocalRunner(HashTree jmxTree) { + this.jmxTree = jmxTree; + } + + public void run() { + JMeterEngine engine = new StandardJMeterEngine(); + engine.configure(jmxTree); + try { + engine.runTest(); + } catch (JMeterEngineException e) { + engine.stopTest(true); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java b/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java new file mode 100644 index 0000000..2fcccaf --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java @@ -0,0 +1,5 @@ +package io.metersphere.api.jmeter.constants; + +public enum ApiRunMode { + RUN, DEBUG, DEFINITION, SCENARIO, API_PLAN, JENKINS_API_PLAN, JENKINS, SCENARIO_PLAN, API, SCHEDULE_API_PLAN, SCHEDULE_SCENARIO,SCHEDULE_SCENARIO_PLAN, SCHEDULE_PERFORMANCE_TEST +} diff --git a/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java b/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java new file mode 100644 index 0000000..377746b --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java @@ -0,0 +1,12 @@ +package io.metersphere.api.jmeter.constants; + +public class RequestType { + + public static final String HTTP = "HTTP"; + + public static final String DUBBO = "DUBBO"; + + public static final String SQL = "SQL"; + + public static final String TCP = "TCP"; +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java b/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java new file mode 100644 index 0000000..d9cec5c --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java @@ -0,0 +1,47 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class RequestResult { + // 请求ID + private String id; + + private String name; + + private String url; + + private String method; + + private long requestSize; + + private long startTime; + + private long endTime; + + private int error; + + private boolean success; + + private String headers; + + private String cookies; + + private String body; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private final List subRequestResults = new ArrayList<>(); + + private final ResponseResult responseResult = new ResponseResult(); + + public void addPassAssertions() { + this.passAssertions++; + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java b/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java new file mode 100644 index 0000000..05c4512 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java @@ -0,0 +1,13 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +@Data +public class ResponseAssertionResult { + + private String name; + + private String message; + + private boolean pass; +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java b/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java new file mode 100644 index 0000000..7e4ad10 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + + +@Data +public class ResponseResult { + + private String responseCode; + + private String responseMessage; + + private long responseTime; + + private long latency; + + private long responseSize; + + private String headers; + + private String body; + + private String vars; + + private String console; + + private final List assertions = new ArrayList<>(); + +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java b/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java new file mode 100644 index 0000000..3be1644 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java @@ -0,0 +1,50 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ScenarioResult { + + private Integer id; + + private String name; + + private long responseTime; + + private int error = 0; + + private int success = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private List requestResults = new ArrayList<>(); + + public void addResponseTime(long time) { + this.responseTime += time; + } + + public void addError(int count) { + this.error += count; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } + + public int getTotal() { + return error + success; + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java b/src/main/java/io/metersphere/api/jmeter/module/TestResult.java new file mode 100644 index 0000000..60ed9b0 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/module/TestResult.java @@ -0,0 +1,67 @@ +package io.metersphere.api.jmeter.module; + +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.apache.dubbo.common.utils.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TestResult { + + private String testId; + + private boolean isDebug; + + private String userId; + + private String runMode; + + private String reportName; + + private int success = 0; + + private int error = 0; + + private int total = 0; + + private int totalAssertions = 0; + + private int passAssertions = 0; + + private List scenarios = new ArrayList<>(); + + public void addError(int count) { + this.error += count; + } + + public void addSuccess() { + this.success++; + } + + public void addTotalAssertions(int count) { + this.totalAssertions += count; + } + + public void addPassAssertions(int count) { + this.passAssertions += count; + } + + private static final String SEPARATOR = "<->"; + + public void addScenario(ScenarioResult result) { + if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) { + result.getRequestResults().forEach(item -> { + if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) { + String array[] = item.getName().split(SEPARATOR); + item.setName(array[1] + array[0]); + item.getSubRequestResults().forEach(subItem -> { + subItem.setName(array[0]); + }); + } + }); + scenarios.add(result); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java b/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java new file mode 100644 index 0000000..edf156f --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/CommonBeanFactory.java @@ -0,0 +1,34 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +public class CommonBeanFactory implements ApplicationContextAware { + private static ApplicationContext context; + + public CommonBeanFactory() { + } + + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + context = ctx; + } + + public static Object getBean(String beanName) { + try { + return context != null && !StringUtils.isBlank(beanName) ? context.getBean(beanName) : null; + } catch (BeansException e) { + return null; + } + } + + public static T getBean(Class className) { + try { + return context != null && className != null ? context.getBean(className) : null; + } catch (BeansException e) { + return null; + } + } +} + diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java new file mode 100644 index 0000000..6a47e83 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.node.util.LogUtil; +import org.aspectj.util.FileUtil; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; + +public class FileUtils { + public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; + public static final String JAR_FILE_DIR = "/opt/metersphere/data/node/jar"; + + public static void createBodyFiles(MultipartFile[] bodyFiles,String path) { + if (bodyFiles != null && bodyFiles.length > 0) { + File testDir = new File(path); + if (!testDir.exists()) { + testDir.mkdirs(); + } + for (int i = 0; i < bodyFiles.length; i++) { + MultipartFile item = bodyFiles[i]; + File file = new File(path + "/" + item.getOriginalFilename()); + try (InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(file)) { + file.createNewFile(); + FileUtil.copyStream(in, out); + } catch (IOException e) { + LogUtil.error(e); + MSException.throwException("文件处理异常"); + } + } + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java b/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java new file mode 100644 index 0000000..9fd5eaa --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/JmeterProperties.java @@ -0,0 +1,21 @@ +package io.metersphere.api.jmeter.utils; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = JmeterProperties.JMETER_PREFIX) +@Setter +@Getter +public class JmeterProperties { + + public static final String JMETER_PREFIX = "jmeter"; + + private String image; + + private String home; + + private String heap = "-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"; + private String gcAlgo = "-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20"; +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/MSException.java b/src/main/java/io/metersphere/api/jmeter/utils/MSException.java new file mode 100644 index 0000000..688df74 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/MSException.java @@ -0,0 +1,24 @@ +package io.metersphere.api.jmeter.utils; + +public class MSException extends RuntimeException { + + private MSException(String message) { + super(message); + } + + private MSException(Throwable t) { + super(t); + } + + public static void throwException(String message) { + throw new MSException(message); + } + + public static MSException getException(String message) { + throw new MSException(message); + } + + public static void throwException(Throwable t) { + throw new MSException(t); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java b/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java new file mode 100644 index 0000000..8330e8a --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java @@ -0,0 +1,11 @@ +package io.metersphere.api.jmeter.utils; + +import lombok.Data; + +@Data +public class RunModeConfig { + private String mode; + private String reportType; + private String reportName; + private boolean onSampleError; +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java new file mode 100644 index 0000000..f34f576 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -0,0 +1,73 @@ +package io.metersphere.api.service; + +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.MalformedURLException; + +@Service +public class JmeterExecuteService { + @Resource + private JMeterService jMeterService; + @Resource + LoadTestProducer loadTestProducer; + + private static InputStream getStrToStream(String sInputString) { + if (StringUtils.isNotEmpty(sInputString)) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); + return tInputStringStream; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] jarFiles) { + if (request == null || request.getJmx() == null) { + return "执行文件为空,无法执行!"; + } + LogUtil.info(request.getJmx()); + // 检查KAFKA + loadTestProducer.checkKafka(); + // 生成附件/JAR文件 + FileUtils.createBodyFiles(bodyFiles, FileUtils.BODY_FILE_DIR); + FileUtils.createBodyFiles(jarFiles, FileUtils.JAR_FILE_DIR); + try { + this.loadJar(FileUtils.JAR_FILE_DIR); + // 生成执行脚本 + InputStream inputSource = getStrToStream(request.getJmx()); + Object scriptWrapper = SaveService.loadElement(inputSource); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + // 开始执行 + jMeterService.runSerial(request, testPlan); + } catch (Exception e) { + LogUtil.error(e.getMessage()); + return e.getMessage(); + } + return "SUCCESS"; + } +} diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java new file mode 100644 index 0000000..0b323c3 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -0,0 +1,53 @@ +package io.metersphere.api.service; + +import io.metersphere.api.config.KafkaProperties; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang.StringUtils; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; + +@Service +public class LoadTestProducer { + private static final String topic = "ms-api-exec-topic"; + + public void checkKafka() { + KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); + String[] servers = StringUtils.split(kafkaProperties.getBootstrapServers(), ","); + try { + for (String s : servers) { + String[] ipAndPort = s.split(":"); + //1,建立tcp + String ip = ipAndPort[0]; + int port = Integer.parseInt(ipAndPort[1]); + Socket soc = new Socket(); + // 1s timeout + soc.connect(new InetSocketAddress(ip, port), 1000); + //2.输入内容 + String content = "1010"; + byte[] bs = content.getBytes(); + OutputStream os = soc.getOutputStream(); + os.write(bs); + //3.关闭 + soc.close(); + } + } catch (Exception e) { + LogUtil.error(e); + MSException.throwException("Failed to connect to Kafka"); + } + } + + + @Resource + private KafkaTemplate kafkaTemplate; + + public void sendMessage(String report) { + this.kafkaTemplate.send(topic, report); + } +} diff --git a/src/main/java/io/metersphere/node/Application.java b/src/main/java/io/metersphere/node/Application.java deleted file mode 100644 index 35b186f..0000000 --- a/src/main/java/io/metersphere/node/Application.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.metersphere.node; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.PropertySource; - -@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) -@SpringBootApplication -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } -} diff --git a/src/main/java/org/apache/jmeter/NewDriver.java b/src/main/java/org/apache/jmeter/NewDriver.java new file mode 100644 index 0000000..fa24fcd --- /dev/null +++ b/src/main/java/org/apache/jmeter/NewDriver.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Main class for JMeter - sets up initial classpath and the loader. + * + */ +public final class NewDriver { + + private static final String CLASSPATH_SEPARATOR = File.pathSeparator; + + private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$ + + private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH); + + private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$ + + private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";// $NON-NLS-1$ + + private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";// $NON-NLS-1$ + /** The class loader to use for loading JMeter classes. */ + private static final DynamicClassLoader loader; + + /** The directory JMeter is installed in. */ + private static final String JMETER_INSTALLATION_DIRECTORY; + + private static final List EXCEPTIONS_IN_INIT = new ArrayList<>(); + + // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + public static void setContextClassLoader() { + Thread.currentThread().setContextClassLoader(loader); + } + + public static void loaderClass(String name) { + try { + loader.loadClass(name); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + static { + final List jars = new LinkedList<>(); + final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH); + + // Find JMeter home dir from the initial classpath + String tmpDir; + + //只从 jmeter.home 加载 + tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ + if (tmpDir.length() == 0) { + File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ + tmpDir = userDir.getAbsoluteFile().getParent(); + } + + JMETER_INSTALLATION_DIRECTORY=tmpDir; + + /* + * Does the system support UNC paths? If so, may need to fix them up + * later + */ + boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$ + + // Add standard jar locations to initial classpath + StringBuilder classpath = new StringBuilder(); + File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$ + new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$ + new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$ + for (File libDir : libDirs) { + File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar")); + if (libJars == null) { + new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here + continue; + } + Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars + for (File libJar : libJars) { + try { + String s = libJar.getPath(); + + // Fix path to allow the use of UNC URLs + if (usesUNC) { + if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "\\\\" + s;// $NON-NLS-1$ + } else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$ + s = "//" + s;// $NON-NLS-1$ + } + } // usesUNC + + jars.add(new File(s).toURI().toURL());// See Java bug 4496398 + classpath.append(CLASSPATH_SEPARATOR); + classpath.append(s); + } catch (MalformedURLException e) { // NOSONAR + EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e)); + } + } + } + + // ClassFinder needs the classpath + System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString()); + loader = AccessController.doPrivileged( + (PrivilegedAction) () -> + new DynamicClassLoader(jars.toArray(new URL[jars.size()])) + ); + } + + /** + * Prevent instantiation. + */ + private NewDriver() { + } + + /** + * Generate an array of jar files located in a directory. + * Jar files located in sub directories will not be added. + * + * @param dir to search for the jar files. + */ + private static File[] listJars(File dir) { + if (dir.isDirectory()) { + return dir.listFiles((f, name) -> { + if (name.endsWith(".jar")) {// $NON-NLS-1$ + File jar = new File(f, name); + return jar.isFile() && jar.canRead(); + } + return false; + }); + } + return new File[0]; + } + + /** + * Add a URL to the loader classpath only; does not update the system classpath. + * + * @param path to be added. + * @throws MalformedURLException when path points to an invalid url + */ + public static void addURL(String path) throws MalformedURLException { + File furl = new File(path); + loader.addURL(furl.toURI().toURL()); // See Java bug 4496398 + File[] jars = listJars(furl); + for (File jar : jars) { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + } + } + + /** + * Add a URL to the loader classpath only; does not update the system + * classpath. + * + * @param url + * The {@link URL} to add to the classpath + */ + public static void addURL(URL url) { + loader.addURL(url); + } + + /** + * Add a directory or jar to the loader and system classpaths. + * + * @param path + * to add to the loader and system classpath + * @throws MalformedURLException + * if path can not be transformed to a valid + * {@link URL} + */ + public static void addPath(String path) throws MalformedURLException { + File file = new File(path); + // Ensure that directory URLs end in "/" + if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$ + file = new File(path + "/");// $NON-NLS-1$ + } + loader.addURL(file.toURI().toURL()); // See Java bug 4496398 + StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH)); + sb.append(CLASSPATH_SEPARATOR); + sb.append(path); + File[] jars = listJars(file); + for (File jar : jars) { + loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 + sb.append(CLASSPATH_SEPARATOR); + sb.append(jar.getPath()); + } + + // ClassFinder needs this + System.setProperty(JAVA_CLASS_PATH,sb.toString()); + } + + /** + * Get the directory where JMeter is installed. This is the absolute path + * name. + * + * @return the directory where JMeter is installed. + */ + public static String getJMeterDir() { + return JMETER_INSTALLATION_DIRECTORY; + } + + /** + * The main program which actually runs JMeter. + * + * @param args + * the command line arguments + */ + public static void main(String[] args) { + if(!EXCEPTIONS_IN_INIT.isEmpty()) { + System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use + } else { + Thread.currentThread().setContextClassLoader(loader); + + + setLoggingProperties(args); + + try { + // Only set property if it has not been set explicitely + if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) { + System.setProperty(HEADLESS_MODE_PROPERTY, "true"); + } + Class initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$ + Object instance = initialClass.getDeclaredConstructor().newInstance(); + Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$ + startup.invoke(instance, new Object[] { args }); + } catch(Throwable e){ // NOSONAR We want to log home directory in case of exception + e.printStackTrace(); // NOSONAR No logger at this step + System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use + } + } + } + + /** + * @param exceptionsInInit List of {@link Exception} + * @return String + */ + private static String exceptionsToString(List exceptionsInInit) { + StringBuilder builder = new StringBuilder(); + for (Exception exception : exceptionsInInit) { + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + exception.printStackTrace(printWriter); // NOSONAR + builder.append(stringWriter.toString()) + .append("\r\n"); + } + return builder.toString(); + } + + /* + * Set logging related system properties. + */ + private static void setLoggingProperties(String[] args) { + String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$ + + if (jmLogFile != null && !jmLogFile.isEmpty()) { + jmLogFile = replaceDateFormatInFileName(jmLogFile); + System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$ + } else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$ + System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$ + } + + String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$ + File logConfFile = null; + + if (jmLogConf != null && !jmLogConf.isEmpty()) { + logConfFile = new File(jmLogConf); + } else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$ + logConfFile = new File("log4j2.xml");// $NON-NLS-1$ + if (!logConfFile.isFile()) { + logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "jmeter.bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$ + } + } + + if (logConfFile != null) { + System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$ + } + } + + private static boolean shouldBeHeadless(String[] args) { + for (String arg : args) { + if("-n".equals(arg) || "-s".equals(arg) || "-g".equals(arg)) { + return true; + } + } + return false; + } + /* + * Find command line argument option value by the id and name. + */ + private static String getCommandLineArgument(String[] args, int id, String name) { + final String shortArgName = "-" + ((char) id);// $NON-NLS-1$ + final String longArgName = "--" + name;// $NON-NLS-1$ + + String value = null; + + for (int i = 0; i < args.length; i++) { + if ((shortArgName.equals(args[i]) && i < args.length - 1) + || longArgName.equals(args[i])) { + if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$ + value = args[i + 1]; + } + break; + } else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) { + value = args[i].substring(shortArgName.length()); + break; + } + } + + return value; + } + + /* + * If the fileName contains at least one set of paired single-quotes, reformat using DateFormat + */ + private static String replaceDateFormatInFileName(String fileName) { + try { + StringBuilder builder = new StringBuilder(); + + final Date date = new Date(); + int fromIndex = 0; + int begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + int end; + + String format; + SimpleDateFormat dateFormat; + + while (begin != -1) { + builder.append(fileName.substring(fromIndex, begin)); + + fromIndex = begin + 1; + end = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + if (end == -1) { + throw new IllegalArgumentException("Invalid pairs of single-quotes in the file name: " + fileName);// $NON-NLS-1$ + } + + format = fileName.substring(begin + 1, end); + dateFormat = new SimpleDateFormat(format); + builder.append(dateFormat.format(date)); + + fromIndex = end + 1; + begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ + } + + if (fromIndex < fileName.length() - 1) { + builder.append(fileName.substring(fromIndex)); + } + + return builder.toString(); + } catch (Exception ex) { + System.err.println("Error replacing date format in file name:"+fileName+", error:"+ex.getMessage()); // NOSONAR + } + + return fileName; + } +} diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java new file mode 100644 index 0000000..4b832e4 --- /dev/null +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -0,0 +1,229 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package org.apache.jmeter.assertions; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Predicate; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Map; + +public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { + private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class); + private static final long serialVersionUID = 2L; + public static final String JSONPATH = "JSON_PATH"; + public static final String EXPECTEDVALUE = "EXPECTED_VALUE"; + public static final String JSONVALIDATION = "JSONVALIDATION"; + public static final String EXPECT_NULL = "EXPECT_NULL"; + public static final String INVERT = "INVERT"; + public static final String ISREGEX = "ISREGEX"; + private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); + + public JSONPathAssertion() { + } + + private static DecimalFormat createDecimalFormat() { + DecimalFormat decimalFormatter = new DecimalFormat("#.#"); + decimalFormatter.setMaximumFractionDigits(340); + decimalFormatter.setMinimumFractionDigits(1); + return decimalFormatter; + } + + public String getOption() { + return getPropertyAsString("ASS_OPTION"); + } + + public String getJsonPath() { + return this.getPropertyAsString("JSON_PATH"); + } + + public void setJsonPath(String jsonPath) { + this.setProperty("JSON_PATH", jsonPath); + } + + public String getExpectedValue() { + return this.getPropertyAsString("EXPECTED_VALUE"); + } + + public void setExpectedValue(String expectedValue) { + this.setProperty("EXPECTED_VALUE", expectedValue); + } + + public void setJsonValidationBool(boolean jsonValidation) { + this.setProperty("JSONVALIDATION", jsonValidation); + } + + public void setExpectNull(boolean val) { + this.setProperty("EXPECT_NULL", val); + } + + public boolean isExpectNull() { + return this.getPropertyAsBoolean("EXPECT_NULL"); + } + + public boolean isJsonValidationBool() { + return this.getPropertyAsBoolean("JSONVALIDATION"); + } + + public void setInvert(boolean invert) { + this.setProperty("INVERT", invert); + } + + public boolean isInvert() { + return this.getPropertyAsBoolean("INVERT"); + } + + public void setIsRegex(boolean flag) { + this.setProperty("ISREGEX", flag); + } + + public boolean isUseRegex() { + return this.getPropertyAsBoolean("ISREGEX", true); + } + + private void doAssert(String jsonString) { + Object value = JsonPath.read(jsonString, this.getJsonPath(), new Predicate[0]); + if (this.isJsonValidationBool()) { + if (value instanceof JSONArray) { + if (this.arrayMatched((JSONArray) value)) { + return; + } + } else if (this.isExpectNull() && value == null || this.isEquals(value)) { + return; + } + + if (this.isExpectNull()) { + throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); + } else { + String msg; + if (this.isUseRegex()) { + msg = "Value expected to match regexp '%s', but it did not match: '%s'"; + } else { + msg = "Value expected to be '%s', but found '%s'"; + } + + throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value))); + } + } + } + + private boolean arrayMatched(JSONArray value) { + if (value.isEmpty() && "[]".equals(this.getExpectedValue())) { + return true; + } else { + Object[] var2 = value.toArray(); + int var3 = var2.length; + + for (int var4 = 0; var4 < var3; ++var4) { + Object subj = var2[var4]; + if (subj == null && this.isExpectNull() || this.isEquals(subj)) { + return true; + } + } + + return this.isEquals(value); + } + } + + private boolean isEquals(Object subj) { + String str = objectToString(subj); + if (this.isUseRegex()) { + Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue()); + return JMeterUtils.getMatcher().matches(str, pattern); + } else { + if (StringUtils.isNotEmpty(getOption())) { + boolean refFlag = false; + switch (getOption()) { + case "CONTAINS": + refFlag = str.contains(getExpectedValue()); + break; + case "NOT_CONTAINS": + refFlag = !str.contains(getExpectedValue()); + break; + case "EQUALS": + refFlag = str.equals(getExpectedValue()); + break; + case "NOT_EQUALS": + refFlag = !str.contains(getExpectedValue()); + break; + } + return refFlag; + } + return str.equals(this.getExpectedValue()); + } + } + + public AssertionResult getResult(SampleResult samplerResult) { + AssertionResult result = new AssertionResult(this.getName()); + String responseData = samplerResult.getResponseDataAsString(); + if (responseData.isEmpty()) { + return result.setResultForNull(); + } else { + result.setFailure(false); + result.setFailureMessage(""); + if (!this.isInvert()) { + try { + this.doAssert(responseData); + } catch (Exception var6) { + log.debug("Assertion failed", var6); + result.setFailure(true); + result.setFailureMessage(var6.getMessage()); + } + } else { + try { + this.doAssert(responseData); + result.setFailure(true); + if (this.isJsonValidationBool()) { + if (this.isExpectNull()) { + result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches null"); + } else { + result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches " + this.getExpectedValue()); + } + } else { + result.setFailureMessage("Failed that JSONPath not exists: " + this.getJsonPath()); + } + } catch (Exception var5) { + log.debug("Assertion failed, as expected", var5); + } + } + + return result; + } + } + + public static String objectToString(Object subj) { + String str; + if (subj == null) { + str = "null"; + } else if (subj instanceof Map) { + str = (new JSONObject((Map) subj)).toJSONString(); + } else if (!(subj instanceof Double) && !(subj instanceof Float)) { + str = subj.toString(); + } else { + str = ((DecimalFormat) decimalFormatter.get()).format(subj); + } + + return str; + } + + public void threadStarted() { + } + + public void threadFinished() { + decimalFormatter.remove(); + } +} diff --git a/src/main/java/org/apache/jmeter/reporters/ResultAction.java b/src/main/java/org/apache/jmeter/reporters/ResultAction.java new file mode 100644 index 0000000..10e611e --- /dev/null +++ b/src/main/java/org/apache/jmeter/reporters/ResultAction.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.reporters; + +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.OnErrorTestElement; +import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; + +/** + * ResultAction - take action based on the status of the last Result + */ +public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener { + + private static final long serialVersionUID = 242L; + + private static final Logger log = LoggerFactory.getLogger(ResultAction.class); + + /** + * Constructor is initially called once for each occurrence in the test plan + * For GUI, several more instances are created Then clear is called at start + * of test Called several times during test startup The name will not + * necessarily have been set at this point. + */ + public ResultAction() { + super(); + } + + /** + * Examine the sample(s) and take appropriate action + * + * @see SampleListener#sampleOccurred(SampleEvent) + */ + @Override + public void sampleOccurred(SampleEvent e) { + SampleResult s = e.getResult(); + if (log.isDebugEnabled()) { + log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful()); + } + if (!s.isSuccessful()) { + if (isStopTestNow()) { + s.setStopTestNow(true); + } else if (isStopTest()) { + s.setStopTest(true); + } else if (isStopThread()) { + s.setStopThread(true); + } else if (isStartNextThreadLoop()) { + s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD); + } else if (isStartNextIterationOfCurrentLoop()) { + s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP); + } else if (isBreakCurrentLoop()) { + s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); + } + } else { + if (getErrorAction() == 1000) { + s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStarted(SampleEvent e) { + // not used + } + + /** + * {@inheritDoc} + */ + @Override + public void sampleStopped(SampleEvent e) { + // not used + } + +} diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java new file mode 100644 index 0000000..ee44117 --- /dev/null +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -0,0 +1,1623 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +// For unit tests, @see TestSampleResult + +/** + * This is a nice packaging for the various information returned from taking a + * sample of an entry. + */ +public class SampleResult implements Serializable, Cloneable, Searchable { + + private static final long serialVersionUID = 241L; + + // Needs to be accessible from Test code + static Logger log = LoggerFactory.getLogger(SampleResult.class); + + /** + * The default encoding to be used if not overridden. + * The value is ISO-8859-1. + */ + public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name(); + + private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); + private static final String OK_MSG = "OK"; // $NON-NLS-1$ + private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ + + + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code + /** + * The default encoding to be used to decode the responseData byte array. + * The value is defined by the property "sampleresult.default.encoding" + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + */ + protected static final String DEFAULT_ENCODING + = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ + DEFAULT_HTTP_ENCODING); + + /** + * The default used by {@link #setResponseData(String, String)} + */ + private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); + + /** + * Data type value ({@value}) indicating that the response data is text. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String TEXT = "text"; // $NON-NLS-1$ + + /** + * Data type value ({@value}) indicating that the response data is binary. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String BINARY = "jmeter.bin"; // $NON-NLS-1$ + + private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); + + // List of types that are known to be binary + private static final String[] BINARY_TYPES = { + "image/", //$NON-NLS-1$ + "audio/", //$NON-NLS-1$ + "video/", //$NON-NLS-1$ + }; + + // List of types that are known to be ascii, although they may appear to be binary + private static final String[] NON_BINARY_TYPES = { + "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "video/f4m", //$NON-NLS-1$ (Flash Media Manifest) + "image/svg+xml" //$NON-NLS-1$ (SVG is xml) + }; + + + /** + * empty array which can be returned instead of null + */ + private static final byte[] EMPTY_BA = new byte[0]; + + private static final SampleResult[] EMPTY_SR = new SampleResult[0]; + + private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; + + private static final boolean START_TIMESTAMP = + JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ + + /** + * Allow read-only access from test code + */ + private static final boolean USE_NANO_TIME = + JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ + + /** + * How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread + */ + private static final long NANOTHREAD_SLEEP = + JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$ + + private static final String NULL_FILENAME = "NULL"; + + static { + if (START_TIMESTAMP) { + log.info("Note: Sample TimeStamps are START times"); + } else { + log.info("Note: Sample TimeStamps are END times"); + } + log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING); + log.info("sampleresult.useNanoTime={}", USE_NANO_TIME); + log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP); + + if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) { + // Make sure we start with a reasonable value + NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); + NanoOffset nanoOffset = new NanoOffset(); + nanoOffset.setDaemon(true); + nanoOffset.setName("NanoOffset"); + nanoOffset.start(); + } + } + + private String samplerId; + + public String getSamplerId() { + return this.samplerId; + } + + private SampleSaveConfiguration saveConfig; + + private SampleResult parent; + + private byte[] responseData = EMPTY_BA; + + private String responseCode = "";// Never return null + + private String label = "";// Never return null + + /** + * Filename used by ResultSaver + */ + private String resultFileName = ""; + + /** + * The data used by the sampler + */ + private String samplerData; + + private String threadName = ""; // Never return null + + private String responseMessage = ""; + + private String responseHeaders = ""; // Never return null + + private String requestHeaders = ""; + + /** + * timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) + * the time stamp - can be start or end + */ + private long timeStamp = 0; + + private long startTime = 0; + + private long endTime = 0; + + private long idleTime = 0;// Allow for non-sample time + + /** + * Start of pause (if any) + */ + private long pauseTime = 0; + + private List assertionResults; + + private List subResults; + + /** + * The data type of the sample + * + * @see #getDataType() + * @see #setDataType(String) + * @see #TEXT + * @see #BINARY + */ + private String dataType = ""; // Don't return null if not set + + private boolean success; + + /** + * Files that this sample has been saved in. + * In Non GUI mode and when best utils is used, size never exceeds 1, + * but as a compromise set it to 2 + */ + private final Set files = ConcurrentHashMap.newKeySet(2); + + // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? + private String dataEncoding;// (is this really the character set?) e.g. + // ISO-8895-1, UTF-8 + + private String contentType = ""; // e.g. text/html; charset=utf-8 + + /** + * elapsed time + */ + private long elapsedTime = 0; + + /** + * time to first response + */ + private long latency = 0; + + /** + * time to end connecting + */ + private long connectTime = 0; + + /** + * Way to signal what to do on Test + */ + private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE; + + /** + * Should thread terminate? + */ + private boolean stopThread = false; + + /** + * Should test terminate? + */ + private boolean stopTest = false; + + /** + * Should test terminate abruptly? + */ + private boolean stopTestNow = false; + + private int sampleCount = 1; + + private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data + + private int headersSize = 0; + + private long bodySize = 0; + + /** + * Currently active threads in this thread group + */ + private volatile int groupThreads = 0; + + /** + * Currently active threads in all thread groups + */ + private volatile int allThreads = 0; + + private final long nanoTimeOffset; + + // Allow testcode access to the settings + final boolean useNanoTime; + + final long nanoThreadSleep; + + private long sentBytes; + + private URL location; + + private transient boolean ignore; + + private transient int subResultIndex; + + /** + * Cache for responseData as string to avoid multiple computations + */ + private transient volatile String responseDataAsString; + + public SampleResult() { + this(USE_NANO_TIME, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime setting + SampleResult(boolean nanoTime) { + this(nanoTime, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime and nanoThreadSleep settings + SampleResult(boolean nanoTime, long nanoThreadSleep) { + this.elapsedTime = 0; + this.useNanoTime = nanoTime; + this.nanoThreadSleep = nanoThreadSleep; + this.nanoTimeOffset = initOffset(); + // 增加请求ID的获取 + Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); + if (sampler != null) { + this.samplerId = sampler.getPropertyAsString("MS-ID"); + } + + } + + /** + * Copy constructor. + * + * @param res existing sample result + */ + public SampleResult(SampleResult res) { + this(); + allThreads = res.allThreads;//OK + assertionResults = res.assertionResults; + bytes = res.bytes; + headersSize = res.headersSize; + bodySize = res.bodySize; + contentType = res.contentType;//OK + dataEncoding = res.dataEncoding;//OK + dataType = res.dataType;//OK + endTime = res.endTime;//OK + // files is created automatically, and applies per instance + groupThreads = res.groupThreads;//OK + idleTime = res.idleTime; + label = res.label;//OK + latency = res.latency; + connectTime = res.connectTime; + location = res.location;//OK + parent = res.parent; + pauseTime = res.pauseTime; + requestHeaders = res.requestHeaders;//OK + responseCode = res.responseCode;//OK + responseData = res.responseData;//OK + responseDataAsString = null; + responseHeaders = res.responseHeaders;//OK + responseMessage = res.responseMessage;//OK + + // Don't copy this; it is per instance resultFileName = res.resultFileName; + + sampleCount = res.sampleCount; + samplerData = res.samplerData; + saveConfig = res.saveConfig; + sentBytes = res.sentBytes; + startTime = res.startTime;//OK + stopTest = res.stopTest; + stopTestNow = res.stopTestNow; + stopThread = res.stopThread; + testLogicalAction = res.testLogicalAction; + subResults = res.subResults; + success = res.success;//OK + threadName = res.threadName;//OK + elapsedTime = res.elapsedTime; + timeStamp = res.timeStamp; + } + + /** + * Create a sample with a specific elapsed time but don't allow the times to + * be changed later + *

+ * (only used by HTTPSampleResult) + * + * @param elapsed time + * @param atend create the sample finishing now, else starting now + */ + protected SampleResult(long elapsed, boolean atend) { + this(); + long now = currentTimeInMillis(); + if (atend) { + setTimes(now - elapsed, now); + } else { + setTimes(now, now + elapsed); + } + } + + /** + * Allow users to create a sample with specific timestamp and elapsed times + * for cloning purposes, but don't allow the times to be changed later + *

+ * Currently used by CSVSaveService and + * StatisticalSampleResult + * + * @param stamp this may be a start time or an end time (both in + * milliseconds) + * @param elapsed time in milliseconds + */ + public SampleResult(long stamp, long elapsed) { + this(); + stampAndTime(stamp, elapsed); + } + + private long initOffset() { + if (useNanoTime) { + return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); + } else { + return Long.MIN_VALUE; + } + } + + /** + * @param propertiesToSave The propertiesToSave to set. + */ + public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { + this.saveConfig = propertiesToSave; + } + + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + public boolean isStampedAtStart() { + return START_TIMESTAMP; + } + + /** + * Create a sample with specific start and end times for test purposes, but + * don't allow the times to be changed later + *

+ * (used by StatVisualizerModel.Test) + * + * @param start start time in milliseconds since unix epoch + * @param end end time in milliseconds since unix epoch + * @return sample with given start and end time + */ + public static SampleResult createTestSample(long start, long end) { + SampleResult res = new SampleResult(); + res.setStartTime(start); + res.setEndTime(end); + return res; + } + + /** + * Create a sample with a specific elapsed time for test purposes, but don't + * allow the times to be changed later + * + * @param elapsed - desired elapsed time in milliseconds + * @return sample that starts 'now' and ends elapsed milliseconds later + */ + public static SampleResult createTestSample(long elapsed) { + long now = System.currentTimeMillis(); + return createTestSample(now, now + elapsed); + } + + private static long sampleNsClockInMs() { + return System.nanoTime() / 1000000; + } + + /** + * Helper method to get 1 ms resolution timing. + * + * @return the current time in milliseconds + * @throws RuntimeException when useNanoTime is true but + * nanoTimeOffset is not set + */ + public long currentTimeInMillis() { + if (useNanoTime) { + if (nanoTimeOffset == Long.MIN_VALUE) { + throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set"); + } + return sampleNsClockInMs() + nanoTimeOffset; + } + return System.currentTimeMillis(); + } + + // Helper method to maintain timestamp relationships + private void stampAndTime(long stamp, long elapsed) { + if (START_TIMESTAMP) { + startTime = stamp; + endTime = stamp + elapsed; + } else { + startTime = stamp - elapsed; + endTime = stamp; + } + timeStamp = stamp; + elapsedTime = elapsed; + } + + /** + * For use by SaveService only. + * + * @param stamp this may be a start time or an end time (both in milliseconds) + * @param elapsed time in milliseconds + * @throws RuntimeException when startTime or endTime has been + * set already + */ + public void setStampAndTime(long stamp, long elapsed) { + if (startTime != 0 || endTime != 0) { + throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set"); + } + stampAndTime(stamp, elapsed); + } + + /** + * Set the "marked" flag to show that the result has been written to the file. + * + * @param filename the name of the file + * @return true if the result was previously marked + */ + public boolean markFile(String filename) { + return !files.add(filename != null ? filename : NULL_FILENAME); + } + + public String getResponseCode() { + return responseCode; + } + + /** + * Set response code to OK, i.e. "200" + */ + public void setResponseCodeOK() { + responseCode = OK_CODE; + } + + public void setResponseCode(String code) { + responseCode = code; + } + + public boolean isResponseCodeOK() { + return responseCode.equals(OK_CODE); + } + + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String msg) { + responseMessage = msg; + } + + public void setResponseMessageOK() { + responseMessage = OK_MSG; + } + + /** + * Set result statuses OK - shorthand method to set: + *

    + *
  • ResponseCode
  • + *
  • ResponseMessage
  • + *
  • Successful status
  • + *
+ */ + public void setResponseOK() { + setResponseCodeOK(); + setResponseMessageOK(); + setSuccessful(true); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /** + * Get the sample timestamp, which may be either the start time or the end time. + * + * @return timeStamp in milliseconds + * @see #getStartTime() + * @see #getEndTime() + */ + public long getTimeStamp() { + return timeStamp; + } + + public String getSampleLabel() { + return label; + } + + /** + * Get the sample label for use in summary reports etc. + * + * @param includeGroup whether to include the thread group name + * @return the label + */ + public String getSampleLabel(boolean includeGroup) { + if (includeGroup) { + return threadName.substring(0, threadName.lastIndexOf(' ')) + ":" + label; + } + return label; + } + + public void setSampleLabel(String label) { + this.label = label; + } + + public void addAssertionResult(AssertionResult assertResult) { + if (assertionResults == null) { + assertionResults = new ArrayList<>(); + } + assertionResults.add(assertResult); + } + + /** + * Gets the assertion results associated with this sample. + * + * @return an array containing the assertion results for this sample. + * Returns empty array if there are no assertion results. + */ + public AssertionResult[] getAssertionResults() { + if (assertionResults == null) { + return EMPTY_AR; + } + return assertionResults.toArray(new AssertionResult[assertionResults.size()]); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addSubResult(SampleResult subResult) { + addSubResult(subResult, isRenameSampleLabel()); + } + + /** + * see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055 + * + * @return true if TestPlan is in functional mode or property subresults.disable_renaming is true + */ + public static boolean isRenameSampleLabel() { + return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + public void addSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResult == null) { + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 + return; + } + String tn = getThreadName(); + if (tn.length() == 0) { + tn = Thread.currentThread().getName(); + this.setThreadName(tn); + } + subResult.setThreadName(tn); + + // Extend the time to the end of the added sample + setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 + // Include the byte count for the added sample + setBytes(getBytesAsLong() + subResult.getBytesAsLong()); + setSentBytes(getSentBytes() + subResult.getSentBytes()); + setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); + setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong()); + addRawSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addRawSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + private void addRawSubResult(SampleResult subResult, boolean renameSubResults) { + storeSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + */ + public void storeSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + private void storeSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResults == null) { + subResults = new ArrayList<>(); + } + if (renameSubResults) { + subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Gets the subresults associated with this sample. + * + * @return an array containing the subresults for this sample. Returns an + * empty array if there are no subresults. + */ + public SampleResult[] getSubResults() { + if (subResults == null) { + return EMPTY_SR; + } + return subResults.toArray(new SampleResult[subResults.size()]); + } + + /** + * Sets the responseData attribute of the SampleResult object. + *

+ * If the parameter is null, then the responseData is set to an empty byte array. + * This ensures that getResponseData() can never be null. + * + * @param response the new responseData value + */ + public void setResponseData(byte[] response) { + responseDataAsString = null; + responseData = response == null ? EMPTY_BA : response; + } + + /** + * Sets the responseData attribute of the SampleResult object. + * Should only be called after setting the dataEncoding (if necessary) + * + * @param response the new responseData value (String) + * @deprecated - only intended for use from BeanShell code + */ + @Deprecated + public void setResponseData(String response) { + responseDataAsString = null; + try { + responseData = response.getBytes(getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string, using default encoding. " + e.getLocalizedMessage()); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + /** + * Sets the encoding and responseData attributes of the SampleResult object. + * + * @param response the new responseData value (String) + * @param encoding the encoding to set and then use (if null, use platform default) + */ + public void setResponseData(final String response, final String encoding) { + responseDataAsString = null; + String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET; + try { + responseData = response.getBytes(encodeUsing); + setDataEncoding(encodeUsing); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string using '" + encodeUsing + + "', using default encoding: " + DEFAULT_CHARSET, e); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + setDataEncoding(DEFAULT_CHARSET); + } + } + + /** + * Gets the responseData attribute of the SampleResult object. + *

+ * Note that some samplers may not store all the data, in which case + * getResponseData().length will be incorrect. + *

+ * Instead, always use {@link #getBytes()} to obtain the sample result byte count. + *

+ * + * @return the responseData value (cannot be null) + */ + public byte[] getResponseData() { + return responseData; + } + + /** + * Gets the responseData of the SampleResult object as a String + * + * @return the responseData value as a String, converted according to the encoding + */ + public String getResponseDataAsString() { + try { + if (responseDataAsString == null) { + responseDataAsString = new String(responseData, getDataEncodingWithDefault()); + } + return responseDataAsString; + } catch (UnsupportedEncodingException e) { + log.warn("Using platform default as " + getDataEncodingWithDefault() + " caused " + e); + return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + public void setSamplerData(String s) { + samplerData = s; + } + + public String getSamplerData() { + return samplerData; + } + + /** + * Get the time it took this sample to occur. + * + * @return elapsed time in milliseconds + */ + public long getTime() { + return elapsedTime; + } + + public boolean isSuccessful() { + return success; + } + + /** + * Sets the data type of the sample. + * + * @param dataType String containing {@link #BINARY} or {@link #TEXT} + * @see #BINARY + * @see #TEXT + */ + public void setDataType(String dataType) { + this.dataType = dataType; + } + + /** + * Returns the data type of the sample. + * + * @return String containing {@link #BINARY} or {@link #TEXT} or the empty string + * @see #BINARY + * @see #TEXT + */ + public String getDataType() { + return dataType; + } + + /** + * Extract and save the DataEncoding and DataType from the parameter provided. + * Does not save the full content Type. + * + * @param ct - content type (may be null) + * @see #setContentType(String) which should be used to save the full content-type string + */ + public void setEncodingAndType(String ct) { + if (ct != null) { + // Extract charset and store as DataEncoding + // N.B. The meta tag: + // + // is now processed by HTTPSampleResult#getDataEncodingWithDefault + final String charsetPrefix = "charset="; // $NON-NLS-1$ + int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix); + if (cset >= 0) { + String charSet = ct.substring(cset + charsetPrefix.length()); + // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed + int semiColon = charSet.indexOf(';'); + if (semiColon >= 0) { + charSet = charSet.substring(0, semiColon); + } + // Check for quoted string + if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$ + setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes + } else { + setDataEncoding(charSet); + } + } + if (isBinaryType(ct)) { + setDataType(BINARY); + } else { + setDataType(TEXT); + } + } + } + + /* + * Determine if content-type is known to be binary, i.e. not displayable as text. + * + * @param ct content type + * @return true if content-type is of type binary. + */ + public static boolean isBinaryType(String ct) { + for (String entry : NON_BINARY_TYPES) { + if (ct.startsWith(entry)) { + return false; + } + } + for (String binaryType : BINARY_TYPES) { + if (ct.startsWith(binaryType)) { + return true; + } + } + return false; + } + + /** + * Sets the successful attribute of the SampleResult object. + * + * @param success the new successful value + */ + public void setSuccessful(boolean success) { + this.success = success; + } + + /** + * Returns the display name. + * + * @return display name of this sample result + */ + @Override + public String toString() { + return getSampleLabel(); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @return the value of the dataEncoding or DEFAULT_ENCODING + */ + public String getDataEncodingWithDefault() { + return getDataEncodingWithDefault(DEFAULT_ENCODING); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @param defaultEncoding the default to be applied + * @return the value of the dataEncoding or the provided default + */ + protected String getDataEncodingWithDefault(String defaultEncoding) { + if (dataEncoding != null && dataEncoding.length() > 0) { + return dataEncoding; + } + return defaultEncoding; + } + + /** + * Returns the dataEncoding. May be null or the empty String. + * + * @return the value of the dataEncoding + */ + public String getDataEncodingNoDefault() { + return dataEncoding; + } + + /** + * Sets the dataEncoding. + * + * @param dataEncoding the dataEncoding to set, e.g. ISO-8895-1, UTF-8 + */ + public void setDataEncoding(String dataEncoding) { + this.dataEncoding = dataEncoding; + } + + /** + * @return whether to stop the test waiting for current running Sampler to end + */ + public boolean isStopTest() { + return stopTest; + } + + /** + * @return whether to stop the test now interrupting current running samplers + */ + public boolean isStopTestNow() { + return stopTestNow; + } + + /** + * @return whether to stop this thread + */ + public boolean isStopThread() { + return stopThread; + } + + public void setStopTest(boolean b) { + stopTest = b; + } + + public void setStopTestNow(boolean b) { + stopTestNow = b; + } + + public void setStopThread(boolean b) { + stopThread = b; + } + + /** + * @return the request headers + */ + public String getRequestHeaders() { + return requestHeaders; + } + + /** + * @return the response headers + */ + public String getResponseHeaders() { + return responseHeaders; + } + + /** + * @param string - + * request headers + */ + public void setRequestHeaders(String string) { + requestHeaders = string; + } + + /** + * @param string - + * response headers + */ + public void setResponseHeaders(String string) { + responseHeaders = string; + } + + /** + * @return the full content type - e.g. text/html [;charset=utf-8 ] + */ + public String getContentType() { + return contentType; + } + + /** + * Get the media type from the Content Type + * + * @return the media type - e.g. text/html (without charset, if any) + */ + public String getMediaType() { + return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH); + } + + /** + * Stores the content-type string, e.g. text/xml; charset=utf-8 + * + * @param string the content-type to be set + * @see #setEncodingAndType(String) which can be used to extract the charset. + */ + public void setContentType(String string) { + contentType = string; + } + + /** + * @return idleTime + */ + public long getIdleTime() { + return idleTime; + } + + /** + * @return the end time + */ + public long getEndTime() { + return endTime; + } + + /** + * @return the start time + */ + public long getStartTime() { + return startTime; + } + + /* + * Helper methods N.B. setStartTime must be called before setEndTime + * + * setStartTime is used by HTTPSampleResult to clone the parent sampler and + * allow the original start time to be kept + */ + protected final void setStartTime(long start) { + startTime = start; + if (START_TIMESTAMP) { + timeStamp = startTime; + } + } + + public void setEndTime(long end) { + endTime = end; + if (!START_TIMESTAMP) { + timeStamp = endTime; + } + if (startTime == 0) { + log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } else { + elapsedTime = endTime - startTime - idleTime; + } + } + + /** + * Set idle time pause. + * For use by SampleResultConverter/CSVSaveService. + * + * @param idle long + */ + public void setIdleTime(long idle) { + idleTime = idle; + } + + private void setTimes(long start, long end) { + setStartTime(start); + setEndTime(end); + } + + /** + * Record the start time of a sample + */ + public void sampleStart() { + if (startTime == 0) { + setStartTime(currentTimeInMillis()); + } else { + log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Record the end time of a sample and calculate the elapsed time + */ + public void sampleEnd() { + if (endTime == 0) { + setEndTime(currentTimeInMillis()); + } else { + log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Pause a sample + */ + public void samplePause() { + if (pauseTime != 0) { + log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + pauseTime = currentTimeInMillis(); + } + + /** + * Resume a sample + */ + public void sampleResume() { + if (pauseTime == 0) { + log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + idleTime += currentTimeInMillis() - pauseTime; + pauseTime = 0; + } + + /** + * When a Sampler is working as a monitor + * + * @param monitor flag whether this sampler is working as a monitor + * @deprecated since 3.2 NOOP + */ + @Deprecated + public void setMonitor(boolean monitor) { + // NOOP + } + + /** + * If the sampler is a monitor, method will return true. + * + * @return true if the sampler is a monitor + * @deprecated since 3.2 always return false + */ + @Deprecated + public boolean isMonitor() { + return false; + } + + /** + * The statistical sample sender aggregates several samples to save on + * transmission costs. + * + * @param count number of samples represented by this instance + */ + public void setSampleCount(int count) { + sampleCount = count; + } + + /** + * return the sample count. by default, the value is 1. + * + * @return the sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Returns the count of errors. + * + * @return 0 - or 1 if the sample failed + *

+ * TODO do we need allow for nested samples? + */ + public int getErrorCount() { + return success ? 0 : 1; + } + + public void setErrorCount(int i) {// for reading from CSV files + // ignored currently + } + + /* + * TODO: error counting needs to be sorted out. + * + * At present the Statistical Sampler tracks errors separately + * It would make sense to move the error count here, but this would + * mean lots of changes. + * It's also tricky maintaining the count - it can't just be incremented/decremented + * when the success flag is set as this may be done multiple times. + * The work-round for now is to do the work in the StatisticalSampleResult, + * which overrides this method. + * Note that some JMS samplers also create samples with > 1 sample count + * Also the Transaction Controller probably needs to be changed to do + * proper sample and error accounting. + * The purpose of this work-round is to allow at least minimal support for + * errors in remote statistical batch mode. + * + */ + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + */ + public void setBytes(long length) { + bytes = length; + } + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + * @deprecated use setBytes(long) + */ + @Deprecated + public void setBytes(int length) { + setBytes((long) length); + } + + /** + * @param sentBytesCount long sent bytes + */ + public void setSentBytes(long sentBytesCount) { + sentBytes = sentBytesCount; + } + + /** + * @return the sentBytes + */ + public long getSentBytes() { + return sentBytes; + } + + /** + * return the bytes returned by the response. + * + * @return byte count + * @deprecated use getBytesAsLong + */ + @Deprecated + public int getBytes() { + return (int) getBytesAsLong(); + } + + /** + * return the bytes returned by the response. + * + * @return byte count + */ + public long getBytesAsLong() { + long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong(); + return tmpSum == 0 ? bytes : tmpSum; + } + + /** + * @return Returns the latency. + */ + public long getLatency() { + return latency; + } + + /** + * Set the time to the first response + */ + public void latencyEnd() { + latency = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param latency The latency to set. + */ + public void setLatency(long latency) { + this.latency = latency; + } + + /** + * @return Returns the connect time. + */ + public long getConnectTime() { + return connectTime; + } + + /** + * Set the time to the end of connecting + */ + public void connectEnd() { + connectTime = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param time The connect time to set. + */ + public void setConnectTime(long time) { + this.connectTime = time; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param timeStamp The timeStamp to set. + */ + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + + public void setURL(URL location) { + this.location = location; + } + + public URL getURL() { + return location; + } + + /** + * Get a String representation of the URL (if defined). + * + * @return ExternalForm of URL, or empty string if url is null + */ + public String getUrlAsString() { + return location == null ? "" : location.toExternalForm(); + } + + /** + * @return Returns the parent. + */ + public SampleResult getParent() { + return parent; + } + + /** + * @param parent The parent to set. + */ + public void setParent(SampleResult parent) { + this.parent = parent; + } + + public String getResultFileName() { + return resultFileName; + } + + public void setResultFileName(String resultFileName) { + this.resultFileName = resultFileName; + } + + public int getGroupThreads() { + return groupThreads; + } + + public void setGroupThreads(int n) { + this.groupThreads = n; + } + + public int getAllThreads() { + return allThreads; + } + + public void setAllThreads(int n) { + this.allThreads = n; + } + + // Bug 47394 + + /** + * Allow custom SampleSenders to drop unwanted assertionResults + */ + public void removeAssertionResults() { + this.assertionResults = null; + } + + /** + * Allow custom SampleSenders to drop unwanted subResults + */ + public void removeSubResults() { + this.subResults = null; + } + + /** + * Set the headers size in bytes + * + * @param size the number of bytes of the header + */ + public void setHeadersSize(int size) { + this.headersSize = size; + } + + /** + * Get the headers size in bytes + * + * @return the headers size + */ + public int getHeadersSize() { + return headersSize; + } + + /** + * @return the body size in bytes + * @deprecated replaced by getBodySizeAsLong() + */ + @Deprecated + public int getBodySize() { + return (int) getBodySizeAsLong(); + } + + /** + * @return the body size in bytes + */ + public long getBodySizeAsLong() { + return bodySize == 0 ? responseData.length : bodySize; + } + + /** + * @param bodySize the body size to set + */ + public void setBodySize(long bodySize) { + this.bodySize = bodySize; + } + + /** + * @param bodySize the body size to set + * @deprecated use setBodySize(long) + */ + @Deprecated + public void setBodySize(int bodySize) { + this.bodySize = bodySize; + } + + private static class NanoOffset extends Thread { + + private static volatile long nanoOffset; + + static long getNanoOffset() { + return nanoOffset; + } + + @Override + public void run() { + // Wait longer than a clock pulse (generally 10-15ms) + getOffset(30L); // Catch an early clock pulse to reduce slop. + while (true) { + getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks + } + } + + private static void getOffset(long wait) { + try { + TimeUnit.MILLISECONDS.sleep(wait); + long clock = System.currentTimeMillis(); + long nano = SampleResult.sampleNsClockInMs(); + nanoOffset = clock - nano; + } catch (InterruptedException ignore) { + // ignored + Thread.currentThread().interrupt(); + } + } + } + + /** + * @return the startNextThreadLoop + * @deprecated use {@link SampleResult#getTestLogicalAction()} + */ + @Deprecated + public boolean isStartNextThreadLoop() { + return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } + + /** + * @param startNextThreadLoop the startNextLoop to set + * @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction) + */ + @Deprecated + public void setStartNextThreadLoop(boolean startNextThreadLoop) { + if (startNextThreadLoop) { + testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } else { + testLogicalAction = TestLogicalAction.CONTINUE; + } + } + + /** + * Clean up cached data + */ + public void cleanAfterSample() { + this.responseDataAsString = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("This should not happen"); + } + } + + @Override + public List getSearchableTokens() throws Exception { + List datasToSearch = new ArrayList<>(4); + datasToSearch.add(getSampleLabel()); + datasToSearch.add(getResponseDataAsString()); + datasToSearch.add(getRequestHeaders()); + datasToSearch.add(getResponseHeaders()); + return datasToSearch; + } + + /** + * @return boolean true if this SampleResult should not be sent to Listeners + */ + public boolean isIgnore() { + return ignore; + } + + /** + * Call this method to tell JMeter to ignore this SampleResult by Listeners + */ + public void setIgnore() { + this.ignore = true; + } + + /** + * @return String first non null assertion failure message if assertionResults is not null, null otherwise + */ + public String getFirstAssertionFailureMessage() { + String message = null; + AssertionResult[] results = getAssertionResults(); + + if (results != null) { + // Find the first non-null message + for (AssertionResult result : results) { + message = result.getFailureMessage(); + if (message != null) { + break; + } + } + } + return message; + } + + /** + * @return the testLogicalAction + */ + public TestLogicalAction getTestLogicalAction() { + return testLogicalAction; + } + + /** + * @param testLogicalAction the testLogicalAction to set + */ + public void setTestLogicalAction(TestLogicalAction testLogicalAction) { + this.testLogicalAction = testLogicalAction; + } +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java b/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java new file mode 100644 index 0000000..67568d3 --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.util; + +import org.apache.jmeter.testbeans.TestBean; + +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; +import java.util.*; +import java.util.Map.Entry; + +/** + * 解决JSR233加载 ScriptEngineFactory 空指针问题 + */ +public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport { + + private static final String[] LANGUAGE_TAGS; + + /** + * Will be removed in next version following 3.2 + * @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()} + */ + @Deprecated + public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility + + private static final String[][] CONSTANT_LANGUAGE_NAMES; + + static { + Map nameMap = new HashMap<>(); + ScriptEngineManager sem = new ScriptEngineManager(); + final List engineFactories = sem.getEngineFactories(); + for(ScriptEngineFactory fact : engineFactories){ + List names = fact.getNames(); + for(String shortName : names) { + if (shortName != null) { + nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact); + } + } + } + LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]); + Arrays.sort(LANGUAGE_TAGS); + CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2]; + int i = 0; + for(Entry me : nameMap.entrySet()) { + final String key = me.getKey(); + CONSTANT_LANGUAGE_NAMES[i][0] = key; + final ScriptEngineFactory fact = me.getValue(); + CONSTANT_LANGUAGE_NAMES[i++][1] = key + + " (" // $NON-NLS-1$ + + fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$ + + " / " // $NON-NLS-1$ + + fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$ + + ")"; // $NON-NLS-1$ + } + + LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility + } + + private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() { + @Override + protected Object[][] getContents() { + return CONSTANT_LANGUAGE_NAMES; + } + }; + + protected JSR223BeanInfoSupport(Class beanClass) { + super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE); + } + + /** + * @return String array of 2 columns array containing Script engine short name / Script Language details + */ + public static final String[][] getLanguageNames() { + return CONSTANT_LANGUAGE_NAMES.clone(); + } + +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java new file mode 100644 index 0000000..d7cd59c --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.util; + +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections.map.LRUMap; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.samplers.Sampler; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jorphan.util.JOrphanUtils; +import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.script.*; +import java.io.*; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; + + +/** + * Base class for JSR223 Test elements + *

+ * 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + * 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 + */ +public abstract class JSR223TestElement extends ScriptingTestElement + implements Serializable, TestStateListener { + private static final long serialVersionUID = 232L; + + private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class); + /** + * Cache of compiled scripts + */ + @SuppressWarnings("unchecked") // LRUMap does not support generics (yet) + private static final Map compiledScriptsCache = + Collections.synchronizedMap( + new LRUMap(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100))); + + /** + * If not empty then script in ScriptText will be compiled and cached + */ + private String cacheKey = ""; + + /** + * md5 of the script, used as an unique key for the cache + */ + private String scriptMd5 = null; + + /** + * Initialization On Demand Holder pattern + */ + private static class LazyHolder { + private LazyHolder() { + super(); + } + + public static final ScriptEngineManager INSTANCE = new ScriptEngineManager(); + } + + /** + * @return ScriptEngineManager singleton + */ + public static ScriptEngineManager getInstance() { + return LazyHolder.INSTANCE; + } + + public JSR223TestElement() { + super(); + } + + /** + * @return {@link ScriptEngine} for language defaulting to groovy if language is not set + * @throws ScriptException when no {@link ScriptEngine} could be found + */ + protected ScriptEngine getScriptEngine() throws ScriptException { + String lang = getScriptLanguageWithDefault(); + ScriptEngine scriptEngine = getInstance().getEngineByName(lang); + if (scriptEngine == null) { + throw new ScriptException("Cannot find engine named: '" + lang + "', ensure you set language field in JSR223 Test Element: " + getName()); + } + + return scriptEngine; + } + + /** + * @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set + */ + private String getScriptLanguageWithDefault() { + String lang = getScriptLanguage(); + if (StringUtils.isNotEmpty(lang)) { + return lang; + } + return DEFAULT_SCRIPT_LANGUAGE; + } + + /** + * Populate variables to be passed to scripts + * + * @param bindings Bindings + */ + protected void populateBindings(Bindings bindings) { + final String label = getName(); + final String fileName = getFilename(); + final String scriptParameters = getParameters(); + // Use actual class name for log + final Logger elementLogger = LoggerFactory.getLogger(getClass().getName() + "." + getName()); + bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed) + bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed) + bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed) + bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed) + String[] args = JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ + bindings.put("args", args); // $NON-NLS-1$ (this name is fixed) + // Add variables for access to context and variables + JMeterContext jmctx = JMeterContextService.getContext(); + bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed) + JMeterVariables vars = jmctx.getVariables(); + bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed) + Properties props = JMeterUtils.getJMeterProperties(); + bindings.put("props", props); // $NON-NLS-1$ (this name is fixed) + // For use in debugging: + bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed) + + // Most subclasses will need these: + Sampler sampler = jmctx.getCurrentSampler(); + bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed) + SampleResult prev = jmctx.getPreviousResult(); + bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed) + } + + + /** + * This method will run inline script or file script with special behaviour for file script: + * - If ScriptEngine implements Compilable script will be compiled and cached + * - If not if will be run + * + * @param scriptEngine ScriptEngine + * @param pBindings {@link Bindings} might be null + * @return Object returned by script + * @throws IOException when reading the script fails + * @throws ScriptException when compiling or evaluation of the script fails + */ + protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings) + throws IOException, ScriptException { + + this.loadGroovyJar(scriptEngine); + + Bindings bindings = pBindings; + if (bindings == null) { + bindings = scriptEngine.createBindings(); + } + populateBindings(bindings); + File scriptFile = new File(getFilename()); + // Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws + // "java.lang.Error: unimplemented" + boolean supportsCompilable = scriptEngine instanceof Compilable + && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + // 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 + NewDriver.setContextClassLoader(); + + try { + if (!StringUtils.isEmpty(getFilename())) { + if (scriptFile.exists() && scriptFile.canRead()) { + if (supportsCompilable) { + String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$ + scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$ + scriptFile.lastModified(); + CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey); + if (compiledScript == null) { + synchronized (compiledScriptsCache) { + compiledScript = compiledScriptsCache.get(newCacheKey); + if (compiledScript == null) { + // TODO Charset ? + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + compiledScript = ((Compilable) scriptEngine).compile(fileReader); + compiledScriptsCache.put(newCacheKey, compiledScript); + } + } + } + } + return compiledScript.eval(bindings); + } else { + // TODO Charset ? + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + return scriptEngine.eval(fileReader, bindings); + } + } + } else { + throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() + + "' does not exist or is unreadable for element:" + getName()); + } + } else if (!StringUtils.isEmpty(getScript())) { + if (supportsCompilable && + !ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) { + computeScriptMD5(); + CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5); + if (compiledScript == null) { + synchronized (compiledScriptsCache) { + compiledScript = compiledScriptsCache.get(this.scriptMd5); + if (compiledScript == null) { + compiledScript = ((Compilable) scriptEngine).compile(getScript()); + compiledScriptsCache.put(this.scriptMd5, compiledScript); + } + } + } + + return compiledScript.eval(bindings); + } else { + return scriptEngine.eval(getScript(), bindings); + } + } else { + throw new ScriptException("Both script file and script text are empty for element:" + getName()); + } + } catch (ScriptException ex) { + Throwable rootCause = ex.getCause(); + if (isStopCondition(rootCause)) { + throw (RuntimeException) ex.getCause(); + } else { + throw ex; + } + } finally { + // 将当前类加载器设置回来,避免加载无法加载到系统类加载加载类 + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + + + /** + * groovy 使用的是自己的类加载器, + * 这里再执行脚本前,使用 groovy的加载器加载jar包, + * 解决groovy脚本无法使用jar包的问题 + * + * @param scriptEngine + * @Auth jianxing + */ + public static void loadGroovyJar(ScriptEngine scriptEngine) { + if (scriptEngine instanceof GroovyScriptEngineImpl) { + GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine; + + try { + File file = new File(FileUtils.JAR_FILE_DIR); + if (file.isFile()) { + groovyScriptEngine.getClassLoader().addURL(file.toURI().toURL()); + } else { + File[] files = file.listFiles(); + for (File f : files) { + groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + } + } + } catch (Exception e) { + e.printStackTrace(); + LogUtil.error(e.getMessage(), e); + } + } + } + + /** + * @return boolean true if element is not compilable or if compilation succeeds + * @throws IOException if script is missing + * @throws ScriptException if compilation fails + */ + public boolean compile() + throws ScriptException, IOException { + String lang = getScriptLanguageWithDefault(); + ScriptEngine scriptEngine = getInstance().getEngineByName(lang); + boolean supportsCompilable = scriptEngine instanceof Compilable + && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ + if (!supportsCompilable) { + return true; + } + if (!StringUtils.isEmpty(getScript())) { + try { + ((Compilable) scriptEngine).compile(getScript()); + return true; + } catch (ScriptException e) { // NOSONAR + logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + return false; + } + } else { + File scriptFile = new File(getFilename()); + try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), + (int) scriptFile.length())) { + try { + ((Compilable) scriptEngine).compile(fileReader); + return true; + } catch (ScriptException e) { // NOSONAR + logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + return false; + } + } + } + } + + /** + * compute MD5 if it is null + */ + private void computeScriptMD5() { + // compute the md5 of the script if needed + if (scriptMd5 == null) { + scriptMd5 = DigestUtils.md5Hex(getScript()); + } + } + + /** + * @return the cacheKey + */ + public String getCacheKey() { + return cacheKey; + } + + /** + * @param cacheKey the cacheKey to set + */ + public void setCacheKey(String cacheKey) { + this.cacheKey = cacheKey; + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted() + */ + @Override + public void testStarted() { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String) + */ + @Override + public void testStarted(String host) { + // NOOP + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded() + */ + @Override + public void testEnded() { + testEnded(""); + } + + /** + * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String) + */ + @Override + public void testEnded(String host) { + compiledScriptsCache.clear(); + this.scriptMd5 = null; + } + + public String getScriptLanguage() { + return scriptLanguage; + } + + public void setScriptLanguage(String s) { + scriptLanguage = s; + } +} diff --git a/src/main/java/org/apache/jorphan/collections/HashTree.java b/src/main/java/org/apache/jorphan/collections/HashTree.java new file mode 100644 index 0000000..828ab58 --- /dev/null +++ b/src/main/java/org/apache/jorphan/collections/HashTree.java @@ -0,0 +1,1095 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jorphan.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.*; + +/** + * This class is used to create a tree structure of objects. Each element in the + * tree is also a key to the next node down in the tree. It provides many ways + * to add objects and branches, as well as many ways to retrieve. + *

+ * HashTree implements the Map interface for convenience reasons. The main + * difference between a Map and a HashTree is that the HashTree organizes the + * data into a recursive tree structure, and provides the means to manipulate + * that structure. + *

+ * Of special interest is the {@link #traverse(HashTreeTraverser)} method, which + * provides an expedient way to traverse any HashTree by implementing the + * {@link HashTreeTraverser} interface in order to perform some operation on the + * tree, or to extract information from the tree. + * + * @see HashTreeTraverser + * @see SearchByClass + */ +public class HashTree implements Serializable, Map, Cloneable { + + private static final long serialVersionUID = 240L; + + // Used for the RuntimeException to short-circuit the traversal + private static final String FOUND = "found"; // $NON-NLS-1$ + + // N.B. The keys can be either JMeterTreeNode or TestElement + protected final Map data; + + /** + * Creates an empty new HashTree. + */ + public HashTree() { + this(null, null); + } + + /** + * Allow subclasses to provide their own Map. + * @param _map {@link Map} to use + */ + protected HashTree(Map _map) { + this(_map, null); + } + + /** + * Creates a new HashTree and adds the given object as a top-level node. + * + * @param key + * name of the new top-level node + */ + public HashTree(Object key) { + this(new LinkedHashMap(), key); + } + + /** + * Uses the new HashTree if not null and adds the given object as a + * top-level node if not null + * + * @param _map + * the map to be used. If null a new {@link LinkedHashMap} + * will be created + * @param key + * the object to be used as the key for the root node (may be + * null, in which case no root node will be created) + */ + private HashTree(Map _map, Object key) { + if(_map != null) { + data = _map; + } else { + data = new LinkedHashMap<>(); + } + if(key != null) { + data.put(key, new HashTree()); + } + } + + /** + * The Map given must also be a HashTree, otherwise an + * UnsupportedOperationException is thrown. If it is a HashTree, this is + * like calling the add(HashTree) method. + * + * @see #add(HashTree) + * @see Map#putAll(Map) + */ + @Override + public void putAll(Map map) { + if (map instanceof HashTree) { + this.add((HashTree) map); + } else { + throw new UnsupportedOperationException("can only putAll other HashTree objects"); + } + } + + /** + * Exists to satisfy the Map interface. + * + * @see Map#entrySet() + */ + @Override + public Set> entrySet() { + return data.entrySet(); + } + + /** + * Implemented as required by the Map interface, but is not very useful + * here. All 'values' in a HashTree are HashTree's themselves. + * + * @param value + * Object to be tested as a value. + * @return True if the HashTree contains the value, false otherwise. + * @see Map#containsValue(Object) + */ + @Override + public boolean containsValue(Object value) { + return data.containsValue(value); + } + + /** + * This is the same as calling HashTree.add(key,value). + * + * @param key + * to use + * @param value + * to store against key + * @see Map#put(Object, Object) + */ + @Override + public HashTree put(Object key, HashTree value) { + HashTree previous = data.get(key); + add(key, value); + return previous; + } + + /** + * Clears the HashTree of all contents. + * + * @see Map#clear() + */ + @Override + public void clear() { + data.clear(); + } + + /** + * Returns a collection of all the sub-trees of the current tree. + * + * @see Map#values() + */ + @Override + public Collection values() { + return data.values(); + } + + /** + * Adds a key as a node at the current level and then adds the given + * HashTree to that new node. + * + * @param key + * key to create in this tree + * @param subTree + * sub tree to add to the node created for the first argument. + */ + public void add(Object key, HashTree subTree) { + add(key).add(subTree); + } + + /** + * Adds all the nodes and branches of the given tree to this tree. Is like + * merging two trees. Duplicates are ignored. + * + * @param newTree the tree to be added + */ + public void add(HashTree newTree) { + for (Object item : newTree.list()) { + add(item).add(newTree.getTree(item)); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given collection + * as top-level nodes in the tree. + * + * @param keys + * a collection of objects to be added to the created HashTree. + */ + public HashTree(Collection keys) { + data = new LinkedHashMap<>(); + for (Object o : keys) { + data.put(o, new HashTree()); + } + } + + /** + * Creates a new HashTree and adds all the objects in the given array as + * top-level nodes in the tree. + * + * @param keys + * array with names for the new top-level nodes + */ + public HashTree(Object[] keys) { + data = new LinkedHashMap<>(); + for (Object key : keys) { + data.put(key, new HashTree()); + } + } + + /** + * If the HashTree contains the given object as a key at the top level, then + * a true result is returned, otherwise false. + * + * @param o + * Object to be tested as a key. + * @return True if the HashTree contains the key, false otherwise. + * @see Map#containsKey(Object) + */ + @Override + public boolean containsKey(Object o) { + return data.containsKey(o); + } + + /** + * If the HashTree is empty, true is returned, false otherwise. + * + * @return True if HashTree is empty, false otherwise. + */ + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + /** + * Sets a key and it's value in the HashTree. It actually sets up a key, and + * then creates a node for the key and sets the value to the new node, as a + * key. Any previous nodes that existed under the given key are lost. + * + * @param key + * key to be set up + * @param value + * value to be set up as a key in the secondary node + */ + public void set(Object key, Object value) { + data.put(key, createNewTree(value)); + } + + /** + * Sets a key into the current tree and assigns it a HashTree as its + * subtree. Any previous entries under the given key are removed. + * + * @param key + * key to be set up + * @param t + * HashTree that the key maps to + */ + public void set(Object key, HashTree t) { + data.put(key, t); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and sets all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are lost. + * + * @param key + * Key to be set up + * @param values + * Array of objects to be added as keys in the secondary node + */ + public void set(Object key, Object[] values) { + data.put(key, createNewTree(Arrays.asList(values))); + } + + /** + * Sets a key and its values in the HashTree. It sets up a key in the + * current node, and then creates a node for that key, and set all the + * values in the array as keys in the new node. Any keys previously held + * under the given key are removed. + * + * @param key + * key to be set up + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void set(Object key, Collection values) { + data.put(key, createNewTree(values)); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the second array are set as + * keys to the bottom-most node. All previous keys of that bottom-most node + * are removed. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Object[] values) { + if (treePath != null && values != null) { + set(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key array as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the array. + * Continues recursing in this manner until the end of the first array is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any keys previously held by the + * bottom-most node are lost. + * + * @param treePath + * array of keys to put into HashTree + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void set(Object[] treePath, Collection values) { + if (treePath != null) { + set(Arrays.asList(treePath), values); + } + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the array of values are set as + * keys to the bottom-most node. Any previously existing keys of that bottom + * node are removed. + * + * @param treePath + * collection of keys to put into HashTree + * @param values + * array of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.set(Arrays.asList(values)); + } + + /** + * Sets the nodes of the current tree to be the objects of the given + * collection. Any nodes previously in the tree are removed. + * + * @param values + * Collection of objects to set as nodes. + */ + public void set(Collection values) { + clear(); + this.add(values); + } + + /** + * Sets a series of keys into the HashTree. It sets up the first object in + * the key list as a key in the current node, recurses into the next + * HashTree node through that key and adds the second object in the list. + * Continues recursing in this manner until the end of the first list is + * reached, at which point all the values of the Collection of values are + * set as keys to the bottom-most node. Any previously existing keys of that + * bottom node are lost. + * + * @param treePath + * list of keys to put into HashTree + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void set(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.set(values); + } + + /** + * Adds an key into the HashTree at the current level. If a HashTree exists + * for the key already, no new tree will be added + * + * @param key + * key to be added to HashTree + * @return newly generated tree, if no tree was found for the given key; + * existing key otherwise + */ + public HashTree add(Object key) { + if (!data.containsKey(key)) { + HashTree newTree = createNewTree(); + data.put(key, newTree); + return newTree; + } + return getTree(key); + } + + /** + * Adds all the given objects as nodes at the current level. + * + * @param keys + * Array of Keys to be added to HashTree. + */ + public void add(Object[] keys) { + for (Object key : keys) { + add(key); + } + } + + /** + * Adds a bunch of keys into the HashTree at the current level. + * + * @param keys + * Collection of Keys to be added to HashTree. + */ + public void add(Collection keys) { + for (Object o : keys) { + add(o); + } + } + + /** + * Adds a key and it's value in the HashTree. The first argument becomes a + * node at the current level, and the second argument becomes a node of it. + * + * @param key + * key to be added + * @param value + * value to be added as a key in the secondary node + * @return HashTree for which value is the key + */ + public HashTree add(Object key, Object value) { + return add(key).add(value); + } + + /** + * Adds a key and it's values in the HashTree. The first argument becomes a + * node at the current level, and adds all the values in the array to the + * new node. + * + * @param key + * key to be added + * @param values + * array of objects to be added as keys in the secondary node + */ + public void add(Object key, Object[] values) { + add(key).add(values); + } + + /** + * Adds a key as a node at the current level and then adds all the objects + * in the second argument as nodes of the new node. + * + * @param key + * key to be added + * @param values + * Collection of objects to be added as keys in the secondary + * node + */ + public void add(Object key, Collection values) { + add(key).add(values); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Object[] values) { + if (treePath != null) { + add(Arrays.asList(treePath), Arrays.asList(values)); + } + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is an array that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * an array of objects representing a path + * @param values + * collection of values to be added as keys to bottom-most node + */ + public void add(Object[] treePath, Collection values) { + if (treePath != null) { + add(Arrays.asList(treePath), values); + } + } + + public HashTree add(Object[] treePath, Object value) { + return add(Arrays.asList(treePath), value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, all the objects in the second argument are + * added as nodes. + * + * @param treePath + * a list of objects representing a path + * @param values + * array of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Object[] values) { + HashTree tree = addTreePath(treePath); + tree.add(Arrays.asList(values)); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a List that represents a path to a specific node in the tree. + * If the path doesn't already exist, it is created (the objects are added + * along the way). At the path, the object in the second argument is added + * as a node. + * + * @param treePath + * a list of objects representing a path + * @param value + * Object to add as a node to bottom-most node + * @return HashTree for which value is the key + */ + public HashTree add(Collection treePath, Object value) { + HashTree tree = addTreePath(treePath); + return tree.add(value); + } + + /** + * Adds a series of nodes into the HashTree using the given path. The first + * argument is a SortedSet that represents a path to a specific node in the + * tree. If the path doesn't already exist, it is created (the objects are + * added along the way). At the path, all the objects in the second argument + * are added as nodes. + * + * @param treePath + * a SortedSet of objects representing a path + * @param values + * Collection of values to be added as keys to bottom-most node + */ + public void add(Collection treePath, Collection values) { + HashTree tree = addTreePath(treePath); + tree.add(values); + } + + protected HashTree addTreePath(Collection treePath) { + HashTree tree = this; + for (Object temp : treePath) { + tree = tree.add(temp); + } + return tree; + } + + /** + * Gets the HashTree mapped to the given key. + * + * @param key + * Key used to find appropriate HashTree() + * @return the HashTree for key + */ + public HashTree getTree(Object key) { + return data.get(key); + } + + /** + * Returns the HashTree object associated with the given key. Same as + * calling {@link #getTree(Object)}. + * + * @see Map#get(Object) + */ + @Override + public HashTree get(Object key) { + return getTree(key); + } + + /** + * Gets the HashTree object mapped to the last key in the array by recursing + * through the HashTree structure one key at a time. + * + * @param treePath + * array of keys. + * @return HashTree at the end of the recursion. + */ + public HashTree getTree(Object[] treePath) { + if (treePath != null) { + return getTree(Arrays.asList(treePath)); + } + return this; + } + + /** + * Create a clone of this HashTree. This is not a deep clone (i.e., the + * contents of the tree are not cloned). + * + */ + @Override + public Object clone() { + HashTree newTree = new HashTree(); + cloneTree(newTree); + return newTree; + } + + protected void cloneTree(HashTree newTree) { + for (Object key : list()) { + newTree.set(key, (HashTree) getTree(key).clone()); + } + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @return HashTree + */ + protected HashTree createNewTree() { + return new HashTree(); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param key + * object to use as the key for the top level + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Object key) { + return new HashTree(key); + } + + /** + * Creates a new tree. This method exists to allow inheriting classes to + * generate the appropriate types of nodes. For instance, when a node is + * added, it's value is a HashTree. Rather than directly calling the + * HashTree() constructor, the createNewTree() method is called. Inheriting + * classes should override these methods and create the appropriate subclass + * of HashTree. + * + * @param values objects to be added to the new {@link HashTree} + * + * @return newly created {@link HashTree} + */ + protected HashTree createNewTree(Collection values) { + return new HashTree(values); + } + + /** + * Gets the HashTree object mapped to the last key in the SortedSet by + * recursing through the HashTree structure one key at a time. + * + * @param treePath + * Collection of keys + * @return HashTree at the end of the recursion + */ + public HashTree getTree(Collection treePath) { + return getTreePath(treePath); + } + + /** + * Gets a Collection of all keys in the current HashTree node. If the + * HashTree represented a file system, this would be like getting a + * collection of all the files in the current folder. + * + * @return Set of all keys in this HashTree + */ + public Collection list() { + return data.keySet(); + } + + /** + * Gets a Set of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down. If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return Set of all keys in found HashTree. + */ + public Collection list(Object key) { + HashTree temp = data.get(key); + if (temp != null) { + return temp.list(); + } + return new HashSet<>(); + } + + /** + * Removes the entire branch specified by the given key. + * + * @see Map#remove(Object) + */ + @Override + public HashTree remove(Object key) { + return data.remove(key); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * array of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * Array of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Object[] treePath) { + if (treePath != null) { + return list(Arrays.asList(treePath)); + } + return list(); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * List of keys, and returns the Set of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * List of keys used to recurse into HashTree structure + * @return Set of all keys found in end HashTree + */ + public Collection list(Collection treePath) { + HashTree tree = getTreePath(treePath); + if (tree != null) { + return tree.list(); + } + return new HashSet<>(); + } + + /** + * Finds the given current key, and replaces it with the given new key. Any + * tree structure found under the original key is moved to the new key. + * + * @param currentKey name of the key to be replaced + * @param newKey name of the new key + */ + public void replaceKey(Object currentKey, Object newKey) { + HashTree tree = getTree(currentKey); + data.remove(currentKey); + data.put(newKey, tree); + } + + /** + * Gets an array of all keys in the current HashTree node. If the HashTree + * represented a file system, this would be like getting an array of all the + * files in the current folder. + * + * @return array of all keys in this HashTree. + */ + public Object[] getArray() { + return data.keySet().toArray(); + } + + /** + * Gets an array of all keys in the HashTree mapped to the given key of the + * current HashTree object (in other words, one level down). If the HashTree + * represented a file system, this would like getting a list of all files in + * a sub-directory (of the current directory) specified by the key argument. + * + * @param key + * key used to find HashTree to get list of + * @return array of all keys in found HashTree + */ + public Object[] getArray(Object key) { + HashTree t = getTree(key); + if (t != null) { + return t.getArray(); + } + return null; + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * array of keys, and returns an array of keys of the HashTree object at the + * end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * array of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Object[] treePath) { + if (treePath != null) { + return getArray(Arrays.asList(treePath)); + } + return getArray(); + } + + /** + * Recurses down into the HashTree structure using each subsequent key in the + * treePath argument, and returns an array of keys of the HashTree object at + * the end of the recursion. If the HashTree represented a file system, this + * would be like getting a list of all the files in a directory specified by + * the treePath, relative from the current directory. + * + * @param treePath + * list of keys used to recurse into HashTree structure + * @return array of all keys found in end HashTree + */ + public Object[] getArray(Collection treePath) { + HashTree tree = getTreePath(treePath); + return (tree != null) ? tree.getArray() : null; + } + + protected HashTree getTreePath(Collection treePath) { + HashTree tree = this; + for (Object aTreePath : treePath) { + tree = tree.getTree(aTreePath); + if (tree == null) { + return null; + } + } + return tree; + } + + /** + * Returns a hashcode for this HashTree. + * + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return data.hashCode() * 7; + } + + /** + * Compares all objects in the tree and verifies that the two trees contain + * the same objects at the same tree levels. Returns true if they do, false + * otherwise. + * + * @param o + * Object to be compared against + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof HashTree)) { + return false; + } + HashTree oo = (HashTree) o; + if (oo.size() != this.size()) { + return false; + } + return data.equals(oo.data); + } + + /** + * Returns a Set of all the keys in the top-level of this HashTree. + * + * @see Map#keySet() + */ + @Override + public Set keySet() { + return data.keySet(); + } + + /** + * Searches the HashTree structure for the given key. If it finds the key, + * it returns the HashTree mapped to the key. If it finds nothing, it + * returns null. + * + * @param key + * Key to search for + * @return HashTree mapped to key, if found, otherwise null + */ + public HashTree search(Object key) { + HashTree result = getTree(key); + if (result != null) { + return result; + } + TreeSearcher searcher = new TreeSearcher(key); + try { + traverse(searcher); + } catch (RuntimeException e) { + if (!e.getMessage().equals(FOUND)){ + throw e; + } + // do nothing - means object is found + } + return searcher.getResult(); + } + + /** + * Method readObject. + * + * @param ois + * the stream to read the objects from + * @throws ClassNotFoundException + * when the class for the deserialization can not be found + * @throws IOException + * when I/O error occurs + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + /** + * Returns the number of top-level entries in the HashTree. + * + * @see Map#size() + */ + @Override + public int size() { + return data.size(); + } + + /** + * Allows any implementation of the HashTreeTraverser interface to easily + * traverse (depth-first) all the nodes of the HashTree. The Traverser + * implementation will be given notification of each node visited. + * + * @see HashTreeTraverser + * @param visitor + * the visitor that wants to traverse the tree + */ + public void traverse(HashTreeTraverser visitor) { + for (Object item : list()) { + visitor.addNode(item, getTree(item)); + getTree(item).traverseInto(visitor); + } + } + + /** + * The recursive method that accomplishes the tree-traversal and performs + * the callbacks to the HashTreeTraverser. + * + * @param visitor + * the {@link HashTreeTraverser} to be notified + */ + private void traverseInto(HashTreeTraverser visitor) { + if (list().isEmpty()) { + visitor.processPath(); + } else { + for (Object item : list()) { + final HashTree treeItem = getTree(item); + visitor.addNode(item, treeItem); + treeItem.traverseInto(visitor); + } + } + visitor.subtractNode(); + } + + /** + * Generate a printable representation of the tree. + * + * @return a representation of the tree + */ + @Override + public String toString() { + ConvertToString converter = new ConvertToString(); + try { + traverse(converter); + } catch (Exception e) { // Just in case + converter.reportError(e); + } + return converter.toString(); + } + + private static class TreeSearcher implements HashTreeTraverser { + + private final Object target; + + private HashTree result; + + public TreeSearcher(Object t) { + target = t; + } + + public HashTree getResult() { + return result; + } + + /** {@inheritDoc} */ + @Override + public void addNode(Object node, HashTree subTree) { + result = subTree.getTree(target); + if (result != null) { + // short circuit traversal when found + throw new RuntimeException(FOUND); + } + } + + /** {@inheritDoc} */ + @Override + public void processPath() { + // Not used + } + + /** {@inheritDoc} */ + @Override + public void subtractNode() { + // Not used + } + } + + private static class ConvertToString implements HashTreeTraverser { + private final StringBuilder string = new StringBuilder(getClass().getName() + "{"); + + private final StringBuilder spaces = new StringBuilder(); + + private int depth = 0; + + @Override + public void addNode(Object key, HashTree subTree) { + depth++; + string.append("\n").append(getSpaces()).append(key); + string.append(" {"); + } + + @Override + public void subtractNode() { + string.append("\n" + getSpaces() + "}"); + depth--; + } + + @Override + public void processPath() { + // NOOP + } + + @Override + public String toString() { + string.append("\n}"); + return string.toString(); + } + + void reportError(Throwable t){ + string.append("Error: ").append(t.toString()); + } + + private String getSpaces() { + if (spaces.length() < depth * 2) { + while (spaces.length() < depth * 2) { + spaces.append(" "); + } + } else if (spaces.length() > depth * 2) { + spaces.setLength(depth * 2); + } + return spaces.toString(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c45cedf..70b2f6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,3 +2,30 @@ spring.application.name=node-controller logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 +#kafka +spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} +spring.kafka.consumer.group-id=metersphere_group_id +kafka.fields= +kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ +kafka.sample-filter= +kafka.test-mode=info +kafka.parse-all-req-headers=false +kafka.parse-all-res-headers=false +kafka.compression-type= +kafka.batch-size=16384 +kafka.client-id=JMeterKafkaBackendListener +kafka.connections-max-idle-ms=180000 +kafka.ssl.enabled=false +kafka.ssl.key-password= +kafka.ssl.keystore-location= +kafka.ssl.keystore-password= +kafka.ssl.truststore-location= +kafka.ssl.truststore-password= +kafka.ssl.enabled-protocols=TLSv1.2,TLSv1.1,TLSv1 +kafka.ssl.keystore-type=JKS +kafka.ssl.protocol=TLS +kafka.ssl.provider= +kafka.ssl.truststore-type= + +# jmeter +jmeter.home=/opt/jmeter diff --git a/src/main/resources/jmeter/bin/jmeter.properties b/src/main/resources/jmeter/bin/jmeter.properties new file mode 100644 index 0000000..38e815f --- /dev/null +++ b/src/main/resources/jmeter/bin/jmeter.properties @@ -0,0 +1,1346 @@ +################################################################################ +# Apache JMeter Property file +################################################################################ + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +################################################################################ +# +# THIS FILE SHOULD NOT BE MODIFIED +# +# This avoids having to re-apply the modifications when upgrading JMeter +# Instead only user.properties should be modified: +# 1/ copy the property you want to modify to user.properties from jmeter.properties +# 2/ Change its value there +# +################################################################################ + +# JMeter properties are described in the file +# http://jmeter.apache.org/usermanual/properties_reference.html +# A local copy can be found in +# printable_docs/usermanual/properties_reference.html + +#Preferred GUI language. Comment out to use the JVM default locale's language. +#language=en + + +# Additional locale(s) to add to the displayed list. +# The current default list is: en, fr, de, no, es, tr, ja, zh_CN, zh_TW, pl, pt_BR +# [see JMeterMenuBar#makeLanguageMenu()] +# The entries are a comma-separated list of language names +#locales.add=zu + + +#--------------------------------------------------------------------------- +# XML Parser +#--------------------------------------------------------------------------- + +# Path to a Properties file containing Namespace mapping in the form +# prefix=Namespace +# Example: +# ns=http://biz.aol.com/schema/2006-12-18 +#xpath.namespace.config= + + +# XPath2 query cache for storing compiled XPath queries +#xpath2query.parser.cache.size=400 + +#--------------------------------------------------------------------------- +# SSL configuration +#--------------------------------------------------------------------------- + +## SSL System properties are now in system.properties + +# JMeter no longer converts javax.xxx property entries in this file into System properties. +# These must now be defined in the system.properties file or on the command-line. +# The system.properties file gives more flexibility. + +# By default, SSL session contexts are now created per-thread, rather than being shared. +# The original behaviour can be enabled by setting the JMeter property to true +#https.sessioncontext.shared=false + +# Be aware that https default protocol may vary depending on the version of JVM +# See https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=58236 +# Default HTTPS protocol level: +#https.default.protocol=TLS +# This may need to be changed here (or in user.properties) to: +#https.default.protocol=SSLv3 + +# List of protocols to enable. You may have to select only a subset if you find issues with target server. +# This is needed when server does not support Socket version negotiation, this can lead to: +# javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated +# java.net.SocketException: Connection reset +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=54759 +#https.socket.protocols=SSLv2Hello SSLv3 TLSv1 + +# Control if we allow reuse of cached SSL context between iterations +# set the value to 'false' to reset the SSL context each iteration +# Deprecated since 5.0 +#https.use.cached.ssl.context=true + +# +# Reset HTTP State when starting a new Thread Group iteration which means: +# true means next iteration is associated to a new user +# false means next iteration is associated to same user +# true involves: +# - Closing opened connection +# - resetting SSL State +#httpclient.reset_state_on_thread_group_iteration=true + +# Start and end index to be used with keystores with many entries +# The default is to use entry 0, i.e. the first +#https.keyStoreStartIndex=0 +#https.keyStoreEndIndex=0 + +#--------------------------------------------------------------------------- +# Look and Feel configuration +#--------------------------------------------------------------------------- + +#Classname of the Swing default UI +# +# The LAF classnames that are available are now displayed as ToolTip text +# when hovering over the Options/Look and Feel selection list. +# +# You can either use a full class name, as shown below, +# or one of the strings "System" or "CrossPlatform" which means +# JMeter will use the corresponding string returned by UIManager.getLookAndFeelClassName() + +# LAF can be overridden by os.name (lowercased, spaces replaced by '_') +# Sample os.name LAF: +#jmeter.laf.windows_xp=javax.swing.plaf.metal.MetalLookAndFeel + +# Failing that, the OS family = os.name, but only up to first space: +# Sample OS family LAF: +#jmeter.laf.windows=com.sun.java.swing.plaf.windows.WindowsLookAndFeel + +# Custom settings for Mac using System LAF if you don't want to use Darcula +#jmeter.laf.mac=System + +# Failing that, the JMeter default laf can be defined: +#jmeter.laf=System + +# If none of the above jmeter.laf properties are defined, JMeter uses the CrossPlatform LAF. +# This is because the CrossPlatform LAF generally looks better than the System LAF. +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=52026 for details +# N.B. the laf can be defined in user.properties. + +# LoggerPanel display +# default to false +#jmeter.loggerpanel.display=false + +# Enable LogViewer Panel to receive log event even if closed +# Enabled since 2.12 +# Note this has some impact on performances, but as GUI mode must +# not be used for Load Test it is acceptable +#jmeter.loggerpanel.enable_when_closed=true + +# Max lines kept in LoggerPanel, default to 1000 chars +# 0 means no limit +#jmeter.loggerpanel.maxlength=1000 + +# Interval period in ms to process the queue of events of the listeners +#jmeter.gui.refresh_period=500 + +# HiDPI mode (default: false) +# Activate a 'pseudo'-hidpi mode. Allows to increase size of some UI elements +# which are not correctly managed by JVM with high resolution screens in Linux or Windows +#jmeter.hidpi.mode=false +# To enable pseudo-hidpi mode change to true +#jmeter.hidpi.mode=true +# HiDPI scale factor +#jmeter.hidpi.scale.factor=1.0 +# Suggested value for HiDPI +#jmeter.hidpi.scale.factor=2.0 + +# Toolbar display +# Toolbar icon definitions +#jmeter.toolbar.icons=org/apache/jmeter/images/toolbar/icons-toolbar.properties +# Toolbar list +#jmeter.toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help +# Toolbar icons default size: 22x22. Available sizes are: 22x22, 32x32, 48x48 +#jmeter.toolbar.icons.size=22x22 +# Suggested value for HiDPI +#jmeter.toolbar.icons.size=48x48 + +# Icon definitions +# default: +#jmeter.icons=org/apache/jmeter/images/icon.properties +# alternate: +#jmeter.icons=org/apache/jmeter/images/icon_1.properties +# Historical icon set (deprecated) +#jmeter.icons=org/apache/jmeter/images/icon_old.properties + +# Tree icons default size: 19x19. Available sizes are: 19x19, 24x24, 32x32, 48x48 +# Useful for HiDPI display (see below) +#jmeter.tree.icons.size=19x19 +# Suggested value for HiDPI screen like 3200x1800: +#jmeter.tree.icons.size=32x32 + +#Components to not display in JMeter GUI (GUI class name or static label) +# These elements are deprecated and will be removed in next version: +# MongoDB Script, MongoDB Source Config, Monitor Results +# BSF Elements +not_in_menu=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler,org.apache.jmeter.protocol.mongodb.config.MongoSourceElement,\ + org.apache.jmeter.timers.BSFTimer,org.apache.jmeter.modifiers.BSFPreProcessor,org.apache.jmeter.extractor.BSFPostProcessor,org.apache.jmeter.assertions.BSFAssertion,\ + org.apache.jmeter.visualizers.BSFListener,org.apache.jmeter.protocol.java.sampler.BSFSampler,\ + org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui + +# Number of items in undo history +# Feature is disabled by default (0) due to known and not fixed bugs: +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57043 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57039 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57040 +# Set it to a number > 0 (25 can be a good default) +# The bigger it is, the more it consumes memory +#undo.history.size=0 + +# Hotkeys to add JMeter components, will add elements when you press Ctrl+0 .. Ctrl+9 (Command+0 .. Command+9 on Mac) +gui.quick_0=ThreadGroupGui +gui.quick_1=HttpTestSampleGui +gui.quick_2=RegexExtractorGui +gui.quick_3=AssertionGui +gui.quick_4=ConstantTimerGui +gui.quick_5=TestActionGui +gui.quick_6=JSR223PostProcessor +gui.quick_7=JSR223PreProcessor +gui.quick_8=DebugSampler +gui.quick_9=ViewResultsFullVisualizer + + +#--------------------------------------------------------------------------- +# JMX Backup configuration +#--------------------------------------------------------------------------- +#Enable auto backups of the .jmx file when a test plan is saved. +#When enabled, before the .jmx is saved, it will be backed up to the directory pointed +#by the jmeter.gui.action.save.backup_directory property (see below). Backup file names are built +#after the jmx file being saved. For example, saving test-plan.jmx will create a test-plan-000012.jmx +#in the backup directory provided that the last created backup file is test-plan-000011.jmx. +#Default value is true indicating that auto backups are enabled +#jmeter.gui.action.save.backup_on_save=true + +#Set the backup directory path where JMX backups will be created upon save in the GUI. +#If not set (what it defaults to) then backup files will be created in +#a sub-directory of the JMeter base installation. The default directory is ${JMETER_HOME}/backups +#If set and the directory does not exist, it will be created. +#jmeter.gui.action.save.backup_directory= + +#Set the maximum time (in hours) that backup files should be preserved since the save time. +#By default no expiration time is set which means we keep backups for ever. +#jmeter.gui.action.save.keep_backup_max_hours=0 + +#Set the maximum number of backup files that should be preserved. By default 10 backups will be preserved. +#Setting this to zero will cause the backups to not being deleted (unless keep_backup_max_hours is set to a non zero value) +#jmeter.gui.action.save.keep_backup_max_count=10 + +#Enable auto saving of the .jmx file before start run a test plan +#When enabled, before the run, the .jmx will be saved and also backed up to the directory pointed +#save_automatically_before_run=true + +#--------------------------------------------------------------------------- +# Remote hosts and RMI configuration +#--------------------------------------------------------------------------- + +# Remote Hosts - comma delimited +remote_hosts=127.0.0.1 +#remote_hosts=localhost:1099,localhost:2010 + +# RMI port to be used by the server (must start rmiregistry with same port) +#server_port=1099 + +# To change the port to (say) 1234: +# On the server(s) +# - set server_port=1234 +# - start rmiregistry with port 1234 +# On Windows this can be done by: +# SET SERVER_PORT=1234 +# JMETER-SERVER +# +# On Unix: +# SERVER_PORT=1234 jmeter-server +# +# On the client: +# - set remote_hosts=server:1234 + +# Parameter that controls the RMI port used by RemoteSampleListenerImpl (The Controller) +# Default value is 0 which means port is randomly assigned +# You may need to open Firewall port on the Controller machine +#client.rmi.localport=0 + +# When distributed test is starting, there may be several attempts to initialize +# remote engines. By default, only single try is made. Increase following property +# to make it retry for additional times +#client.tries=1 + +# If there is initialization retries, following property sets delay between attempts +#client.retries_delay=5000 + +# When all initialization tries was made, test will fail if some remote engines are failed +# Set following property to true to ignore failed nodes and proceed with test +#client.continue_on_fail=false + +# To change the default port (1099) used to access the server: +#server.rmi.port=1234 + +# To use a specific port for the JMeter server engine, define +# the following property before starting the server: +#server.rmi.localport=4000 + +# The jmeter server creates by default the RMI registry as part of the server process. +# To stop the server creating the RMI registry: +#server.rmi.create=false + +# Define the following property to cause JMeter to exit after the first test +#server.exitaftertest=true + +# +# Configuration of Secure RMI connection +# +# Type of keystore : JKS +#server.rmi.ssl.keystore.type=JKS +# +# Keystore file that contains private key +#server.rmi.ssl.keystore.file=rmi_keystore.jks +# +# Password of Keystore +#server.rmi.ssl.keystore.password=changeit +# +# Key alias +#server.rmi.ssl.keystore.alias=rmi +# +# Type of truststore : JKS +#server.rmi.ssl.truststore.type=JKS +# +# Keystore file that contains certificate +#server.rmi.ssl.truststore.file=rmi_keystore.jks +# +# Password of Trust store +#server.rmi.ssl.truststore.password=changeit +# +# Set this if you don't want to use SSL for RMI +#server.rmi.ssl.disable=false +#--------------------------------------------------------------------------- +# Include Controller +#--------------------------------------------------------------------------- + +# Prefix used by IncludeController when building file name +#includecontroller.prefix= + +#--------------------------------------------------------------------------- +# Shared HTTP configuration between HC4 and Java Implementations +#--------------------------------------------------------------------------- + +# +# Should JMeter add to POST request content-type header if missing: +# Content-Type: application/x-www-form-urlencoded +# Was true before version 5.0 +#post_add_content_type_if_missing=false + +#--------------------------------------------------------------------------- +# HTTP Java configuration +#--------------------------------------------------------------------------- + +# Number of connection retries performed by HTTP Java sampler before giving up +# 0 means no retry since version 3.0 +#http.java.sampler.retries=0 + +#--------------------------------------------------------------------------- +# Following properties apply to Apache HttpClient +#--------------------------------------------------------------------------- + +# set the socket timeout (or use the parameter http.socket.timeout) +# for AJP Sampler implementation. +# Value is in milliseconds +#httpclient.timeout=0 +# 0 == no timeout + +# Set the http version (defaults to 1.1) +#httpclient.version=1.1 (or use the parameter http.protocol.version) + +# Define characters per second > 0 to emulate slow connections +#httpclient.socket.http.cps=0 +#httpclient.socket.https.cps=0 + +#Enable loopback protocol +#httpclient.loopback=true + +# Define the local host address to be used for multi-homed hosts +#httpclient.localaddress=1.2.3.4 + +#--------------------------------------------------------------------------- +# AuthManager Kerberos configuration +#--------------------------------------------------------------------------- + +# AuthManager Kerberos configuration +# Name of application module used in jaas.conf +#kerberos_jaas_application=JMeter + +# Should ports be stripped from urls before constructing SPNs +# for SPNEGO authentication +#kerberos.spnego.strip_port=true + +# Should credentials be delegated to webservers when using +# SPNEGO authentication +#kerberos.spnego.delegate_cred=false + +#--------------------------------------------------------------------------- +# Apache HttpComponents HTTPClient configuration (HTTPClient4) +#--------------------------------------------------------------------------- + +# define a properties file for overriding Apache HttpClient parameters +# Uncomment this line if you put anything in hc.parameters file +#hc.parameters.file=hc.parameters + +# If true, default HC4 User-Agent will not be added +#httpclient4.default_user_agent_disabled=false + +# Preemptively send Authorization Header when BASIC auth is used +#httpclient4.auth.preemptive=true + +# Number of retries to attempt (default 0) +#httpclient4.retrycount=0 + +# true if it's OK to retry requests that have been sent +# This will retry Idempotent and non Idempotent requests +# This should usually be false, but it can be useful +# when testing against some Load Balancers like Amazon ELB +#httpclient4.request_sent_retry_enabled=false + +# Idle connection timeout (Milliseconds) to apply if the server does not send +# Keep-Alive headers (default 0) +# Set this > 0 to compensate for servers that don't send a Keep-Alive header +# If <= 0, idle timeout will only apply if the server sends a Keep-Alive header +#httpclient4.idletimeout=0 + +# Check connections if the elapsed time (Milliseconds) since the last +# use of the connection exceed this value +#httpclient4.validate_after_inactivity=1700 + +# TTL (in Milliseconds) represents an absolute value. +# No matter what, the connection will not be re-used beyond its TTL. +#httpclient4.time_to_live=2000 + +# Max size in bytes of PUT body to retain in result sampler. +# Bigger results will be clipped. +#httpclient4.max_body_retain_size=32768 + +# Ignore EOFException that some edgy application may emit to signal end of GZIP stream +# Defaults to false +#httpclient4.gzip_relax_mode=false + +# Ignore EOFException that some edgy application may emit to signal end of Deflated stream +# Defaults to false +#httpclient4.deflate_relax_mode=false + +#--------------------------------------------------------------------------- +# HTTP Cache Manager configuration +#--------------------------------------------------------------------------- +# +# Space or comma separated list of methods that can be cached +#cacheable_methods=GET +# N.B. This property is currently a temporary solution for Bug 56162 + +# Since 2.12, JMeter does not create anymore a Sample Result with 204 response +# code for a resource found in cache which is inline with what browser do. +#cache_manager.cached_resource_mode=RETURN_NO_SAMPLE + +# You can choose between 3 modes: +# RETURN_NO_SAMPLE (default) +# RETURN_200_CACHE +# RETURN_CUSTOM_STATUS + +# Those mode have the following behaviours: +# RETURN_NO_SAMPLE: +# this mode returns no Sample Result, it has no additional configuration + +# RETURN_200_CACHE: +# this mode will return Sample Result with response code to 200 and +# response message to "(ex cache)", you can modify response message by setting +# RETURN_200_CACHE.message=(ex cache) + +# RETURN_CUSTOM_STATUS: +# This mode lets you select what response code and message you want to return, +# if you use this mode you need to set those properties +# RETURN_CUSTOM_STATUS.code= +# RETURN_CUSTOM_STATUS.message= + +#--------------------------------------------------------------------------- +# Results file configuration +#--------------------------------------------------------------------------- + +# This section helps determine how result data will be saved. +# The commented out values are the defaults. + +# legitimate values: xml, csv, db. Only xml and csv are currently supported. +#jmeter.save.saveservice.output_format=csv + +# The below properties are true when field should be saved; false otherwise +# +# assertion_results_failure_message only affects CSV output +#jmeter.save.saveservice.assertion_results_failure_message=true +# +# legitimate values: none, first, all +#jmeter.save.saveservice.assertion_results=none +# +#jmeter.save.saveservice.data_type=true +#jmeter.save.saveservice.label=true +#jmeter.save.saveservice.response_code=true +# response_data is not currently supported for CSV output +#jmeter.save.saveservice.response_data=false +# Save ResponseData for failed samples +#jmeter.save.saveservice.response_data.on_error=false +#jmeter.save.saveservice.response_message=true +#jmeter.save.saveservice.successful=true +#jmeter.save.saveservice.thread_name=true +#jmeter.save.saveservice.time=true +#jmeter.save.saveservice.subresults=true +#jmeter.save.saveservice.assertions=true +#jmeter.save.saveservice.latency=true +# Only available with HttpClient4 +#jmeter.save.saveservice.connect_time=true +#jmeter.save.saveservice.samplerData=false +#jmeter.save.saveservice.responseHeaders=false +#jmeter.save.saveservice.requestHeaders=false +#jmeter.save.saveservice.encoding=false +#jmeter.save.saveservice.bytes=true +# Only available with HttpClient4 +#jmeter.save.saveservice.sent_bytes=true +#jmeter.save.saveservice.url=true +#jmeter.save.saveservice.filename=false +#jmeter.save.saveservice.hostname=false +#jmeter.save.saveservice.thread_counts=true +#jmeter.save.saveservice.sample_count=false +#jmeter.save.saveservice.idle_time=true + +# Timestamp format - this only affects CSV output files +# legitimate values: none, ms, or a format suitable for SimpleDateFormat +#jmeter.save.saveservice.timestamp_format=ms +#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS + +# For use with Comma-separated value (CSV) files or other formats +# where the fields' values are separated by specified delimiters. +# Default: +#jmeter.save.saveservice.default_delimiter=, +# For TAB, one can use: +#jmeter.save.saveservice.default_delimiter=\t + +# Only applies to CSV format files: +# Print field names as first line in CSV +#jmeter.save.saveservice.print_field_names=true + +# Optional list of JMeter variable names whose values are to be saved in the result data files. +# Use commas to separate the names. For example: +#sample_variables=SESSION_ID,REFERENCE +# N.B. The current implementation saves the values in XML as attributes, +# so the names must be valid XML names. +# By default JMeter sends the variable to all servers +# to ensure that the correct data is available at the client. + +# Optional xml processing instruction for line 2 of the file: +# Example: +#jmeter.save.saveservice.xml_pi= +# Default value: +#jmeter.save.saveservice.xml_pi= + +# Prefix used to identify filenames that are relative to the current base +#jmeter.save.saveservice.base_prefix=~/ + +# AutoFlush on each line written in XML or CSV output +# Setting this to true will result in less test results data loss in case of Crash +# but with impact on performances, particularly for intensive tests (low or no pauses) +# Since JMeter 2.10, this is false by default +#jmeter.save.saveservice.autoflush=false + +#--------------------------------------------------------------------------- +# Settings that affect SampleResults +#--------------------------------------------------------------------------- + +# Save the start time stamp instead of the end +# This also affects the timestamp stored in result files +sampleresult.timestamp.start=true + +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis() +#sampleresult.useNanoTime=true + +# Use a background thread to calculate the nanoTime offset +# Set this to <= 0 to disable the background thread +#sampleresult.nanoThreadSleep=5000 + +# Since version 5.0 JMeter has a new SubResult Naming Policy which numbers subresults by default +# This property if set to true discards renaming policy. This can be required if you're using JMeter for functional testing. +# Defaults to: false +#subresults.disable_renaming=false + +#--------------------------------------------------------------------------- +# Upgrade property +#--------------------------------------------------------------------------- + +# File that holds a record of name changes for backward compatibility issues +upgrade_properties=/bin/upgrade.properties + +#--------------------------------------------------------------------------- +# JMeter Test Script recorder configuration +# +# N.B. The element was originally called the Proxy recorder, which is why the +# properties have the prefix "proxy". +#--------------------------------------------------------------------------- + +# If the recorder detects a gap of at least 5s (default) between HTTP requests, +# it assumes that the user has clicked a new URL +#proxy.pause=5000 + +# Add numeric suffix to Sampler names (default true) +#proxy.number.requests=true + +# List of URL patterns that will be added to URL Patterns to exclude +# Separate multiple lines with ; +#proxy.excludes.suggested=.*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2) + +# Change the default HTTP Sampler (currently HttpClient4) +# Java: +#jmeter.httpsampler=HTTPSampler +#or +#jmeter.httpsampler=Java +# +# HttpClient4.x +#jmeter.httpsampler=HttpClient4 + +# By default JMeter tries to be more lenient with RFC2616 redirects and allows +# relative paths. +# If you want to test strict conformance, set this value to true +# When the property is true, JMeter follows http://tools.ietf.org/html/rfc3986#section-5.2 +#jmeter.httpclient.strict_rfc2616=false + +# Default content-type include filter to use +#proxy.content_type_include=text/html|text/plain|text/xml +# Default content-type exclude filter to use +#proxy.content_type_exclude=image/.*|text/css|application/.* + +# Default headers to remove from Header Manager elements +# (Cookie and Authorization are always removed) +#proxy.headers.remove=If-Modified-Since,If-None-Match,Host + +# Binary content-type handling +# These content-types will be handled by saving the request in a file: +#proxy.binary.types=application/x-amf,application/x-java-serialized-object,binary/octet-stream +# The files will be saved in this directory: +#proxy.binary.directory=user.dir +# The files will be created with this file filesuffix: +#proxy.binary.filesuffix=.binary + +#--------------------------------------------------------------------------- +# Test Script Recorder certificate configuration +#--------------------------------------------------------------------------- + +#proxy.cert.directory= +#proxy.cert.file=proxyserver.jks +#proxy.cert.type=JKS +#proxy.cert.keystorepass=password +#proxy.cert.keypassword=password +#proxy.cert.factory=SunX509 +# define this property if you wish to use your own keystore +#proxy.cert.alias= +# The default validity for certificates created by JMeter +#proxy.cert.validity=7 +# Use dynamic key generation (if supported by JMeter/JVM) +# If false, will revert to using a single key with no certificate +#proxy.cert.dynamic_keys=true + +#--------------------------------------------------------------------------- +# Test Script Recorder miscellaneous configuration +#--------------------------------------------------------------------------- + +# Whether to attempt disabling of samples that resulted from redirects +# where the generated samples use auto-redirection +#proxy.redirect.disabling=true + +# SSL configuration +#proxy.ssl.protocol=TLS + +#--------------------------------------------------------------------------- +# JMeter Proxy configuration +#--------------------------------------------------------------------------- +# use command-line flags for user-name and password +#http.proxyDomain=NTLM domain, if required by HTTPClient sampler + +#--------------------------------------------------------------------------- +# HTTPSampleResponse Parser configuration +#--------------------------------------------------------------------------- + +# Space-separated list of parser groups +HTTPResponse.parsers=htmlParser wmlParser cssParser +# for each parser, there should be a parser.types and a parser.className property + +# CSS Parser based on ph-css +cssParser.className=org.apache.jmeter.protocol.http.parser.CssParser +cssParser.types=text/css + +# CSS parser LRU cache size +# This cache stores the URLs found in a CSS to avoid continuously parsing the CSS +# By default the cache size is 400 +# It can be disabled by setting its value to 0 +#css.parser.cache.size=400 + +# Let the CSS Parser ignore all css errors +#css.parser.ignore_all_css_errors=true + +#--------------------------------------------------------------------------- +# HTML Parser configuration +#--------------------------------------------------------------------------- + +# Define the HTML parser to be used. +# Default parser: +# This new parser (since 2.10) should perform better than all others +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=55632 +# Do not comment this property +htmlParser.className=org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser + +# Other parsers: +# Default parser before 2.10 +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JTidyHTMLParser +# Note that Regexp extractor may detect references that have been commented out. +# In many cases it will work OK, but you should be aware that it may generate +# additional references. +#htmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser +# This parser is based on JSoup, it should be the most accurate but less +# performant than LagartoBasedHtmlParser +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser + +#Used by HTTPSamplerBase to associate htmlParser with content types below +htmlParser.types=text/html application/xhtml+xml application/xml text/xml + +#--------------------------------------------------------------------------- +# WML Parser configuration +#--------------------------------------------------------------------------- + +wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser + +#Used by HTTPSamplerBase to associate wmlParser with content types below +wmlParser.types=text/vnd.wap.wml + +#--------------------------------------------------------------------------- +# Remote batching configuration +#--------------------------------------------------------------------------- +# How is Sample sender implementations configured: +# - true (default) means client configuration will be used +# - false means server configuration will be used +#sample_sender_client_configured=true + +# By default when Stripping modes are used JMeter since 3.1 will strip +# response even for SampleResults in error. +# If you want to revert to previous behaviour (no stripping of Responses in error) +# set this property to false +#sample_sender_strip_also_on_error=true + +# Remote batching support +# Since JMeter 2.9, default is MODE_STRIPPED_BATCH, which returns samples in +# batch mode (every 100 samples or every minute by default) +# Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, so if you need it change to +# another mode +# Batch returns samples in batches +# Statistical returns sample summary statistics +# mode can also be the class name of an implementation of org.apache.jmeter.samplers.SampleSender +#mode=Standard +#mode=Batch +#mode=Statistical +#Set to true to key statistical samples on threadName rather than threadGroup +#key_on_threadname=false +#mode=Stripped +#mode=StrippedBatch +#mode=org.example.load.MySampleSender +# +#num_sample_threshold=100 +# Value is in milliseconds +#time_threshold=60000 +# +# Asynchronous sender; uses a queue and background worker process to return the samples +#mode=Asynch +# default queue size +#asynch.batch.queue.size=100 +# Same as Asynch but strips response data from SampleResult +#mode=StrippedAsynch +# +# DiskStore: Serialises the samples to disk, rather than saving in memory +#mode=DiskStore +# Same as DiskStore but strips response data from SampleResult +#mode=StrippedDiskStore +# Note: the mode is currently resolved on the client; +# other properties (e.g. time_threshold) are resolved on the server. + +#--------------------------------------------------------------------------- +# JDBC Request configuration +#--------------------------------------------------------------------------- + +# String used to indicate a null value +#jdbcsampler.nullmarker=]NULL[ +# +# Max size of BLOBs and CLOBs to store in JDBC sampler. Result will be cut off +#jdbcsampler.max_retain_result_size=65536 + +# Database validation query +# based in https://stackoverflow.com/questions/10684244/dbcp-validationquery-for-different-databases list +jdbc.config.check.query=select 1 from INFORMATION_SCHEMA.SYSTEM_USERS|select 1 from dual|select 1 from sysibm.sysdummy1|select 1|select 1 from rdb$database +jdbc.config.jdbc.driver.class=com.mysql.jdbc.Driver|org.postgresql.Driver|oracle.jdbc.OracleDriver|com.ingres.jdbc.IngresDriver|com.microsoft.sqlserver.jdbc.SQLServerDriver|com.microsoft.jdbc.sqlserver.SQLServerDriver|org.apache.derby.jdbc.ClientDriver|org.hsqldb.jdbc.JDBCDriver|com.ibm.db2.jcc.DB2Driver|org.apache.derby.jdbc.ClientDriver|org.h2.Driver|org.firebirdsql.jdbc.FBDriver|org.mariadb.jdbc.Driver|org.sqlite.JDBC|net.sourceforge.jtds.jdbc.Driver|com.exasol.jdbc.EXADriver + +#--------------------------------------------------------------------------- +# OS Process Sampler configuration +#--------------------------------------------------------------------------- +# Polling to see if process has finished its work, used when a timeout is configured on sampler +#os_sampler.poll_for_timeout=100 + +#--------------------------------------------------------------------------- +# TCP Sampler configuration +#--------------------------------------------------------------------------- + +# The default handler class +#tcp.handler=TCPClientImpl +# +# eolByte = byte value for end of line +# set this to a value outside the range -128 to +127 to skip eol checking +#tcp.eolByte=1000 +# +# TCP Charset, used by org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl +# default to Platform defaults charset as returned by Charset.defaultCharset().name() +#tcp.charset= +# +# status.prefix and suffix = strings that enclose the status response code +#tcp.status.prefix=Status= +#tcp.status.suffix=. +# +# status.properties = property file to convert codes to messages +#tcp.status.properties=mytestfiles/tcpstatus.properties + +# The length prefix used by LengthPrefixedBinaryTCPClientImpl implementation +# defaults to 2 bytes. +#tcp.binarylength.prefix.length=2 + +#--------------------------------------------------------------------------- +# Summariser - Generate Summary Results - configuration (mainly applies to non-GUI mode) +#--------------------------------------------------------------------------- +# +# Comment the following property to disable the default non-GUI summariser +# [or change the value to rename it] +# (applies to non-GUI mode only) +summariser.name=summary +# +# interval between summaries (in seconds) default 30 seconds +#summariser.interval=30 +# +# Write messages to log file +#summariser.log=true +# +# Write messages to System.out +#summariser.out=true + +# Ignore SampleResults generated by TransactionControllers +# defaults to true +#summariser.ignore_transaction_controller_sample_result=true + + +#--------------------------------------------------------------------------- +# Aggregate Report and Aggregate Graph - configuration +#--------------------------------------------------------------------------- +# +# Percentiles to display in reports +# Can be float value between 0 and 100 +# First percentile to display, defaults to 90% +#aggregate_rpt_pct1=90 +# Second percentile to display, defaults to 95% +#aggregate_rpt_pct2=95 +# Second percentile to display, defaults to 99% +#aggregate_rpt_pct3=99 + +#--------------------------------------------------------------------------- +# BackendListener - configuration +#--------------------------------------------------------------------------- +# +# Backend metrics window mode (fixed=fixed-size window, timed=time boxed) +#backend_metrics_window_mode=fixed +# Backend metrics sliding window size for Percentiles, Min, Max +#backend_metrics_window=100 + +# Backend metrics sliding window size for Percentiles, Min, Max +# when backend_metrics_window_mode is timed +# Setting this value too high can lead to OOM +#backend_metrics_large_window=5000 + +######################## +# Graphite Backend +######################## +# Send interval in second +# Defaults to 1 second +#backend_graphite.send_interval=1 + +######################## +# Influx Backend +######################## + +# Send interval in second +# Defaults to 5 seconds +#backend_influxdb.send_interval=5 +#Influxdb timeouts +#backend_influxdb.connection_timeout=1000 +#backend_influxdb.socket_timeout=3000 +#backend_influxdb.connection_request_timeout=100 + +#--------------------------------------------------------------------------- +# BeanShell configuration +#--------------------------------------------------------------------------- + +# BeanShell Server properties +# +# Define the port number as non-zero to start the http server on that port +#beanshell.server.port=9000 +# The telnet server will be started on the next port + +# +# Define the server initialisation file +beanshell.server.file=../extras/startup.bsh + +# +# Define a file to be processed at startup +# This is processed using its own interpreter. +#beanshell.init.file= + +# +# Define the intialisation files for BeanShell Sampler, Function and other BeanShell elements +# N.B. Beanshell test elements do not share interpreters. +# Each element in each thread has its own interpreter. +# This is retained between samples. +#beanshell.sampler.init=BeanShellSampler.bshrc +#beanshell.function.init=BeanShellFunction.bshrc +#beanshell.assertion.init=BeanShellAssertion.bshrc +#beanshell.listener.init=etc +#beanshell.postprocessor.init=etc +#beanshell.preprocessor.init=etc +#beanshell.timer.init=etc + +# The file BeanShellListeners.bshrc contains sample definitions +# of Test and Thread Listeners. + +#--------------------------------------------------------------------------- +# JSR-223 function +#--------------------------------------------------------------------------- + +# Path to JSR-223 file containing script to call on JMeter startup +# JMeter will try to guess the engine to use by the extension +# of the name of the file. +# This script can use pre-defined variables: +# log : Logger to log any message +# props : JMeter Property +# OUT : System.OUT +#jsr223.init.file= + +#--------------------------------------------------------------------------- +# Groovy function +#--------------------------------------------------------------------------- + +#Path to Groovy file containing utility functions to make available to __groovy function +#groovy.utilities= + +# Example +#groovy.utilities=jmeter.bin/utility.groovy + +#--------------------------------------------------------------------------- +# MailerModel configuration +#--------------------------------------------------------------------------- + +# Number of successful samples before a message is sent +#mailer.successlimit=2 +# +# Number of failed samples before a message is sent +#mailer.failurelimit=2 + +#--------------------------------------------------------------------------- +# CSVRead configuration +#--------------------------------------------------------------------------- + +# CSVRead delimiter setting (default ",") +# Make sure that there are no trailing spaces or tabs after the delimiter +# characters, or these will be included in the list of valid delimiters +#csvread.delimiter=, +#csvread.delimiter=; +#csvread.delimiter=! +#csvread.delimiter=~ +# The following line has a tab after the = +#csvread.delimiter= + +#--------------------------------------------------------------------------- +# __time() function configuration +# +# The properties below can be used to redefine the default formats +#--------------------------------------------------------------------------- +#time.YMD=yyyyMMdd +#time.HMS=HHmmss +#time.YMDHMS=yyyyMMdd-HHmmss +#time.USER1= +#time.USER2= + +#--------------------------------------------------------------------------- +# CSV DataSet configuration +#--------------------------------------------------------------------------- + +# String to return at EOF (if recycle not used) +#csvdataset.eofstring= +#list in https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html +csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII + + +#--------------------------------------------------------------------------- +# LDAP Sampler configuration +#--------------------------------------------------------------------------- +# Maximum number of search results returned by a search that will be sorted +# to guarantee a stable ordering (if more results then this limit are returned +# then no sorting is done). Set to 0 to turn off all sorting, in which case +# "Equals" response assertions will be very likely to fail against search results. +# +#ldapsampler.max_sorted_results=1000 + +# Number of characters to log for each of three sections (starting matching section, diff section, +# ending matching section where not all sections will appear for all diffs) diff display when an Equals +# assertion fails. So a value of 100 means a maximum of 300 characters of diff text will be displayed +# (+ a number of extra characters like "..." and "[[["/"]]]" which are used to decorate it). +#assertion.equals_section_diff_len=100 +# test written out to log to signify start/end of diff delta +#assertion.equals_diff_delta_start=[[[ +#assertion.equals_diff_delta_end=]]] + +#--------------------------------------------------------------------------- +# Miscellaneous configuration +#--------------------------------------------------------------------------- + +# Size of cache used by CSS Selector Extractor (for JODD implementation only) +# to store parsed CSS Selector expressions. +#cssselector.parser.cache.size=400 + + +# Used to control what happens when you start a test and +# have listeners that could overwrite existing result files +# Possible values: +# ASK : Ask user +# APPEND : Append results to existing file +# DELETE : Delete existing file and start a new file +#resultcollector.action_if_file_exists=ASK + +# If defined, then start the mirror server on the port +#mirror.server.port=8081 + +# ORO PatternCacheLRU size +#oro.patterncache.size=1000 + +#TestBeanGui +# +#propertyEditorSearchPath=null + +# Turn expert mode on/off: expert mode will show expert-mode beans and properties +#jmeter.expertMode=true + +# Max size of bytes stored in memory per SampleResult +# Ensure you don't exceed max capacity of a Java Array and remember +# that the higher it is, the higher JMeter will consume heap +# Defaults to 0, which means no truncation +#httpsampler.max_bytes_to_store_per_request=0 + +# Max size of buffer in bytes used when reading responses +# Defaults to 64k +#httpsampler.max_buffer_size=66560 + +# Maximum redirects to follow in a single sequence (default 20) +#httpsampler.max_redirects=20 +# Maximum frame/iframe nesting depth (default 5) +#httpsampler.max_frame_depth=5 + +# Revert to BUG 51939 behaviour (no separate container for embedded resources) by setting the following false: +#httpsampler.separate.container=true + +# If embedded resources download fails due to missing resources or other reasons, if this property is true +# Parent sample will not be marked as failed +#httpsampler.ignore_failed_embedded_resources=false + +#keep alive time for the parallel download threads (in seconds) +#httpsampler.parallel_download_thread_keepalive_inseconds=60 + +# Don't keep the embedded resources response data : just keep the size and the md5 +# default to false +#httpsampler.embedded_resources_use_md5=false + +# List of extra HTTP methods that should be available in select box +#httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY + +# The encoding to be used if none is provided (default ISO-8859-1) +sampleresult.default.encoding=UTF-8 + +# CookieManager behaviour - should cookies with null/empty values be deleted? +# Default is true. Use false to revert to original behaviour +#CookieManager.delete_null_cookies=true + +# CookieManager behaviour - should variable cookies be allowed? +# Default is true. Use false to revert to original behaviour +#CookieManager.allow_variable_cookies=true + +# CookieManager behaviour - should Cookies be stored as variables? +# Default is false +#CookieManager.save.cookies=false + +# CookieManager behaviour - prefix to add to cookie name before storing it as a variable +# Default is COOKIE_; to remove the prefix, define it as one or more spaces +#CookieManager.name.prefix= + +# CookieManager behaviour - check received cookies are valid before storing them? +# Default is true. Use false to revert to previous behaviour +#CookieManager.check.cookies=true + +# Netscape HTTP Cookie file +cookies=cookies + +# Ability to switch to Nashorn as default Javascript Engine used by MsIfController and __javaScript function +# JMeter works as following: +# - JDK >= 8 and javascript.use_rhino=false or not set : Nashorn +# - JDK >= 8 and javascript.use_rhino=true: Rhino +# If you want to use Rhino on JDK8, set this property to true +#javascript.use_rhino=false + +# Number of milliseconds to wait for a thread to stop +#jmeterengine.threadstop.wait=5000 + +#Whether to invoke System.exit(0) in server exit code after stopping RMI +#jmeterengine.remote.system.exit=false + +# Whether to call System.exit(1) on failure to stop threads in non-GUI mode. +# This only takes effect if the test was explicitly requested to stop. +# If this is disabled, it may be necessary to kill the JVM externally +#jmeterengine.stopfail.system.exit=true + +# Whether to force call System.exit(0) at end of test in non-GUI mode, even if +# there were no failures and the test was not explicitly asked to stop. +# Without this, the JVM may never exit if there are other threads spawned by +# the test which never exit. +#jmeterengine.force.system.exit=false + +# How long to pause (in ms) in the daemon thread before reporting that the JVM has failed to exit. +# If the value is <= 0, the JMeter does not start the daemon thread +#jmeter.exit.check.pause=2000 + +# If running non-GUI, then JMeter listens on the following port for a shutdown message. +# To disable, set the port to 1000 or less. +#jmeterengine.nongui.port=4445 +# +# If the initial port is busy, keep trying until this port is reached +# (to disable searching, set the value less than or equal to the .port property) +#jmeterengine.nongui.maxport=4455 + +# How often to check for shutdown during ramp-up (milliseconds) +#jmeterthread.rampup.granularity=1000 + +#Should JMeter expand the tree when loading a test plan? +# default value is false since JMeter 2.7 +#onload.expandtree=false + +#JSyntaxTextArea configuration +#jsyntaxtextarea.wrapstyleword=true +#jsyntaxtextarea.linewrap=true +#jsyntaxtextarea.codefolding=true +# Set 0 to disable undo feature in JSyntaxTextArea +#jsyntaxtextarea.maxundos=50 +# Change the font on the (JSyntax) Text Areas. (Useful for HiDPI screens) +#jsyntaxtextarea.font.family=Hack +#jsyntaxtextarea.font.size=14 + +# Set this to false to disable the use of JSyntaxTextArea for the Console Logger panel +#loggerpanel.usejsyntaxtext=true + +# Maximum size of HTML page that can be displayed; default=10 mbytes +# Set to 0 to disable the size check and display the whole response +#view.results.tree.max_size=10485760 + +# Order of Renderers in View Results Tree +# Note full class names should be used for non jmeter core renderers +# For JMeter core renderers, class names start with . and are automatically +# prefixed with org.apache.jmeter.visualizers +view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundaryExtractor,.RenderAsCssJQuery,.RenderAsXPath,org.apache.jmeter.extractor.json.render.RenderAsJsonRenderer,.RenderAsHTML,.RenderAsHTMLFormatted,.RenderAsHTMLWithEmbedded,.RenderAsDocument,.RenderAsJSON,.RenderAsXML + +# Maximum number of results in the results tree +# Set to 0 to store all results (might consume a lot of memory) +#view.results.tree.max_results=500 + +# Maximum size of Document that can be parsed by Tika engine; defaut=10 * 1024 * 1024 (10MB) +# Set to 0 to disable the size check +#document.max_size=0 + +#JMS options +# Enable the following property to stop JMS Point-to-Point Sampler from using +# the properties java.naming.security.[principal|credentials] when creating the queue connection +#JMSSampler.useSecurity.properties=false + +# Set the following value to true in order to skip the delete confirmation dialogue +#confirm.delete.skip=false + +# Used by JSR223 elements +# Size of compiled scripts cache +#jsr223.compiled_scripts_cache_size=100 + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- + +# List of directories (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of directories that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator to separate multiple paths. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#user.classpath=../classes;../lib + +# List of directories (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies this property should be used instead of user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/;../app2/ + +# Classpath finder +# ================ +# The classpath finder currently needs to load every single JMeter class to find +# the classes it needs. +# For non-GUI mode, it's only necessary to scan for Function classes, but all classes +# are still loaded. +# All current Function classes include ".function." in their name, +# and none include ".gui." in the name, so the number of unwanted classes loaded can be +# reduced by checking for these. However, if a valid function class name does not match +# these restrictions, it will not be loaded. If problems are encountered, then comment +# or change the following properties: +classfinder.functions.contain=.functions. +classfinder.functions.notContain=.gui. + + +#--------------------------------------------------------------------------- +# Additional property files to load +#--------------------------------------------------------------------------- + +# Should JMeter automatically load additional JMeter properties? +# File name to look for (comment to disable) +user.properties=user.properties + +# Should JMeter automatically load additional system properties? +# File name to look for (comment to disable) +system.properties=system.properties + +# Comma separated list of files that contain reference to templates and their description +# Path must be relative to JMeter root folder +#template.files=/jmeter.bin/templates/templates.xml + + +#--------------------------------------------------------------------------- +# Thread Group Validation feature +#--------------------------------------------------------------------------- + +# Validation is the name of the feature used to rapidly validate a Thread Group runs fine +# Default implementation is org.apache.jmeter.gui.action.validation.TreeClonerForValidation +# It runs validation without timers, with 1 thread, 1 iteration and Startup Delay set to 0 +# You can implement your own policy that must extend org.apache.jmeter.engine.TreeCloner +# JMeter will instantiate it and use it to create the Tree used to run validation on Thread Group +#testplan_validation.tree_cloner_class=org.apache.jmeter.validation.ComponentTreeClonerForValidation + +# Number of threads to use to validate a Thread Group +#testplan_validation.nb_threads_per_thread_group=1 + +# Ignore BackendListener when validating the thread group of plan +#testplan_validation.ignore_backends=true + +# Ignore timers when validating the thread group of plan +#testplan_validation.ignore_timers=true + +# Number of iterations to use to validate a Thread Group +#testplan_validation.number_iterations=1 + +# Force throuput controllers that work in percentage mode to be a 100% +# Disabled by default +#testplan_validation.tpc_force_100_pct=false + +#--------------------------------------------------------------------------- +# Think Time configuration +#--------------------------------------------------------------------------- + +# +# Apply a factor on computed pauses by the following Timers: +# - Gaussian Random Timer +# - Uniform Random Timer +# - Poisson Random Timer +# +#timer.factor=1.0f + +# Default implementation that create the Timer structure to add to Test Plan +# Implementation of interface org.apache.jmeter.gui.action.thinktime.ThinkTimeCreator +#think_time_creator.impl=org.apache.jmeter.thinktime.DefaultThinkTimeCreator + +# Default Timer GUI class added to Test Plan by DefaultThinkTimeCreator +#think_time_creator.default_timer_implementation=org.apache.jmeter.timers.gui.UniformRandomTimerGui + +# Default constant pause of Timer +#think_time_creator.default_constant_pause=1000 + +# Default range pause of Timer +#think_time_creator.default_range=100 + + +# Change this parameter if you want to override the APDEX satisfaction threshold. +jmeter.reportgenerator.apdex_satisfied_threshold=500 + +# Change this parameter if you want to override the APDEX tolerance threshold. +jmeter.reportgenerator.apdex_tolerated_threshold=1500 + +# Timeout in milliseconds for Report generation when using Tools > Generate HTML report +#generate_report_ui.generation_timeout=120000 +#--------------------------------------------------------------------------- +# Naming Policy configuration +#--------------------------------------------------------------------------- + +# Prefix used when naming elements +#naming_policy.prefix= +# Suffix used when naming elements +#naming_policy.suffix= + +# Implementation of interface org.apache.jmeter.gui.action.TreeNodeNamingPolicy +#naming_policy.impl=org.apache.jmeter.gui.action.impl.DefaultTreeNodeNamingPolicy + +#--------------------------------------------------------------------------- +# Help Documentation +#--------------------------------------------------------------------------- + +# Switch that allows using Local documentation opened in JMeter GUI +# By default we use Online documentation opened in Browser +#help.local=false + +#--------------------------------------------------------------------------- +# Documentation generation +#--------------------------------------------------------------------------- + +# Path to XSL file used to generate Schematic View of Test Plan +# When empty, JMeter will use the embedded one in src/core/org/apache/jmeter/gui/action/schematic.xsl +#docgeneration.schematic_xsl= diff --git a/src/main/resources/jmeter/bin/saveservice.properties b/src/main/resources/jmeter/bin/saveservice.properties new file mode 100644 index 0000000..fd64d6b --- /dev/null +++ b/src/main/resources/jmeter/bin/saveservice.properties @@ -0,0 +1,420 @@ +#--------------------------------------------------------- +# SAVESERVICE PROPERTIES - JMETER INTERNAL USE ONLY +#--------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +# This file is used to define how XStream (de-)serializes classnames +# in JMX test plan files. +# FOR JMETER INTERNAL USE ONLY +#--------------------------------------------------------- +# N.B. To ensure backward compatibility, please do NOT change or delete any entries +# New entries can be added as necessary. +# +# Note that keys starting with an underscore are special, +# and are not used as aliases. +# +# Please keep the entries in alphabetical order within the sections +# to reduce the likelihood of duplicates +# +# version number of this file is now computed by a sha1 sum, so no need for +# an explicit _file_version property anymore. +# +# For this sha1 sum we ignore every newline character. It can be computed +# by the following command: +# +# cat jmeter.bin/saveservice.properties | perl -ne 'chomp; print' | sha1sum +# +# Be aware, that every change in this file will change the sha1 sum! +# +# Conversion version (for JMX output files) +# Must be updated if the file has been changed since the previous release +# Format is: +# Save service version=JMeter version at which change occurred +# 1.7 = 2.1.1 +# 1.8 = 2.1.2 +# (Some version updates were missed here...) +# 2.0 = 2.3.1 +# 2.1 = 2.3.2 +# (Some version updates were missed here...) +# 2.2 = 2.6 +# 2.3 = 2.7 +# 2.4 = 2.9 +# 2.5 = 2.10 +# 2.6 = 2.11 +# 2.7 = 2.12 +# 2.8 = 2.13 +# 2.9 = 2.14 +# 3.1 = 3.1 +# 3.2 = 3.2 +# 4.0 = 4.0 +# 5.0 = 5.0 +_version=5.0 +# +# +# Character set encoding used to read and write JMeter XML files and CSV results +# +_file_encoding=UTF-8 +# +#--------------------------------------------------------- +# +# The following properties are used to create aliases +# [Must all start with capital letter] +# +AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler +AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler +AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui +AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier +AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui +Argument=org.apache.jmeter.config.Argument +Arguments=org.apache.jmeter.config.Arguments +ArgumentsPanel=org.apache.jmeter.config.gui.ArgumentsPanel +AssertionGui=org.apache.jmeter.assertions.gui.AssertionGui +AssertionVisualizer=org.apache.jmeter.visualizers.AssertionVisualizer +AuthManager=org.apache.jmeter.protocol.http.control.AuthManager +Authorization=org.apache.jmeter.protocol.http.control.Authorization +AuthPanel=org.apache.jmeter.protocol.http.gui.AuthPanel +BackendListener=org.apache.jmeter.visualizers.backend.BackendListener +BackendListenerGui=org.apache.jmeter.visualizers.backend.BackendListenerGui +BeanShellAssertion=org.apache.jmeter.assertions.BeanShellAssertion +BeanShellAssertionGui=org.apache.jmeter.assertions.gui.BeanShellAssertionGui +BeanShellListener=org.apache.jmeter.visualizers.BeanShellListener +BeanShellPostProcessor=org.apache.jmeter.extractor.BeanShellPostProcessor +BeanShellPreProcessor=org.apache.jmeter.modifiers.BeanShellPreProcessor +BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler +BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui +BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer +BoundaryExtractor=org.apache.jmeter.extractor.BoundaryExtractor +BoundaryExtractorGui=org.apache.jmeter.extractor.gui.BoundaryExtractorGui +BSFAssertion=org.apache.jmeter.assertions.BSFAssertion +BSFListener=org.apache.jmeter.visualizers.BSFListener +BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor +BSFPostProcessor=org.apache.jmeter.extractor.BSFPostProcessor +BSFSampler=org.apache.jmeter.protocol.java.sampler.BSFSampler +BSFSamplerGui=org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui +BSFTimer=org.apache.jmeter.timers.BSFTimer +CacheManager=org.apache.jmeter.protocol.http.control.CacheManager +CacheManagerGui=org.apache.jmeter.protocol.http.gui.CacheManagerGui +CompareAssertion=org.apache.jmeter.assertions.CompareAssertion +ComparisonVisualizer=org.apache.jmeter.visualizers.ComparisonVisualizer +ConfigTestElement=org.apache.jmeter.config.ConfigTestElement +ConstantThroughputTimer=org.apache.jmeter.timers.ConstantThroughputTimer +ConstantTimer=org.apache.jmeter.timers.ConstantTimer +ConstantTimerGui=org.apache.jmeter.timers.gui.ConstantTimerGui +Cookie=org.apache.jmeter.protocol.http.control.Cookie +CookieManager=org.apache.jmeter.protocol.http.control.CookieManager +CookiePanel=org.apache.jmeter.protocol.http.gui.CookiePanel +CounterConfig=org.apache.jmeter.modifiers.CounterConfig +CriticalSectionController=org.apache.jmeter.control.CriticalSectionController +CriticalSectionControllerGui=org.apache.jmeter.control.gui.CriticalSectionControllerGui +CounterConfigGui=org.apache.jmeter.modifiers.gui.CounterConfigGui +CSVDataSet=org.apache.jmeter.config.CSVDataSet +DebugPostProcessor=org.apache.jmeter.extractor.DebugPostProcessor +DebugSampler=org.apache.jmeter.sampler.DebugSampler +# removed in 3.1, class was deleted in r1763837 +DistributionGraphVisualizer=org.apache.jmeter.visualizers.DistributionGraphVisualizer +DNSCacheManager=org.apache.jmeter.protocol.http.control.DNSCacheManager +DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel +DubboSample=io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample +DubboSampleGui=io.github.ningyu.jmeter.plugin.dubbo.gui.DubboSampleGui +DubboDefaultConfigGui=io.github.ningyu.jmeter.plugin.dubbo.gui.DubboDefaultConfigGui +DurationAssertion=org.apache.jmeter.assertions.DurationAssertion +DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui +PreciseThroughputTimer=org.apache.jmeter.timers.poissonarrivals.PreciseThroughputTimer +# Should really have been defined as floatProp to agree with other properties +# No point changing this now +FloatProperty=org.apache.jmeter.testelement.property.FloatProperty +ForeachController=org.apache.jmeter.control.ForeachController +ForeachControlPanel=org.apache.jmeter.control.gui.ForeachControlPanel +FtpConfigGui=org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui +FTPSampler=org.apache.jmeter.protocol.ftp.sampler.FTPSampler +FtpTestSamplerGui=org.apache.jmeter.protocol.ftp.control.gui.FtpTestSamplerGui +GaussianRandomTimer=org.apache.jmeter.timers.GaussianRandomTimer +GaussianRandomTimerGui=org.apache.jmeter.timers.gui.GaussianRandomTimerGui +GenericController=org.apache.jmeter.control.GenericController +GraphAccumVisualizer=org.apache.jmeter.visualizers.GraphAccumVisualizer +GraphVisualizer=org.apache.jmeter.visualizers.GraphVisualizer +Header=org.apache.jmeter.protocol.http.control.Header +HeaderManager=org.apache.jmeter.protocol.http.control.HeaderManager +HeaderPanel=org.apache.jmeter.protocol.http.gui.HeaderPanel +HTMLAssertion=org.apache.jmeter.assertions.HTMLAssertion +HTMLAssertionGui=org.apache.jmeter.assertions.gui.HTMLAssertionGui +HTTPArgument=org.apache.jmeter.protocol.http.util.HTTPArgument +HTTPArgumentsPanel=org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel +HTTPFileArg=org.apache.jmeter.protocol.http.util.HTTPFileArg +HTTPFileArgs=org.apache.jmeter.protocol.http.util.HTTPFileArgs +HttpDefaultsGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +HtmlExtractor=org.apache.jmeter.extractor.HtmlExtractor +HtmlExtractorGui=org.apache.jmeter.extractor.gui.HtmlExtractorGui +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSampler=org.apache.jmeter.protocol.http.sampler.HttpGenericSampler +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSamplerGui=org.apache.jmeter.protocol.http.control.gui.HttpGenericSamplerGui +HttpMirrorControl=org.apache.jmeter.protocol.http.control.HttpMirrorControl +HttpMirrorControlGui=org.apache.jmeter.protocol.http.control.gui.HttpMirrorControlGui +# r397955 - removed test class. Keep as commented entry for info only. +#HTTPNullSampler=org.apache.jmeter.protocol.http.sampler.HTTPNullSampler +# Merge previous 2 HTTP samplers into one +HTTPSampler_=org.apache.jmeter.protocol.http.sampler.HTTPSampler +HTTPSampler2_=org.apache.jmeter.protocol.http.sampler.HTTPSampler2 +HTTPSamplerProxy,HTTPSampler,HTTPSampler2=org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy +# Merge GUIs +HttpTestSampleGui,HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui +#HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui2 +IfController=org.apache.jmeter.control.IfController +IfControllerPanel=org.apache.jmeter.control.gui.IfControllerPanel +IncludeController=org.apache.jmeter.control.IncludeController +IncludeControllerGui=org.apache.jmeter.control.gui.IncludeControllerGui +InterleaveControl=org.apache.jmeter.control.InterleaveControl +InterleaveControlGui=org.apache.jmeter.control.gui.InterleaveControlGui +JavaConfig=org.apache.jmeter.protocol.java.config.JavaConfig +JavaConfigGui=org.apache.jmeter.protocol.java.config.gui.JavaConfigGui +JavaSampler=org.apache.jmeter.protocol.java.sampler.JavaSampler +JavaTest=org.apache.jmeter.protocol.java.test.JavaTest +JavaTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JavaTestSamplerGui +JDBCDataSource=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +JDBCPostProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor +JDBCPreProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor +JDBCSampler=org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler +JMESPathAssertion=org.apache.jmeter.assertions.jmespath.JMESPathAssertion +JMESPathAssertionGui=org.apache.jmeter.assertions.jmespath.gui.JMESPathAssertionGui +JMESPathExtractor=org.apache.jmeter.extractor.json.jmespath.JMESPathExtractor +JMESPathExtractorGui=org.apache.jmeter.extractor.json.jmespath.gui.JMESPathExtractorGui +# Renamed to JMSSamplerGui; keep original entry for backwards compatibility +JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui +JMSProperties=org.apache.jmeter.protocol.jms.sampler.JMSProperties +JMSProperty=org.apache.jmeter.protocol.jms.sampler.JMSProperty +JMSPublisherGui=org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui +JMSSampler=org.apache.jmeter.protocol.jms.sampler.JMSSampler +JMSSamplerGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui +JMSSubscriberGui=org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui +JSONPathAssertion=org.apache.jmeter.assertions.JSONPathAssertion +JSONPathAssertionGui=org.apache.jmeter.assertions.gui.JSONPathAssertionGui +JSONPostProcessor=org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor +JSONPostProcessorGui=org.apache.jmeter.extractor.json.jsonpath.gui.JSONPostProcessorGui +# Removed in r545311 as Jndi no longer present; keep for compat. +JndiDefaultsGui=org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui +JSR223Assertion=org.apache.jmeter.assertions.JSR223Assertion +JSR223Listener=org.apache.jmeter.visualizers.JSR223Listener +JSR223PostProcessor=org.apache.jmeter.extractor.JSR223PostProcessor +JSR223PreProcessor=org.apache.jmeter.modifiers.JSR223PreProcessor +JSR223Sampler=org.apache.jmeter.protocol.java.sampler.JSR223Sampler +JSR223Timer=org.apache.jmeter.timers.JSR223Timer +JUnitSampler=org.apache.jmeter.protocol.java.sampler.JUnitSampler +JUnitTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JUnitTestSamplerGui +KeystoreConfig=org.apache.jmeter.config.KeystoreConfig +LDAPArgument=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument +LDAPArguments=org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments +LDAPArgumentsPanel=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel +LdapConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui +LdapExtConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui +LDAPExtSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler +LdapExtTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapExtTestSamplerGui +LDAPSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPSampler +LdapTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapTestSamplerGui +LogicControllerGui=org.apache.jmeter.control.gui.LogicControllerGui +LoginConfig=org.apache.jmeter.config.LoginConfig +LoginConfigGui=org.apache.jmeter.config.gui.LoginConfigGui +LoopController=org.apache.jmeter.control.LoopController +LoopControlPanel=org.apache.jmeter.control.gui.LoopControlPanel +MailerModel=org.apache.jmeter.reporters.MailerModel +MailerResultCollector=org.apache.jmeter.reporters.MailerResultCollector +MailerVisualizer=org.apache.jmeter.visualizers.MailerVisualizer +MailReaderSampler=org.apache.jmeter.protocol.mail.sampler.MailReaderSampler +MailReaderSamplerGui=org.apache.jmeter.protocol.mail.sampler.gui.MailReaderSamplerGui +MD5HexAssertion=org.apache.jmeter.assertions.MD5HexAssertion +MD5HexAssertionGUI=org.apache.jmeter.assertions.gui.MD5HexAssertionGUI +ModuleController=org.apache.jmeter.control.ModuleController +ModuleControllerGui=org.apache.jmeter.control.gui.ModuleControllerGui +MongoScriptSampler=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler +MongoSourceElement=org.apache.jmeter.protocol.mongodb.config.MongoSourceElement +# removed in 3.2, class was deleted in r +MonitorHealthVisualizer=org.apache.jmeter.visualizers.MonitorHealthVisualizer +NamePanel=org.apache.jmeter.gui.NamePanel +BoltSampler=org.apache.jmeter.protocol.bolt.sampler.BoltSampler +BoltConnectionElement=org.apache.jmeter.protocol.bolt.config.BoltConnectionElement +ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui +OnceOnlyController=org.apache.jmeter.control.OnceOnlyController +OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui +# removed in 3.0, class was deleted in r1722962 +ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask +# removed in 3.0, class was deleted in r1722757 +ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier +# removed in 3.0, class was deleted in r1722757 +ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui +PoissonRandomTimer=org.apache.jmeter.timers.PoissonRandomTimer +PoissonRandomTimerGui=org.apache.jmeter.timers.gui.PoissonRandomTimerGui +PropertyControlGui=org.apache.jmeter.visualizers.PropertyControlGui +ProxyControl=org.apache.jmeter.protocol.http.proxy.ProxyControl +ProxyControlGui=org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui +PublisherSampler=org.apache.jmeter.protocol.jms.sampler.PublisherSampler +RandomControlGui=org.apache.jmeter.control.gui.RandomControlGui +RandomController=org.apache.jmeter.control.RandomController +RandomOrderController=org.apache.jmeter.control.RandomOrderController +RandomOrderControllerGui=org.apache.jmeter.control.gui.RandomOrderControllerGui +RandomVariableConfig=org.apache.jmeter.config.RandomVariableConfig +RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController +RecordingController=org.apache.jmeter.protocol.http.control.RecordingController +# removed in r1039684, class was deleted in r580452 +ReflectionThreadGroup=org.apache.jmeter.threads.ReflectionThreadGroup +RegexExtractor=org.apache.jmeter.extractor.RegexExtractor +RegexExtractorGui=org.apache.jmeter.extractor.gui.RegexExtractorGui +RegExUserParameters=org.apache.jmeter.protocol.http.modifier.RegExUserParameters +RegExUserParametersGui=org.apache.jmeter.protocol.http.modifier.gui.RegExUserParametersGui +RemoteListenerWrapper=org.apache.jmeter.samplers.RemoteListenerWrapper +RemoteSampleListenerWrapper=org.apache.jmeter.samplers.RemoteSampleListenerWrapper +RemoteTestListenerWrapper=org.apache.jmeter.samplers.RemoteTestListenerWrapper +RemoteThreadsListenerWrapper=org.apache.jmeter.threads.RemoteThreadsListenerWrapper +ResponseAssertion=org.apache.jmeter.assertions.ResponseAssertion +RespTimeGraphVisualizer=org.apache.jmeter.visualizers.RespTimeGraphVisualizer +ResultAction=org.apache.jmeter.reporters.ResultAction +ResultActionGui=org.apache.jmeter.reporters.gui.ResultActionGui +ResultCollector=org.apache.jmeter.reporters.ResultCollector +ResultSaver=org.apache.jmeter.reporters.ResultSaver +ResultSaverGui=org.apache.jmeter.reporters.gui.ResultSaverGui +RunTime=org.apache.jmeter.control.RunTime +RunTimeGui=org.apache.jmeter.control.gui.RunTimeGui +SampleSaveConfiguration=org.apache.jmeter.samplers.SampleSaveConfiguration +SampleTimeout=org.apache.jmeter.modifiers.SampleTimeout +SampleTimeoutGui=org.apache.jmeter.modifiers.gui.SampleTimeoutGui +SimpleConfigGui=org.apache.jmeter.config.gui.SimpleConfigGui +SimpleDataWriter=org.apache.jmeter.visualizers.SimpleDataWriter +SizeAssertion=org.apache.jmeter.assertions.SizeAssertion +SizeAssertionGui=org.apache.jmeter.assertions.gui.SizeAssertionGui +SMIMEAssertion=org.apache.jmeter.assertions.SMIMEAssertionTestElement +SMIMEAssertionGui=org.apache.jmeter.assertions.gui.SMIMEAssertionGui +SmtpSampler=org.apache.jmeter.protocol.smtp.sampler.SmtpSampler +SmtpSamplerGui=org.apache.jmeter.protocol.smtp.sampler.gui.SmtpSamplerGui +# removed in 3.2, class was deleted in r +SoapSampler=org.apache.jmeter.protocol.http.sampler.SoapSampler +# removed in 3.2, class was deleted in r +SoapSamplerGui=org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui +# removed in 3.1, class was deleted in r1763837 +SplineVisualizer=org.apache.jmeter.visualizers.SplineVisualizer +# Originally deleted in r397955 as class is obsolete; needed for compat. +SqlConfigGui=org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui +StaticHost=org.apache.jmeter.protocol.http.control.StaticHost +StatGraphVisualizer=org.apache.jmeter.visualizers.StatGraphVisualizer +StatVisualizer=org.apache.jmeter.visualizers.StatVisualizer +SubscriberSampler=org.apache.jmeter.protocol.jms.sampler.SubscriberSampler +SubstitutionElement=org.apache.jmeter.assertions.SubstitutionElement +Summariser=org.apache.jmeter.reporters.Summariser +SummariserGui=org.apache.jmeter.reporters.gui.SummariserGui +SummaryReport=org.apache.jmeter.visualizers.SummaryReport +SwitchController=org.apache.jmeter.control.SwitchController +SwitchControllerGui=org.apache.jmeter.control.gui.SwitchControllerGui +SyncTimer=org.apache.jmeter.timers.SyncTimer +SystemSampler=org.apache.jmeter.protocol.system.SystemSampler +SystemSamplerGui=org.apache.jmeter.protocol.system.gui.SystemSamplerGui +TableVisualizer=org.apache.jmeter.visualizers.TableVisualizer +TCPConfigGui=org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui +TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler +TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui +TestAction=org.apache.jmeter.sampler.TestAction +TestActionGui=org.apache.jmeter.sampler.gui.TestActionGui +TestBeanGUI=org.apache.jmeter.testbeans.gui.TestBeanGUI +TestFragmentController=org.apache.jmeter.control.TestFragmentController +TestFragmentControllerGui=org.apache.jmeter.control.gui.TestFragmentControllerGui +TestPlan=org.apache.jmeter.testelement.TestPlan +TestPlanGui=org.apache.jmeter.control.gui.TestPlanGui +ThreadGroup=org.apache.jmeter.threads.ThreadGroup +ThreadGroupGui=org.apache.jmeter.threads.gui.ThreadGroupGui +PostThreadGroup=org.apache.jmeter.threads.PostThreadGroup +PostThreadGroupGui=org.apache.jmeter.threads.gui.PostThreadGroupGui +SetupThreadGroup=org.apache.jmeter.threads.SetupThreadGroup +SetupThreadGroupGui=org.apache.jmeter.threads.gui.SetupThreadGroupGui +ThroughputController=org.apache.jmeter.control.ThroughputController +ThroughputControllerGui=org.apache.jmeter.control.gui.ThroughputControllerGui +TransactionController=org.apache.jmeter.control.TransactionController +TransactionControllerGui=org.apache.jmeter.control.gui.TransactionControllerGui +TransactionSampler=org.apache.jmeter.control.TransactionSampler +UniformRandomTimer=org.apache.jmeter.timers.UniformRandomTimer +UniformRandomTimerGui=org.apache.jmeter.timers.gui.UniformRandomTimerGui +URLRewritingModifier=org.apache.jmeter.protocol.http.modifier.URLRewritingModifier +URLRewritingModifierGui=org.apache.jmeter.protocol.http.modifier.gui.URLRewritingModifierGui +UserParameterModifier=org.apache.jmeter.protocol.http.modifier.UserParameterModifier +UserParameterModifierGui=org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui +UserParameters=org.apache.jmeter.modifiers.UserParameters +UserParametersGui=org.apache.jmeter.modifiers.gui.UserParametersGui +ViewResultsFullVisualizer=org.apache.jmeter.visualizers.ViewResultsFullVisualizer +# removed in 3.0, class was deleted in r1722757 +WebServiceSampler=org.apache.jmeter.protocol.http.sampler.WebServiceSampler +# removed in 3.0, class was deleted in r1722757 +WebServiceSamplerGui=org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui +WhileController=org.apache.jmeter.control.WhileController +WhileControllerGui=org.apache.jmeter.control.gui.WhileControllerGui +WorkBench=org.apache.jmeter.testelement.WorkBench +WorkBenchGui=org.apache.jmeter.control.gui.WorkBenchGui +XMLAssertion=org.apache.jmeter.assertions.XMLAssertion +XMLAssertionGui=org.apache.jmeter.assertions.gui.XMLAssertionGui +XMLSchemaAssertion=org.apache.jmeter.assertions.XMLSchemaAssertion +XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI +XPathAssertion=org.apache.jmeter.assertions.XPathAssertion +XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui +XPath2Assertion=org.apache.jmeter.assertions.XPath2Assertion +XPath2AssertionGui=org.apache.jmeter.assertions.gui.XPath2AssertionGui +XPathExtractor=org.apache.jmeter.extractor.XPathExtractor +XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui +XPath2Extractor=org.apache.jmeter.extractor.XPath2Extractor +XPath2ExtractorGui=org.apache.jmeter.extractor.gui.XPath2ExtractorGui +# Properties - all start with lower case letter and end with Prop +# +boolProp=org.apache.jmeter.testelement.property.BooleanProperty +collectionProp=org.apache.jmeter.testelement.property.CollectionProperty +doubleProp=org.apache.jmeter.testelement.property.DoubleProperty +elementProp=org.apache.jmeter.testelement.property.TestElementProperty +# see above - already defined as FloatProperty +#floatProp=org.apache.jmeter.testelement.property.FloatProperty +intProp=org.apache.jmeter.testelement.property.IntegerProperty +longProp=org.apache.jmeter.testelement.property.LongProperty +mapProp=org.apache.jmeter.testelement.property.MapProperty +objProp=org.apache.jmeter.testelement.property.ObjectProperty +stringProp=org.apache.jmeter.testelement.property.StringProperty +# +# Other - must start with a lower case letter (and not end with Prop) +# (otherwise they could clash with the initial set of aliases) +# +hashTree=org.apache.jorphan.collections.ListedHashTree +jmeterTestPlan=org.apache.jmeter.save.ScriptWrapper +sample=org.apache.jmeter.samplers.SampleResult +httpSample=org.apache.jmeter.protocol.http.sampler.HTTPSampleResult +statSample=org.apache.jmeter.samplers.StatisticalSampleResult +testResults=org.apache.jmeter.save.TestResultWrapper +assertionResult=org.apache.jmeter.assertions.AssertionResult +# removed in 3.2, class was deleted in r +monitorStats=org.apache.jmeter.visualizers.MonitorStats +sampleEvent=org.apache.jmeter.samplers.SampleEvent +# +# Converters to register. Must start line with '_' +# If the converter is a collection of subitems, set equal to "collection" +# If the converter needs to know the class mappings but is not a collection of +# subitems, set it equal to "mapping" +_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection +_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection +_org.apache.jmeter.save.converters.BooleanPropertyConverter= +_org.apache.jmeter.save.converters.IntegerPropertyConverter= +_org.apache.jmeter.save.converters.LongPropertyConverter= +_org.apache.jmeter.save.converters.MultiPropertyConverter=collection +_org.apache.jmeter.save.converters.SampleEventConverter= +_org.apache.jmeter.save.converters.SampleResultConverter=collection +_org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection +_org.apache.jmeter.save.converters.StringPropertyConverter= +_org.apache.jmeter.save.converters.HashTreeConverter=collection +_org.apache.jmeter.save.converters.TestElementConverter=collection +_org.apache.jmeter.save.converters.TestElementPropertyConverter=collection +_org.apache.jmeter.save.converters.TestResultWrapperConverter=collection +_org.apache.jmeter.save.ScriptWrapperConverter=mapping +# +# Remember to update the _version entry +# \ No newline at end of file diff --git a/src/main/resources/jmeter/bin/upgrade.properties b/src/main/resources/jmeter/bin/upgrade.properties new file mode 100644 index 0000000..03742c0 --- /dev/null +++ b/src/main/resources/jmeter/bin/upgrade.properties @@ -0,0 +1,123 @@ +# Class, property and value upgrade equivalences. + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +# +# Format is as follows -- +# for renamed test element & GUI classes: +# old.class.Name=new.class.Name +# old.class.Name|guiClassName=new.class.Name +# (e.g. for ConfigTestElement) +# +# for renamed / deleted properties: +# class.Name/Old.propertyName=newPropertyName +# if newPropertyName is omitted, then property is deleted +# +# for renamed values: +# old.class.Name.old.propertyName/oldValue=newValue +# + +org.apache.jmeter.protocol.http.config.gui.UrlConfigGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +org.apache.jmeter.assertions.Assertion=org.apache.jmeter.assertions.ResponseAssertion +org.apache.jmeter.protocol.http.sampler.HTTPSamplerFull=org.apache.jmeter.protocol.http.sampler.HTTPSampler +org.apache.jmeter.control.gui.RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController + +org.apache.jmeter.timers.gui.ConstantThroughputTimerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.timers.ConstantThroughputTimer/ConstantThroughputTimer.throughput=throughput + +org.apache.jmeter.protocol.jdbc.control.gui.JdbcTestSampleGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler/JDBCSampler.query=query +#org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.JDBCSampler.dataSource/NULL= + +# Convert DBconfig +org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.url=dbUrl +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.driver=driver +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.query=query +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.username=username +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.password=password + +# Convert PoolConfig +org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connections= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connPoolClass= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.maxuse=poolMax + +# SQL Config +org.apache.jmeter.config.ConfigTestElement/JDBCSampler.query=query +org.apache.jmeter.protocol.http.control.Header/TestElement.name=Header.name + +# Upgrade AccessLogSampler +org.apache.jmeter.protocol.http.control.gui.AccessLogSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.log_file=logFile +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.port=portString +#Is the following used now? +#org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.generator_class_name= +#Looks to be a new field +#filterClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.domain=domain +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.parser_class_name=parserClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.image_parser=imageParsing + +# Renamed class +org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui + +# These classes have been deleted; there's no defined replacement +org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui=org.apache.jmeter.config.gui.ObsoleteGui +org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui=org.apache.jmeter.config.gui.ObsoleteGui +# Should probably map to something other than ObsoleteGui... +org.apache.jmeter.threads.ReflectionThreadGroup=org.apache.jmeter.config.gui.ObsoleteGui + +# Convert BSFSamplerGui +org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.filename=filename +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.language=scriptLanguage +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.parameters=parameters +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.query=script + +# Obsolete Http user Parameters modifier test element +# Note: ConfigTestElement is the test element associated with ObsoleteGui +org.apache.jmeter.protocol.http.modifier.UserParameterModifier=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# Obsolete Graph Full Results listener +org.apache.jmeter.visualizers.GraphAccumVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.sampler.WebServiceSampler=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.ParamModifier=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722962 +org.apache.jmeter.protocol.http.modifier.ParamMask=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.1, class was deleted in r1774947 +org.apache.jmeter.visualizers.SplineVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.1 class was deleted in r1763837 +org.apache.jmeter.visualizers.DistributionGraphVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1771608 +org.apache.jmeter.visualizers.MonitorStats=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.visualizers.MonitorHealthVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1783280 +org.apache.jmeter.protocol.http.sampler.HTTPSampler2=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.sampler.SoapSampler=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui \ No newline at end of file diff --git a/src/main/resources/jmeter/bin/user.properties b/src/main/resources/jmeter/bin/user.properties new file mode 100644 index 0000000..d6a2625 --- /dev/null +++ b/src/main/resources/jmeter/bin/user.properties @@ -0,0 +1,147 @@ +# Sample user.properties file +# +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- +# +# List of paths (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of paths that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator (java.io.File.pathSeparatorChar in Java) to separate multiple paths. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#Example for windows (; separator) +#user.classpath=../classes;../lib;../app1/jar1.jar;../app2/jar2.jar +#Example for linux (:separator) +#user.classpath=../classes:../lib:../app1/jar1.jar:../app2/jar2.jar + +# List of paths (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# A path item can either be a jar file or a directory. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies using plugin_dependency_paths should be preferred over +# user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/jar1.jar;../app2/jar2.jar + +#--------------------------------------------------------------------------- +# Reporting configuration +#--------------------------------------------------------------------------- + +# Configure this property to change the report title +#jmeter.reportgenerator.report_title=Apache JMeter Dashboard + +# Used to generate a report based on a date range if needed +# Default date format (from SimpleDateFormat Java API and Locale.ENGLISH) +#jmeter.reportgenerator.date_format=yyyyMMddHHmmss +# Date range start date using date_format property +#jmeter.reportgenerator.start_date= +# Date range end date using date_format property +#jmeter.reportgenerator.end_date= + +# Change this parameter if you want to change the granularity of over time graphs. +#jmeter.reportgenerator.overall_granularity=60000 + +# Change this parameter if you want to change the granularity of Response time distribution +# Set to 100 ms by default +#jmeter.reportgenerator.graph.responseTimeDistribution.property.set_granularity=100 + +# Change this parameter if you want to keep only some samples. +# Regular Expression which Indicates which samples to keep for graphs and statistics generation. +# Empty value means no filtering +#jmeter.reportgenerator.sample_filter= + +# Change this parameter if you want to override the APDEX satisfaction threshold. +#jmeter.reportgenerator.apdex_satisfied_threshold=500 + +# Change this parameter if you want to override the APDEX tolerance threshold. +#jmeter.reportgenerator.apdex_tolerated_threshold=1500 + +# Indicates which graph series are filtered (regular expression) +# In the below example we filter on Search and Order samples +# Note that the end of the pattern should always include (-success|-failure)?$ +# TransactionsPerSecondGraphConsumer suffixes transactions with "-success" or "-failure" depending +# on the result +#jmeter.reportgenerator.exporter.html.series_filter=^(Search|Order)(-success|-failure)?$ + +# Indicates whether only controller samples are displayed on graphs that support it. +#jmeter.reportgenerator.exporter.html.show_controllers_only=false + +# This property is used by menu item "Export transactions for report" +# It is used to select which transactions by default will be exported +#jmeter.reportgenerator.exported_transactions_pattern=[a-zA-Z0-9_\\-{}\\$\\.]*[-_][0-9]* + + +## Custom graph definition +#jmeter.reportgenerator.graph.custom_mm_hit.classname=org.apache.jmeter.report.processor.graph.impl.CustomGraphConsumer +#jmeter.reportgenerator.graph.custom_mm_hit.title=Graph Title +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_Y_Axis=Response Time (ms) +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_X_Axis=Over Time +#jmeter.reportgenerator.graph.custom_mm_hit.property.set_granularity=${jmeter.reportgenerator.overall_granularity} +#jmeter.reportgenerator.graph.custom_mm_hit.property.setSampleVariableName=VarName +#jmeter.reportgenerator.graph.custom_mm_hit.property.setContentMessage=Message for graph point label + +######################################################################## +################## DISTRIBUTED TESTING CONFIGURATION ################## +######################################################################## +# Type of keystore : JKS +# +#server.rmi.ssl.keystore.type=JKS +# +# Keystore file that contains private key +# +#server.rmi.ssl.keystore.file=rmi_keystore.jks +# +# Password of Keystore +# +#server.rmi.ssl.keystore.password=changeit +# +# Key alias +# +#server.rmi.ssl.keystore.alias=rmi +# +# Type of truststore : JKS +# +#server.rmi.ssl.truststore.type=JKS +# +# Keystore file that contains certificate +# +#server.rmi.ssl.truststore.file=rmi_keystore.jks +# +# Password of Trust store +# +#server.rmi.ssl.truststore.password=changeit +# +# Set this if you don't want to use SSL for RMI +# +#server.rmi.ssl.disable=false \ No newline at end of file From 2671372a9bb74a66b86f570e5c718c08adebd126 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 16 Apr 2021 20:46:26 +0800 Subject: [PATCH 049/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cea8bc7..a99da6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,12 @@ MAINTAINER FIT2CLOUD ARG MS_VERSION=dev -RUN mkdir -p /opt/apps +RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter/lib/junit ADD target/node-controller-1.8.jar /opt/apps +COPY target/classes/jmeter/ /opt/jmeter/ + ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar ENV AB_OFF=true From 8edc50fe373e6651356b3d05f1fe3674d320c120 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Fri, 16 Apr 2021 21:29:11 +0800 Subject: [PATCH 050/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pom.xml b/pom.xml index 127e7fa..88e6033 100644 --- a/pom.xml +++ b/pom.xml @@ -403,6 +403,44 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + generate-resources + + copy + + + + + + + org.apache.jmeter + ApacheJMeter_functions + ${jmeter.version} + jar + true + src/main/resources/jmeter/lib/ext + ApacheJMeter_functions.jar + + + org.python + jython-standalone + 2.7.0 + jar + true + src/main/resources/jmeter/lib/ext + jython-standalone.jar + + + ${project.build.directory}/wars + false + true + + From c5b09600a4d8f0d3b0b7b91255c201571c8bd9fa Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 14:30:45 +0800 Subject: [PATCH 051/157] refactor: ignore .jar --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 179b07f..b9d234f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ target/ *.iml *.ipr src/test/ +*.jar \ No newline at end of file From 3832ddb8a4a879f58407f705cbb49ab2874abb58 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 14:41:16 +0800 Subject: [PATCH 052/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= =?UTF-8?q?=E5=8E=BB=E6=8E=89dubbo=E4=B8=AD=E5=B8=A6=E6=9C=89=E7=9A=84spri?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 88e6033..7190ae3 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,12 @@ org.apache.dubbo dubbo ${dubbo.version} + + + spring-context + org.springframework + + org.apache.zookeeper From 48d3d19a919edfd314bcf111f17e29a9fdd6db08 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 15:51:00 +0800 Subject: [PATCH 053/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom=20gro?= =?UTF-8?q?ovy=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7190ae3..d61197e 100644 --- a/pom.xml +++ b/pom.xml @@ -259,7 +259,7 @@ org.codehaus.groovy groovy-jsr223 - 2.5.10 + 3.0.8 From 111559a96230e78398e1bc5ba6a0296b803859d0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 21 Apr 2021 18:34:51 +0800 Subject: [PATCH 054/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom=20gro?= =?UTF-8?q?ovy=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d61197e..94c86e4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 5.4.1 1.1.3 2.7.8 + 3.0.8 @@ -259,7 +260,6 @@ org.codehaus.groovy groovy-jsr223 - 3.0.8 From e77f6ce00eb5484a979f349b958a0c18b5ef1cdd Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 22 Apr 2021 10:08:45 +0800 Subject: [PATCH 055/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=20NewDri?= =?UTF-8?q?ver=20loader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/apache/jmeter/NewDriver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/jmeter/NewDriver.java b/src/main/java/org/apache/jmeter/NewDriver.java index fa24fcd..9b30948 100644 --- a/src/main/java/org/apache/jmeter/NewDriver.java +++ b/src/main/java/org/apache/jmeter/NewDriver.java @@ -127,7 +127,7 @@ public static void loaderClass(String name) { System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString()); loader = AccessController.doPrivileged( (PrivilegedAction) () -> - new DynamicClassLoader(jars.toArray(new URL[jars.size()])) + new DynamicClassLoader(jars.toArray(new URL[jars.size()]), Thread.currentThread().getContextClassLoader()) ); } From 73b41b682bae93ea4491cac639eab8910f5e51bb Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 26 Apr 2021 18:59:43 +0800 Subject: [PATCH 056/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E5=8F=98=E6=9B=B4?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 8 +++++ .../api/jmeter/APIBackendListenerClient.java | 10 +++--- .../metersphere/api/jmeter/JMeterService.java | 2 +- .../io/metersphere/api/module/JvmInfo.java | 35 +++++++++++++++++++ .../{jmeter => }/module/RequestResult.java | 2 +- .../module/ResponseAssertionResult.java | 2 +- .../{jmeter => }/module/ResponseResult.java | 2 +- .../{jmeter => }/module/ScenarioResult.java | 2 +- .../api/{jmeter => }/module/TestResult.java | 10 +++--- .../metersphere/api/service/JvmService.java | 35 +++++++++++++++++++ 10 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 src/main/java/io/metersphere/api/module/JvmInfo.java rename src/main/java/io/metersphere/api/{jmeter => }/module/RequestResult.java (94%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ResponseAssertionResult.java (78%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ResponseResult.java (91%) rename src/main/java/io/metersphere/api/{jmeter => }/module/ScenarioResult.java (95%) rename src/main/java/io/metersphere/api/{jmeter => }/module/TestResult.java (92%) create mode 100644 src/main/java/io/metersphere/api/service/JvmService.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 03a9c1b..7d0a7e4 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -2,7 +2,9 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.api.service.JvmService; import io.metersphere.node.util.LogUtil; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -28,4 +30,10 @@ public String getStatus() { return "OK"; } + + @GetMapping("/getJvmInfo") + public JvmInfo getJvmInfo() { + return JvmService.jvmInfo(); + } + } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index c78488a..262eee3 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -4,7 +4,7 @@ import io.metersphere.api.service.LoadTestProducer; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; -import io.metersphere.api.jmeter.module.*; +import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; @@ -28,7 +28,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_ID = "ms.test.id"; - public final static String TEST_REPORT_NAME = "ms.test.report.name"; + public final static String TEST_REPORT_ID = "ms.test.report.name"; private final static String THREAD_SPLIT = " "; @@ -52,7 +52,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl /** * 只有合并报告是这个有值 */ - private String reportName; + private String setReportId; /** * 获得控制台内容 @@ -88,7 +88,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { TestResult testResult = new TestResult(); testResult.setTestId(testId); testResult.setTotal(queue.size()); - testResult.setReportName(this.reportName); + testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); @@ -224,7 +224,7 @@ private String getMethod(SampleResult result) { private void setParam(BackendListenerContext context) { this.testId = context.getParameter(TEST_ID); - this.reportName = context.getParameter(TEST_REPORT_NAME); + this.setReportId = context.getParameter(TEST_REPORT_ID); this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 670023d..152aedc 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -59,7 +59,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { backendListener.setName(request.getTestId()); Arguments arguments = new Arguments(); if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { - arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, request.getConfig().getReportName()); + arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); if (StringUtils.isNotBlank(request.getRunMode())) { diff --git a/src/main/java/io/metersphere/api/module/JvmInfo.java b/src/main/java/io/metersphere/api/module/JvmInfo.java new file mode 100644 index 0000000..05c8dab --- /dev/null +++ b/src/main/java/io/metersphere/api/module/JvmInfo.java @@ -0,0 +1,35 @@ +package io.metersphere.api.module; + +import lombok.Data; + +@Data +public class JvmInfo { + /** + * JVM内存的空闲空间为 + */ + private long vmFree; + /** + * JVM内存已用的空间为 + */ + private long vmUse; + + private long vmTotal; + /** + * JVM总内存空间为 + */ + private long vmMax; + + private int totalThread; + + public JvmInfo() { + + } + + public JvmInfo(long vmTotal, long vmFree, long vmMax, long vmUse, int totalThread) { + this.vmFree = vmFree; + this.vmTotal = vmTotal; + this.vmMax = vmMax; + this.vmUse = vmUse; + this.totalThread = totalThread; + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java similarity index 94% rename from src/main/java/io/metersphere/api/jmeter/module/RequestResult.java rename to src/main/java/io/metersphere/api/module/RequestResult.java index d9cec5c..b650fff 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java b/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java similarity index 78% rename from src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java rename to src/main/java/io/metersphere/api/module/ResponseAssertionResult.java index 05c4512..f75904c 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ResponseAssertionResult.java +++ b/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java b/src/main/java/io/metersphere/api/module/ResponseResult.java similarity index 91% rename from src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java rename to src/main/java/io/metersphere/api/module/ResponseResult.java index 7e4ad10..f1ecd30 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ResponseResult.java +++ b/src/main/java/io/metersphere/api/module/ResponseResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java b/src/main/java/io/metersphere/api/module/ScenarioResult.java similarity index 95% rename from src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java rename to src/main/java/io/metersphere/api/module/ScenarioResult.java index 3be1644..d388e3f 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/ScenarioResult.java +++ b/src/main/java/io/metersphere/api/module/ScenarioResult.java @@ -1,4 +1,4 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; diff --git a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java similarity index 92% rename from src/main/java/io/metersphere/api/jmeter/module/TestResult.java rename to src/main/java/io/metersphere/api/module/TestResult.java index 60ed9b0..9e818bc 100644 --- a/src/main/java/io/metersphere/api/jmeter/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -1,8 +1,8 @@ -package io.metersphere.api.jmeter.module; +package io.metersphere.api.module; import lombok.Data; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.dubbo.common.utils.CollectionUtils; import java.util.ArrayList; import java.util.List; @@ -12,13 +12,13 @@ public class TestResult { private String testId; - private boolean isDebug; + private String setReportId; private String userId; - private String runMode; + private boolean isDebug; - private String reportName; + private String runMode; private int success = 0; diff --git a/src/main/java/io/metersphere/api/service/JvmService.java b/src/main/java/io/metersphere/api/service/JvmService.java new file mode 100644 index 0000000..2e470ea --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JvmService.java @@ -0,0 +1,35 @@ +/** + * + */ +package io.metersphere.api.service; + +import io.metersphere.api.module.JvmInfo; + +/** + * @author mr.zhao + */ +public class JvmService { + + // 日志输出 + public static JvmInfo jvmInfo() { + // 虚拟机级内存情况查询 + long vmFree = 0; + long vmUse = 0; + long vmTotal = 0; + long vmMax = 0; + int byteToMb = 1024 * 1024; + Runtime rt = Runtime.getRuntime(); + vmTotal = rt.totalMemory() / byteToMb; + vmFree = rt.freeMemory() / byteToMb; + vmMax = rt.maxMemory() / byteToMb; + vmUse = vmTotal - vmFree; + // 获得线程总数 + ThreadGroup parentThread; + int totalThread = 0; + for (parentThread = Thread.currentThread().getThreadGroup(); parentThread + .getParent() != null; parentThread = parentThread.getParent()) { + totalThread = parentThread.activeCount(); + } + return new JvmInfo(vmTotal, vmFree, vmMax, vmUse, totalThread); + } +} From 242fcac78f027189c52954ba9b7332e54cc67a1a Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 29 Apr 2021 10:23:32 +0800 Subject: [PATCH 057/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8Dcsv=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/jmeter/services/FileServer.java | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 src/main/java/org/apache/jmeter/services/FileServer.java diff --git a/src/main/java/org/apache/jmeter/services/FileServer.java b/src/main/java/org/apache/jmeter/services/FileServer.java new file mode 100644 index 0000000..d51e9a8 --- /dev/null +++ b/src/main/java/org/apache/jmeter/services/FileServer.java @@ -0,0 +1,582 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.services; + +import org.apache.commons.io.input.BOMInputStream; +import org.apache.jmeter.gui.JMeterFileFilter; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +/** + * This class provides thread-safe access to files, and to + * provide some simplifying assumptions about where to find files and how to + * name them. For instance, putting supporting files in the same directory as + * the saved test plan file allows users to refer to the file with just it's + * name - this FileServer class will find the file without a problem. + * Eventually, I want all in-test file access to be done through here, with the + * goal of packaging up entire test plans as a directory structure that can be + * sent via rmi to remote servers (currently, one must make sure the remote + * server has all support files in a relative-same location) and to package up + * test plans to execute on unknown boxes that only have Java installed. + */ +public class FileServer { + + private static final Logger log = LoggerFactory.getLogger(FileServer.class); + + /** + * The default base used for resolving relative files, i.e.
+ * {@code System.getProperty("user.dir")} + */ + private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ + + /** Default base prefix: {@value} */ + private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ + + private static final String BASE_PREFIX = + JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ + BASE_PREFIX_DEFAULT); + + private File base; + + private final Map files = new HashMap<>(); + + private static final FileServer server = new FileServer(); + + // volatile needed to ensure safe publication + private volatile String scriptName; + + // Cannot be instantiated + private FileServer() { + base = new File(DEFAULT_BASE); + log.info("Default base='{}'", DEFAULT_BASE); + } + + /** + * @return the singleton instance of the server. + */ + public static FileServer getFileServer() { + return server; + } + + /** + * Resets the current base to DEFAULT_BASE. + */ + public synchronized void resetBase() { + checkForOpenFiles(); + base = new File(DEFAULT_BASE); + log.info("Reset base to '{}'", base); + } + + /** + * Sets the current base directory for relative file names from the provided path. + * If the path does not refer to an existing directory, then its parent is used. + * Normally the provided path is a file, so using the parent directory is appropriate. + * + * @param basedir the path to set, or {@code null} if the GUI is being cleared + * @throws IllegalStateException if files are still open + */ + public synchronized void setBasedir(String basedir) { + checkForOpenFiles(); // TODO should this be called if basedir == null? + if (basedir != null) { + File newBase = new File(basedir); + if (!newBase.isDirectory()) { + newBase = newBase.getParentFile(); + } + base = newBase; + log.info("Set new base='{}'", base); + } + } + + /** + * Sets the current base directory for relative file names from the provided script file. + * The parameter is assumed to be the path to a JMX file, so the base directory is derived + * from its parent. + * + * @param scriptPath the path of the script file; must be not be {@code null} + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if scriptPath parameter is null + */ + public synchronized void setBaseForScript(File scriptPath) { + if (scriptPath == null){ + throw new IllegalArgumentException("scriptPath must not be null"); + } + setScriptName(scriptPath.getName()); + // getParentFile() may not work on relative paths + setBase(scriptPath.getAbsoluteFile().getParentFile()); + } + + /** + * Sets the current base directory for relative file names. + * + * @param jmxBase the path of the script file base directory, cannot be null + * @throws IllegalStateException if files are still open + * @throws IllegalArgumentException if {@code basepath} is null + */ + public synchronized void setBase(File jmxBase) { + if (jmxBase == null) { + throw new IllegalArgumentException("jmxBase must not be null"); + } + checkForOpenFiles(); + base = jmxBase; + log.info("Set new base='{}'", base); + } + + /** + * Check if there are entries in use. + *

+ * Caller must ensure that access to the files map is single-threaded as + * there is a window between checking the files Map and clearing it. + * + * @throws IllegalStateException if there are any entries still in use + */ + private void checkForOpenFiles() throws IllegalStateException { + if (filesOpen()) { // checks for entries in use + throw new IllegalStateException("Files are still open, cannot change base directory"); + } + files.clear(); // tidy up any unused entries + } + + public synchronized String getBaseDir() { + return base.getAbsolutePath(); + } + + public static String getDefaultBase(){ + return DEFAULT_BASE; + } + + /** + * Calculates the relative path from DEFAULT_BASE to the current base, + * which must be the same as or a child of the default. + * + * @return the relative path, or {@code "."} if the path cannot be determined + */ + public synchronized File getBaseDirRelative() { + // Must first convert to absolute path names to ensure parents are available + File parent = new File(DEFAULT_BASE).getAbsoluteFile(); + File f = base.getAbsoluteFile(); + ArrayDeque l = new ArrayDeque<>(); + while (f != null) { + if (f.equals(parent)){ + if (l.isEmpty()){ + break; + } + File rel = new File(l.pop()); + while(!l.isEmpty()) { + rel = new File(rel, l.pop()); + } + return rel; + } + l.push(f.getName()); + f = f.getParentFile(); + } + return new File("."); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + */ + public void reserveFile(String filename) { + reserveFile(filename,null); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + */ + public void reserveFile(String filename, String charsetName) { + reserveFile(filename, charsetName, filename, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + */ + public void reserveFile(String filename, String charsetName, String alias) { + reserveFile(filename, charsetName, alias, false); + } + + /** + * Creates an association between a filename and a File inputOutputObject, + * and stores it for later use - unless it is already stored. + * + * @param filename - relative (to base) or absolute file name (must not be null or empty) + * @param charsetName - the character set encoding to use for the file (may be null) + * @param alias - the name to be used to access the object (must not be null) + * @param hasHeader true if the file has a header line describing the contents + * @return the header line; may be null + * @throws IllegalArgumentException if header could not be read or filename is null or empty + */ + public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { + if (filename == null || filename.isEmpty()){ + throw new IllegalArgumentException("Filename must not be null or empty"); + } + if (alias == null){ + throw new IllegalArgumentException("Alias must not be null"); + } + FileEntry fileEntry = files.get(alias); + if (fileEntry == null) { + fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); + if (filename.equals(alias)){ + log.info("Stored: {}", filename); + } else { + log.info("Stored: {} Alias: {}", filename, alias); + } + files.put(alias, fileEntry); + if (hasHeader) { + try { + fileEntry.headerLine = readLine(alias, false); + if (fileEntry.headerLine == null) { + fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); + } + } catch (IOException | IllegalArgumentException e) { + fileEntry.exception = e; + } + } + } + if (hasHeader && fileEntry.headerLine == null) { + throw new IllegalArgumentException("Could not read file header line for file " + filename, + fileEntry.exception); + } + return fileEntry.headerLine; + } + + /** + * Resolves file name into {@link File} instance. + * When filename is not absolute and not found from current working dir, + * it tries to find it under current base directory + * @param filename original file name + * @return {@link File} instance + */ + private File resolveFileFromPath(String filename) { + File f = new File(filename); + if (f.isAbsolute() || f.exists()) { + return f; + } else { + return new File(base, filename); + } + } + + /** + * Get the next line of the named file, recycle by default. + * + * @param filename the filename or alias that was used to reserve the file + * @return String containing the next line in the file + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename) throws IOException { + return readLine(filename, true); + } + + /** + * Get the next line of the named file, first line is name to false + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public String readLine(String filename, boolean recycle) throws IOException { + return readLine(filename, recycle, false); + } + /** + * Get the next line of the named file + * + * @param filename the filename or alias that was used to reserve the file + * @param recycle - should file be restarted at EOF? + * @param ignoreFirstLine - Ignore first line + * @return String containing the next line in the file (null if EOF reached and not recycle) + * @throws IOException when reading of the file fails, or the file was not reserved properly + */ + public synchronized String readLine(String filename, boolean recycle, + boolean ignoreFirstLine) throws IOException { + FileEntry fileEntry = files.get(filename); + if(fileEntry == null ){ + this.reserveFile(filename); + fileEntry = files.get(filename); + } + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedReader(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; + String line = reader.readLine(); + if (line == null && recycle) { + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine();//NOSONAR + } + line = reader.readLine(); + } + log.debug("Read:{}", line); + return line; + } + throw new IOException("File never reserved: "+filename); + } + + /** + * + * @param alias the file name or alias + * @param recycle whether the file should be re-started on EOF + * @param ignoreFirstLine whether the file contains a file header which will be ignored + * @param delim the delimiter to use for parsing + * @return the parsed line, will be empty if the file is at EOF + * @throws IOException when reading of the aliased file fails, or the file was not reserved properly + */ + public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { + BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); + return CSVSaveService.csvReadFile(reader, delim); + } + + /** + * Return BufferedReader handling close if EOF reached and recycle is true + * and ignoring first line if ignoreFirstLine is true + * + * @param alias String alias + * @param recycle Recycle at eof + * @param ignoreFirstLine Ignore first line + * @return {@link BufferedReader} + */ + private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { + FileEntry fileEntry = files.get(alias); + if (fileEntry != null) { + BufferedReader reader; + if (fileEntry.inputOutputObject == null) { + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine(); //NOSONAR + } + } else if (!(fileEntry.inputOutputObject instanceof Reader)) { + throw new IOException("File " + alias + " already in use"); + } else { + reader = (BufferedReader) fileEntry.inputOutputObject; + if (recycle) { // need to check if we are at EOF already + reader.mark(1); + int peek = reader.read(); + if (peek == -1) { // already at EOF + reader.close(); + reader = createBufferedReader(fileEntry); + fileEntry.inputOutputObject = reader; + if (ignoreFirstLine) { + // read first line and forget + reader.readLine(); //NOSONAR + } + } else { // OK, we still have some data, restore it + reader.reset(); + } + } + } + return reader; + } else { + throw new IOException("File never reserved: "+alias); + } + } + + private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { + if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { + throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); + } + BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR + InputStreamReader isr = null; + // If file encoding is specified, read using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + isr = new InputStreamReader(fis, charsetName); + } else if (fis.hasBOM()) { + isr = new InputStreamReader(fis, fis.getBOM().getCharsetName()); + } else { + @SuppressWarnings("DefaultCharset") + final InputStreamReader withPlatformEncoding = new InputStreamReader(fis); + isr = withPlatformEncoding; + } + return new BufferedReader(isr); + } + + public synchronized void write(String filename, String value) throws IOException { + FileEntry fileEntry = files.get(filename); + if (fileEntry != null) { + if (fileEntry.inputOutputObject == null) { + fileEntry.inputOutputObject = createBufferedWriter(fileEntry); + } else if (!(fileEntry.inputOutputObject instanceof Writer)) { + throw new IOException("File " + filename + " already in use"); + } + BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; + log.debug("Write:{}", value); + writer.write(value); + } else { + throw new IOException("File never reserved: "+filename); + } + } + + private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { + OutputStream fos = Files.newOutputStream(fileEntry.file.toPath()); + OutputStreamWriter osw; + // If file encoding is specified, write using that encoding, otherwise use default platform encoding + String charsetName = fileEntry.charSetEncoding; + if(!JOrphanUtils.isBlank(charsetName)) { + osw = new OutputStreamWriter(fos, charsetName); + } else { + @SuppressWarnings("DefaultCharset") + final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos); + osw = withPlatformEncoding; + } + return new BufferedWriter(osw); + } + + public synchronized void closeFiles() throws IOException { + for (Map.Entry me : files.entrySet()) { + closeFile(me.getKey(),me.getValue() ); + } + files.clear(); + } + + /** + * @param name the name or alias of the file to be closed + * @throws IOException when closing of the aliased file fails + */ + public synchronized void closeFile(String name) throws IOException { + FileEntry fileEntry = files.get(name); + closeFile(name, fileEntry); + } + + private void closeFile(String name, FileEntry fileEntry) throws IOException { + if (fileEntry != null && fileEntry.inputOutputObject != null) { + log.info("Close: {}", name); + fileEntry.inputOutputObject.close(); + fileEntry.inputOutputObject = null; + } + } + + boolean filesOpen() { // package access for test code only + return files.values().stream() + .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); + } + + /** + * Method will get a random file in a base directory + *

+ * TODO hey, not sure this method belongs here. + * FileServer is for thread safe File access relative to current test's base directory. + * + * @param basedir name of the directory in which the files can be found + * @param extensions array of allowed extensions, if null is given, + * any file be allowed + * @return a random File from the basedir that matches one of + * the extensions + */ + public File getRandomFile(String basedir, String[] extensions) { + File input = null; + if (basedir != null) { + File src = new File(basedir); + File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); + if (lfiles != null) { + // lfiles cannot be null as it has been checked before + int count = lfiles.length; + input = lfiles[ThreadLocalRandom.current().nextInt(count)]; + } + } + return input; + } + + /** + * Get {@link File} instance for provided file path, + * resolve file location relative to base dir or script dir when needed + * + * @param path original path to file, maybe relative + * @return {@link File} instance + */ + public File getResolvedFile(String path) { + reserveFile(path); + return files.get(path).file; + } + + private static class FileEntry{ + private String headerLine; + private Throwable exception; + private final File file; + private Closeable inputOutputObject; + private final String charSetEncoding; + + FileEntry(File f, Closeable o, String e) { + file = f; + inputOutputObject = o; + charSetEncoding = e; + } + } + + /** + * Resolve a file name that may be relative to the base directory. If the + * name begins with the value of the JMeter property + * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is + * assumed to be relative to the basename. + * + * @param relativeName + * filename that should be checked for + * jmeter.save.saveservice.base_prefix + * @return the updated filename + */ + public static String resolveBaseRelativeName(String relativeName) { + if (relativeName.startsWith(BASE_PREFIX)){ + String newName = relativeName.substring(BASE_PREFIX.length()); + return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); + } + return relativeName; + } + + /** + * @return JMX Script name + * @since 2.6 + */ + public String getScriptName() { + return scriptName; + } + + /** + * @param scriptName Script name + * @since 2.6 + */ + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } +} From cde85c1f231320b3fc7d3517349afbbe02da6131 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 30 Apr 2021 15:53:01 +0800 Subject: [PATCH 058/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=97=B6CSV=E5=8F=96=E4=B8=8D=E5=88=B0?= =?UTF-8?q?=E5=80=BC=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/apache/jmeter/config/CSVDataSet.java | 346 +++++++++++ .../apache/jmeter/services/FileServer.java | 582 ------------------ 2 files changed, 346 insertions(+), 582 deletions(-) create mode 100644 src/main/java/org/apache/jmeter/config/CSVDataSet.java delete mode 100644 src/main/java/org/apache/jmeter/services/FileServer.java diff --git a/src/main/java/org/apache/jmeter/config/CSVDataSet.java b/src/main/java/org/apache/jmeter/config/CSVDataSet.java new file mode 100644 index 0000000..7fd554f --- /dev/null +++ b/src/main/java/org/apache/jmeter/config/CSVDataSet.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.engine.event.LoopIterationListener; +import org.apache.jmeter.engine.util.NoConfigMerge; +import org.apache.jmeter.gui.GUIMenuSortOrder; +import org.apache.jmeter.gui.TestElementMetadata; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jmeter.testelement.property.StringProperty; +import org.apache.jmeter.threads.JMeterContext; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JMeterStopThreadException; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.IOException; +import java.util.ResourceBundle; + +/** + * Read lines from a file and split int variables. + *

+ * The iterationStart() method is used to set up each set of values. + *

+ * By default, the same file is shared between all threads + * (and other thread groups, if they use the same file name). + *

+ * The shareMode can be set to: + *

    + *
  • All threads - default, as described above
  • + *
  • Current thread group
  • + *
  • Current thread
  • + *
  • Identifier - all threads sharing the same identifier
  • + *
+ *

+ * The class uses the FileServer alias mechanism to provide the different share modes. + * For all threads, the file alias is set to the file name. + * Otherwise, a suffix is appended to the filename to make it unique within the required context. + * For current thread group, the thread group identityHashcode is used; + * for individual threads, the thread hashcode is used as the suffix. + * Or the user can provide their own suffix, in which case the file is shared between all + * threads with the same suffix. + */ +@GUIMenuSortOrder(1) +@TestElementMetadata(labelResource = "displayName") +public class CSVDataSet extends ConfigTestElement + implements TestBean, LoopIterationListener, NoConfigMerge { + private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class); + + private static final long serialVersionUID = 233L; + + private static final String EOFVALUE = // value to return at EOF + JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + private transient String filename; + + private transient String fileEncoding; + + private transient String variableNames; + + private transient String delimiter; + + private transient boolean quoted; + + private transient boolean recycle = true; + + private transient boolean stopThread; + + private transient String[] vars; + + private transient String alias; + + private transient String shareMode; + + private boolean firstLineIsNames = false; + + private boolean ignoreFirstLine = false; + + private Object readResolve() { + recycle = true; + return this; + } + + /** + * Override the setProperty method in order to convert + * the original String shareMode property. + * This used the locale-dependent display value, so caused + * problems when the language was changed. + * If the "shareMode" value matches a resource value then it is converted + * into the resource key. + * To reduce the need to look up resources, we only attempt to + * convert values with spaces in them, as these are almost certainly + * not variables (and they are definitely not resource keys). + */ + @Override + public void setProperty(JMeterProperty property) { + if (!(property instanceof StringProperty)) { + super.setProperty(property); + return; + } + + final String propName = property.getName(); + if (!"shareMode".equals(propName)) { + super.setProperty(property); + return; + } + + final String propValue = property.getStringValue(); + if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation + try { + final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); + final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); + for (String resKey : CSVDataSetBeanInfo.getShareTags()) { + if (propValue.equals(rb.getString(resKey))) { + if (log.isDebugEnabled()) { + log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale()); + } + ((StringProperty) property).setValue(resKey); // reset the value + super.setProperty(property); + return; + } + } + // This could perhaps be a variable name + log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale()); + } catch (IntrospectionException e) { + log.error("Could not find BeanInfo; cannot translate shareMode entries", e); + } + } + super.setProperty(property); + } + + @Override + public void iterationStart(LoopIterationEvent iterEvent) { + FileServer server = FileServer.getFileServer(); + final JMeterContext context = getThreadContext(); + String delim = getDelimiter(); + if ("\\t".equals(delim)) { // $NON-NLS-1$ + delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ + } else if (delim.isEmpty()) { + log.debug("Empty delimiter, will use ','"); + delim = ","; + } + //if (vars == null) { + initVars(server, context, delim); + //} + + // TODO: fetch this once as per vars above? + JMeterVariables threadVars = context.getVariables(); + String[] lineValues = {}; + try { + if (getQuotedData()) { + lineValues = server.getParsedLine(alias, recycle, + firstLineIsNames || ignoreFirstLine, delim.charAt(0)); + } else { + String line = server.readLine(alias, recycle, + firstLineIsNames || ignoreFirstLine); + lineValues = JOrphanUtils.split(line, delim, false); + } + for (int a = 0; a < vars.length && a < lineValues.length; a++) { + threadVars.put(vars[a], lineValues[a]); + } + } catch (IOException e) { // treat the same as EOF + log.error(e.toString()); + } + if (lineValues.length == 0) {// i.e. EOF + if (getStopThread()) { + throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:" + + getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle()); + } + for (String var : vars) { + threadVars.put(var, EOFVALUE); + } + } + } + + private void initVars(FileServer server, final JMeterContext context, String delim) { + String fileName = getFilename().trim(); + setAlias(context, fileName); + final String names = getVariableNames(); + if (StringUtils.isEmpty(names)) { + String header = server.reserveFile(fileName, getFileEncoding(), alias, true); + try { + vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); + firstLineIsNames = true; + } catch (IOException e) { + throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e); + } + } else { + server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine); + vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ + } + trimVarNames(vars); + } + + private void setAlias(final JMeterContext context, String alias) { + String mode = getShareMode(); + int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); + switch (modeInt) { + case CSVDataSetBeanInfo.SHARE_ALL: + this.alias = alias; + break; + case CSVDataSetBeanInfo.SHARE_GROUP: + this.alias = alias + "@" + System.identityHashCode(context.getThreadGroup()); + break; + case CSVDataSetBeanInfo.SHARE_THREAD: + this.alias = alias + "@" + System.identityHashCode(context.getThread()); + break; + default: + this.alias = alias + "@" + mode; // user-specified key + break; + } + } + + /** + * trim content of array varNames + * + * @param varsNames + */ + private void trimVarNames(String[] varsNames) { + for (int i = 0; i < varsNames.length; i++) { + varsNames[i] = varsNames[i].trim(); + } + } + + /** + * @return Returns the filename. + */ + public String getFilename() { + return filename; + } + + /** + * @param filename The filename to set. + */ + public void setFilename(String filename) { + this.filename = filename; + } + + /** + * @return Returns the file encoding. + */ + public String getFileEncoding() { + return fileEncoding; + } + + /** + * @param fileEncoding The fileEncoding to set. + */ + public void setFileEncoding(String fileEncoding) { + this.fileEncoding = fileEncoding; + } + + /** + * @return Returns the variableNames. + */ + public String getVariableNames() { + return variableNames; + } + + /** + * @param variableNames The variableNames to set. + */ + public void setVariableNames(String variableNames) { + this.variableNames = variableNames; + } + + public String getDelimiter() { + return delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public boolean getQuotedData() { + return quoted; + } + + public void setQuotedData(boolean quoted) { + this.quoted = quoted; + } + + public boolean getRecycle() { + return recycle; + } + + public void setRecycle(boolean recycle) { + this.recycle = recycle; + } + + public boolean getStopThread() { + return stopThread; + } + + public void setStopThread(boolean value) { + this.stopThread = value; + } + + public String getShareMode() { + return shareMode; + } + + public void setShareMode(String value) { + this.shareMode = value; + } + + /** + * @return the ignoreFirstLine + */ + public boolean isIgnoreFirstLine() { + return ignoreFirstLine; + } + + /** + * @param ignoreFirstLine the ignoreFirstLine to set + */ + public void setIgnoreFirstLine(boolean ignoreFirstLine) { + this.ignoreFirstLine = ignoreFirstLine; + } +} diff --git a/src/main/java/org/apache/jmeter/services/FileServer.java b/src/main/java/org/apache/jmeter/services/FileServer.java deleted file mode 100644 index d51e9a8..0000000 --- a/src/main/java/org/apache/jmeter/services/FileServer.java +++ /dev/null @@ -1,582 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.services; - -import org.apache.commons.io.input.BOMInputStream; -import org.apache.jmeter.gui.JMeterFileFilter; -import org.apache.jmeter.save.CSVSaveService; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JOrphanUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.file.Files; -import java.util.ArrayDeque; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; - -/** - * This class provides thread-safe access to files, and to - * provide some simplifying assumptions about where to find files and how to - * name them. For instance, putting supporting files in the same directory as - * the saved test plan file allows users to refer to the file with just it's - * name - this FileServer class will find the file without a problem. - * Eventually, I want all in-test file access to be done through here, with the - * goal of packaging up entire test plans as a directory structure that can be - * sent via rmi to remote servers (currently, one must make sure the remote - * server has all support files in a relative-same location) and to package up - * test plans to execute on unknown boxes that only have Java installed. - */ -public class FileServer { - - private static final Logger log = LoggerFactory.getLogger(FileServer.class); - - /** - * The default base used for resolving relative files, i.e.
- * {@code System.getProperty("user.dir")} - */ - private static final String DEFAULT_BASE = System.getProperty("user.dir");// $NON-NLS-1$ - - /** Default base prefix: {@value} */ - private static final String BASE_PREFIX_DEFAULT = "~/"; // $NON-NLS-1$ - - private static final String BASE_PREFIX = - JMeterUtils.getPropDefault("jmeter.save.saveservice.base_prefix", // $NON-NLS-1$ - BASE_PREFIX_DEFAULT); - - private File base; - - private final Map files = new HashMap<>(); - - private static final FileServer server = new FileServer(); - - // volatile needed to ensure safe publication - private volatile String scriptName; - - // Cannot be instantiated - private FileServer() { - base = new File(DEFAULT_BASE); - log.info("Default base='{}'", DEFAULT_BASE); - } - - /** - * @return the singleton instance of the server. - */ - public static FileServer getFileServer() { - return server; - } - - /** - * Resets the current base to DEFAULT_BASE. - */ - public synchronized void resetBase() { - checkForOpenFiles(); - base = new File(DEFAULT_BASE); - log.info("Reset base to '{}'", base); - } - - /** - * Sets the current base directory for relative file names from the provided path. - * If the path does not refer to an existing directory, then its parent is used. - * Normally the provided path is a file, so using the parent directory is appropriate. - * - * @param basedir the path to set, or {@code null} if the GUI is being cleared - * @throws IllegalStateException if files are still open - */ - public synchronized void setBasedir(String basedir) { - checkForOpenFiles(); // TODO should this be called if basedir == null? - if (basedir != null) { - File newBase = new File(basedir); - if (!newBase.isDirectory()) { - newBase = newBase.getParentFile(); - } - base = newBase; - log.info("Set new base='{}'", base); - } - } - - /** - * Sets the current base directory for relative file names from the provided script file. - * The parameter is assumed to be the path to a JMX file, so the base directory is derived - * from its parent. - * - * @param scriptPath the path of the script file; must be not be {@code null} - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if scriptPath parameter is null - */ - public synchronized void setBaseForScript(File scriptPath) { - if (scriptPath == null){ - throw new IllegalArgumentException("scriptPath must not be null"); - } - setScriptName(scriptPath.getName()); - // getParentFile() may not work on relative paths - setBase(scriptPath.getAbsoluteFile().getParentFile()); - } - - /** - * Sets the current base directory for relative file names. - * - * @param jmxBase the path of the script file base directory, cannot be null - * @throws IllegalStateException if files are still open - * @throws IllegalArgumentException if {@code basepath} is null - */ - public synchronized void setBase(File jmxBase) { - if (jmxBase == null) { - throw new IllegalArgumentException("jmxBase must not be null"); - } - checkForOpenFiles(); - base = jmxBase; - log.info("Set new base='{}'", base); - } - - /** - * Check if there are entries in use. - *

- * Caller must ensure that access to the files map is single-threaded as - * there is a window between checking the files Map and clearing it. - * - * @throws IllegalStateException if there are any entries still in use - */ - private void checkForOpenFiles() throws IllegalStateException { - if (filesOpen()) { // checks for entries in use - throw new IllegalStateException("Files are still open, cannot change base directory"); - } - files.clear(); // tidy up any unused entries - } - - public synchronized String getBaseDir() { - return base.getAbsolutePath(); - } - - public static String getDefaultBase(){ - return DEFAULT_BASE; - } - - /** - * Calculates the relative path from DEFAULT_BASE to the current base, - * which must be the same as or a child of the default. - * - * @return the relative path, or {@code "."} if the path cannot be determined - */ - public synchronized File getBaseDirRelative() { - // Must first convert to absolute path names to ensure parents are available - File parent = new File(DEFAULT_BASE).getAbsoluteFile(); - File f = base.getAbsoluteFile(); - ArrayDeque l = new ArrayDeque<>(); - while (f != null) { - if (f.equals(parent)){ - if (l.isEmpty()){ - break; - } - File rel = new File(l.pop()); - while(!l.isEmpty()) { - rel = new File(rel, l.pop()); - } - return rel; - } - l.push(f.getName()); - f = f.getParentFile(); - } - return new File("."); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - */ - public void reserveFile(String filename) { - reserveFile(filename,null); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - */ - public void reserveFile(String filename, String charsetName) { - reserveFile(filename, charsetName, filename, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - */ - public void reserveFile(String filename, String charsetName, String alias) { - reserveFile(filename, charsetName, alias, false); - } - - /** - * Creates an association between a filename and a File inputOutputObject, - * and stores it for later use - unless it is already stored. - * - * @param filename - relative (to base) or absolute file name (must not be null or empty) - * @param charsetName - the character set encoding to use for the file (may be null) - * @param alias - the name to be used to access the object (must not be null) - * @param hasHeader true if the file has a header line describing the contents - * @return the header line; may be null - * @throws IllegalArgumentException if header could not be read or filename is null or empty - */ - public synchronized String reserveFile(String filename, String charsetName, String alias, boolean hasHeader) { - if (filename == null || filename.isEmpty()){ - throw new IllegalArgumentException("Filename must not be null or empty"); - } - if (alias == null){ - throw new IllegalArgumentException("Alias must not be null"); - } - FileEntry fileEntry = files.get(alias); - if (fileEntry == null) { - fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName); - if (filename.equals(alias)){ - log.info("Stored: {}", filename); - } else { - log.info("Stored: {} Alias: {}", filename, alias); - } - files.put(alias, fileEntry); - if (hasHeader) { - try { - fileEntry.headerLine = readLine(alias, false); - if (fileEntry.headerLine == null) { - fileEntry.exception = new EOFException("File is empty: " + fileEntry.file); - } - } catch (IOException | IllegalArgumentException e) { - fileEntry.exception = e; - } - } - } - if (hasHeader && fileEntry.headerLine == null) { - throw new IllegalArgumentException("Could not read file header line for file " + filename, - fileEntry.exception); - } - return fileEntry.headerLine; - } - - /** - * Resolves file name into {@link File} instance. - * When filename is not absolute and not found from current working dir, - * it tries to find it under current base directory - * @param filename original file name - * @return {@link File} instance - */ - private File resolveFileFromPath(String filename) { - File f = new File(filename); - if (f.isAbsolute() || f.exists()) { - return f; - } else { - return new File(base, filename); - } - } - - /** - * Get the next line of the named file, recycle by default. - * - * @param filename the filename or alias that was used to reserve the file - * @return String containing the next line in the file - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename) throws IOException { - return readLine(filename, true); - } - - /** - * Get the next line of the named file, first line is name to false - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public String readLine(String filename, boolean recycle) throws IOException { - return readLine(filename, recycle, false); - } - /** - * Get the next line of the named file - * - * @param filename the filename or alias that was used to reserve the file - * @param recycle - should file be restarted at EOF? - * @param ignoreFirstLine - Ignore first line - * @return String containing the next line in the file (null if EOF reached and not recycle) - * @throws IOException when reading of the file fails, or the file was not reserved properly - */ - public synchronized String readLine(String filename, boolean recycle, - boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(filename); - if(fileEntry == null ){ - this.reserveFile(filename); - fileEntry = files.get(filename); - } - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedReader(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedReader reader = (BufferedReader) fileEntry.inputOutputObject; - String line = reader.readLine(); - if (line == null && recycle) { - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine();//NOSONAR - } - line = reader.readLine(); - } - log.debug("Read:{}", line); - return line; - } - throw new IOException("File never reserved: "+filename); - } - - /** - * - * @param alias the file name or alias - * @param recycle whether the file should be re-started on EOF - * @param ignoreFirstLine whether the file contains a file header which will be ignored - * @param delim the delimiter to use for parsing - * @return the parsed line, will be empty if the file is at EOF - * @throws IOException when reading of the aliased file fails, or the file was not reserved properly - */ - public synchronized String[] getParsedLine(String alias, boolean recycle, boolean ignoreFirstLine, char delim) throws IOException { - BufferedReader reader = getReader(alias, recycle, ignoreFirstLine); - return CSVSaveService.csvReadFile(reader, delim); - } - - /** - * Return BufferedReader handling close if EOF reached and recycle is true - * and ignoring first line if ignoreFirstLine is true - * - * @param alias String alias - * @param recycle Recycle at eof - * @param ignoreFirstLine Ignore first line - * @return {@link BufferedReader} - */ - private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException { - FileEntry fileEntry = files.get(alias); - if (fileEntry != null) { - BufferedReader reader; - if (fileEntry.inputOutputObject == null) { - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else if (!(fileEntry.inputOutputObject instanceof Reader)) { - throw new IOException("File " + alias + " already in use"); - } else { - reader = (BufferedReader) fileEntry.inputOutputObject; - if (recycle) { // need to check if we are at EOF already - reader.mark(1); - int peek = reader.read(); - if (peek == -1) { // already at EOF - reader.close(); - reader = createBufferedReader(fileEntry); - fileEntry.inputOutputObject = reader; - if (ignoreFirstLine) { - // read first line and forget - reader.readLine(); //NOSONAR - } - } else { // OK, we still have some data, restore it - reader.reset(); - } - } - } - return reader; - } else { - throw new IOException("File never reserved: "+alias); - } - } - - private BufferedReader createBufferedReader(FileEntry fileEntry) throws IOException { - if (!fileEntry.file.canRead() || !fileEntry.file.isFile()) { - throw new IllegalArgumentException("File "+ fileEntry.file.getName()+ " must exist and be readable"); - } - BOMInputStream fis = new BOMInputStream(Files.newInputStream(fileEntry.file.toPath())); //NOSONAR - InputStreamReader isr = null; - // If file encoding is specified, read using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - isr = new InputStreamReader(fis, charsetName); - } else if (fis.hasBOM()) { - isr = new InputStreamReader(fis, fis.getBOM().getCharsetName()); - } else { - @SuppressWarnings("DefaultCharset") - final InputStreamReader withPlatformEncoding = new InputStreamReader(fis); - isr = withPlatformEncoding; - } - return new BufferedReader(isr); - } - - public synchronized void write(String filename, String value) throws IOException { - FileEntry fileEntry = files.get(filename); - if (fileEntry != null) { - if (fileEntry.inputOutputObject == null) { - fileEntry.inputOutputObject = createBufferedWriter(fileEntry); - } else if (!(fileEntry.inputOutputObject instanceof Writer)) { - throw new IOException("File " + filename + " already in use"); - } - BufferedWriter writer = (BufferedWriter) fileEntry.inputOutputObject; - log.debug("Write:{}", value); - writer.write(value); - } else { - throw new IOException("File never reserved: "+filename); - } - } - - private BufferedWriter createBufferedWriter(FileEntry fileEntry) throws IOException { - OutputStream fos = Files.newOutputStream(fileEntry.file.toPath()); - OutputStreamWriter osw; - // If file encoding is specified, write using that encoding, otherwise use default platform encoding - String charsetName = fileEntry.charSetEncoding; - if(!JOrphanUtils.isBlank(charsetName)) { - osw = new OutputStreamWriter(fos, charsetName); - } else { - @SuppressWarnings("DefaultCharset") - final OutputStreamWriter withPlatformEncoding = new OutputStreamWriter(fos); - osw = withPlatformEncoding; - } - return new BufferedWriter(osw); - } - - public synchronized void closeFiles() throws IOException { - for (Map.Entry me : files.entrySet()) { - closeFile(me.getKey(),me.getValue() ); - } - files.clear(); - } - - /** - * @param name the name or alias of the file to be closed - * @throws IOException when closing of the aliased file fails - */ - public synchronized void closeFile(String name) throws IOException { - FileEntry fileEntry = files.get(name); - closeFile(name, fileEntry); - } - - private void closeFile(String name, FileEntry fileEntry) throws IOException { - if (fileEntry != null && fileEntry.inputOutputObject != null) { - log.info("Close: {}", name); - fileEntry.inputOutputObject.close(); - fileEntry.inputOutputObject = null; - } - } - - boolean filesOpen() { // package access for test code only - return files.values().stream() - .anyMatch(fileEntry -> fileEntry.inputOutputObject != null); - } - - /** - * Method will get a random file in a base directory - *

- * TODO hey, not sure this method belongs here. - * FileServer is for thread safe File access relative to current test's base directory. - * - * @param basedir name of the directory in which the files can be found - * @param extensions array of allowed extensions, if null is given, - * any file be allowed - * @return a random File from the basedir that matches one of - * the extensions - */ - public File getRandomFile(String basedir, String[] extensions) { - File input = null; - if (basedir != null) { - File src = new File(basedir); - File[] lfiles = src.listFiles(new JMeterFileFilter(extensions)); - if (lfiles != null) { - // lfiles cannot be null as it has been checked before - int count = lfiles.length; - input = lfiles[ThreadLocalRandom.current().nextInt(count)]; - } - } - return input; - } - - /** - * Get {@link File} instance for provided file path, - * resolve file location relative to base dir or script dir when needed - * - * @param path original path to file, maybe relative - * @return {@link File} instance - */ - public File getResolvedFile(String path) { - reserveFile(path); - return files.get(path).file; - } - - private static class FileEntry{ - private String headerLine; - private Throwable exception; - private final File file; - private Closeable inputOutputObject; - private final String charSetEncoding; - - FileEntry(File f, Closeable o, String e) { - file = f; - inputOutputObject = o; - charSetEncoding = e; - } - } - - /** - * Resolve a file name that may be relative to the base directory. If the - * name begins with the value of the JMeter property - * "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is - * assumed to be relative to the basename. - * - * @param relativeName - * filename that should be checked for - * jmeter.save.saveservice.base_prefix - * @return the updated filename - */ - public static String resolveBaseRelativeName(String relativeName) { - if (relativeName.startsWith(BASE_PREFIX)){ - String newName = relativeName.substring(BASE_PREFIX.length()); - return new File(getFileServer().getBaseDir(),newName).getAbsolutePath(); - } - return relativeName; - } - - /** - * @return JMX Script name - * @since 2.6 - */ - public String getScriptName() { - return scriptName; - } - - /** - * @param scriptName Script name - * @since 2.6 - */ - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } -} From c3191ced1981c541ac02ff03ac1507e4d6242f87 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 6 May 2021 17:47:02 +0800 Subject: [PATCH 059/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=8A=A5=E5=91=8A=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9C=BA=E6=99=AF=E6=AD=A5=E9=AA=A4=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 1 + .../metersphere/api/module/RequestResult.java | 2 ++ .../io/metersphere/api/module/TestResult.java | 20 ++++++------------- .../apache/jmeter/samplers/SampleResult.java | 16 +++++++++++++++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 262eee3..3b0a798 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -155,6 +155,7 @@ private RequestResult getRequestResult(SampleResult result) { requestResult.setTotalAssertions(result.getAssertionResults().length); requestResult.setSuccess(result.isSuccessful()); requestResult.setError(result.getErrorCount()); + requestResult.setScenario(result.getScenario()); if (result instanceof HTTPSampleResult) { HTTPSampleResult res = (HTTPSampleResult) result; requestResult.setCookies(res.getCookies()); diff --git a/src/main/java/io/metersphere/api/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java index b650fff..d3ac1c2 100644 --- a/src/main/java/io/metersphere/api/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -16,6 +16,8 @@ public class RequestResult { private String method; + private String scenario; + private long requestSize; private long startTime; diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 9e818bc..45b80f4 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -14,6 +14,12 @@ public class TestResult { private String setReportId; + private int scenarioTotal; + + private int scenarioSuccess; + + private int scenarioError; + private String userId; private boolean isDebug; @@ -50,18 +56,4 @@ public void addPassAssertions(int count) { private static final String SEPARATOR = "<->"; - public void addScenario(ScenarioResult result) { - if (result != null && CollectionUtils.isNotEmpty(result.getRequestResults())) { - result.getRequestResults().forEach(item -> { - if (StringUtils.isNotEmpty(item.getName()) && item.getName().indexOf(SEPARATOR) != -1) { - String array[] = item.getName().split(SEPARATOR); - item.setName(array[1] + array[0]); - item.getSubRequestResults().forEach(subItem -> { - subItem.setName(array[0]); - }); - } - }); - scenarios.add(result); - } - } } diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java index ee44117..a10b2a2 100644 --- a/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -163,12 +163,27 @@ public class SampleResult implements Serializable, Cloneable, Searchable { } } + + /** + * 定制自定义添加 ================= + */ private String samplerId; public String getSamplerId() { return this.samplerId; } + // 数据格式 List 多层父级按照同级统计 + private String scenario; + + public String getScenario() { + return this.scenario; + } + + /** + * 定制自定义添加 ================= + */ + private SampleSaveConfiguration saveConfig; private SampleResult parent; @@ -335,6 +350,7 @@ public SampleResult() { Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); if (sampler != null) { this.samplerId = sampler.getPropertyAsString("MS-ID"); + this.scenario = sampler.getPropertyAsString("MS-SCENARIO"); } } From dd8ccc24245a2afcf55b64a83a133fe148dc0fac Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 17 May 2021 16:23:18 +0800 Subject: [PATCH 060/157] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0SSL=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/jmeter/config/KeystoreConfig.java | 174 ++++++++ .../org/apache/jmeter/util/SSLManager.java | 378 ++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 src/main/java/org/apache/jmeter/config/KeystoreConfig.java create mode 100644 src/main/java/org/apache/jmeter/util/SSLManager.java diff --git a/src/main/java/org/apache/jmeter/config/KeystoreConfig.java b/src/main/java/org/apache/jmeter/config/KeystoreConfig.java new file mode 100644 index 0000000..16e4fcf --- /dev/null +++ b/src/main/java/org/apache/jmeter/config/KeystoreConfig.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.config; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.gui.TestElementMetadata; +import org.apache.jmeter.testbeans.TestBean; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jmeter.util.SSLManager; +import org.apache.jorphan.util.JMeterStopTestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Configure Keystore + */ +@TestElementMetadata(labelResource = "displayName") +public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener { + + private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class); + + private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$ + private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$ + + private String startIndex; + private String endIndex; + private String preload; + private String clientCertAliasVarName; + + public KeystoreConfig() { + super(); + } + + @Override + public void testEnded() { + testEnded(null); + } + + @Override + public void testEnded(String host) { + log.info("Destroying Keystore"); + SSLManager.getInstance().destroyKeystore(); + } + + @Override + public void testStarted() { + testStarted(null); + } + + @Override + public void testStarted(String host) { + String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context"); + if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) { + log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used"); + } + int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0); + int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1); + + if (!StringUtils.isEmpty(this.startIndex)) { + try { + startIndexAsInt = Integer.parseInt(this.startIndex); + } catch (NumberFormatException e) { + log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex, + startIndexAsInt, e, e); + } + } + + if (!StringUtils.isEmpty(this.endIndex)) { + try { + endIndexAsInt = Integer.parseInt(this.endIndex); + } catch (NumberFormatException e) { + log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex, + endIndexAsInt, e, e); + } + } + if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) { + throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index"); + } + log.info( + "Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')", + preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName); + // 加载认证文件 + String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH"); + String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD"); + InputStream in = null; + try { + in = new FileInputStream(new File(path)); + } catch (IOException e) { + log.error(e.getMessage()); + } + SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), + startIndexAsInt, + endIndexAsInt, + clientCertAliasVarName, in, password); + } + + /** + * @return the endIndex + */ + public String getEndIndex() { + return endIndex; + } + + /** + * @param endIndex the endIndex to set + */ + public void setEndIndex(String endIndex) { + this.endIndex = endIndex; + } + + /** + * @return the startIndex + */ + public String getStartIndex() { + return startIndex; + } + + /** + * @param startIndex the startIndex to set + */ + public void setStartIndex(String startIndex) { + this.startIndex = startIndex; + } + + /** + * @return the preload + */ + public String getPreload() { + return preload; + } + + /** + * @param preload the preload to set + */ + public void setPreload(String preload) { + this.preload = preload; + } + + /** + * @return the clientCertAliasVarName + */ + public String getClientCertAliasVarName() { + return clientCertAliasVarName; + } + + /** + * @param clientCertAliasVarName the clientCertAliasVarName to set + */ + public void setClientCertAliasVarName(String clientCertAliasVarName) { + this.clientCertAliasVarName = clientCertAliasVarName; + } +} diff --git a/src/main/java/org/apache/jmeter/util/SSLManager.java b/src/main/java/org/apache/jmeter/util/SSLManager.java new file mode 100644 index 0000000..93363ed --- /dev/null +++ b/src/main/java/org/apache/jmeter/util/SSLManager.java @@ -0,0 +1,378 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.util; + +import org.apache.commons.lang3.Validate; +import org.apache.jmeter.gui.GuiPackage; +import org.apache.jmeter.util.keystore.JmeterKeyStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.Locale; + +/** + * The SSLManager handles the KeyStore information for JMeter. Basically, it + * handles all the logic for loading and initializing all the JSSE parameters + * and selecting the alias to authenticate against if it is available. + * SSLManager will try to automatically select the client certificate for you, + * but if it can't make a decision, it will pop open a dialog asking you for + * more information. + *

+ * TODO? - N.B. does not currently allow the selection of a client certificate. + */ +public abstract class SSLManager { + private static final Logger log = LoggerFactory.getLogger(SSLManager.class); + + private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$ + + private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password + + public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$ + + private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$ + + private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ + + /** + * Singleton instance of the manager + */ + private static SSLManager manager; + + private static final boolean IS_SSL_SUPPORTED = true; + + /** + * Cache the KeyStore instance + */ + private JmeterKeyStore keyStore; + + /** + * Cache the TrustStore instance - null if no truststore name was provided + */ + private KeyStore trustStore = null; + // Have we yet tried to load the truststore? + private volatile boolean truststoreLoaded = false; + + /** + * Have the password available + */ + protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD); + + private int keystoreAliasStartIndex; + + private int keystoreAliasEndIndex; + + private String clientCertAliasVarName; + + /** + * Resets the SSLManager so that we can create a new one with a new keystore + */ + public static synchronized void reset() { + SSLManager.manager = null; + } + + public abstract void setContext(HttpURLConnection conn); + + /** + * Default implementation of setting the Provider + * + * @param provider the provider to use + */ + protected void setProvider(Provider provider) { + if (null != provider) { + Security.addProvider(provider); + } + } + + protected synchronized JmeterKeyStore getKeyStore() { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); + } + + try { + + // The string 'NONE' is used for the keystore location when using PKCS11 + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE + if ("NONE".equalsIgnoreCase(fileName)) { + retryLoadKeys(null, false); + log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); + } else { + File initStore = new File(fileName); + if (fileName.length() > 0 && initStore.exists()) { + retryLoadKeys(initStore, true); + if (log.isInfoEnabled()) { + log.info("Total of {} aliases loaded OK from keystore {}", + keyStore.getAliasCount(), fileName); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(null, ""); + } + } + } catch (Exception e) { + log.error("Problem loading keystore: {}", e.getMessage(), e); + } + + if (log.isDebugEnabled()) { + log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); + } + } + + return this.keyStore; + } + + /** + * Opens and initializes the KeyStore. If the password for the KeyStore is + * not set, this method will prompt you to enter it. Unfortunately, there is + * no PasswordEntryField available from JOptionPane. + * + * @return the configured {@link JmeterKeyStore} + */ + protected synchronized JmeterKeyStore getKeyStore(InputStream is, String password) { + if (null == this.keyStore) { + String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided + String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type + fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name + log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); + try { + this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); + log.info("KeyStore created OK"); + } catch (Exception e) { + this.keyStore = null; + throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); + } + + try { + + // The string 'NONE' is used for the keystore location when using PKCS11 + // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE + if ("NONE".equalsIgnoreCase(fileName)) { + retryLoadKeys(null, false); + log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); + } else { + File initStore = new File(fileName); + if (fileName.length() > 0 && initStore.exists()) { + retryLoadKeys(initStore, true); + if (log.isInfoEnabled()) { + log.info("Total of {} aliases loaded OK from keystore {}", + keyStore.getAliasCount(), fileName); + } + } else { + log.warn("Keystore file not found, loading empty keystore"); + this.defaultpw = ""; // Ensure not null + this.keyStore.load(is, password); + } + } + } catch (Exception e) { + log.error("Problem loading keystore: {}", e.getMessage(), e); + } + + if (log.isDebugEnabled()) { + log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); + } + } + + return this.keyStore; + } + + private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException, + CertificateException, IOException, KeyStoreException, UnrecoverableKeyException { + for (int i = 0; i < 3; i++) { + String password = getPassword(); + if (!allowEmptyPassword) { + Validate.notNull(password, "Password for keystore must not be null"); + } + try { + if (initStore == null) { + this.keyStore.load(null, password); + } else { + try (InputStream fis = new FileInputStream(initStore)) { + this.keyStore.load(fis, password); + } + } + return; + } catch (IOException e) { + log.debug("Could not load keystore. Wrong password for keystore?", e); + } + this.defaultpw = null; + } + } + + /* + * The password can be defined as a property; this dialogue is provided to allow it + * to be entered at run-time. + */ + private String getPassword() { + String password = this.defaultpw; + if (null == password) { + final GuiPackage guiInstance = GuiPackage.getInstance(); +// if (guiInstance != null) { +// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]")); +// JLabel passwordLabel = new JLabel("Password: "); +// JPasswordField pwf = new JPasswordField(64); +// pwf.setEchoChar('*'); +// passwordLabel.setLabelFor(pwf); +// panel.add(passwordLabel); +// panel.add(pwf); +// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel, +// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION, +// JOptionPane.PLAIN_MESSAGE); +// if (choice == JOptionPane.OK_OPTION) { +// char[] pwchars = pwf.getPassword(); +// this.defaultpw = new String(pwchars); +// Arrays.fill(pwchars, '*'); +// } +// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw); +// password = this.defaultpw; +// } + } else { + log.warn("No password provided, and no GUI present so cannot prompt"); + } + return password; + } + + /** + * Opens and initializes the TrustStore. + *

+ * There are 3 possibilities: + *

    + *
  • no truststore name provided, in which case the default Java truststore + * should be used
  • + *
  • truststore name is provided, and loads OK
  • + *
  • truststore name is provided, but is not found or does not load OK, in + * which case an empty + * truststore is created
  • + *
+ * If the KeyStore object cannot be created, then this is currently treated the + * same as if no truststore name was provided. + * + * @return {@code null} when Java truststore should be used. + * Otherwise the truststore, which may be empty if the file could not be + * loaded. + */ + protected KeyStore getTrustStore() { + if (!truststoreLoaded) { + + truststoreLoaded = true;// we've tried ... + + String fileName = System.getProperty(SSL_TRUST_STORE); + if (fileName == null) { + return null; + } + log.info("TrustStore Location: {}", fileName); + + try { + this.trustStore = KeyStore.getInstance("JKS"); + log.info("TrustStore created OK, Type: JKS"); + } catch (Exception e) { + this.trustStore = null; + throw new RuntimeException("Problem creating truststore: " + e.getMessage(), e); + } + + try { + File initStore = new File(fileName); + + if (initStore.exists()) { + try (InputStream fis = new FileInputStream(initStore)) { + this.trustStore.load(fis, null); + log.info("Truststore loaded OK from file"); + } + } else { + log.warn("Truststore file not found, loading empty truststore"); + this.trustStore.load(null, null); + } + } catch (Exception e) { + throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e); + } + } + + return this.trustStore; + } + + /** + * Protected Constructor to remove the possibility of directly instantiating + * this object. Create the SSLContext, and wrap all the X509KeyManagers with + * our X509KeyManager so that we can choose our alias. + */ + protected SSLManager() { + } + + /** + * Static accessor for the SSLManager object. The SSLManager is a singleton. + * + * @return the singleton {@link SSLManager} + */ + public static synchronized SSLManager getInstance() { + if (null == SSLManager.manager) { + SSLManager.manager = new JsseSSLManager(null); + } + + return SSLManager.manager; + } + + /** + * Test whether SSL is supported or not. + * + * @return flag whether SSL is supported + */ + public static boolean isSSLSupported() { + return SSLManager.IS_SSL_SUPPORTED; + } + + /** + * Configure Keystore + * + * @param preload flag whether the keystore should be opened within this method, + * or the opening should be delayed + * @param startIndex first index to consider for a key + * @param endIndex last index to consider for a key + * @param clientCertAliasVarName name of the default key, if empty the first key will be used + * as default key + */ + public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName, InputStream is, String password) { + this.keystoreAliasStartIndex = startIndex; + this.keystoreAliasEndIndex = endIndex; + this.clientCertAliasVarName = clientCertAliasVarName; + if (preload) { + keyStore = getKeyStore(is, password); + } + } + + /** + * Destroy Keystore + */ + public synchronized void destroyKeystore() { + keyStore = null; + } +} From 03cc69ac328e55f34f70f42521beb8db5a9b8498 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sat, 22 May 2021 20:54:49 +0800 Subject: [PATCH 061/157] =?UTF-8?q?refactor:=20=E5=AE=B9=E5=99=A8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E5=89=8D=E5=8F=91=E9=80=81=E7=BB=93=E6=9D=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 9 +++++++++ .../metersphere/node/service/KafkaProducer.java | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/io/metersphere/node/service/KafkaProducer.java diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index b3e6668..a64fd22 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import javax.annotation.Resource; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -26,6 +27,8 @@ @Service public class JmeterOperateService { + @Resource + private KafkaProducer kafkaProducer; public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); @@ -65,6 +68,12 @@ public void onComplete() { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } + // 上传结束消息,取保正常结束 + String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); + String reportId = testRequest.getEnv().get("REPORT_ID"); + String[] contents = new String[]{reportId, "none", "0", "Notifying test listeners of end of test"}; + String log = StringUtils.join(contents, " "); + kafkaProducer.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { LogUtil.error("Remove container error: ", e); diff --git a/src/main/java/io/metersphere/node/service/KafkaProducer.java b/src/main/java/io/metersphere/node/service/KafkaProducer.java new file mode 100644 index 0000000..b05ecd3 --- /dev/null +++ b/src/main/java/io/metersphere/node/service/KafkaProducer.java @@ -0,0 +1,17 @@ +package io.metersphere.node.service; + +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service +public class KafkaProducer { + + @Resource + private KafkaTemplate kafkaTemplate; + + public void sendMessage(String topic, String report) { + this.kafkaTemplate.send(topic, report); + } +} From 1a3efa0b71d96fa407646a085e83f25ff58dd65e Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 25 May 2021 17:19:31 +0800 Subject: [PATCH 062/157] =?UTF-8?q?refactor:=20=E5=AE=B9=E5=99=A8=E9=80=80?= =?UTF-8?q?=E5=87=BA=E5=89=8D=E5=8F=91=E9=80=81=E7=BB=93=E6=9D=9F=E6=B6=88?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index a64fd22..fd43d91 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -71,7 +71,7 @@ public void onComplete() { // 上传结束消息,取保正常结束 String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); - String[] contents = new String[]{reportId, "none", "0", "Notifying test listeners of end of test"}; + String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; String log = StringUtils.join(contents, " "); kafkaProducer.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); From af9053b37b59ab0930ce7051fa1491a1deeb26b8 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 May 2021 14:54:50 +0800 Subject: [PATCH 063/157] refactor: spring boot version 2.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94c86e4..6ece8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.6.RELEASE + 2.5.0 io.metersphere From c206a554a48248684d26af32e71addea9f183840 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 31 May 2021 14:54:50 +0800 Subject: [PATCH 064/157] refactor: spring boot version 2.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94c86e4..6ece8b1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.6.RELEASE + 2.5.0 io.metersphere From b18e949bc6783443580c5eec98584ee05c19e14f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 31 May 2021 18:44:56 +0800 Subject: [PATCH 065/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8DJSONPath=E6=96=AD?= =?UTF-8?q?=E8=A8=80=E6=8F=90=E7=A4=BA=E9=94=99=E8=AF=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/jmeter/JMeterService.java | 2 +- .../api/jmeter/utils/FileUtils.java | 2 +- .../api/service/JmeterExecuteService.java | 6 +++--- .../jmeter/assertions/JSONPathAssertion.java | 18 ++++++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 152aedc..8c37fdb 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -72,7 +72,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { testPlan.add(testPlan.getArray()[0], backendListener); } - public void runSerial(RunRequest request, HashTree testPlan) { + public void run(RunRequest request, HashTree testPlan) { try { init(); addBackendListener(testPlan, request); diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 6a47e83..57fcc34 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -10,7 +10,7 @@ public class FileUtils { public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; public static final String JAR_FILE_DIR = "/opt/metersphere/data/node/jar"; - public static void createBodyFiles(MultipartFile[] bodyFiles,String path) { + public static void createFiles(MultipartFile[] bodyFiles, String path) { if (bodyFiles != null && bodyFiles.length > 0) { File testDir = new File(path); if (!testDir.exists()) { diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index f34f576..8c55378 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -54,8 +54,8 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] // 检查KAFKA loadTestProducer.checkKafka(); // 生成附件/JAR文件 - FileUtils.createBodyFiles(bodyFiles, FileUtils.BODY_FILE_DIR); - FileUtils.createBodyFiles(jarFiles, FileUtils.JAR_FILE_DIR); + FileUtils.createFiles(bodyFiles, FileUtils.BODY_FILE_DIR); + FileUtils.createFiles(jarFiles, FileUtils.JAR_FILE_DIR); try { this.loadJar(FileUtils.JAR_FILE_DIR); // 生成执行脚本 @@ -63,7 +63,7 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] Object scriptWrapper = SaveService.loadElement(inputSource); HashTree testPlan = JMeterService.getHashTree(scriptWrapper); // 开始执行 - jMeterService.runSerial(request, testPlan); + jMeterService.run(request, testPlan); } catch (Exception e) { LogUtil.error(e.getMessage()); return e.getMessage(); diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index 4b832e4..c6cdcc0 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -109,13 +109,27 @@ private void doAssert(String jsonString) { if (this.isExpectNull()) { throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); } else { - String msg; + String msg = ""; if (this.isUseRegex()) { msg = "Value expected to match regexp '%s', but it did not match: '%s'"; + } else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) { + switch (getOption()) { + case "CONTAINS": + msg = "Value contains to be '%s', but found '%s'"; + break; + case "NOT_CONTAINS": + msg = "Value not contains to be '%s', but found '%s'"; + break; + case "EQUALS": + msg = "Value equals to be '%s', but found '%s'"; + break; + case "NOT_EQUALS": + msg = "Value not equals to be '%s', but found '%s'"; + break; + } } else { msg = "Value expected to be '%s', but found '%s'"; } - throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value))); } } From dfb58eb98d598b8213f0b5394af76e0944f2e738 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 2 Jun 2021 16:04:22 +0800 Subject: [PATCH 066/157] =?UTF-8?q?refactor:=20=E5=A4=84=E7=90=86oom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index fd43d91..18bad59 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -59,6 +59,10 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, DockerClientService.startContainer(dockerClient, containerId); LogUtil.info("Container create started containerId: " + containerId); + + String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); + String reportId = testRequest.getEnv().get("REPORT_ID"); + dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback() { @Override @@ -68,9 +72,7 @@ public void onComplete() { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { DockerClientService.removeContainer(dockerClient, containerId); } - // 上传结束消息,取保正常结束 - String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); - String reportId = testRequest.getEnv().get("REPORT_ID"); + // 上传结束消息 String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; String log = StringUtils.join(contents, " "); kafkaProducer.sendMessage(topic, log); @@ -90,7 +92,15 @@ public void onComplete() { .exec(new InvocationBuilder.AsyncResultCallback() { @Override public void onNext(Frame item) { - LogUtil.info(new String(item.getPayload()).trim()); + String log = new String(item.getPayload()).trim(); + String oomMessage = "There is insufficient memory for the Java Runtime Environment to continue."; + if (StringUtils.contains(log, oomMessage)) { + // oom 退出 + String[] contents = new String[]{reportId, "none", "0", oomMessage}; + String message = StringUtils.join(contents, " "); + kafkaProducer.sendMessage(topic, message); + } + LogUtil.info(log); } }); } From fd6988d538aea4582681609437364230c0f25133 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 6 Jun 2021 17:02:48 +0800 Subject: [PATCH 067/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6ece8b1..e845d8f 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ com.thoughtworks.xstream xstream - 1.4.16 + 1.4.17 @@ -318,7 +318,6 @@ org.apache.httpcomponents httpclient - 4.5.6 From 3fe4d0e0b8c2c9fd734b5cf7ca74431dd08cf312 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 6 Jun 2021 17:02:48 +0800 Subject: [PATCH 068/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6ece8b1..e845d8f 100644 --- a/pom.xml +++ b/pom.xml @@ -130,7 +130,7 @@ com.thoughtworks.xstream xstream - 1.4.16 + 1.4.17 @@ -318,7 +318,6 @@ org.apache.httpcomponents httpclient - 4.5.6 From 429e22b493887aca3b3d7caa3ce84146b2195091 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 7 Jun 2021 10:55:06 +0800 Subject: [PATCH 069/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E4=BC=98=E5=8C=96cons?= =?UTF-8?q?ole=E6=95=B0=E6=8D=AE=E5=AD=98=E5=82=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/module/TestResult.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 45b80f4..8780d81 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -36,6 +36,8 @@ public class TestResult { private int passAssertions = 0; + private String console; + private List scenarios = new ArrayList<>(); public void addError(int count) { From ba729566eec164218518241bba127c9d83a9680b Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 11 Jun 2021 15:21:01 +0800 Subject: [PATCH 070/157] =?UTF-8?q?refactor:=20=E5=88=86=E5=B1=82=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a99da6c..ab75490 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,28 @@ +FROM openjdk:8-jdk-alpine as build +WORKDIR /workspace/app + +COPY target/node-controller-1.8.jar . + +RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) + FROM metersphere/fabric8-java-alpine-openjdk8-jre MAINTAINER FIT2CLOUD ARG MS_VERSION=dev +ARG DEPENDENCY=/workspace/app/dependency -RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter/lib/junit - -ADD target/node-controller-1.8.jar /opt/apps +COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF +COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app +RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.8.jar - +ENV JAVA_CLASSPATH=/app:/app/lib/* +ENV JAVA_MAIN_CLASS=io.metersphere.Application ENV AB_OFF=true - ENV MS_VERSION=${MS_VERSION} - ENV JAVA_OPTIONS=-Dfile.encoding=utf-8 CMD ["/deployments/run-java.sh"] From c67eb3dade374c3dcc3e568996e1c8ba5893927c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 11 Jun 2021 18:09:08 +0800 Subject: [PATCH 071/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8E=A5=E6=94=B6=E6=96=B9=E5=BC=8F=EF=BC=8C=E7=94=B1=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E6=96=B9=E6=94=B9=E4=B8=BA=E6=8B=89=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 6 + .../api/controller/request/RunRequest.java | 1 + .../api/service/JmeterExecuteService.java | 34 ++++ .../io/metersphere/api/service/ZipSpider.java | 187 ++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 src/main/java/io/metersphere/api/service/ZipSpider.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 7d0a7e4..d5c283d 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -25,6 +25,12 @@ public String apiRun(@RequestParam(value = "files") MultipartFile[] bodyFiles, @ return jmeterExecuteService.run(runRequest, bodyFiles, jarFiles); } + @PostMapping(value = "/api/start") + public String apiStartRun(@RequestBody RunRequest runRequest) { + LogUtil.info("接收到测试请求 start "); + return jmeterExecuteService.runStart(runRequest); + } + @GetMapping("/status") public String getStatus() { return "OK"; diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index f0e6408..5e97072 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -6,6 +6,7 @@ @Data public class RunRequest { private String testId; + private String url; private String userId; private boolean isDebug; private String runMode; diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8c55378..6e8ebd0 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -2,6 +2,7 @@ import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -14,8 +15,10 @@ import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URL; @Service public class JmeterExecuteService { @@ -70,4 +73,35 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] } return "SUCCESS"; } + + public String runStart(RunRequest runRequest) { + // 检查KAFKA + loadTestProducer.checkKafka(); + try { + // 生成附件/JAR文件 + URL urlObject = new URL(runRequest.getUrl()); + String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + + File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + // 开始执行 + jMeterService.run(runRequest, testPlan); + } catch (Exception e) { + LogUtil.error(e.getMessage()); + return e.getMessage(); + } + return "SUCCESS"; + } + } diff --git a/src/main/java/io/metersphere/api/service/ZipSpider.java b/src/main/java/io/metersphere/api/service/ZipSpider.java new file mode 100644 index 0000000..baa9c8a --- /dev/null +++ b/src/main/java/io/metersphere/api/service/ZipSpider.java @@ -0,0 +1,187 @@ +package io.metersphere.api.service; + +import java.io.*; +import java.net.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import io.metersphere.node.util.LogUtil; + +public class ZipSpider { + + //根据网址返回网页源代码 + public static String getHtmlFromUrl(String url, String encoding) { + StringBuffer html = new StringBuffer(); + InputStreamReader isr = null; + BufferedReader buf = null; + String str = null; + try { + URL urlObj = new URL(url); + URLConnection con = urlObj.openConnection(); + isr = new InputStreamReader(con.getInputStream(), encoding); + buf = new BufferedReader(isr); + while ((str = buf.readLine()) != null) { + html.append(str + "\n"); + } + //sop(html.toString()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (isr != null) { + try { + buf.close(); + isr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return html.toString(); + } + + //根据网址下载网络文件到硬盘,包括图片,Gif图,以及压缩包 + public static void download(String url, String path) { + File file = null; + FileOutputStream fos = null; + String downloadName = url.substring(url.lastIndexOf("/") + 1); + HttpURLConnection httpCon = null; + URLConnection con = null; + URL urlObj = null; + InputStream in = null; + byte[] size = new byte[1024]; + int num = 0; + try { + file = new File(path + downloadName); + fos = new FileOutputStream(file); + if (url.startsWith("http")) { + urlObj = new URL(url); + con = urlObj.openConnection(); + httpCon = (HttpURLConnection) con; + in = httpCon.getInputStream(); + while ((num = in.read(size)) != -1) { + for (int i = 0; i < num; i++) + fos.write(size[i]); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + in.close(); + fos.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + //解压本地文件至目的文件路径 + public static void unzip(String fromFile, String toFile) { + try { + ZipInputStream Zin = new ZipInputStream(new FileInputStream(fromFile)); + BufferedInputStream Bin = new BufferedInputStream(Zin); + String Parent = toFile; + File Fout = null; + ZipEntry entry; + try { + while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) { + Fout = new File(Parent, entry.getName()); + if (!Fout.exists()) { + (new File(Fout.getParent())).mkdirs(); + } + FileOutputStream out = new FileOutputStream(Fout); + BufferedOutputStream Bout = new BufferedOutputStream(out); + int b; + while ((b = Bin.read()) != -1) { + Bout.write(b); + } + Bout.close(); + out.close(); + LogUtil.info(Fout + "解压成功"); + } + Bin.close(); + Zin.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + //从总目录下解压文件里所有的压缩包至目的文件路径 + public static void unzipFromLoc(String filePath) throws Exception { + File file = new File(filePath); + File[] list = file.listFiles(); + String from = ""; + String to = "E:\\myDownload\\unzipFileFromWeb\\"; + for (File f : list) { + boolean bool = f.isFile(); + if (bool) { + from = f.getAbsolutePath(); + from = from.replace("\\", "\\\\"); + sop(from); + unzip(from, to); + } + } + } + + public static void sop(Object obj) { + LogUtil.info(obj); + } + + public static void seperate(char c) { + for (int x = 0; x < 100; x++) { + System.out.print(c); + } + sop(""); + } + + @SuppressWarnings("finally") + public static File downloadFile(String urlPath, String downloadDir) { + File file = null; + try { + URL url = new URL(urlPath); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 + httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.connect();// 打开连接 + + BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 + file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + OutputStream out = new FileOutputStream(file); + int size = 0; + + byte[] b = new byte[2048]; + //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 + while ((size = bin.read(b)) != -1) { + out.write(b, 0, size); + } + // 关闭资源 + bin.close(); + out.close(); + LogUtil.info("文件下载成功!"); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + LogUtil.info("文件下载失败!"); + } finally { + return file; + } + } +} From 59024f0fa333834a971d2a5642fd9686a47b07eb Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sun, 13 Jun 2021 13:40:47 +0800 Subject: [PATCH 072/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ab75490..d9c0345 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM openjdk:8-jdk-alpine as build WORKDIR /workspace/app -COPY target/node-controller-1.8.jar . +COPY target/*.jar . RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) From 84cd44a91f1ce5f86d86ea92762118ee3f829f2f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 15 Jun 2021 16:21:44 +0800 Subject: [PATCH 073/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E4=B8=8B=E8=BD=BD=E6=89=A7=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/request/RunRequest.java | 2 + .../api/jmeter/APIBackendListenerClient.java | 65 ++++++++++--------- .../metersphere/api/jmeter/JMeterService.java | 13 +++- .../io/metersphere/api/module/TestResult.java | 2 + 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index 5e97072..180ec4f 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -6,6 +6,8 @@ @Data public class RunRequest { private String testId; + // api / case 或有这个属性值 + private String reportId; private String url; private String userId; private boolean isDebug; diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 3b0a798..f9a8957 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -96,41 +96,46 @@ public void teardownTest(BackendListenerContext context) throws Exception { final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 - String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); - String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); - String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); - ScenarioResult scenarioResult; - if (!scenarios.containsKey(scenarioId)) { - scenarioResult = new ScenarioResult(); - try { - scenarioResult.setId(Integer.parseInt(scenarioId)); - } catch (Exception e) { - scenarioResult.setId(0); - LogUtil.error("场景ID转换异常: " + e.getMessage()); + if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { + String evnStr = result.getResponseDataAsString(); + testResult.setRunningDebugSampler(result.getResponseDataAsString()); + } else { + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); } - scenarioResult.setName(scenarioName); - scenarios.put(scenarioId, scenarioResult); - } else { - scenarioResult = scenarios.get(scenarioId); - } - if (result.isSuccessful()) { - scenarioResult.addSuccess(); - testResult.addSuccess(); - } else { - scenarioResult.addError(result.getErrorCount()); - testResult.addError(result.getErrorCount()); - } + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } - RequestResult requestResult = getRequestResult(result); - scenarioResult.getRequestResults().add(requestResult); - scenarioResult.addResponseTime(result.getTime()); + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); - testResult.addPassAssertions(requestResult.getPassAssertions()); - testResult.addTotalAssertions(requestResult.getTotalAssertions()); + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); - scenarioResult.addPassAssertions(requestResult.getPassAssertions()); - scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + } }); testResult.getScenarios().addAll(scenarios.values()); testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 8c37fdb..1a3baf6 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -9,6 +9,7 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; +import org.python.antlr.ast.arguments; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -56,12 +57,20 @@ public static HashTree getHashTree(Object scriptWrapper) throws Exception { private void addBackendListener(HashTree testPlan, RunRequest request) { BackendListener backendListener = new BackendListener(); - backendListener.setName(request.getTestId()); + if (StringUtils.isNotEmpty(request.getReportId())) { + backendListener.setName(request.getReportId()); + } else { + backendListener.setName(request.getTestId()); + } Arguments arguments = new Arguments(); if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + if (StringUtils.isNotEmpty(request.getReportId())) { + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); + } else { + arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); + } if (StringUtils.isNotBlank(request.getRunMode())) { arguments.addArgument("runMode", request.getRunMode()); } diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java index 8780d81..c0d48e4 100644 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ b/src/main/java/io/metersphere/api/module/TestResult.java @@ -38,6 +38,8 @@ public class TestResult { private String console; + private String runningDebugSampler; + private List scenarios = new ArrayList<>(); public void addError(int count) { From 4453a1633fb056d69eccea6fe3adbdb04c36af3c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 16 Jun 2021 10:44:31 +0800 Subject: [PATCH 074/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E4=B8=8B=E8=BD=BD=E6=89=A7=E8=A1=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/AppStartListener.java | 2 +- .../io/metersphere/api/controller/JmeterExecuteController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/AppStartListener.java b/src/main/java/io/metersphere/api/config/AppStartListener.java index fd4b171..6d2e6db 100644 --- a/src/main/java/io/metersphere/api/config/AppStartListener.java +++ b/src/main/java/io/metersphere/api/config/AppStartListener.java @@ -26,7 +26,7 @@ public class AppStartListener implements ApplicationListener Date: Thu, 17 Jun 2021 14:17:05 +0800 Subject: [PATCH 075/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=94=AF=E6=8C=81K8s=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/jmeter/JMeterService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 1a3baf6..1eb36c0 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,6 +1,7 @@ package io.metersphere.api.jmeter; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -66,7 +67,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - if (StringUtils.isNotEmpty(request.getReportId())) { + if (StringUtils.isNotEmpty(request.getReportId()) && ApiRunMode.API_PLAN.name().equals(request.getRunMode())) { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); } else { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); From 0cb5544af7bd25582076efae71cad5f122d5dd44 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 17 Jun 2021 15:45:36 +0800 Subject: [PATCH 076/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E7=BB=9F=E4=B8=80topic=20=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 2 +- .../java/io/metersphere/api/service/LoadTestProducer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 80ce8a6..940317f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -7,7 +7,7 @@ @Configuration public class KafkaConfig { - private final String testTopic = "ms-api-exec-topic"; + public final static String testTopic = "ms-api-exec-topic"; @Bean public NewTopic apiExecTopic() { diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java index 0b323c3..00ec653 100644 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.config.KafkaProperties; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.MSException; @@ -15,7 +16,6 @@ @Service public class LoadTestProducer { - private static final String topic = "ms-api-exec-topic"; public void checkKafka() { KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); @@ -48,6 +48,6 @@ public void checkKafka() { private KafkaTemplate kafkaTemplate; public void sendMessage(String report) { - this.kafkaTemplate.send(topic, report); + this.kafkaTemplate.send(KafkaConfig.testTopic, report); } } From 17ecb1ec0e3e52cd4b28930bfe022ebdcedad233 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sat, 19 Jun 2021 11:18:29 +0800 Subject: [PATCH 077/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d9c0345..897d08e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN mkdir -p dependency && (cd dependency; jar -xf ../*.jar) FROM metersphere/fabric8-java-alpine-openjdk8-jre -MAINTAINER FIT2CLOUD +LABEL maintainer="FIT2CLOUD " ARG MS_VERSION=dev ARG DEPENDENCY=/workspace/app/dependency @@ -18,6 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ +COPY backend/target/classes/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From dc9118d6ae3bc27dec1ff24e6879d996ffcab6e8 Mon Sep 17 00:00:00 2001 From: liuruibin Date: Sat, 19 Jun 2021 11:19:17 +0800 Subject: [PATCH 078/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 897d08e..a6ba570 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app RUN mkdir -p /opt/jmeter/lib/junit COPY target/classes/jmeter/ /opt/jmeter/ -COPY backend/target/classes/jmeter/ /app/jmeter/ +COPY target/classes/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 240e904ab09f51b2b77ef7362c2f3e01b88b9227 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 20 Jun 2021 09:44:13 +0800 Subject: [PATCH 079/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pom.xml b/pom.xml index e845d8f..627a933 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ 1.1.3 2.7.8 3.0.8 + 1.2 @@ -377,6 +378,12 @@ ApacheJMeter_native ${jmeter.version} + + io.metersphere + metersphere-jmeter-functions + ${metersphere-jmeter-functions.version} + + com.jayway.jsonpath @@ -431,6 +438,15 @@ src/main/resources/jmeter/lib/ext ApacheJMeter_functions.jar + + io.metersphere + metersphere-jmeter-functions + ${metersphere-jmeter-functions.version} + jar + true + src/main/resources/jmeter/lib/ext + metersphere-jmeter-functions.jar + org.python jython-standalone From 7e7b6a7229378d6255292449bcdd049db91001ba Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sat, 26 Jun 2021 18:27:19 +0800 Subject: [PATCH 080/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9pom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 627a933..dc08624 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 1.1.3 2.7.8 3.0.8 - 1.2 + 1.4 From 02f52108f35a83e91b1b91fce00d370589bb18c7 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 30 Jun 2021 18:12:39 +0800 Subject: [PATCH 081/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a6ba570..cd15d07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,10 @@ ARG DEPENDENCY=/workspace/app/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app +COPY target/classes/jmeter/ /opt/jmeter/ RUN mkdir -p /opt/jmeter/lib/junit -COPY target/classes/jmeter/ /opt/jmeter/ -COPY target/classes/jmeter/ /app/jmeter/ +RUN cp -rf /opt/jmeter/ /app/jmeter/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 7120fda7cd5405007e5a64a002b9486e09f64a2c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 30 Jun 2021 18:34:31 +0800 Subject: [PATCH 082/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cd15d07..74c5d55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app COPY target/classes/jmeter/ /opt/jmeter/ RUN mkdir -p /opt/jmeter/lib/junit -RUN cp -rf /opt/jmeter/ /app/jmeter/ +RUN cp -rf /opt/jmeter /app/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From 06e2495a15e57553814e422be8cb07e3736b1e97 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:26:34 +0800 Subject: [PATCH 083/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74c5d55..6b0280c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,10 +15,9 @@ ARG DEPENDENCY=/workspace/app/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -COPY target/classes/jmeter/ /opt/jmeter/ +RUN cp -rf /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit -RUN cp -rf /opt/jmeter /app/ ENV JAVA_CLASSPATH=/app:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application From a007981d6327ec85cb830a281fed141c3d672a7c Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:41:42 +0800 Subject: [PATCH 084/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6b0280c..2a8637e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -RUN cp -rf /app/jmeter /opt/ +RUN mv -rf /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit ENV JAVA_CLASSPATH=/app:/app/lib/* From d7711359630dd8f9696ed3db964520f02286030d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Thu, 1 Jul 2021 12:44:20 +0800 Subject: [PATCH 085/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9dockerfil?= =?UTF-8?q?e,=20=E9=81=BF=E5=85=8D=E5=A4=8D=E5=88=B6=E5=90=8C=E6=A0=B7?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2a8637e..4abaf45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -RUN mv -rf /app/jmeter /opt/ +RUN mv /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit ENV JAVA_CLASSPATH=/app:/app/lib/* From bbd086f5e5db606e2c5c84b5136a47a0d0922f3d Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 13 Jul 2021 15:41:56 +0800 Subject: [PATCH 086/157] =?UTF-8?q?build:=20=E6=8C=87=E5=AE=9A=20kafka=20p?= =?UTF-8?q?roducer=20=E7=9A=84=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70b2f6a..f241ab2 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,7 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id +spring.kafka.producer.properties.security.protocol=PLAINTEXT kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= From 9a73386037ac8a2ff9ad1414b962a4755ddc1937 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Tue, 13 Jul 2021 16:58:13 +0800 Subject: [PATCH 087/157] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=20check=20k?= =?UTF-8?q?afka=20=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/service/LoadTestProducer.java | 16 +++++----------- .../node/service/JmeterOperateService.java | 15 +++++---------- src/main/resources/application.properties | 1 - 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java index 00ec653..42948c6 100644 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ b/src/main/java/io/metersphere/api/service/LoadTestProducer.java @@ -10,7 +10,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; @@ -26,16 +25,11 @@ public void checkKafka() { //1,建立tcp String ip = ipAndPort[0]; int port = Integer.parseInt(ipAndPort[1]); - Socket soc = new Socket(); - // 1s timeout - soc.connect(new InetSocketAddress(ip, port), 1000); - //2.输入内容 - String content = "1010"; - byte[] bs = content.getBytes(); - OutputStream os = soc.getOutputStream(); - os.write(bs); - //3.关闭 - soc.close(); + try ( + Socket soc = new Socket() + ) { + soc.connect(new InetSocketAddress(ip, port), 1000); + } } } catch (Exception e) { LogUtil.error(e); diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 18bad59..3487dfa 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -15,7 +15,6 @@ import org.springframework.util.CollectionUtils; import javax.annotation.Resource; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; @@ -124,15 +123,11 @@ private void checkKafka(String bootstrapServers) { //1,建立tcp String ip = ipAndPort[0]; int port = Integer.parseInt(ipAndPort[1]); - Socket soc = new Socket(); - soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout - //2.输入内容 - String content = "1010"; - byte[] bs = content.getBytes(); - OutputStream os = soc.getOutputStream(); - os.write(bs); - //3.关闭 - soc.close(); + try ( + Socket soc = new Socket() + ) { + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + } } } catch (Exception e) { LogUtil.error(e); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f241ab2..70b2f6a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,7 +5,6 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id -spring.kafka.producer.properties.security.protocol=PLAINTEXT kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= From 2b28108867bb6c23caf7e73d42199378abaa5280 Mon Sep 17 00:00:00 2001 From: Cong Date: Wed, 14 Jul 2021 21:25:06 +0800 Subject: [PATCH 088/157] =?UTF-8?q?=E4=B8=8D=E7=AD=89=E4=BA=8E=E6=96=AD?= =?UTF-8?q?=E8=A8=80bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 包含情况断言失败 (cherry picked from commit c534bdba59ca1ef3392780b518d6d45d2067aa14) --- .../java/org/apache/jmeter/assertions/JSONPathAssertion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index c6cdcc0..a20750a 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -172,7 +172,7 @@ private boolean isEquals(Object subj) { refFlag = str.equals(getExpectedValue()); break; case "NOT_EQUALS": - refFlag = !str.contains(getExpectedValue()); + refFlag = !str.equals(getExpectedValue()); break; } return refFlag; From 0773b068bf16a5818737e3f4cdf55341e59843bd Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 19 Jul 2021 13:17:26 +0800 Subject: [PATCH 089/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=90=8C=E6=AD=A5=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E8=8E=B7=E5=8F=96=E5=94=AF=E4=B8=80?= =?UTF-8?q?=E6=AD=A5=E9=AA=A4=E6=A0=87=E8=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 1 + .../metersphere/api/module/RequestResult.java | 2 ++ .../api/service/JmeterExecuteService.java | 17 ++++++++++------- .../apache/jmeter/samplers/SampleResult.java | 13 +++++++++---- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index f9a8957..f09e88e 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -149,6 +149,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { private RequestResult getRequestResult(SampleResult result) { RequestResult requestResult = new RequestResult(); requestResult.setId(result.getSamplerId()); + requestResult.setResourceId(result.getResourceId()); requestResult.setName(result.getSampleLabel()); requestResult.setUrl(result.getUrlAsString()); requestResult.setMethod(getMethod(result)); diff --git a/src/main/java/io/metersphere/api/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java index d3ac1c2..3732394 100644 --- a/src/main/java/io/metersphere/api/module/RequestResult.java +++ b/src/main/java/io/metersphere/api/module/RequestResult.java @@ -10,6 +10,8 @@ public class RequestResult { // 请求ID private String id; + private String resourceId; + private String name; private String url; diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 6e8ebd0..3851a3a 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -2,7 +2,6 @@ import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -91,12 +90,16 @@ public String runStart(RunRequest runRequest) { LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); - ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); - // 生成执行脚本 - HashTree testPlan = SaveService.loadTree(jmxFile); - // 开始执行 - jMeterService.run(runRequest, testPlan); + if (bodyFile != null) { + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + // 开始执行 + jMeterService.run(runRequest, testPlan); + } else { + MSException.throwException("未找到执行的JMX文件"); + } } catch (Exception e) { LogUtil.error(e.getMessage()); return e.getMessage(); diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java index a10b2a2..529ab10 100644 --- a/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ b/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -97,7 +97,7 @@ public class SampleResult implements Serializable, Cloneable, Searchable { * @see #getDataType * @see #setDataType(String) */ - public static final String BINARY = "jmeter.bin"; // $NON-NLS-1$ + public static final String BINARY = "bin"; // $NON-NLS-1$ private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); @@ -163,16 +163,21 @@ public class SampleResult implements Serializable, Cloneable, Searchable { } } - /** * 定制自定义添加 ================= */ private String samplerId; + private String resourceId; + public String getSamplerId() { return this.samplerId; } + public String getResourceId() { + return this.resourceId; + } + // 数据格式 List 多层父级按照同级统计 private String scenario; @@ -183,7 +188,6 @@ public String getScenario() { /** * 定制自定义添加 ================= */ - private SampleSaveConfiguration saveConfig; private SampleResult parent; @@ -247,7 +251,7 @@ public String getScenario() { /** * Files that this sample has been saved in. - * In Non GUI mode and when best utils is used, size never exceeds 1, + * In Non GUI mode and when best config is used, size never exceeds 1, * but as a compromise set it to 2 */ private final Set files = ConcurrentHashMap.newKeySet(2); @@ -350,6 +354,7 @@ public SampleResult() { Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); if (sampler != null) { this.samplerId = sampler.getPropertyAsString("MS-ID"); + this.resourceId = sampler.getPropertyAsString("MS-RESOURCE-ID"); this.scenario = sampler.getPropertyAsString("MS-SCENARIO"); } From e4a55221385a1601fe5572aded4678681f0e9409 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Sun, 1 Aug 2021 22:06:49 +0800 Subject: [PATCH 090/157] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 53 ++++++ .../metersphere/node/util/CompressUtils.java | 153 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 src/main/java/io/metersphere/node/util/CompressUtils.java diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 3487dfa..1e1dcb3 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -8,13 +8,19 @@ import com.github.dockerjava.api.model.Image; import com.github.dockerjava.core.InvocationBuilder; import io.metersphere.node.controller.request.TestRequest; +import io.metersphere.node.util.CompressUtils; import io.metersphere.node.util.DockerClientService; import io.metersphere.node.util.LogUtil; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Arrays; @@ -23,6 +29,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.zip.ZipOutputStream; @Service public class JmeterOperateService { @@ -61,6 +68,7 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); + String resourceIndex = testRequest.getEnv().get("RESOURCE_INDEX"); dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback() { @@ -69,6 +77,9 @@ public void onComplete() { // 清理文件夹 try { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { + + copyTestResources(dockerClient, containerId, reportId, resourceIndex); + DockerClientService.removeContainer(dockerClient, containerId); } // 上传结束消息 @@ -104,6 +115,48 @@ public void onNext(Frame item) { }); } + private void copyTestResources(DockerClient dockerClient, String containerId, String reportId, String resourceIndex) throws IOException { + InputStream testIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/test/") + .exec(); + testIn.available(); + String pathname = reportId + "_" + resourceIndex; + File dir = new File(pathname); + + FileUtils.forceMkdir(dir); + File testDir = new File(pathname + "/test.tar"); + FileUtils.copyInputStreamToFile(testIn, testDir); + + + InputStream jtlIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + ".jtl") + .exec(); + jtlIn.available(); + File jtl = new File(pathname + "/report.jtl.tar"); + FileUtils.copyInputStreamToFile(jtlIn, jtl); + + InputStream logIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/jmeter.log") + .exec(); + logIn.available(); + File log = new File(pathname + "/jmeter.log.tar"); + FileUtils.copyInputStreamToFile(logIn, log); + + InputStream generateLogIn = dockerClient + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/generate-report.log") + .exec(); + generateLogIn.available(); + File generateLog = new File(pathname + "/generate-report.log.tar"); + FileUtils.copyInputStreamToFile(generateLogIn, generateLog); + + try ( + ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(reportId + "_" + resourceIndex + ".zip")) + ) { + CompressUtils.zipDirectory(dir, zipOutputStream, ""); + FileUtils.forceDelete(dir); + } + } + private void checkContainerExists(DockerClient dockerClient, String testId) { List list = dockerClient.listContainersCmd() .withShowAll(true) diff --git a/src/main/java/io/metersphere/node/util/CompressUtils.java b/src/main/java/io/metersphere/node/util/CompressUtils.java new file mode 100644 index 0000000..04bcd95 --- /dev/null +++ b/src/main/java/io/metersphere/node/util/CompressUtils.java @@ -0,0 +1,153 @@ +package io.metersphere.node.util; + +import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class CompressUtils { + + /*** + * 压缩Zip + * + * @param data + * @return + */ + public static Object zip(Object data) { + if (!(data instanceof byte[])) { + return data; + } + + byte[] temp = (byte[]) data; + byte[] b = (byte[]) data; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(bos); + ZipEntry entry = new ZipEntry("zip"); + entry.setSize(temp.length); + zip.putNextEntry(entry); + zip.write(temp); + zip.closeEntry(); + zip.close(); + b = bos.toByteArray(); + bos.close(); + } catch (Exception ex) { + LogUtil.error(ex); + } + + return b; + } + + /*** + * 解压Zip + * + * @param data + * @return + */ + public static Object unzip(Object data) { + if (!(data instanceof byte[])) { + return data; + } + byte[] temp = (byte[]) data; + byte[] b = (byte[]) data; + try { + ByteArrayInputStream bis = new ByteArrayInputStream(temp); + ZipInputStream zip = new ZipInputStream(bis); + while (zip.getNextEntry() != null) { + byte[] buf = new byte[1024]; + int num; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((num = zip.read(buf, 0, buf.length)) != -1) { + baos.write(buf, 0, num); + } + b = baos.toByteArray(); + baos.flush(); + baos.close(); + } + zip.close(); + bis.close(); + } catch (Exception ex) { + LogUtil.error(ex); + } + return b; + } + + + public static void zipFiles(File srcfile, File targetFile) { + + ZipOutputStream out = null; + try { + out = new ZipOutputStream(new FileOutputStream(targetFile)); + + if (srcfile.isFile()) { + zipFile(srcfile, out, ""); + } else { + File[] list = srcfile.listFiles(); + for (int i = 0; i < list.length; i++) { + compress(list[i], out, ""); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void compress(File file, ZipOutputStream out, String basedir) { + /* 判断是目录还是文件 */ + if (file.isDirectory()) { + zipDirectory(file, out, basedir); + } else { + zipFile(file, out, basedir); + } + } + + public static void zipDirectory(File dir, ZipOutputStream out, String basedir) { + if (!dir.exists()) + return; + + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + /* 递归 */ + compress(files[i], out, basedir + dir.getName() + "/"); + } + } + + + public static void zipFile(File srcfile, ZipOutputStream out, String basedir) { + if (!srcfile.exists()) + return; + + byte[] buf = new byte[8192]; + FileInputStream in = null; + + try { + int len; + in = new FileInputStream(srcfile); + out.putNextEntry(new ZipEntry(basedir + srcfile.getName())); + + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (out != null) + out.closeEntry(); + if (in != null) + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + +} From ae245f60855ee4f37a4abac16431fd0617160ad6 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 4 Aug 2021 12:17:31 +0800 Subject: [PATCH 091/157] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=E3=80=90=E3=80=90github#5188=E3=80=91[FEATU?= =?UTF-8?q?RE]JSONpath=20=E6=96=AD=E8=A8=80=E6=8F=90=E4=BE=9B=E5=A4=A7?= =?UTF-8?q?=E4=BA=8E=E5=92=8C=E5=B0=8F=E4=BA=8E=E5=8A=9F=E8=83=BD=20#5188?= =?UTF-8?q?=E3=80=91=20https://www.tapd.cn/55049933/prong/stories/view/115?= =?UTF-8?q?5049933001002278?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jmeter/assertions/JSONPathAssertion.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index a20750a..ef459e7 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -5,10 +5,10 @@ package org.apache.jmeter.assertions; +import com.google.gson.Gson; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.AbstractTestElement; @@ -126,6 +126,12 @@ private void doAssert(String jsonString) { case "NOT_EQUALS": msg = "Value not equals to be '%s', but found '%s'"; break; + case "GT": + msg = "Value > '%s', but found '%s'"; + break; + case "LT": + msg = "Value < '%s', but found '%s'"; + break; } } else { msg = "Value expected to be '%s', but found '%s'"; @@ -153,6 +159,22 @@ private boolean arrayMatched(JSONArray value) { } } + private boolean isGt(String v1, String v2) { + try { + return Long.parseLong(v1) > Long.parseLong(v2); + } catch (Exception e) { + return false; + } + } + + private boolean isLt(String v1, String v2) { + try { + return Long.parseLong(v1) < Long.parseLong(v2); + } catch (Exception e) { + return false; + } + } + private boolean isEquals(Object subj) { String str = objectToString(subj); if (this.isUseRegex()) { @@ -174,6 +196,13 @@ private boolean isEquals(Object subj) { case "NOT_EQUALS": refFlag = !str.equals(getExpectedValue()); break; + case "GT": + refFlag = isGt(str, getExpectedValue()); + break; + case "LT": + refFlag = isLt(str, getExpectedValue()); + break; + } return refFlag; } @@ -224,7 +253,7 @@ public static String objectToString(Object subj) { if (subj == null) { str = "null"; } else if (subj instanceof Map) { - str = (new JSONObject((Map) subj)).toJSONString(); + str = new Gson().toJson(subj); } else if (!(subj instanceof Double) && !(subj instanceof Float)) { str = subj.toString(); } else { From 5448e3d0712dc0be53f1d1b631f0eb6cb06565d3 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 4 Aug 2021 19:18:41 +0800 Subject: [PATCH 092/157] =?UTF-8?q?refactor:=20=E5=90=AF=E5=8A=A8=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 1e1dcb3..a7d8fdc 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -129,7 +129,7 @@ private void copyTestResources(DockerClient dockerClient, String containerId, St InputStream jtlIn = dockerClient - .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + ".jtl") + .copyArchiveFromContainerCmd(containerId, "/jmeter-log/" + reportId + "_" + resourceIndex + ".jtl") .exec(); jtlIn.available(); File jtl = new File(pathname + "/report.jtl.tar"); From 02ef2d5e5746dcacaa2c3bfd752af277f6d7aaa7 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 24 Aug 2021 10:33:18 +0800 Subject: [PATCH 093/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E6=94=B9jsonpath=E7=89=88?= =?UTF-8?q?=E6=9C=AC=EF=BC=8C=E5=85=BC=E5=AE=B9=E5=8E=86=E5=8F=B2=E7=89=88?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E6=8F=90=E5=8F=96$..a=20=E7=9A=84=E5=86=99?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc08624..f5644b9 100644 --- a/pom.xml +++ b/pom.xml @@ -388,7 +388,7 @@ com.jayway.jsonpath json-path - 2.5.0 + 2.4.0 org.aspectj From de26f03be3d364ffca33d7e9d2d4774b11461c1f Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 25 Aug 2021 13:43:28 +0800 Subject: [PATCH 094/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc08624..277b7dc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.8 + 1.12 node-controller node-controller From 70ab55caa0f64e0de5520432c84d53ba40bd0124 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 27 Aug 2021 12:16:10 +0800 Subject: [PATCH 095/157] build: spring version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5644b9..d398445 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.5.0 + 2.5.4 io.metersphere From 586ebe0536f4e569ca4db91059b67df17e93cddf Mon Sep 17 00:00:00 2001 From: zhaoyong Date: Sun, 29 Aug 2021 17:43:12 +0800 Subject: [PATCH 096/157] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/WebConfig.java | 27 +++++ .../api/service/JmeterExecuteService.java | 73 ++++++++++--- .../api/service/utils/BodyFile.java | 10 ++ .../api/service/{ => utils}/ZipSpider.java | 100 +++++++++++++++++- 4 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 src/main/java/io/metersphere/api/config/WebConfig.java create mode 100644 src/main/java/io/metersphere/api/service/utils/BodyFile.java rename src/main/java/io/metersphere/api/service/{ => utils}/ZipSpider.java (59%) diff --git a/src/main/java/io/metersphere/api/config/WebConfig.java b/src/main/java/io/metersphere/api/config/WebConfig.java new file mode 100644 index 0000000..b4a2469 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/WebConfig.java @@ -0,0 +1,27 @@ +package io.metersphere.api.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public RestTemplate restTemplateWithTimeOut() { + RestTemplate restTemplate = new RestTemplate(); + HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); + httpRequestFactory.setConnectionRequestTimeout(4000); + httpRequestFactory.setConnectTimeout(4000); + httpRequestFactory.setReadTimeout(10 * 1000); + restTemplate.setRequestFactory(httpRequestFactory); + return restTemplate; + } +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 3851a3a..8005167 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -4,17 +4,18 @@ import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; @@ -25,6 +26,8 @@ public class JmeterExecuteService { private JMeterService jMeterService; @Resource LoadTestProducer loadTestProducer; + @Resource + private RestTemplate restTemplate; private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { @@ -39,6 +42,21 @@ private static InputStream getStrToStream(String sInputString) { return null; } + private HashTree getHashTree(byte[] hashTree) { + if (hashTree.length > 0) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(hashTree); + Object scriptWrapper = SaveService.loadElement(tInputStringStream); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + return testPlan; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + private void loadJar(String path) { try { NewDriver.addPath(path); @@ -73,6 +91,40 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "SUCCESS"; } +// public String runStart(RunRequest runRequest) { +// // 检查KAFKA +// loadTestProducer.checkKafka(); +// try { +// // 生成附件/JAR文件 +// URL urlObject = new URL(runRequest.getUrl()); +// String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; +// LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); +// +// File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); +// if (file != null) { +// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); +// this.loadJar(FileUtils.JAR_FILE_DIR); +// } +// LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); +// +// File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); +// if (bodyFile != null) { +// ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); +// File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); +// // 生成执行脚本 +// HashTree testPlan = SaveService.loadTree(jmxFile); +// // 开始执行 +// jMeterService.run(runRequest, testPlan); +// } else { +// MSException.throwException("未找到执行的JMX文件"); +// } +// } catch (Exception e) { +// LogUtil.error(e.getMessage()); +// return e.getMessage(); +// } +// return "SUCCESS"; +// } + public String runStart(RunRequest runRequest) { // 检查KAFKA loadTestProducer.checkKafka(); @@ -80,21 +132,13 @@ public String runStart(RunRequest runRequest) { // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); - - File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); - - File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); - if (bodyFile != null) { - ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); + if (jmx != null) { // 生成执行脚本 - HashTree testPlan = SaveService.loadTree(jmxFile); + HashTree testPlan = this.getHashTree(jmx); + //加载附件 + ZipSpider.downloadFiles(runRequest.getUrl(), testPlan); // 开始执行 jMeterService.run(runRequest, testPlan); } else { @@ -106,5 +150,4 @@ public String runStart(RunRequest runRequest) { } return "SUCCESS"; } - } diff --git a/src/main/java/io/metersphere/api/service/utils/BodyFile.java b/src/main/java/io/metersphere/api/service/utils/BodyFile.java new file mode 100644 index 0000000..f73d457 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/BodyFile.java @@ -0,0 +1,10 @@ +package io.metersphere.api.service.utils; + +import lombok.Data; + +@Data +public class BodyFile { + + private String id; + private String name; +} diff --git a/src/main/java/io/metersphere/api/service/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java similarity index 59% rename from src/main/java/io/metersphere/api/service/ZipSpider.java rename to src/main/java/io/metersphere/api/service/utils/ZipSpider.java index baa9c8a..fe49244 100644 --- a/src/main/java/io/metersphere/api/service/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -1,11 +1,25 @@ -package io.metersphere.api.service; +package io.metersphere.api.service.utils; import java.io.*; import java.net.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import com.alibaba.fastjson.JSON; +import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.node.util.LogUtil; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.jmeter.config.CSVDataSet; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jorphan.collections.HashTree; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; public class ZipSpider { @@ -139,19 +153,35 @@ public static void seperate(char c) { } @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir) { + public static File downloadFile(String urlPath, String downloadDir,String json) { File file = null; + BufferedInputStream bin = null; + OutputStream out = null; try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setUseCaches(false); + httpURLConnection.setDoOutput(true); + httpURLConnection.setDoInput(true); httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 - httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestMethod("POST");//设置请求方式,默认是GET httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); + httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + httpURLConnection.setInstanceFollowRedirects(false); + byte[] writebytes = json.getBytes(); + // 设置文件长度 + httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writebytes.length)); + OutputStream outwritestream = httpURLConnection.getOutputStream(); + outwritestream.write(json.getBytes()); + outwritestream.flush(); + outwritestream.close(); + httpURLConnection.connect();// 打开连接 + bin = new BufferedInputStream(httpURLConnection.getInputStream()); - BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); String fileName = httpURLConnection.getHeaderField("Content-Disposition"); fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 @@ -161,7 +191,7 @@ public static File downloadFile(String urlPath, String downloadDir) { file.getParentFile().mkdirs(); } - OutputStream out = new FileOutputStream(file); + out = new FileOutputStream(file); int size = 0; byte[] b = new byte[2048]; @@ -181,7 +211,67 @@ public static File downloadFile(String urlPath, String downloadDir) { e.printStackTrace(); LogUtil.info("文件下载失败!"); } finally { + try { + bin.close(); + out.close(); + } catch (Exception e) { + + } return file; } } + + public static void getFiles(HashTree tree, List files) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy) { + HTTPSamplerProxy source = (HTTPSamplerProxy) key; + if (source != null && source.getHTTPFiles().length > 0) { + for (HTTPFileArg arg : source.getHTTPFiles()) { + BodyFile file = new BodyFile(); + file.setId(arg.getParamName()); + file.setName(arg.getPath()); + files.add(file); + } + } + } else if (key instanceof CSVDataSet) { + CSVDataSet source = (CSVDataSet) key; + if (source != null && source.getFilename() != null) { + BodyFile file = new BodyFile(); + file.setId(source.getFilename()); + file.setName(source.getFilename()); + files.add(file); + } + } + if (node != null) { + getFiles(node, files); + } + } + } + + public static void downloadFiles(String uri, HashTree hashTree) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + List files = new ArrayList<>(); + ZipSpider.getFiles(hashTree, files); + if (CollectionUtils.isNotEmpty(files)) { + try { + URL urlObject = new URL(uri); + String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; + LogUtil.info("开始同步下载附件:" + url); + ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); + }catch (Exception e){ + e.printStackTrace(); + } + } + } + + public static byte[] get(String uri, RestTemplate restTemplate) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); + return result.getBody(); + } } From da755a7b369c5904d932bd5e3c16607cb5dca5b9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 09:28:15 +0800 Subject: [PATCH 097/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/FixedTask.java | 38 ++++++++ .../api/service/JmeterExecuteService.java | 16 +++- .../api/service/utils/ZipSpider.java | 93 ++++++++++++++----- 3 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 src/main/java/io/metersphere/api/config/FixedTask.java diff --git a/src/main/java/io/metersphere/api/config/FixedTask.java b/src/main/java/io/metersphere/api/config/FixedTask.java new file mode 100644 index 0000000..3407fb1 --- /dev/null +++ b/src/main/java/io/metersphere/api/config/FixedTask.java @@ -0,0 +1,38 @@ +package io.metersphere.api.config; + +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.net.MalformedURLException; + +@Component +public class FixedTask { + public static String url = null; + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + @Scheduled(cron = "0 0/5 * * * ?") + public void execute() { + if (StringUtils.isNotEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + } +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8005167..cee60b3 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import io.metersphere.api.config.FixedTask; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -16,6 +17,7 @@ import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; @@ -126,13 +128,21 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] // } public String runStart(RunRequest runRequest) { - // 检查KAFKA - loadTestProducer.checkKafka(); try { + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + // 检查KAFKA + loadTestProducer.checkKafka(); // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + if (StringUtils.isEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + FixedTask.url = jarUrl; byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); if (jmx != null) { // 生成执行脚本 diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index fe49244..4b7de92 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -92,35 +92,31 @@ public static void download(String url, String path) { //解压本地文件至目的文件路径 public static void unzip(String fromFile, String toFile) { - try { - ZipInputStream Zin = new ZipInputStream(new FileInputStream(fromFile)); - BufferedInputStream Bin = new BufferedInputStream(Zin); + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fromFile)); BufferedInputStream bin = new BufferedInputStream(zin);) { String Parent = toFile; - File Fout = null; + File fout = null; ZipEntry entry; - try { - while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) { - Fout = new File(Parent, entry.getName()); - if (!Fout.exists()) { - (new File(Fout.getParent())).mkdirs(); - } - FileOutputStream out = new FileOutputStream(Fout); - BufferedOutputStream Bout = new BufferedOutputStream(out); - int b; - while ((b = Bin.read()) != -1) { - Bout.write(b); - } - Bout.close(); - out.close(); - LogUtil.info(Fout + "解压成功"); + while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) { + fout = new File(Parent, entry.getName()); + if (!fout.exists()) { + (new File(fout.getParent())).mkdirs(); } - Bin.close(); - Zin.close(); - } catch (IOException e) { - e.printStackTrace(); + FileOutputStream out = new FileOutputStream(fout); + BufferedOutputStream Bout = new BufferedOutputStream(out); + int b; + while ((b = bin.read()) != -1) { + Bout.write(b); + } + Bout.close(); + out.close(); + LogUtil.info(fout + "解压成功"); } + bin.close(); + zin.close(); } catch (FileNotFoundException e) { e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); } } @@ -153,7 +149,7 @@ public static void seperate(char c) { } @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir,String json) { + public static File downloadFile(String urlPath, String downloadDir, String json) { File file = null; BufferedInputStream bin = null; OutputStream out = null; @@ -261,7 +257,7 @@ public static void downloadFiles(String uri, HashTree hashTree) { String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; LogUtil.info("开始同步下载附件:" + url); ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); - }catch (Exception e){ + } catch (Exception e) { e.printStackTrace(); } } @@ -274,4 +270,51 @@ public static byte[] get(String uri, RestTemplate restTemplate) { ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); return result.getBody(); } + + @SuppressWarnings("finally") + public static File downloadFile(String urlPath, String downloadDir) { + File file = null; + try { + URL url = new URL(urlPath); + URLConnection urlConnection = url.openConnection(); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, + httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 + httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.connect();// 打开连接 + + BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 + file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + OutputStream out = new FileOutputStream(file); + int size = 0; + + byte[] b = new byte[2048]; + //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 + while ((size = bin.read(b)) != -1) { + out.write(b, 0, size); + } + // 关闭资源 + bin.close(); + out.close(); + LogUtil.info("文件下载成功!"); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + LogUtil.info("文件下载失败!"); + } finally { + return file; + } + } } From c539f406fe90c50fe10527b03d7671653c7ca5f0 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 10:31:06 +0800 Subject: [PATCH 098/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index cee60b3..90d7713 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -136,6 +136,7 @@ public String runStart(RunRequest runRequest) { URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; if (StringUtils.isEmpty(FixedTask.url)) { + FixedTask.url = jarUrl; File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); From 49586ed6a3f3281d788875f3213b48390d4da61c Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 17:29:49 +0800 Subject: [PATCH 099/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/config/KafkaConfig.java | 8 ++ .../api/service/JmeterExecuteService.java | 12 +-- .../api/service/MsKafkaListener.java | 86 +++++++++++++++++++ .../apache/jmeter/util/JSR223TestElement.java | 6 +- src/main/resources/application.properties | 3 +- 5 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/metersphere/api/service/MsKafkaListener.java diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 940317f..d0c6616 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -14,4 +14,12 @@ public NewTopic apiExecTopic() { return TopicBuilder.name(testTopic) .build(); } + + public final static String execTopic = "ms-automation-exec-topic"; + + @Bean + public NewTopic automationTopic() { + return TopicBuilder.name(execTopic) + .build(); + } } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 90d7713..a5781d5 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -136,12 +136,12 @@ public String runStart(RunRequest runRequest) { URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; if (StringUtils.isEmpty(FixedTask.url)) { - FixedTask.url = jarUrl; - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } +// FixedTask.url = jarUrl; +// File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); +// if (file != null) { +// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); +// this.loadJar(FileUtils.JAR_FILE_DIR); +// } } FixedTask.url = jarUrl; byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java new file mode 100644 index 0000000..8174f89 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -0,0 +1,86 @@ +package io.metersphere.api.service; + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.config.FixedTask; +import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.NewDriver; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.net.MalformedURLException; + +@Service +public class MsKafkaListener { + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + + public static final String CONSUME_ID = "ms-api-automation-consume"; + @Resource + private JMeterService jMeterService; + + private static InputStream getStrToStream(String sInputString) { + if (StringUtils.isNotEmpty(sInputString)) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); + return tInputStringStream; + } catch (Exception ex) { + ex.printStackTrace(); + MSException.throwException("生成脚本异常"); + } + } + return null; + } + + private void loadJar(String path) { + try { + NewDriver.addPath(path); + } catch (MalformedURLException e) { + LogUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + + @KafkaListener(id = CONSUME_ID, topics = EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") + public void consume(ConsumerRecord record, Acknowledgment ack) { + LogUtil.info("接收到执行执行请求开始处理"); + try { + if (record.value() != null) { + RunRequest request = JSON.parseObject(record.value(), RunRequest.class); + // 生成执行脚本 + InputStream inputSource = getStrToStream(request.getJmx()); + Object scriptWrapper = SaveService.loadElement(inputSource); + HashTree testPlan = JMeterService.getHashTree(scriptWrapper); + //加载附件 + ZipSpider.downloadFiles(request.getUrl(), testPlan); + if (StringUtils.isNotEmpty(FixedTask.url)) { + File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + // 开始执行 + jMeterService.run(request, testPlan); + } + } catch (Exception e) { + LogUtil.error(e.getMessage()); + } finally { + ack.acknowledge(); + } + LogUtil.info("执行执行请求处理结束"); + } + +} diff --git a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index d7cd59c..2dd7645 100644 --- a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -272,8 +272,10 @@ public static void loadGroovyJar(ScriptEngine scriptEngine) { groovyScriptEngine.getClassLoader().addURL(file.toURI().toURL()); } else { File[] files = file.listFiles(); - for (File f : files) { - groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + if(files!= null && files.length > 0){ + for (File f : files) { + groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); + } } } } catch (Exception e) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70b2f6a..1e3ee1e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,6 +5,7 @@ server.port=8082 #kafka spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id +spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE kafka.fields= kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ kafka.sample-filter= @@ -26,6 +27,6 @@ kafka.ssl.keystore-type=JKS kafka.ssl.protocol=TLS kafka.ssl.provider= kafka.ssl.truststore-type= - +spring.kafka.producer.properties.max.request.size=32428800 # jmeter jmeter.home=/opt/jmeter From b971be6aad61876615593ffe66ac93ec257b76ef Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 19:12:25 +0800 Subject: [PATCH 100/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/WebConfig.java | 27 ------ .../api/jmeter/APIBackendListenerClient.java | 8 +- .../api/service/JmeterExecuteService.java | 89 +++++-------------- .../api/service/LoadTestProducer.java | 47 ---------- .../api/service/MsKafkaListener.java | 3 + 5 files changed, 27 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/WebConfig.java delete mode 100644 src/main/java/io/metersphere/api/service/LoadTestProducer.java diff --git a/src/main/java/io/metersphere/api/config/WebConfig.java b/src/main/java/io/metersphere/api/config/WebConfig.java deleted file mode 100644 index b4a2469..0000000 --- a/src/main/java/io/metersphere/api/config/WebConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.metersphere.api.config; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - public RestTemplate restTemplateWithTimeOut() { - RestTemplate restTemplate = new RestTemplate(); - HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); - httpRequestFactory.setConnectionRequestTimeout(4000); - httpRequestFactory.setConnectTimeout(4000); - httpRequestFactory.setReadTimeout(10 * 1000); - restTemplate.setRequestFactory(httpRequestFactory); - return restTemplate; - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index f09e88e..b6dfeea 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -1,11 +1,11 @@ package io.metersphere.api.jmeter; import com.alibaba.fastjson.JSON; -import io.metersphere.api.service.LoadTestProducer; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.dubbo.common.utils.CollectionUtils; @@ -42,7 +42,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private boolean isDebug; - private LoadTestProducer loadTestProducer; + private JmeterExecuteService jmeterExecuteService; /** * 测试ID @@ -91,7 +91,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); - loadTestProducer = CommonBeanFactory.getBean(LoadTestProducer.class); + jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { @@ -141,7 +141,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); testResult.setRunMode(this.runMode); // 推送执行结果 - loadTestProducer.sendMessage(JSON.toJSONString(testResult)); + jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); queue.clear(); super.teardownTest(context); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a5781d5..a834420 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import com.alibaba.fastjson.JSON; import io.metersphere.api.config.FixedTask; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; @@ -11,6 +12,7 @@ import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; +import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -27,9 +29,7 @@ public class JmeterExecuteService { @Resource private JMeterService jMeterService; @Resource - LoadTestProducer loadTestProducer; - @Resource - private RestTemplate restTemplate; + private KafkaTemplate kafkaTemplate; private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { @@ -44,20 +44,6 @@ private static InputStream getStrToStream(String sInputString) { return null; } - private HashTree getHashTree(byte[] hashTree) { - if (hashTree.length > 0) { - try { - ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(hashTree); - Object scriptWrapper = SaveService.loadElement(tInputStringStream); - HashTree testPlan = JMeterService.getHashTree(scriptWrapper); - return testPlan; - } catch (Exception ex) { - ex.printStackTrace(); - MSException.throwException("生成脚本异常"); - } - } - return null; - } private void loadJar(String path) { try { @@ -73,8 +59,6 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "执行文件为空,无法执行!"; } LogUtil.info(request.getJmx()); - // 检查KAFKA - loadTestProducer.checkKafka(); // 生成附件/JAR文件 FileUtils.createFiles(bodyFiles, FileUtils.BODY_FILE_DIR); FileUtils.createFiles(jarFiles, FileUtils.JAR_FILE_DIR); @@ -93,63 +77,26 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] return "SUCCESS"; } -// public String runStart(RunRequest runRequest) { -// // 检查KAFKA -// loadTestProducer.checkKafka(); -// try { -// // 生成附件/JAR文件 -// URL urlObject = new URL(runRequest.getUrl()); -// String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; -// LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); -// -// File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); -// if (file != null) { -// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); -// this.loadJar(FileUtils.JAR_FILE_DIR); -// } -// LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); -// -// File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); -// if (bodyFile != null) { -// ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); -// File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); -// // 生成执行脚本 -// HashTree testPlan = SaveService.loadTree(jmxFile); -// // 开始执行 -// jMeterService.run(runRequest, testPlan); -// } else { -// MSException.throwException("未找到执行的JMX文件"); -// } -// } catch (Exception e) { -// LogUtil.error(e.getMessage()); -// return e.getMessage(); -// } -// return "SUCCESS"; -// } - public String runStart(RunRequest runRequest) { try { - LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); - // 检查KAFKA - loadTestProducer.checkKafka(); // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - if (StringUtils.isEmpty(FixedTask.url)) { -// FixedTask.url = jarUrl; -// File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); -// if (file != null) { -// ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); -// this.loadJar(FileUtils.JAR_FILE_DIR); -// } + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); } - FixedTask.url = jarUrl; - byte[] jmx = ZipSpider.get(runRequest.getUrl(), restTemplate); - if (jmx != null) { + LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); + + File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); + if (bodyFile != null) { + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); // 生成执行脚本 - HashTree testPlan = this.getHashTree(jmx); - //加载附件 - ZipSpider.downloadFiles(runRequest.getUrl(), testPlan); + HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 jMeterService.run(runRequest, testPlan); } else { @@ -161,4 +108,8 @@ public String runStart(RunRequest runRequest) { } return "SUCCESS"; } + + public void sendMessage(String message) { + kafkaTemplate.send(MsKafkaListener.TOPICS, message); + } } diff --git a/src/main/java/io/metersphere/api/service/LoadTestProducer.java b/src/main/java/io/metersphere/api/service/LoadTestProducer.java deleted file mode 100644 index 42948c6..0000000 --- a/src/main/java/io/metersphere/api/service/LoadTestProducer.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.metersphere.api.service; - -import io.metersphere.api.config.KafkaConfig; -import io.metersphere.api.config.KafkaProperties; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang.StringUtils; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -import java.net.InetSocketAddress; -import java.net.Socket; - -@Service -public class LoadTestProducer { - - public void checkKafka() { - KafkaProperties kafkaProperties = CommonBeanFactory.getBean(KafkaProperties.class); - String[] servers = StringUtils.split(kafkaProperties.getBootstrapServers(), ","); - try { - for (String s : servers) { - String[] ipAndPort = s.split(":"); - //1,建立tcp - String ip = ipAndPort[0]; - int port = Integer.parseInt(ipAndPort[1]); - try ( - Socket soc = new Socket() - ) { - soc.connect(new InetSocketAddress(ip, port), 1000); - } - } - } catch (Exception e) { - LogUtil.error(e); - MSException.throwException("Failed to connect to Kafka"); - } - } - - - @Resource - private KafkaTemplate kafkaTemplate; - - public void sendMessage(String report) { - this.kafkaTemplate.send(KafkaConfig.testTopic, report); - } -} diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index 8174f89..e3a24fd 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -25,7 +25,10 @@ @Service public class MsKafkaListener { + // 执行内容监听 public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + //执行结果回传 + public static final String TOPICS = "ms-api-exec-topic"; public static final String CONSUME_ID = "ms-api-automation-consume"; @Resource From cff17598db3ed9a18fc746776afe269240cc086d Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 30 Aug 2021 19:28:48 +0800 Subject: [PATCH 101/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=8B=E5=8A=A8=20flush=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a834420..8f2e952 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -111,5 +111,6 @@ public String runStart(RunRequest runRequest) { public void sendMessage(String message) { kafkaTemplate.send(MsKafkaListener.TOPICS, message); + kafkaTemplate.flush(); } } From 189f487f0bd4553113e50221d7b84f3afeb9aaf1 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 31 Aug 2021 18:24:00 +0800 Subject: [PATCH 102/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=B9=B6=E5=8F=91=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A2=9E=E5=8A=A0=E5=AE=88=E6=8A=A4=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/config/FixedTask.java | 38 ----- .../metersphere/api/config/KafkaConfig.java | 11 +- .../controller/JmeterExecuteController.java | 5 + .../api/controller/request/RunRequest.java | 2 + .../api/jmeter/APIBackendListenerClient.java | 95 ++++++------ .../metersphere/api/jmeter/JMeterService.java | 3 + .../api/service/JmeterExecuteService.java | 62 ++++++-- .../api/service/MsKafkaListener.java | 62 +------- .../api/service/utils/ZipSpider.java | 143 ++++-------------- 9 files changed, 158 insertions(+), 263 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/FixedTask.java diff --git a/src/main/java/io/metersphere/api/config/FixedTask.java b/src/main/java/io/metersphere/api/config/FixedTask.java deleted file mode 100644 index 3407fb1..0000000 --- a/src/main/java/io/metersphere/api/config/FixedTask.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.metersphere.api.config; - -import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.api.service.utils.ZipSpider; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.NewDriver; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.net.MalformedURLException; - -@Component -public class FixedTask { - public static String url = null; - - private void loadJar(String path) { - try { - NewDriver.addPath(path); - } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(e.getMessage()); - } - } - - @Scheduled(cron = "0 0/5 * * * ?") - public void execute() { - if (StringUtils.isNotEmpty(FixedTask.url)) { - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } - } - } -} diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index d0c6616..4249b6b 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -7,19 +7,20 @@ @Configuration public class KafkaConfig { - public final static String testTopic = "ms-api-exec-topic"; + // 执行内容监听 + public final static String EXEC_TOPIC = "ms-automation-exec-topic121"; + //执行结果回传 + public static final String TOPICS = "ms-api-exec-topic1212"; @Bean public NewTopic apiExecTopic() { - return TopicBuilder.name(testTopic) + return TopicBuilder.name(TOPICS) .build(); } - public final static String execTopic = "ms-automation-exec-topic"; - @Bean public NewTopic automationTopic() { - return TopicBuilder.name(execTopic) + return TopicBuilder.name(EXEC_TOPIC) .build(); } } diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index c497d66..16a4c5e 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -42,4 +42,9 @@ public JvmInfo getJvmInfo() { return JvmService.jvmInfo(); } + @GetMapping("/getRunning/{key}") + public Integer getRunning(@PathVariable String key) { + return jmeterExecuteService.getRunningTasks(key); + } + } diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index 180ec4f..6efb04c 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -13,5 +13,7 @@ public class RunRequest { private boolean isDebug; private String runMode; private String jmx; + // 集成报告ID + private String amassReport; private RunModeConfig config; } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index b6dfeea..ca731f2 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -30,6 +30,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_REPORT_ID = "ms.test.report.name"; + public final static String AMASS_REPORT = "ms.test.amass.report.id"; + private final static String THREAD_SPLIT = " "; private final static String ID_SPLIT = "-"; @@ -54,6 +56,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl */ private String setReportId; + private String amassReport; /** * 获得控制台内容 */ @@ -92,56 +95,61 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id - final Map scenarios = new LinkedHashMap<>(); - queue.forEach(result -> { - // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 - if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { - String evnStr = result.getResponseDataAsString(); - testResult.setRunningDebugSampler(result.getResponseDataAsString()); - } else { - String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); - String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); - String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); - ScenarioResult scenarioResult; - if (!scenarios.containsKey(scenarioId)) { - scenarioResult = new ScenarioResult(); - try { - scenarioResult.setId(Integer.parseInt(scenarioId)); - } catch (Exception e) { - scenarioResult.setId(0); - LogUtil.error("场景ID转换异常: " + e.getMessage()); - } - scenarioResult.setName(scenarioName); - scenarios.put(scenarioId, scenarioResult); + try { + + // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id + final Map scenarios = new LinkedHashMap<>(); + queue.forEach(result -> { + // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 + if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { + testResult.setRunningDebugSampler(result.getResponseDataAsString()); } else { - scenarioResult = scenarios.get(scenarioId); - } + String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); + String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); + String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); + ScenarioResult scenarioResult; + if (!scenarios.containsKey(scenarioId)) { + scenarioResult = new ScenarioResult(); + try { + scenarioResult.setId(Integer.parseInt(scenarioId)); + } catch (Exception e) { + scenarioResult.setId(0); + LogUtil.error("场景ID转换异常: " + e.getMessage()); + } + scenarioResult.setName(scenarioName); + scenarios.put(scenarioId, scenarioResult); + } else { + scenarioResult = scenarios.get(scenarioId); + } - if (result.isSuccessful()) { - scenarioResult.addSuccess(); - testResult.addSuccess(); - } else { - scenarioResult.addError(result.getErrorCount()); - testResult.addError(result.getErrorCount()); - } + if (result.isSuccessful()) { + scenarioResult.addSuccess(); + testResult.addSuccess(); + } else { + scenarioResult.addError(result.getErrorCount()); + testResult.addError(result.getErrorCount()); + } - RequestResult requestResult = getRequestResult(result); - scenarioResult.getRequestResults().add(requestResult); - scenarioResult.addResponseTime(result.getTime()); + RequestResult requestResult = getRequestResult(result); + scenarioResult.getRequestResults().add(requestResult); + scenarioResult.addResponseTime(result.getTime()); - testResult.addPassAssertions(requestResult.getPassAssertions()); - testResult.addTotalAssertions(requestResult.getTotalAssertions()); + testResult.addPassAssertions(requestResult.getPassAssertions()); + testResult.addTotalAssertions(requestResult.getTotalAssertions()); - scenarioResult.addPassAssertions(requestResult.getPassAssertions()); - scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); - } - }); - testResult.getScenarios().addAll(scenarios.values()); - testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); - testResult.setRunMode(this.runMode); + scenarioResult.addPassAssertions(requestResult.getPassAssertions()); + scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); + } + }); + testResult.getScenarios().addAll(scenarios.values()); + testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); + testResult.setRunMode(this.runMode); + } catch (Exception e) { + LogUtil.error("处理执行数据异常:" + e.getMessage()); + } // 推送执行结果 jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); + jmeterExecuteService.remove(amassReport,testId); queue.clear(); super.teardownTest(context); } @@ -232,6 +240,7 @@ private String getMethod(SampleResult result) { private void setParam(BackendListenerContext context) { this.testId = context.getParameter(TEST_ID); this.setReportId = context.getParameter(TEST_REPORT_ID); + this.amassReport = context.getParameter(AMASS_REPORT); this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 1eb36c0..f2eb9bc 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -64,6 +64,9 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { backendListener.setName(request.getTestId()); } Arguments arguments = new Arguments(); + if (StringUtils.isNotEmpty(request.getAmassReport())) { + arguments.addArgument(APIBackendListenerClient.AMASS_REPORT, request.getAmassReport()); + } if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8f2e952..2335096 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,7 +1,6 @@ package io.metersphere.api.service; -import com.alibaba.fastjson.JSON; -import io.metersphere.api.config.FixedTask; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -13,8 +12,8 @@ import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; @@ -23,6 +22,10 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service public class JmeterExecuteService { @@ -31,6 +34,10 @@ public class JmeterExecuteService { @Resource private KafkaTemplate kafkaTemplate; + private static String url = null; + // 记录所以执行中的请求/场景 + private Map> runningTasks = new HashMap<>(); + private static InputStream getStrToStream(String sInputString) { if (StringUtils.isNotEmpty(sInputString)) { try { @@ -44,7 +51,6 @@ private static InputStream getStrToStream(String sInputString) { return null; } - private void loadJar(String path) { try { NewDriver.addPath(path); @@ -82,13 +88,16 @@ public String runStart(RunRequest runRequest) { // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); - File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); + if (StringUtils.isEmpty(url)) { + LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } } + url = jarUrl; LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); @@ -110,7 +119,40 @@ public String runStart(RunRequest runRequest) { } public void sendMessage(String message) { - kafkaTemplate.send(MsKafkaListener.TOPICS, message); + kafkaTemplate.send(KafkaConfig.TOPICS, message); kafkaTemplate.flush(); } + + public void putRunningTasks(String key, String value) { + List list = new ArrayList<>(); + if (this.runningTasks.containsKey(key)) { + list = this.runningTasks.get(key); + } + list.add(value); + this.runningTasks.put(key, list); + } + + public int getRunningTasks(String key) { + if (this.runningTasks.containsKey(key)) { + return this.runningTasks.get(key).size(); + } + return 0; + } + + public void remove(String key, String value) { + if (this.runningTasks.containsKey(key)) { + this.runningTasks.get(key).remove(value); + } + } + + @Scheduled(cron = "0 0/5 * * * ?") + public void execute() { + if (StringUtils.isNotEmpty(url)) { + File file = ZipSpider.downloadFile(url, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + } } diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index e3a24fd..52d0bcb 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -1,82 +1,35 @@ package io.metersphere.api.service; import com.alibaba.fastjson.JSON; -import io.metersphere.api.config.FixedTask; +import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.JMeterService; -import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.api.service.utils.ZipSpider; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.NewDriver; -import org.apache.jmeter.save.SaveService; -import org.apache.jorphan.collections.HashTree; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.net.MalformedURLException; @Service public class MsKafkaListener { - // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic"; - //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic"; public static final String CONSUME_ID = "ms-api-automation-consume"; - @Resource - private JMeterService jMeterService; - - private static InputStream getStrToStream(String sInputString) { - if (StringUtils.isNotEmpty(sInputString)) { - try { - ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); - return tInputStringStream; - } catch (Exception ex) { - ex.printStackTrace(); - MSException.throwException("生成脚本异常"); - } - } - return null; - } - private void loadJar(String path) { - try { - NewDriver.addPath(path); - } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(e.getMessage()); - } - } + @Resource + private JmeterExecuteService jmeterExecuteService; - @KafkaListener(id = CONSUME_ID, topics = EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") + @KafkaListener(id = CONSUME_ID, topics = KafkaConfig.EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") public void consume(ConsumerRecord record, Acknowledgment ack) { LogUtil.info("接收到执行执行请求开始处理"); try { if (record.value() != null) { RunRequest request = JSON.parseObject(record.value(), RunRequest.class); - // 生成执行脚本 - InputStream inputSource = getStrToStream(request.getJmx()); - Object scriptWrapper = SaveService.loadElement(inputSource); - HashTree testPlan = JMeterService.getHashTree(scriptWrapper); - //加载附件 - ZipSpider.downloadFiles(request.getUrl(), testPlan); - if (StringUtils.isNotEmpty(FixedTask.url)) { - File file = ZipSpider.downloadFile(FixedTask.url, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } + if (StringUtils.isNotEmpty(request.getAmassReport())) { + jmeterExecuteService.putRunningTasks(request.getAmassReport(), request.getTestId()); } - // 开始执行 - jMeterService.run(request, testPlan); + jmeterExecuteService.runStart(request); } } catch (Exception e) { LogUtil.error(e.getMessage()); @@ -85,5 +38,4 @@ public void consume(ConsumerRecord record, Acknowledgment ack) { } LogUtil.info("执行执行请求处理结束"); } - } diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index 4b7de92..89b749f 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -94,22 +94,25 @@ public static void download(String url, String path) { public static void unzip(String fromFile, String toFile) { try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fromFile)); BufferedInputStream bin = new BufferedInputStream(zin);) { String Parent = toFile; - File fout = null; ZipEntry entry; while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) { - fout = new File(Parent, entry.getName()); + File fout = new File(Parent, entry.getName()); if (!fout.exists()) { (new File(fout.getParent())).mkdirs(); } - FileOutputStream out = new FileOutputStream(fout); - BufferedOutputStream Bout = new BufferedOutputStream(out); - int b; - while ((b = bin.read()) != -1) { - Bout.write(b); + try (FileOutputStream out = new FileOutputStream(fout); + BufferedOutputStream bout = new BufferedOutputStream(out);) { + int b; + while ((b = bin.read()) != -1) { + bout.write(b); + } + bout.close(); + out.close(); + LogUtil.info(fout + "解压成功"); + } catch (Exception e) { + e.printStackTrace(); } - Bout.close(); - out.close(); - LogUtil.info(fout + "解压成功"); + } bin.close(); zin.close(); @@ -148,75 +151,6 @@ public static void seperate(char c) { sop(""); } - @SuppressWarnings("finally") - public static File downloadFile(String urlPath, String downloadDir, String json) { - File file = null; - BufferedInputStream bin = null; - OutputStream out = null; - try { - URL url = new URL(urlPath); - URLConnection urlConnection = url.openConnection(); - HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 - //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, - httpURLConnection.setUseCaches(false); - httpURLConnection.setDoOutput(true); - httpURLConnection.setDoInput(true); - httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 - httpURLConnection.setRequestMethod("POST");//设置请求方式,默认是GET - httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 - httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); - httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - httpURLConnection.setInstanceFollowRedirects(false); - byte[] writebytes = json.getBytes(); - // 设置文件长度 - httpURLConnection.setRequestProperty("Content-Length", String.valueOf(writebytes.length)); - OutputStream outwritestream = httpURLConnection.getOutputStream(); - outwritestream.write(json.getBytes()); - outwritestream.flush(); - outwritestream.close(); - - httpURLConnection.connect();// 打开连接 - bin = new BufferedInputStream(httpURLConnection.getInputStream()); - - String fileName = httpURLConnection.getHeaderField("Content-Disposition"); - fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); - String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 - file = new File(path); - // 校验文件夹目录是否存在,不存在就创建一个目录 - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - - out = new FileOutputStream(file); - int size = 0; - - byte[] b = new byte[2048]; - //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 - while ((size = bin.read(b)) != -1) { - out.write(b, 0, size); - } - // 关闭资源 - bin.close(); - out.close(); - LogUtil.info("文件下载成功!"); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - LogUtil.info("文件下载失败!"); - } finally { - try { - bin.close(); - out.close(); - } catch (Exception e) { - - } - return file; - } - } - public static void getFiles(HashTree tree, List files) { for (Object key : tree.keySet()) { HashTree node = tree.get(key); @@ -245,56 +179,30 @@ public static void getFiles(HashTree tree, List files) { } } - public static void downloadFiles(String uri, HashTree hashTree) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); - List files = new ArrayList<>(); - ZipSpider.getFiles(hashTree, files); - if (CollectionUtils.isNotEmpty(files)) { - try { - URL urlObject = new URL(uri); - String url = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/files"; - LogUtil.info("开始同步下载附件:" + url); - ZipSpider.downloadFile(url, FileUtils.BODY_FILE_DIR, JSON.toJSONString(files)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static byte[] get(String uri, RestTemplate restTemplate) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); - ResponseEntity result = restTemplate.getForEntity(uri, byte[].class); - return result.getBody(); - } - @SuppressWarnings("finally") public static File downloadFile(String urlPath, String downloadDir) { - File file = null; + OutputStream out = null; + BufferedInputStream bin = null; try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 - //String contentType = httpURLConnection.getContentType();//请求类型,可用来过滤请求, httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 httpURLConnection.connect();// 打开连接 - BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream()); + bin = new BufferedInputStream(httpURLConnection.getInputStream()); String fileName = httpURLConnection.getHeaderField("Content-Disposition"); fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 - file = new File(path); + File file = new File(path); // 校验文件夹目录是否存在,不存在就创建一个目录 - if (!file.getParentFile().exists()) { + if (file.getParentFile() != null && !file.getParentFile().exists()) { file.getParentFile().mkdirs(); } - OutputStream out = new FileOutputStream(file); + out = new FileOutputStream(file); int size = 0; byte[] b = new byte[2048]; @@ -306,6 +214,7 @@ public static File downloadFile(String urlPath, String downloadDir) { bin.close(); out.close(); LogUtil.info("文件下载成功!"); + return file; } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -314,7 +223,17 @@ public static File downloadFile(String urlPath, String downloadDir) { e.printStackTrace(); LogUtil.info("文件下载失败!"); } finally { - return file; + try { + if (bin != null) { + bin.close(); + } + if (out != null) { + out.close(); + } + } catch (Exception e) { + + } } + return null; } } From 459ed0b347ba41b894cc2e5a406086bddafd1065 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 31 Aug 2021 18:24:56 +0800 Subject: [PATCH 103/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20topic=20=E5=90=8D=E7=A7=B0=E6=81=A2?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 4249b6b..2b9f3ce 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -8,9 +8,9 @@ @Configuration public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic121"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic1212"; + public static final String TOPICS = "ms-api-exec-topic"; @Bean public NewTopic apiExecTopic() { From 4bb6739adb60c93671d4e0c6f41408d047ab4962 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 1 Sep 2021 16:28:56 +0800 Subject: [PATCH 104/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=8A=A8=E6=80=81=E5=88=9B=E5=BB=BAKafka?= =?UTF-8?q?=20=E4=BB=A3=E7=90=86=EF=BC=9B=E7=BB=9F=E4=B8=80=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E4=B8=BB=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 3 - .../metersphere/api/config/KafkaConfig.java | 18 ----- .../api/config/KafkaProperties.java | 51 ------------ .../api/controller/ConsumerController.java | 77 +++++++++++++++++++ .../api/controller/ProducerController.java | 20 +++++ .../api/jmeter/APIBackendListenerClient.java | 8 +- .../api/jmeter/utils/FileUtils.java | 6 ++ .../api/service/JmeterExecuteService.java | 8 +- .../api/service/MsKafkaListener.java | 10 +-- .../api/service/ProducerService.java | 38 +++++++++ src/main/resources/application.properties | 27 +------ 11 files changed, 153 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/config/KafkaProperties.java create mode 100644 src/main/java/io/metersphere/api/controller/ConsumerController.java create mode 100644 src/main/java/io/metersphere/api/controller/ProducerController.java create mode 100644 src/main/java/io/metersphere/api/service/ProducerService.java diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java index d304b57..5212b82 100644 --- a/src/main/java/io/metersphere/Application.java +++ b/src/main/java/io/metersphere/Application.java @@ -1,6 +1,4 @@ package io.metersphere; - -import io.metersphere.api.config.KafkaProperties; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.JmeterProperties; import org.springframework.boot.SpringApplication; @@ -13,7 +11,6 @@ @ServletComponentScan @EnableConfigurationProperties({ - KafkaProperties.class, JmeterProperties.class, }) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 2b9f3ce..3dac81f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,26 +1,8 @@ package io.metersphere.api.config; - -import org.apache.kafka.clients.admin.NewTopic; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.kafka.config.TopicBuilder; - -@Configuration public class KafkaConfig { // 执行内容监听 public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 public static final String TOPICS = "ms-api-exec-topic"; - @Bean - public NewTopic apiExecTopic() { - return TopicBuilder.name(TOPICS) - .build(); - } - - @Bean - public NewTopic automationTopic() { - return TopicBuilder.name(EXEC_TOPIC) - .build(); - } } diff --git a/src/main/java/io/metersphere/api/config/KafkaProperties.java b/src/main/java/io/metersphere/api/config/KafkaProperties.java deleted file mode 100644 index e52b95a..0000000 --- a/src/main/java/io/metersphere/api/config/KafkaProperties.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.metersphere.api.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = KafkaProperties.KAFKA_PREFIX) -@Getter -@Setter -public class KafkaProperties { - public static final String KAFKA_PREFIX = "kafka"; - - private String acks = "0"; // 不要设置all - private String expectedDelayEndTime = "30000"; // 30s - private String topic; - private String fields; - private String timestamp; - private String bootstrapServers; - private String sampleFilter; - private String testMode; - private String parseAllReqHeaders; - private String parseAllResHeaders; - private String compressionType; - private String batchSize; - private String clientId; - private String connectionsMaxIdleMs; - private KafkaProperties.Ssl ssl = new KafkaProperties.Ssl(); - private KafkaProperties.Log log = new KafkaProperties.Log(); - - @Getter - @Setter - public static class Ssl { - private String enabled = "false"; - private String keyPassword; - private String keystoreLocation; - private String keystorePassword; - private String keystoreType; - private String truststoreLocation; - private String truststorePassword; - private String truststoreType; - private String protocol; - private String enabledProtocols; - private String provider; - } - - @Getter - @Setter - public static class Log { - private String topic; - } -} diff --git a/src/main/java/io/metersphere/api/controller/ConsumerController.java b/src/main/java/io/metersphere/api/controller/ConsumerController.java new file mode 100644 index 0000000..6cf2981 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/ConsumerController.java @@ -0,0 +1,77 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.service.MsKafkaListener; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Scope; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.config.KafkaListenerEndpointRegistry; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Map; + +@RestController +@RequestMapping("consumer") +public class ConsumerController { + /** + * 应用程序上行文 + */ + @Resource + ApplicationContext context; + /** + * 监听器容器工厂 + */ + @Resource + ConcurrentKafkaListenerContainerFactory containerFactory; + /** + * 所有@KafkaListener这个注解所标注的方法都会被注册在这里面中 + */ + @Resource + KafkaListenerEndpointRegistry registry; + + private MsKafkaListener listener; + + /** + * 创建消费者分组 + */ + @PostMapping("/create") + public void create(@RequestBody Map consumerProps) { + try { + if (consumerProps != null && consumerProps.size() > 0 && listener == null) { + consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringDeserializer"); + // 设置监听器容器工厂 + containerFactory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerProps)); + // 获取监听类实例 + listener = context.getBean(MsKafkaListener.class); + } + } catch (Exception e) { + } + } + + /** + * 停止所有消费监听 + */ + @GetMapping("/stop") + public void stop() { + registry.getListenerContainers().forEach(container -> { + container.stop(); + }); + } + + /** + * 获取监听类实例 + */ + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public MsKafkaListener listener() { + return new MsKafkaListener(); + } +} diff --git a/src/main/java/io/metersphere/api/controller/ProducerController.java b/src/main/java/io/metersphere/api/controller/ProducerController.java new file mode 100644 index 0000000..3d88387 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/ProducerController.java @@ -0,0 +1,20 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.service.ProducerService; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.Map; + +@RestController +@RequestMapping("producer") +public class ProducerController { + @Resource + private ProducerService producerService; + + @PostMapping("/create") + public String create(@RequestBody Map producerProps) { + return producerService.init(producerProps); + } +} + diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index ca731f2..40c7785 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -6,6 +6,7 @@ import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.api.service.ProducerService; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.dubbo.common.utils.CollectionUtils; @@ -45,7 +46,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private boolean isDebug; private JmeterExecuteService jmeterExecuteService; - + private ProducerService producerServer; /** * 测试ID */ @@ -95,6 +96,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + producerServer = CommonBeanFactory.getBean(ProducerService.class); try { // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id @@ -148,8 +150,8 @@ public void teardownTest(BackendListenerContext context) throws Exception { LogUtil.error("处理执行数据异常:" + e.getMessage()); } // 推送执行结果 - jmeterExecuteService.sendMessage(JSON.toJSONString(testResult)); - jmeterExecuteService.remove(amassReport,testId); + producerServer.send(JSON.toJSONString(testResult)); + jmeterExecuteService.remove(amassReport, testId); queue.clear(); super.teardownTest(context); } diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 57fcc34..de94ce0 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -29,4 +29,10 @@ public static void createFiles(MultipartFile[] bodyFiles, String path) { } } } + public static void deleteFile(String path) { + File file = new File(path); + if (file.exists()) { + file.delete(); + } + } } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 2335096..8ba4dd4 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,6 +1,5 @@ package io.metersphere.api.service; -import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -108,6 +107,7 @@ public String runStart(RunRequest runRequest) { HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 jMeterService.run(runRequest, testPlan); + FileUtils.deleteFile(bodyFile.getPath()); } else { MSException.throwException("未找到执行的JMX文件"); } @@ -118,11 +118,6 @@ public String runStart(RunRequest runRequest) { return "SUCCESS"; } - public void sendMessage(String message) { - kafkaTemplate.send(KafkaConfig.TOPICS, message); - kafkaTemplate.flush(); - } - public void putRunningTasks(String key, String value) { List list = new ArrayList<>(); if (this.runningTasks.containsKey(key)) { @@ -152,6 +147,7 @@ public void execute() { if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); this.loadJar(FileUtils.JAR_FILE_DIR); + FileUtils.deleteFile(file.getPath()); } } } diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index 52d0bcb..a945d1c 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -1,28 +1,26 @@ package io.metersphere.api.service; - import com.alibaba.fastjson.JSON; import io.metersphere.api.config.KafkaConfig; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.node.util.LogUtil; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; -@Service public class MsKafkaListener { public static final String CONSUME_ID = "ms-api-automation-consume"; - @Resource private JmeterExecuteService jmeterExecuteService; @KafkaListener(id = CONSUME_ID, topics = KafkaConfig.EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") public void consume(ConsumerRecord record, Acknowledgment ack) { LogUtil.info("接收到执行执行请求开始处理"); + if (jmeterExecuteService == null) { + jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + } try { if (record.value() != null) { RunRequest request = JSON.parseObject(record.value(), RunRequest.class); diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java new file mode 100644 index 0000000..c19894e --- /dev/null +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -0,0 +1,38 @@ +package io.metersphere.api.service; + +import io.metersphere.api.config.KafkaConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class ProducerService { + private KafkaTemplate kafkaTemplate; + + public String init(Map producerProps) { + try { + if (kafkaTemplate == null) { + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); + KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); + this.kafkaTemplate = kafkaTemplate; + } + return "SUCCESS"; + } catch (Exception e) { + return e.getMessage(); + } + } + + public void send(String message) { + if (this.kafkaTemplate != null) { + this.kafkaTemplate.send(KafkaConfig.TOPICS, message); + this.kafkaTemplate.flush(); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1e3ee1e..21c0c57 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,32 +1,7 @@ spring.application.name=node-controller logging.file.path=/opt/metersphere/logs/${spring.application.name} server.port=8082 - -#kafka -spring.kafka.bootstrap-servers=${kafka.bootstrap-servers} spring.kafka.consumer.group-id=metersphere_group_id spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE -kafka.fields= -kafka.timestamp=yyyy-MM-dd'T'HH:mm:ss.SSSZZ -kafka.sample-filter= -kafka.test-mode=info -kafka.parse-all-req-headers=false -kafka.parse-all-res-headers=false -kafka.compression-type= -kafka.batch-size=16384 -kafka.client-id=JMeterKafkaBackendListener -kafka.connections-max-idle-ms=180000 -kafka.ssl.enabled=false -kafka.ssl.key-password= -kafka.ssl.keystore-location= -kafka.ssl.keystore-password= -kafka.ssl.truststore-location= -kafka.ssl.truststore-password= -kafka.ssl.enabled-protocols=TLSv1.2,TLSv1.1,TLSv1 -kafka.ssl.keystore-type=JKS -kafka.ssl.protocol=TLS -kafka.ssl.provider= -kafka.ssl.truststore-type= -spring.kafka.producer.properties.max.request.size=32428800 # jmeter -jmeter.home=/opt/jmeter +jmeter.home=/opt/jmeter \ No newline at end of file From 778beaa87dc033e426f2478cf72557cf34518aa9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 15 Sep 2021 11:31:20 +0800 Subject: [PATCH 105/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=A2=9E=E5=8A=A0=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 7 ++++ .../api/jmeter/APIBackendListenerClient.java | 3 ++ .../metersphere/api/jmeter/JMeterService.java | 3 +- .../metersphere/api/jmeter/LocalRunner.java | 34 ++++++++++++++++--- .../api/jmeter/utils/MessageCache.java | 10 ++++++ 5 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 16a4c5e..ffcdab3 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -2,6 +2,7 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.JvmService; @@ -10,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import java.util.List; @RestController @RequestMapping("/jmeter") @@ -47,4 +49,9 @@ public Integer getRunning(@PathVariable String key) { return jmeterExecuteService.getRunningTasks(key); } + @GetMapping("/stop") + public void stop(@RequestBody List keys) { + new LocalRunner().stop(keys); + } + } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 40c7785..3263780 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.utils.MessageCache; import io.metersphere.api.module.*; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; @@ -97,6 +98,8 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setUserId(this.userId); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); + MessageCache.runningEngine.remove(testId); + MessageCache.runningEngine.remove(setReportId); try { // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index f2eb9bc..72d376b 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -10,7 +10,6 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; -import org.python.antlr.ast.arguments; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -90,7 +89,7 @@ public void run(RunRequest request, HashTree testPlan) { init(); addBackendListener(testPlan, request); LocalRunner runner = new LocalRunner(testPlan); - runner.run(); + runner.run(request.getReportId()); } catch (Exception e) { LogUtil.error(e.getMessage(), e); MSException.throwException("读取脚本失败"); diff --git a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java index 3db1bb7..60ab90a 100644 --- a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java +++ b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java @@ -1,24 +1,48 @@ package io.metersphere.api.jmeter; -import org.apache.jmeter.engine.JMeterEngine; +import io.metersphere.api.jmeter.utils.MessageCache; +import io.metersphere.node.util.LogUtil; +import org.apache.commons.collections.CollectionUtils; import org.apache.jmeter.engine.JMeterEngineException; import org.apache.jmeter.engine.StandardJMeterEngine; import org.apache.jorphan.collections.HashTree; +import java.util.List; + public class LocalRunner { - private final HashTree jmxTree; + private HashTree jmxTree; public LocalRunner(HashTree jmxTree) { this.jmxTree = jmxTree; } - public void run() { - JMeterEngine engine = new StandardJMeterEngine(); + public LocalRunner() { + } + + public void run(String report) { + StandardJMeterEngine engine = new StandardJMeterEngine(); engine.configure(jmxTree); try { engine.runTest(); + MessageCache.runningEngine.put(report, engine); } catch (JMeterEngineException e) { engine.stopTest(true); } } -} + + public void stop(List reports) { + try { + if (CollectionUtils.isNotEmpty(reports)) { + for (String report : reports) { + StandardJMeterEngine engine = MessageCache.runningEngine.get(report); + if (engine != null) { + engine.stopTest(); + MessageCache.runningEngine.remove(report); + } + } + } + } catch (Exception e) { + LogUtil.error(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java b/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java new file mode 100644 index 0000000..ee0cd9d --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java @@ -0,0 +1,10 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.jmeter.engine.StandardJMeterEngine; + +import java.util.concurrent.ConcurrentHashMap; + +public class MessageCache { + public static ConcurrentHashMap runningEngine = new ConcurrentHashMap<>(); + +} From bad2eac95737f765ee1dfb525706a80f01039eaf Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 15 Sep 2021 14:29:33 +0800 Subject: [PATCH 106/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8Dconsole=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E4=B8=A2=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/jmeter/APIBackendListenerClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 40c7785..7d3ac99 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -95,6 +95,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setSetReportId(this.setReportId); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); + testResult.setConsole(getConsole()); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); try { @@ -209,8 +210,6 @@ private RequestResult getRequestResult(SampleResult result) { responseResult.getAssertions().add(responseAssertionResult); } } - responseResult.setConsole(getConsole()); - return requestResult; } From 5d6e3a87146074b5fac1cecd29228c53aca023d0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 22 Sep 2021 17:05:05 +0800 Subject: [PATCH 107/157] =?UTF-8?q?refactor:=20=E9=81=BF=E5=85=8D=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=BF=87=E5=A4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/JmeterOperateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index a7d8fdc..b545025 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -78,7 +78,7 @@ public void onComplete() { try { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { - copyTestResources(dockerClient, containerId, reportId, resourceIndex); +// copyTestResources(dockerClient, containerId, reportId, resourceIndex); DockerClientService.removeContainer(dockerClient, containerId); } From b85447e2910ba801c406c96bf6d2eab6908bf194 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 22 Sep 2021 18:55:59 +0800 Subject: [PATCH 108/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- .../api/jmeter/APIBackendListenerClient.java | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index 3dac81f..f69fa6a 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,8 +1,8 @@ package io.metersphere.api.config; public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic99"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic"; + public static final String TOPICS = "ms-api-exec-topic99"; } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index fd32a8a..46d400b 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -3,9 +3,9 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.constants.RequestType; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.MessageCache; import io.metersphere.api.module.*; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.ProducerService; import io.metersphere.node.util.LogUtil; @@ -99,10 +99,13 @@ public void teardownTest(BackendListenerContext context) throws Exception { testResult.setConsole(getConsole()); jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); producerServer = CommonBeanFactory.getBean(ProducerService.class); - MessageCache.runningEngine.remove(testId); - MessageCache.runningEngine.remove(setReportId); try { - + if (StringUtils.isNotEmpty(testId) && !MessageCache.runningEngine.isEmpty()) { + MessageCache.runningEngine.remove(testId); + } + if (StringUtils.isNotEmpty(setReportId) && !MessageCache.runningEngine.isEmpty()) { + MessageCache.runningEngine.remove(setReportId); + } // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { From 769ba01b966e80399023f76e2a34d5f02e8c118e Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 22 Sep 2021 19:00:52 +0800 Subject: [PATCH 109/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/config/KafkaConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/config/KafkaConfig.java b/src/main/java/io/metersphere/api/config/KafkaConfig.java index f69fa6a..3dac81f 100644 --- a/src/main/java/io/metersphere/api/config/KafkaConfig.java +++ b/src/main/java/io/metersphere/api/config/KafkaConfig.java @@ -1,8 +1,8 @@ package io.metersphere.api.config; public class KafkaConfig { // 执行内容监听 - public final static String EXEC_TOPIC = "ms-automation-exec-topic99"; + public final static String EXEC_TOPIC = "ms-automation-exec-topic"; //执行结果回传 - public static final String TOPICS = "ms-api-exec-topic99"; + public static final String TOPICS = "ms-api-exec-topic"; } From efdb424ea2175eac9bf3336bd73659b6b044f6cb Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 24 Sep 2021 14:17:36 +0800 Subject: [PATCH 110/157] =?UTF-8?q?refactor:=20kafka=20producer=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/service/JmeterOperateService.java | 8 +++-- .../node/service/KafkaProducer.java | 17 ----------- .../node/service/KafkaProducerService.java | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 20 deletions(-) delete mode 100644 src/main/java/io/metersphere/node/service/KafkaProducer.java create mode 100644 src/main/java/io/metersphere/node/service/KafkaProducerService.java diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index b545025..5fc5bb3 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -34,7 +34,7 @@ @Service public class JmeterOperateService { @Resource - private KafkaProducer kafkaProducer; + private KafkaProducerService kafkaProducerService; public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); @@ -43,6 +43,8 @@ public void startContainer(TestRequest testRequest) { String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); // 检查kafka连通性 checkKafka(bootstrapServers); + // 初始化kafka + kafkaProducerService.init(bootstrapServers); DockerClient dockerClient = DockerClientService.connectDocker(testRequest); @@ -85,7 +87,7 @@ public void onComplete() { // 上传结束消息 String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; String log = StringUtils.join(contents, " "); - kafkaProducer.sendMessage(topic, log); + kafkaProducerService.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { LogUtil.error("Remove container error: ", e); @@ -108,7 +110,7 @@ public void onNext(Frame item) { // oom 退出 String[] contents = new String[]{reportId, "none", "0", oomMessage}; String message = StringUtils.join(contents, " "); - kafkaProducer.sendMessage(topic, message); + kafkaProducerService.sendMessage(topic, message); } LogUtil.info(log); } diff --git a/src/main/java/io/metersphere/node/service/KafkaProducer.java b/src/main/java/io/metersphere/node/service/KafkaProducer.java deleted file mode 100644 index b05ecd3..0000000 --- a/src/main/java/io/metersphere/node/service/KafkaProducer.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.metersphere.node.service; - -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Service; - -import javax.annotation.Resource; - -@Service -public class KafkaProducer { - - @Resource - private KafkaTemplate kafkaTemplate; - - public void sendMessage(String topic, String report) { - this.kafkaTemplate.send(topic, report); - } -} diff --git a/src/main/java/io/metersphere/node/service/KafkaProducerService.java b/src/main/java/io/metersphere/node/service/KafkaProducerService.java new file mode 100644 index 0000000..0febc66 --- /dev/null +++ b/src/main/java/io/metersphere/node/service/KafkaProducerService.java @@ -0,0 +1,30 @@ +package io.metersphere.node.service; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; + +import java.util.Properties; + +public class KafkaProducerService { + private KafkaTemplate kafkaTemplate; + + public void init(String bootstrapServers) { + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + + if (kafkaTemplate == null) { + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory(props); + KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); + this.kafkaTemplate = kafkaTemplate; + } + } + + public void sendMessage(String topic, String report) { + this.kafkaTemplate.send(topic, report); + } +} From 34b3f1c1df30e5167ca7971c24de4171c77d8d77 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Fri, 24 Sep 2021 14:22:43 +0800 Subject: [PATCH 111/157] =?UTF-8?q?refactor:=20kafka=20producer=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/node/service/KafkaProducerService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/metersphere/node/service/KafkaProducerService.java b/src/main/java/io/metersphere/node/service/KafkaProducerService.java index 0febc66..21f7209 100644 --- a/src/main/java/io/metersphere/node/service/KafkaProducerService.java +++ b/src/main/java/io/metersphere/node/service/KafkaProducerService.java @@ -3,9 +3,11 @@ import org.apache.kafka.clients.producer.ProducerConfig; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; import java.util.Properties; +@Service public class KafkaProducerService { private KafkaTemplate kafkaTemplate; From 98f95487080c658142a8b8cab5df91e7128818bf Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 22 Sep 2021 19:49:54 +0800 Subject: [PATCH 112/157] =?UTF-8?q?refactor:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 283a442..fa4fced 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.12 + 1.13 node-controller node-controller From c15d22b6eeca72c0c8d8ec34884b890811776b14 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 23 Sep 2021 18:19:00 +0800 Subject: [PATCH 113/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96)=20=E5=8F=91=E9=80=81=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E7=9A=84=E6=B6=88=E6=81=AF=E8=A1=A5=E5=81=BF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 18 ++++++++++++++++-- .../api/service/JmeterExecuteService.java | 15 ++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 46d400b..1da481f 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -157,8 +157,22 @@ public void teardownTest(BackendListenerContext context) throws Exception { LogUtil.error("处理执行数据异常:" + e.getMessage()); } // 推送执行结果 - producerServer.send(JSON.toJSONString(testResult)); - jmeterExecuteService.remove(amassReport, testId); + try { + producerServer.send(JSON.toJSONString(testResult)); + } catch (Exception ex) { + LogUtil.error("KAFKA 推送结果异常:[" + testId + "]" + ex.getMessage()); + // 补偿一个结果防止持续Running + testResult.getScenarios().clear(); + producerServer.send(JSON.toJSONString(testResult)); + } + LogUtil.info("接口收到集合报告ID:" + amassReport); + + if (StringUtils.isNotEmpty(amassReport)) { + jmeterExecuteService.remove(amassReport, testId); + LogUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); + LogUtil.info("正在执行中的场景[" + amassReport + "]的数量:" + jmeterExecuteService.getRunningTasks(amassReport)); + LogUtil.info("正在执行中的场景[" + amassReport + "]的内容:" + jmeterExecuteService.getRunningList(amassReport)); + } queue.clear(); super.teardownTest(context); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 8ba4dd4..73d434b 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,5 +1,6 @@ package io.metersphere.api.service; +import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; @@ -10,7 +11,6 @@ import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; -import org.springframework.kafka.core.KafkaTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -30,8 +30,6 @@ public class JmeterExecuteService { @Resource private JMeterService jMeterService; - @Resource - private KafkaTemplate kafkaTemplate; private static String url = null; // 记录所以执行中的请求/场景 @@ -134,6 +132,17 @@ public int getRunningTasks(String key) { return 0; } + public int getRunningSize() { + return this.runningTasks.size(); + } + + public String getRunningList(String key) { + if (this.runningTasks.containsKey(key)) { + return JSON.toJSONString(this.runningTasks.get(key)); + } + return null; + } + public void remove(String key, String value) { if (this.runningTasks.containsKey(key)) { this.runningTasks.get(key).remove(value); From 63bdcf9f5f7d275558a32f425e5947e68a696bdb Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 28 Sep 2021 16:46:28 +0800 Subject: [PATCH 114/157] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=89=A7=E8=A1=8C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 3 +++ .../metersphere/api/jmeter/APIBackendListenerClient.java | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java index 5212b82..f40f17e 100644 --- a/src/main/java/io/metersphere/Application.java +++ b/src/main/java/io/metersphere/Application.java @@ -1,4 +1,5 @@ package io.metersphere; + import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.JmeterProperties; import org.springframework.boot.SpringApplication; @@ -8,6 +9,7 @@ import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.EnableScheduling; @ServletComponentScan @EnableConfigurationProperties({ @@ -15,6 +17,7 @@ }) @PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) @SpringBootApplication +@EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 1da481f..5e01dac 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -162,7 +162,13 @@ public void teardownTest(BackendListenerContext context) throws Exception { } catch (Exception ex) { LogUtil.error("KAFKA 推送结果异常:[" + testId + "]" + ex.getMessage()); // 补偿一个结果防止持续Running - testResult.getScenarios().clear(); + if (testResult != null && testResult.getScenarios().size() > 0) { + for (ScenarioResult scenario : testResult.getScenarios()) { + if (scenario.getRequestResults() != null) { + scenario.getRequestResults().clear(); + } + } + } producerServer.send(JSON.toJSONString(testResult)); } LogUtil.info("接口收到集合报告ID:" + amassReport); From 710510a2cb986df7d2eef4ded6305788d7029203 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 30 Sep 2021 18:22:51 +0800 Subject: [PATCH 115/157] =?UTF-8?q?refactor(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=89=A7=E8=A1=8C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 3 +++ src/main/java/io/metersphere/api/service/MsKafkaListener.java | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 73d434b..e843461 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -82,6 +82,9 @@ public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] public String runStart(RunRequest runRequest) { try { + if (runRequest != null && StringUtils.isNotEmpty(runRequest.getAmassReport())) { + this.putRunningTasks(runRequest.getAmassReport(), runRequest.getTestId()); + } // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java index a945d1c..8425993 100644 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ b/src/main/java/io/metersphere/api/service/MsKafkaListener.java @@ -4,7 +4,6 @@ import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; @@ -24,9 +23,6 @@ public void consume(ConsumerRecord record, Acknowledgment ack) { try { if (record.value() != null) { RunRequest request = JSON.parseObject(record.value(), RunRequest.class); - if (StringUtils.isNotEmpty(request.getAmassReport())) { - jmeterExecuteService.putRunningTasks(request.getAmassReport(), request.getTestId()); - } jmeterExecuteService.runStart(request); } } catch (Exception e) { From 3ae69f4b524e5fd390a8d97f199c31860eb91305 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 28 Oct 2021 13:45:37 +0800 Subject: [PATCH 116/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8Dk8s=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=8F=91=E4=B8=8D=E5=87=BA=E6=B6=88=E6=81=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/controller/request/RunRequest.java | 5 +++++ .../io/metersphere/api/service/JmeterExecuteService.java | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java index 6efb04c..11b49b1 100644 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ b/src/main/java/io/metersphere/api/controller/request/RunRequest.java @@ -3,6 +3,8 @@ import io.metersphere.api.jmeter.utils.RunModeConfig; import lombok.Data; +import java.util.Map; + @Data public class RunRequest { private String testId; @@ -16,4 +18,7 @@ public class RunRequest { // 集成报告ID private String amassReport; private RunModeConfig config; + + private Map kafka; + } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index e843461..39e880e 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -30,6 +30,8 @@ public class JmeterExecuteService { @Resource private JMeterService jMeterService; + @Resource + private ProducerService producerService; private static String url = null; // 记录所以执行中的请求/场景 @@ -85,6 +87,13 @@ public String runStart(RunRequest runRequest) { if (runRequest != null && StringUtils.isNotEmpty(runRequest.getAmassReport())) { this.putRunningTasks(runRequest.getAmassReport(), runRequest.getTestId()); } + if (runRequest.getKafka() != null) { + LogUtil.info("KAFKA 信息:", JSON.toJSONString(runRequest.getKafka())); + String res = producerService.init(runRequest.getKafka()); + if (!"SUCCESS".equals(res)) { + return "KAFKA 初始化失败,请检查配置"; + } + } // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; From 7887cf8787ae8109e89bae49c12bd57d57341d60 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 9 Nov 2021 11:22:13 +0800 Subject: [PATCH 117/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=9B=9E=E4=BC=A0=E9=9B=86=E5=90=88?= =?UTF-8?q?=E6=8A=A5=E5=91=8Aid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/jmeter/APIBackendListenerClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 5e01dac..1f9485d 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -93,7 +93,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { TestResult testResult = new TestResult(); testResult.setTestId(testId); testResult.setTotal(queue.size()); - testResult.setSetReportId(this.setReportId); + testResult.setSetReportId(this.amassReport); testResult.setDebug(this.isDebug); testResult.setUserId(this.userId); testResult.setConsole(getConsole()); From 6b38de6aaeab1747019005b1f1c63ada29526118 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 9 Nov 2021 17:14:44 +0800 Subject: [PATCH 118/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/jmeter/JMeterService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 72d376b..0481472 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,7 +1,6 @@ package io.metersphere.api.jmeter; import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -69,7 +68,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - if (StringUtils.isNotEmpty(request.getReportId()) && ApiRunMode.API_PLAN.name().equals(request.getRunMode())) { + if (StringUtils.isNotEmpty(request.getReportId())) { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); } else { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); From a635893c879efd94a720744cc358d5f7fe044fa6 Mon Sep 17 00:00:00 2001 From: song-tianyang Date: Tue, 16 Nov 2021 15:37:30 +0800 Subject: [PATCH 119/157] =?UTF-8?q?fix(=E6=B5=8B=E8=AF=95=E8=AE=A1?= =?UTF-8?q?=E5=88=92=E6=8E=A5=E5=8F=A3=E6=89=A7=E8=A1=8C):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E6=97=B6testID=E8=B5=8B=E5=80=BC=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/api/jmeter/JMeterService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 0481472..72d376b 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,6 +1,7 @@ package io.metersphere.api.jmeter; import io.metersphere.api.controller.request.RunRequest; +import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.node.util.LogUtil; @@ -68,7 +69,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); } - if (StringUtils.isNotEmpty(request.getReportId())) { + if (StringUtils.isNotEmpty(request.getReportId()) && ApiRunMode.API_PLAN.name().equals(request.getRunMode())) { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); } else { arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); From 18cfc6dee4b9d27e26be8af97c73ff8842832360 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 17 Nov 2021 10:14:54 +0800 Subject: [PATCH 120/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=94=AF=E6=8C=81=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jmeter/assertions/DocumentUtils.java | 131 +++++++++++++ .../jmeter/assertions/JSONPathAssertion.java | 46 ++--- .../jmeter/assertions/XMLAssertion.java | 174 ++++++++++++++++++ 3 files changed, 325 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/apache/jmeter/assertions/DocumentUtils.java create mode 100644 src/main/java/org/apache/jmeter/assertions/XMLAssertion.java diff --git a/src/main/java/org/apache/jmeter/assertions/DocumentUtils.java b/src/main/java/org/apache/jmeter/assertions/DocumentUtils.java new file mode 100644 index 0000000..bc5f0a0 --- /dev/null +++ b/src/main/java/org/apache/jmeter/assertions/DocumentUtils.java @@ -0,0 +1,131 @@ +package org.apache.jmeter.assertions; + +import com.alibaba.fastjson.JSON; +import com.google.gson.Gson; +import io.metersphere.api.dto.definition.request.assertions.document.Condition; +import io.metersphere.api.dto.definition.request.assertions.document.ElementCondition; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.Pattern; + +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; + +public class DocumentUtils { + + public static boolean documentChecked(Object subj, String condition, ThreadLocal decimalFormatter) { + if (StringUtils.isNotEmpty(condition)) { + ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class); + boolean isTrue = true; + if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) { + for (Condition item : elementCondition.getConditions()) { + String expectedValue = ifValue(item.getValue()); + String resValue = objectToString(subj, decimalFormatter); + switch (item.getKey()) { + case "value_eq": + isTrue = StringUtils.equals(resValue, expectedValue); + break; + case "value_not_eq": + isTrue = !StringUtils.equals(resValue, expectedValue); + break; + case "value_in": + isTrue = StringUtils.contains(resValue, expectedValue); + break; + case "length_eq": + isTrue = getLength(subj, decimalFormatter) == getLength(item.getValue(), decimalFormatter); + break; + case "length_not_eq": + isTrue = getLength(subj, decimalFormatter) != getLength(item.getValue(), decimalFormatter); + break; + case "length_gt": + isTrue = getLength(subj, decimalFormatter) > getLength(item.getValue(), decimalFormatter); + break; + case "length_lt": + isTrue = getLength(subj, decimalFormatter) < getLength(item.getValue(), decimalFormatter); + break; + case "regular": + Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue); + isTrue = JMeterUtils.getMatcher().matches(resValue, pattern); + break; + } + if (!isTrue) { + break; + } + } + } + return isTrue; + } + return true; + } + + public static String objectToString(Object subj, ThreadLocal decimalFormatter) { + String str; + if (subj == null) { + str = "null"; + } else if (subj instanceof Map) { + str = new Gson().toJson(subj); + } else if (!(subj instanceof Double) && !(subj instanceof Float)) { + str = subj.toString(); + } else { + str = ((DecimalFormat) decimalFormatter.get()).format(subj); + } + + return str; + } + + + private static int getLength(Object value) { + if (value != null) { + if (value instanceof List) { + return ((List) value).size(); + } + return value.toString().length(); + } + return 0; + } + + private static String ifValue(Object value) { + if (value != null) { + return value.toString(); + } + return ""; + } + + private static int getLength(Object value, ThreadLocal decimalFormatter) { + if (value != null) { + if (value instanceof Map) { + return ((Map) value).size(); + } else if (value instanceof List) { + return ((List) value).size(); + } else if (!(value instanceof Double) && !(value instanceof Float)) { + return value.toString().length(); + } else { + return ((DecimalFormat) decimalFormatter.get()).format(value).length(); + } + } + return 0; + } + + + public static String documentMsg(Object resValue, String condition) { + String msg = ""; + if (StringUtils.isNotEmpty(condition)) { + ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class); + if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) { + for (Condition item : elementCondition.getConditions()) { + if (StringUtils.equalsAny(item.getKey(), "value_eq", "value_not_eq", "value_in")) { + msg = resValue != null ? resValue.toString() : ""; + } else if (StringUtils.equalsAny(item.getKey(), "length_eq", "length_not_eq", "length_gt", "length_lt")) { + msg = "长度是:" + getLength(resValue) + ""; + } else { + msg = resValue != null ? resValue.toString() : ""; + } + } + } + } + return msg; + } + +} diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index ef459e7..6b4bba0 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -5,7 +5,6 @@ package org.apache.jmeter.assertions; -import com.google.gson.Gson; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; import net.minidev.json.JSONArray; @@ -20,17 +19,16 @@ import java.io.Serializable; import java.text.DecimalFormat; -import java.util.Map; public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class); private static final long serialVersionUID = 2L; public static final String JSONPATH = "JSON_PATH"; - public static final String EXPECTEDVALUE = "EXPECTED_VALUE"; - public static final String JSONVALIDATION = "JSONVALIDATION"; + public static final String EXPECTED_VALUE = "EXPECTED_VALUE"; + public static final String JSON_VALIDATION = "JSONVALIDATION"; public static final String EXPECT_NULL = "EXPECT_NULL"; public static final String INVERT = "INVERT"; - public static final String ISREGEX = "ISREGEX"; + public static final String IS_REGEX = "ISREGEX"; private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); public JSONPathAssertion() { @@ -47,6 +45,10 @@ public String getOption() { return getPropertyAsString("ASS_OPTION"); } + public String getElementCondition() { + return getPropertyAsString("ElementCondition"); + } + public String getJsonPath() { return this.getPropertyAsString("JSON_PATH"); } @@ -64,7 +66,7 @@ public void setExpectedValue(String expectedValue) { } public void setJsonValidationBool(boolean jsonValidation) { - this.setProperty("JSONVALIDATION", jsonValidation); + this.setProperty(JSON_VALIDATION, jsonValidation); } public void setExpectNull(boolean val) { @@ -76,7 +78,7 @@ public boolean isExpectNull() { } public boolean isJsonValidationBool() { - return this.getPropertyAsBoolean("JSONVALIDATION"); + return this.getPropertyAsBoolean(JSON_VALIDATION); } public void setInvert(boolean invert) { @@ -88,11 +90,11 @@ public boolean isInvert() { } public void setIsRegex(boolean flag) { - this.setProperty("ISREGEX", flag); + this.setProperty(IS_REGEX, flag); } public boolean isUseRegex() { - return this.getPropertyAsBoolean("ISREGEX", true); + return this.getPropertyAsBoolean(IS_REGEX, true); } private void doAssert(String jsonString) { @@ -132,11 +134,14 @@ private void doAssert(String jsonString) { case "LT": msg = "Value < '%s', but found '%s'"; break; + case "DOCUMENT": + msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,实际返回:" + DocumentUtils.documentMsg(value, this.getElementCondition()); + break; } } else { msg = "Value expected to be '%s', but found '%s'"; } - throw new IllegalStateException(String.format(msg, this.getExpectedValue(), objectToString(value))); + throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter))); } } } @@ -176,7 +181,7 @@ private boolean isLt(String v1, String v2) { } private boolean isEquals(Object subj) { - String str = objectToString(subj); + String str = DocumentUtils.objectToString(subj, decimalFormatter); if (this.isUseRegex()) { Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue()); return JMeterUtils.getMatcher().matches(str, pattern); @@ -202,7 +207,9 @@ private boolean isEquals(Object subj) { case "LT": refFlag = isLt(str, getExpectedValue()); break; - + case "DOCUMENT": + refFlag = DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter); + break; } return refFlag; } @@ -210,6 +217,7 @@ private boolean isEquals(Object subj) { } } + public AssertionResult getResult(SampleResult samplerResult) { AssertionResult result = new AssertionResult(this.getName()); String responseData = samplerResult.getResponseDataAsString(); @@ -248,20 +256,6 @@ public AssertionResult getResult(SampleResult samplerResult) { } } - public static String objectToString(Object subj) { - String str; - if (subj == null) { - str = "null"; - } else if (subj instanceof Map) { - str = new Gson().toJson(subj); - } else if (!(subj instanceof Double) && !(subj instanceof Float)) { - str = subj.toString(); - } else { - str = ((DecimalFormat) decimalFormatter.get()).format(subj); - } - - return str; - } public void threadStarted() { } diff --git a/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java b/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java new file mode 100644 index 0000000..16f56eb --- /dev/null +++ b/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.assertions; + +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Predicate; +import net.minidev.json.JSONArray; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.json.JSONObject; +import org.json.XML; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; +import java.text.DecimalFormat; + +/** + * Checks if the result is a well-formed XML content using {@link XMLReader} + */ +public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { + private static final Logger log = LoggerFactory.getLogger(XMLAssertion.class); + private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(XMLAssertion::createDecimalFormat); + + private static final long serialVersionUID = 242L; + + public String getXmlPath() { + return this.getPropertyAsString("XML_PATH"); + } + + public String getExpectedValue() { + return this.getPropertyAsString("EXPECTED_VALUE"); + } + + public String getCondition() { + return getPropertyAsString("ElementCondition"); + } + + private static DecimalFormat createDecimalFormat() { + DecimalFormat decimalFormatter = new DecimalFormat("#.#"); + decimalFormatter.setMaximumFractionDigits(340); + decimalFormatter.setMinimumFractionDigits(1); + return decimalFormatter; + } + + // one builder for all requests in a thread + private static final ThreadLocal XML_READER = new ThreadLocal() { + @Override + protected XMLReader initialValue() { + try { + XMLReader reader = SAXParserFactory.newInstance() + .newSAXParser() + .getXMLReader(); + reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + return reader; + } catch (SAXException | ParserConfigurationException e) { + log.error("Error initializing XMLReader in XMLAssertion", e); + return null; + } + } + }; + + /** + * Returns the result of the Assertion. + * Here it checks whether the Sample data is XML. + * If so an AssertionResult containing a FailureMessage will be returned. + * Otherwise the returned AssertionResult will reflect the success of the Sample. + */ + @Override + public AssertionResult getResult(SampleResult response) { + // no error as default + AssertionResult result = new AssertionResult(getName()); + String resultData = response.getResponseDataAsString(); + if (resultData.length() == 0) { + return result.setResultForNull(); + } + result.setFailure(false); + XMLReader builder = XML_READER.get(); + if (builder != null) { + try { + builder.setErrorHandler(new LogErrorHandler()); + builder.parse(new InputSource(new StringReader(resultData))); + try { + JSONObject xmlJSONObj = XML.toJSONObject(resultData); + String jsonPrettyPrintString = xmlJSONObj.toString(4); + doAssert(jsonPrettyPrintString); + } catch (Exception e) { + result.setError(true); + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } + } catch (SAXException | IOException e) { + result.setError(true); + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } + } else { + result.setError(true); + result.setFailureMessage("Cannot initialize XMLReader in element:" + getName() + ", check jmeter.log file"); + } + return result; + } + + + private void doAssert(String jsonString) { + Object value = JsonPath.read(jsonString, this.getXmlPath(), new Predicate[0]); + if (value instanceof JSONArray) { + if (this.arrayMatched((JSONArray) value)) { + return; + } + } + if (!this.isEquals(value)) { + String msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,实际返回:" + DocumentUtils.documentMsg(value, this.getCondition()); + throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter))); + } + } + + + private boolean isEquals(Object subj) { + String str = DocumentUtils.objectToString(subj, decimalFormatter); + return DocumentUtils.documentChecked(str, this.getCondition(), decimalFormatter); + } + + private boolean arrayMatched(JSONArray value) { + if (value.isEmpty() && "[]".equals(this.getExpectedValue())) { + return true; + } else { + Object[] var2 = value.toArray(); + int var3 = var2.length; + + for (int var4 = 0; var4 < var3; ++var4) { + Object subj = var2[var4]; + if (subj == null || this.isEquals(subj)) { + return true; + } + } + + return this.isEquals(value); + } + } + + @Override + public void threadStarted() { + } + + @Override + public void threadFinished() { + XML_READER.remove(); + } +} From 718b906d04adf9160501d3a12940c03ac139c6fd Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 17 Nov 2021 10:49:58 +0800 Subject: [PATCH 121/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=94=AF=E6=8C=81=E6=96=AD=E8=A8=80?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/utils/Condition.java | 18 ++++++++++++++ .../api/jmeter/utils}/DocumentUtils.java | 4 +--- .../api/jmeter/utils/ElementCondition.java | 24 +++++++++++++++++++ .../jmeter/assertions/JSONPathAssertion.java | 1 + .../jmeter/assertions/XMLAssertion.java | 1 + 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/Condition.java rename src/main/java/{org/apache/jmeter/assertions => io/metersphere/api/jmeter/utils}/DocumentUtils.java (96%) create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java diff --git a/src/main/java/io/metersphere/api/jmeter/utils/Condition.java b/src/main/java/io/metersphere/api/jmeter/utils/Condition.java new file mode 100644 index 0000000..e6e4828 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/Condition.java @@ -0,0 +1,18 @@ +package io.metersphere.api.jmeter.utils; + +import lombok.Data; + +@Data +public class Condition { + private String key; + private Object value; + + public Condition() { + + } + + public Condition(String key, Object value) { + this.key = key; + this.value = value; + } +} diff --git a/src/main/java/org/apache/jmeter/assertions/DocumentUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java similarity index 96% rename from src/main/java/org/apache/jmeter/assertions/DocumentUtils.java rename to src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java index bc5f0a0..62e09c2 100644 --- a/src/main/java/org/apache/jmeter/assertions/DocumentUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java @@ -1,9 +1,7 @@ -package org.apache.jmeter.assertions; +package io.metersphere.api.jmeter.utils; import com.alibaba.fastjson.JSON; import com.google.gson.Gson; -import io.metersphere.api.dto.definition.request.assertions.document.Condition; -import io.metersphere.api.dto.definition.request.assertions.document.ElementCondition; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.util.JMeterUtils; diff --git a/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java b/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java new file mode 100644 index 0000000..f6a7044 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java @@ -0,0 +1,24 @@ +package io.metersphere.api.jmeter.utils; + +import lombok.Data; + +import java.util.List; + +@Data +public class ElementCondition { + private boolean include; + private boolean typeVerification; + private boolean arrayVerification; + List conditions; + + public ElementCondition() { + + } + + public ElementCondition(boolean include, boolean typeVerification, boolean arrayVerification, List conditions) { + this.include = include; + this.typeVerification = typeVerification; + this.arrayVerification = arrayVerification; + this.conditions = conditions; + } +} diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java index 6b4bba0..8b569a3 100644 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -7,6 +7,7 @@ import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; +import io.metersphere.api.jmeter.utils.DocumentUtils; import net.minidev.json.JSONArray; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.samplers.SampleResult; diff --git a/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java b/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java index 16f56eb..5db9ad9 100644 --- a/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java +++ b/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java @@ -19,6 +19,7 @@ import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Predicate; +import io.metersphere.api.jmeter.utils.DocumentUtils; import net.minidev.json.JSONArray; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.samplers.SampleResult; From a2aaa1785568b1bb32a914bd4391b39f6a7566fa Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 19 Nov 2021 20:22:37 +0800 Subject: [PATCH 122/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E6=94=AF=E6=8C=81=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/utils/FileUtils.java | 1 + .../api/service/JmeterExecuteService.java | 55 ++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index de94ce0..69c460c 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -9,6 +9,7 @@ public class FileUtils { public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; public static final String JAR_FILE_DIR = "/opt/metersphere/data/node/jar"; + public static final String JAR_PLUG_FILE_DIR = "/opt/metersphere/data/node/plug/jar"; public static void createFiles(MultipartFile[] bodyFiles, String path) { if (bodyFiles != null && bodyFiles.length > 0) { diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 39e880e..b8c6f62 100644 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -19,8 +19,10 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -59,6 +61,50 @@ private void loadJar(String path) { } } + private static File[] listJars(File dir) { + if (dir.isDirectory()) { + return dir.listFiles((f, name) -> { + if (name.endsWith(".jar")) {// $NON-NLS-1$ + File jar = new File(f, name); + return jar.isFile() && jar.canRead(); + } + return false; + }); + } + return new File[0]; + } + + private void loadPlugJar(String jarPath) { + File file = new File(jarPath); + if (file.isDirectory() && !jarPath.endsWith("/")) {// $NON-NLS-1$ + file = new File(jarPath + "/");// $NON-NLS-1$ + } + + File[] jars = listJars(file); + for (File jarFile : jars) { + // 从URLClassLoader类中获取类所在文件夹的方法,jar也可以认为是一个文件夹 + Method method = null; + try { + method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + } catch (NoSuchMethodException | SecurityException e1) { + e1.printStackTrace(); + } + // 获取方法的访问权限以便写回 + try { + method.setAccessible(true); + // 获取系统类加载器 + URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + + URL url = jarFile.toURI().toURL(); + //URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); + + method.invoke(classLoader, url); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] jarFiles) { if (request == null || request.getJmx() == null) { return "执行文件为空,无法执行!"; @@ -97,14 +143,21 @@ public String runStart(RunRequest runRequest) { // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; + String plugJarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/plug/jar"; if (StringUtils.isEmpty(url)) { - LogUtil.info("开始同步上传的第三方JAR:" + jarUrl); + LogUtil.info("开始同步上传的JAR:" + jarUrl); File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); this.loadJar(FileUtils.JAR_FILE_DIR); } + LogUtil.info("开始同步插件JAR:" + plugJarUrl); + File plugFile = ZipSpider.downloadFile(plugJarUrl, FileUtils.JAR_PLUG_FILE_DIR); + if (plugFile != null) { + ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); + this.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); + } } url = jarUrl; LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); From d9cf0bc2cf4068103491e0ba1ebbd655f23b1177 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 23 Nov 2021 16:08:17 +0800 Subject: [PATCH 123/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=E5=AE=9A=E6=97=B6=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/service/JmeterExecuteService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) mode change 100644 => 100755 src/main/java/io/metersphere/api/service/JmeterExecuteService.java diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java old mode 100644 new mode 100755 index b8c6f62..a8eddb5 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -36,6 +36,8 @@ public class JmeterExecuteService { private ProducerService producerService; private static String url = null; + private static String plugUrl = null; + // 记录所以执行中的请求/场景 private Map> runningTasks = new HashMap<>(); @@ -152,6 +154,8 @@ public String runStart(RunRequest runRequest) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); this.loadJar(FileUtils.JAR_FILE_DIR); } + } + if (StringUtils.isEmpty(plugUrl)) { LogUtil.info("开始同步插件JAR:" + plugJarUrl); File plugFile = ZipSpider.downloadFile(plugJarUrl, FileUtils.JAR_PLUG_FILE_DIR); if (plugFile != null) { @@ -160,6 +164,7 @@ public String runStart(RunRequest runRequest) { } } url = jarUrl; + plugUrl = plugJarUrl; LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); @@ -223,6 +228,12 @@ public void execute() { this.loadJar(FileUtils.JAR_FILE_DIR); FileUtils.deleteFile(file.getPath()); } + LogUtil.info("开始同步插件JAR:" + plugUrl); + File plugFile = ZipSpider.downloadFile(plugUrl, FileUtils.JAR_PLUG_FILE_DIR); + if (plugFile != null) { + ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); + this.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); + } } } } From 247edd53ee39a6614708f4a5e2699987b902df0e Mon Sep 17 00:00:00 2001 From: BugKing Date: Tue, 23 Nov 2021 17:02:43 +0800 Subject: [PATCH 124/157] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=E7=AE=80?= =?UTF-8?q?=E7=89=88readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e1ae25..a58eecc 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# metersphere-node-controller \ No newline at end of file +# MeterSphere Node-Controller 组件 +[MeterSphere](https://github.com/metersphere/metersphere) 是一站式开源持续测试平台,涵盖测试跟踪、接口测试、性能测试、团队协作等功能,兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。 + +该项目为 MeterSphere 配套的 Node-Controller 组件,该组件用于部署到资源池节点用作 MeterSphere 中接口测试测试及性能测试的执行机。 + + +## 问题反馈 + +如果您在使用过程中遇到什么问题,或有进一步的需求需要反馈,请提交 GitHub Issue 到 [MeterSphere 项目的主仓库](https://github.com/metersphere/metersphere/issues) From 640c81fee1524842d3396e137241867a4ff329dd Mon Sep 17 00:00:00 2001 From: BugKing Date: Tue, 23 Nov 2021 22:57:47 +0800 Subject: [PATCH 125/157] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fa4fced..3da96da 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.13 + 1.15 node-controller node-controller From db94fe0af9f1649e3cf6c618d1a26784c0a034ad Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 25 Nov 2021 16:56:19 +0800 Subject: [PATCH 126/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96):=20=20=E4=BF=AE=E5=A4=8D=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E6=A0=A1=E9=AA=8C=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/utils/DocumentUtils.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java index 62e09c2..c8cad89 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java @@ -19,7 +19,7 @@ public static boolean documentChecked(Object subj, String condition, ThreadLocal boolean isTrue = true; if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) { for (Condition item : elementCondition.getConditions()) { - String expectedValue = ifValue(item.getValue()); + String expectedValue = item.getValue() != null ? item.getValue().toString() : ""; String resValue = objectToString(subj, decimalFormatter); switch (item.getKey()) { case "value_eq": @@ -32,16 +32,16 @@ public static boolean documentChecked(Object subj, String condition, ThreadLocal isTrue = StringUtils.contains(resValue, expectedValue); break; case "length_eq": - isTrue = getLength(subj, decimalFormatter) == getLength(item.getValue(), decimalFormatter); + isTrue = getLength(subj, decimalFormatter) == numberOf(item.getValue()); break; case "length_not_eq": - isTrue = getLength(subj, decimalFormatter) != getLength(item.getValue(), decimalFormatter); + isTrue = getLength(subj, decimalFormatter) != numberOf(item.getValue()); break; case "length_gt": - isTrue = getLength(subj, decimalFormatter) > getLength(item.getValue(), decimalFormatter); + isTrue = getLength(subj, decimalFormatter) > numberOf(item.getValue()); break; case "length_lt": - isTrue = getLength(subj, decimalFormatter) < getLength(item.getValue(), decimalFormatter); + isTrue = getLength(subj, decimalFormatter) < numberOf(item.getValue()); break; case "regular": Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue); @@ -69,7 +69,6 @@ public static String objectToString(Object subj, ThreadLocal deci } else { str = ((DecimalFormat) decimalFormatter.get()).format(subj); } - return str; } @@ -84,13 +83,6 @@ private static int getLength(Object value) { return 0; } - private static String ifValue(Object value) { - if (value != null) { - return value.toString(); - } - return ""; - } - private static int getLength(Object value, ThreadLocal decimalFormatter) { if (value != null) { if (value instanceof Map) { @@ -106,6 +98,16 @@ private static int getLength(Object value, ThreadLocal decimalFor return 0; } + private static long numberOf(Object value) { + if (value != null) { + try { + return Long.parseLong(value.toString()); + } catch (Exception e) { + return 0; + } + } + return 0; + } public static String documentMsg(Object resValue, String condition) { String msg = ""; @@ -125,5 +127,4 @@ public static String documentMsg(Object resValue, String condition) { } return msg; } - } From 9fceb811359ac10c66d60f48fb29cc25467bed47 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 25 Nov 2021 16:57:43 +0800 Subject: [PATCH 127/157] =?UTF-8?q?fix=20(=E9=80=9A=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE):=20=E7=89=88=E6=9C=AC=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3da96da..edf9946 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - 1.15 + dev node-controller node-controller From 05a4945b3e35106ef70ec5fc4c3f3c207898c35f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 29 Nov 2021 18:47:56 +0800 Subject: [PATCH 128/157] =?UTF-8?q?fix=20(=E9=80=9A=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE):=20=E6=8F=92=E4=BB=B6jar=E5=90=8C=E6=AD=A5=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/utils/FileUtils.java | 21 +++++++++++++++++++ .../api/service/JmeterExecuteService.java | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 69c460c..4fd5378 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -30,10 +30,31 @@ public static void createFiles(MultipartFile[] bodyFiles, String path) { } } } + public static void deleteFile(String path) { File file = new File(path); if (file.exists()) { file.delete(); } } + + private static File[] getFiles(File dir) { + return dir.listFiles((f, name) -> { + File jar = new File(f, name); + return jar.isFile() && jar.canRead(); + }); + } + + + public static void deletePath(String path) { + File file = new File(path); + if (file.isDirectory()) {// $NON-NLS-1$ + file = new File(path + "/"); + } + + File[] files = getFiles(file); + for (int i = 0; i < files.length; i++) { + files[i].delete(); + } + } } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a8eddb5..a2671c5 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -228,10 +228,14 @@ public void execute() { this.loadJar(FileUtils.JAR_FILE_DIR); FileUtils.deleteFile(file.getPath()); } + // 清理历史jar + FileUtils.deletePath(FileUtils.JAR_PLUG_FILE_DIR); + LogUtil.info("开始同步插件JAR:" + plugUrl); File plugFile = ZipSpider.downloadFile(plugUrl, FileUtils.JAR_PLUG_FILE_DIR); if (plugFile != null) { ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); + FileUtils.deleteFile(file.getPath()); this.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); } } From 454b80f82f50df52f1b3e3710fe1a109994e854e Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 29 Nov 2021 19:15:46 +0800 Subject: [PATCH 129/157] =?UTF-8?q?fix=20(=E9=80=9A=E7=94=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE):=20=E6=8F=92=E4=BB=B6jar=E5=90=8C=E6=AD=A5=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/metersphere/api/service/JmeterExecuteService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a2671c5..0c605d9 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -222,6 +222,7 @@ public void remove(String key, String value) { @Scheduled(cron = "0 0/5 * * * ?") public void execute() { if (StringUtils.isNotEmpty(url)) { + FileUtils.deletePath(FileUtils.JAR_FILE_DIR); File file = ZipSpider.downloadFile(url, FileUtils.JAR_FILE_DIR); if (file != null) { ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); @@ -230,7 +231,6 @@ public void execute() { } // 清理历史jar FileUtils.deletePath(FileUtils.JAR_PLUG_FILE_DIR); - LogUtil.info("开始同步插件JAR:" + plugUrl); File plugFile = ZipSpider.downloadFile(plugUrl, FileUtils.JAR_PLUG_FILE_DIR); if (plugFile != null) { From 0ba2189a9ca353c8c1a945f17f6f1869425409df Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 30 Nov 2021 13:20:49 +0800 Subject: [PATCH 130/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E9=87=8D=E5=86=99HTTPSamplerProxy=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=AF=B9=20path=20=E5=A4=9A'/'=E5=92=8C=E5=B0=91'/'?= =?UTF-8?q?=E7=9A=84=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复依据来源QA群讨论结果 --- .../http/sampler/HTTPSamplerProxy.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java diff --git a/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java b/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java new file mode 100644 index 0000000..b95a166 --- /dev/null +++ b/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jmeter.protocol.http.sampler; + +import io.metersphere.node.util.LogUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.event.LoopIterationEvent; +import org.apache.jmeter.samplers.Interruptible; + +import java.net.URL; + +/** + * Proxy class that dispatches to the appropriate HTTP sampler. + *

+ * This class is stored in the test plan, and holds all the configuration settings. + * The actual implementation is created at run-time, and is passed a reference to this class + * so it can get access to all the settings stored by HTTPSamplerProxy. + */ +public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible { + + private static final long serialVersionUID = 1L; + + private transient HTTPAbstractImpl impl; + + public HTTPSamplerProxy() { + super(); + } + + /** + * Convenience method used to initialise the implementation. + * + * @param impl the implementation to use. + */ + public HTTPSamplerProxy(String impl) { + super(); + setImplementation(impl); + } + + protected String toExternalForm(URL u) { + int len = u.getProtocol().length() + 1; + if (u.getAuthority() != null && u.getAuthority().length() > 0) + len += 2 + u.getAuthority().length(); + if (u.getPath() != null) { + len += u.getPath().length(); + } + if (u.getQuery() != null) { + len += 1 + u.getQuery().length(); + } + if (u.getRef() != null) + len += 1 + u.getRef().length(); + + StringBuffer result = new StringBuffer(len); + result.append(u.getProtocol()); + result.append(":"); + if (u.getAuthority() != null && u.getAuthority().length() > 0) { + result.append("//"); + result.append(u.getAuthority()); + } + if (StringUtils.isNotEmpty(u.getPath())) { + int index = 0; + for (int i = 0; i < u.getPath().length(); i++) { + char ch = u.getPath().charAt(i); + if (String.valueOf(ch).equals("/")) { + index++; + } else { + break; + } + } + result.append("/" + u.getPath().substring(index)); + } + if (u.getQuery() != null) { + result.append('?'); + result.append(u.getQuery()); + } + if (u.getRef() != null) { + result.append("#"); + result.append(u.getRef()); + } + return result.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) { + // When Retrieve Embedded resources + Concurrent Pool is used + // as the instance of Proxy is cloned, we end up with impl being null + // testIterationStart will not be executed but it's not a problem for 51380 as it's download of resources + // so SSL context is to be reused + if (impl == null) { // Not called from multiple threads, so this is OK + try { + impl = HTTPSamplerFactory.getImplementation(getImplementation(), this); + } catch (Exception ex) { + return errorResult(ex, new HTTPSampleResult()); + } + } + try { + String url = toExternalForm(u); + u = new URL(url); + } catch (Exception ex) { + LogUtil.error(ex); + } + return impl.sample(u, method, areFollowingRedirect, depth); + } + + // N.B. It's not possible to forward threadStarted() to the implementation class. + // This is because Config items are not processed until later, and HTTPDefaults may define the implementation + + @Override + public void threadFinished() { + if (impl != null) { + impl.threadFinished(); // Forward to sampler + } + } + + @Override + public boolean interrupt() { + if (impl != null) { + return impl.interrupt(); // Forward to sampler + } + return false; + } + + /* (non-Javadoc) + * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) + */ + @Override + public void testIterationStart(LoopIterationEvent event) { + if (impl != null) { + impl.notifyFirstSampleAfterLoopRestart(); + } + } +} From e947779dc790686c66fed72a3e7ac030373ee4f0 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Tue, 30 Nov 2021 14:36:55 +0800 Subject: [PATCH 131/157] =?UTF-8?q?refactor:=20=E5=8E=BB=E6=8E=89=E4=B8=8D?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=B6=88=E6=81=AF=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/node/service/JmeterOperateService.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 5fc5bb3..1ece425 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -70,7 +70,6 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); - String resourceIndex = testRequest.getEnv().get("RESOURCE_INDEX"); dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback() { @@ -79,15 +78,9 @@ public void onComplete() { // 清理文件夹 try { if (DockerClientService.existContainer(dockerClient, containerId) > 0) { - // copyTestResources(dockerClient, containerId, reportId, resourceIndex); - DockerClientService.removeContainer(dockerClient, containerId); } - // 上传结束消息 - String[] contents = new String[]{reportId, "none", "0", "Remove container completed"}; - String log = StringUtils.join(contents, " "); - kafkaProducerService.sendMessage(topic, log); LogUtil.info("Remove container completed: " + containerId); } catch (Exception e) { LogUtil.error("Remove container error: ", e); From 6f805ab96f8fbe80469571626665d61aa9eb203f Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 30 Nov 2021 19:05:29 +0800 Subject: [PATCH 132/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E6=94=AF=E6=8C=81kafka=E6=AF=8F=E6=AC=A1?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E9=83=BD=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/ProducerController.java | 3 +- .../api/jmeter/APIBackendListenerClient.java | 10 ++++-- .../metersphere/api/jmeter/JMeterService.java | 8 +++-- .../api/service/JmeterExecuteService.java | 8 ++--- .../api/service/ProducerService.java | 33 +++++++++---------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/main/java/io/metersphere/api/controller/ProducerController.java b/src/main/java/io/metersphere/api/controller/ProducerController.java index 3d88387..8ae16f4 100644 --- a/src/main/java/io/metersphere/api/controller/ProducerController.java +++ b/src/main/java/io/metersphere/api/controller/ProducerController.java @@ -14,7 +14,8 @@ public class ProducerController { @PostMapping("/create") public String create(@RequestBody Map producerProps) { - return producerService.init(producerProps); + producerService.init(producerProps); + return "SUCCESS"; } } diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 1f9485d..ff90769 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -30,6 +30,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_ID = "ms.test.id"; + public final static String KAFKA_CONFIG = "ms.kafka.config"; + public final static String TEST_REPORT_ID = "ms.test.report.name"; public final static String AMASS_REPORT = "ms.test.amass.report.id"; @@ -59,6 +61,9 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private String setReportId; private String amassReport; + + private Map producerProps; + /** * 获得控制台内容 */ @@ -158,7 +163,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { } // 推送执行结果 try { - producerServer.send(JSON.toJSONString(testResult)); + producerServer.send(JSON.toJSONString(testResult), producerProps); } catch (Exception ex) { LogUtil.error("KAFKA 推送结果异常:[" + testId + "]" + ex.getMessage()); // 补偿一个结果防止持续Running @@ -169,7 +174,7 @@ public void teardownTest(BackendListenerContext context) throws Exception { } } } - producerServer.send(JSON.toJSONString(testResult)); + producerServer.send(JSON.toJSONString(testResult), producerProps); } LogUtil.info("接口收到集合报告ID:" + amassReport); @@ -271,6 +276,7 @@ private void setParam(BackendListenerContext context) { this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); + this.producerProps = JSON.parseObject(context.getParameter(KAFKA_CONFIG), Map.class); if (StringUtils.isBlank(this.runMode)) { this.runMode = ApiRunMode.RUN.name(); } diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 72d376b..810e4fe 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,5 +1,6 @@ package io.metersphere.api.jmeter; +import com.alibaba.fastjson.JSON; import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; @@ -17,6 +18,7 @@ import javax.annotation.Resource; import java.io.File; import java.lang.reflect.Field; +import java.util.Map; @Service public class JMeterService { @@ -55,7 +57,7 @@ public static HashTree getHashTree(Object scriptWrapper) throws Exception { } - private void addBackendListener(HashTree testPlan, RunRequest request) { + private void addBackendListener(HashTree testPlan, RunRequest request, Map producerProps) { BackendListener backendListener = new BackendListener(); if (StringUtils.isNotEmpty(request.getReportId())) { backendListener.setName(request.getReportId()); @@ -77,6 +79,8 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { if (StringUtils.isNotBlank(request.getRunMode())) { arguments.addArgument("runMode", request.getRunMode()); } + arguments.addArgument(APIBackendListenerClient.KAFKA_CONFIG, JSON.toJSONString(producerProps)); + arguments.addArgument("DEBUG", request.isDebug() ? "DEBUG" : "RUN"); arguments.addArgument("USER_ID", request.getUserId()); backendListener.setArguments(arguments); @@ -87,7 +91,7 @@ private void addBackendListener(HashTree testPlan, RunRequest request) { public void run(RunRequest request, HashTree testPlan) { try { init(); - addBackendListener(testPlan, request); + addBackendListener(testPlan, request, request.getKafka()); LocalRunner runner = new LocalRunner(testPlan); runner.run(request.getReportId()); } catch (Exception e) { diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 0c605d9..a267072 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -135,12 +135,8 @@ public String runStart(RunRequest runRequest) { if (runRequest != null && StringUtils.isNotEmpty(runRequest.getAmassReport())) { this.putRunningTasks(runRequest.getAmassReport(), runRequest.getTestId()); } - if (runRequest.getKafka() != null) { - LogUtil.info("KAFKA 信息:", JSON.toJSONString(runRequest.getKafka())); - String res = producerService.init(runRequest.getKafka()); - if (!"SUCCESS".equals(res)) { - return "KAFKA 初始化失败,请检查配置"; - } + if (runRequest.getKafka() == null) { + return "KAFKA 初始化失败,请检查配置"; } // 生成附件/JAR文件 URL urlObject = new URL(runRequest.getUrl()); diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java index c19894e..63b576c 100644 --- a/src/main/java/io/metersphere/api/service/ProducerService.java +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -1,6 +1,7 @@ package io.metersphere.api.service; import io.metersphere.api.config.KafkaConfig; +import io.metersphere.node.util.LogUtil; import org.apache.kafka.clients.producer.ProducerConfig; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -10,29 +11,27 @@ @Service public class ProducerService { - private KafkaTemplate kafkaTemplate; - public String init(Map producerProps) { + public KafkaTemplate init(Map producerProps) { try { - if (kafkaTemplate == null) { - producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); - KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); - this.kafkaTemplate = kafkaTemplate; - } - return "SUCCESS"; + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); + KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); + return kafkaTemplate; } catch (Exception e) { - return e.getMessage(); + LogUtil.error(e); + return null; } } - public void send(String message) { - if (this.kafkaTemplate != null) { - this.kafkaTemplate.send(KafkaConfig.TOPICS, message); - this.kafkaTemplate.flush(); + public void send(String message, Map producerProps) { + KafkaTemplate kafkaTemplate = this.init(producerProps); + if (kafkaTemplate != null) { + kafkaTemplate.send(KafkaConfig.TOPICS, message); + kafkaTemplate.flush(); } } } From 473950e999d21f85e1573022db881ae15c3beadf Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 30 Nov 2021 19:26:35 +0800 Subject: [PATCH 133/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/api/jmeter/APIBackendListenerClient.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index ff90769..955093c 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -163,7 +163,10 @@ public void teardownTest(BackendListenerContext context) throws Exception { } // 推送执行结果 try { + LogUtil.info("执行完成开始同步发送KAFKA【" + testResult.getTestId() + "】"); producerServer.send(JSON.toJSONString(testResult), producerProps); + LogUtil.info("同步发送报告信息到KAFKA完成【" + testResult.getTestId() + "】"); + } catch (Exception ex) { LogUtil.error("KAFKA 推送结果异常:[" + testId + "]" + ex.getMessage()); // 补偿一个结果防止持续Running From fdf49d25b1f2b2b7d0d94d57d177561767886b35 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 2 Dec 2021 12:42:40 +0800 Subject: [PATCH 134/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E9=87=8A=E6=94=BE=E7=BC=93=E5=AD=98=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APIBackendListenerClient.java | 8 +++ .../metersphere/api/jmeter/JMeterService.java | 2 + .../api/service/utils/ZipSpider.java | 53 ++++++++----------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 955093c..1cc1ed7 100644 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -34,6 +34,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl public final static String TEST_REPORT_ID = "ms.test.report.name"; + public final static String REPORT_ID = "ms.test.report.id"; + public final static String AMASS_REPORT = "ms.test.amass.report.id"; private final static String THREAD_SPLIT = " "; @@ -62,6 +64,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private String amassReport; + private String reportId; + private Map producerProps; /** @@ -111,6 +115,9 @@ public void teardownTest(BackendListenerContext context) throws Exception { if (StringUtils.isNotEmpty(setReportId) && !MessageCache.runningEngine.isEmpty()) { MessageCache.runningEngine.remove(setReportId); } + if (StringUtils.isNotEmpty(reportId) && !MessageCache.runningEngine.isEmpty()) { + MessageCache.runningEngine.remove(reportId); + } // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id final Map scenarios = new LinkedHashMap<>(); queue.forEach(result -> { @@ -276,6 +283,7 @@ private void setParam(BackendListenerContext context) { this.testId = context.getParameter(TEST_ID); this.setReportId = context.getParameter(TEST_REPORT_ID); this.amassReport = context.getParameter(AMASS_REPORT); + this.reportId = context.getParameter(REPORT_ID); this.runMode = context.getParameter("runMode"); this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; this.userId = context.getParameter("USER_ID"); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 810e4fe..5a06bf6 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -76,6 +76,8 @@ private void addBackendListener(HashTree testPlan, RunRequest request, Map files) { public static File downloadFile(String urlPath, String downloadDir) { OutputStream out = null; BufferedInputStream bin = null; + HttpURLConnection httpURLConnection = null; try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); - HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 @@ -217,10 +205,12 @@ public static File downloadFile(String urlPath, String downloadDir) { return file; } catch (MalformedURLException e) { // TODO Auto-generated catch block - e.printStackTrace(); + LogUtil.error(e); + } catch (IOException e) { // TODO Auto-generated catch block - e.printStackTrace(); + LogUtil.error(e); + LogUtil.info("文件下载失败!"); } finally { try { @@ -230,6 +220,9 @@ public static File downloadFile(String urlPath, String downloadDir) { if (out != null) { out.close(); } + if (httpURLConnection != null) { + httpURLConnection.disconnect(); + } } catch (Exception e) { } From bec17bc9137f582f5e3c1a70241cad99ef9b4ebd Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 2 Dec 2021 15:33:18 +0800 Subject: [PATCH 135/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E4=BF=AE=E5=A4=8D=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E4=B8=BA=E5=85=A8=E8=B7=AF=E5=BE=84=EF=BC=8C?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=B8=AD=E4=B8=8D=E9=85=8D=E7=BD=AEurl?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=89=A7=E8=A1=8C=E5=A4=9A=E5=87=BAhttp:/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1008494 --user=赵勇 [github#8244] url不能完全使用环境变量或参数形式 https://www.tapd.cn/55049933/s/1077354 --- .../apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java b/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java index b95a166..86feab0 100644 --- a/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java +++ b/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java @@ -112,6 +112,9 @@ protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedi } try { String url = toExternalForm(u); + if (StringUtils.isNotEmpty(url) && url.startsWith("http:/http")) { + url = url.substring(6); + } u = new URL(url); } catch (Exception ex) { LogUtil.error(ex); From f1501487cca050dd0938ca2736079a7e29f057b9 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 2 Dec 2021 22:27:22 +0800 Subject: [PATCH 136/157] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dkafka=E6=AF=8F?= =?UTF-8?q?=E6=AC=A1=E5=88=9D=E5=A7=8B=E5=8C=96=E5=A4=9A=E5=87=BA=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=BA=BF=E7=A8=8B=E9=97=AE=E9=A2=98=EF=BC=8C=E6=8C=89?= =?UTF-8?q?=E7=85=A7kafka=E5=9C=B0=E5=9D=80=E7=BC=93=E5=AD=98=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=EF=BC=8C=E9=81=BF=E5=85=8D=E9=87=8D=E5=A4=8D=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/ConsumerController.java | 55 +------------------ .../api/service/MsKafkaListener.java | 35 ------------ .../api/service/ProducerService.java | 23 +++++--- 3 files changed, 17 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/service/MsKafkaListener.java diff --git a/src/main/java/io/metersphere/api/controller/ConsumerController.java b/src/main/java/io/metersphere/api/controller/ConsumerController.java index 6cf2981..27c9166 100644 --- a/src/main/java/io/metersphere/api/controller/ConsumerController.java +++ b/src/main/java/io/metersphere/api/controller/ConsumerController.java @@ -1,59 +1,17 @@ package io.metersphere.api.controller; - -import io.metersphere.api.service.MsKafkaListener; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Scope; -import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; -import org.springframework.kafka.config.KafkaListenerEndpointRegistry; -import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; import java.util.Map; @RestController @RequestMapping("consumer") public class ConsumerController { - /** - * 应用程序上行文 - */ - @Resource - ApplicationContext context; - /** - * 监听器容器工厂 - */ - @Resource - ConcurrentKafkaListenerContainerFactory containerFactory; - /** - * 所有@KafkaListener这个注解所标注的方法都会被注册在这里面中 - */ - @Resource - KafkaListenerEndpointRegistry registry; - - private MsKafkaListener listener; /** * 创建消费者分组 */ @PostMapping("/create") public void create(@RequestBody Map consumerProps) { - try { - if (consumerProps != null && consumerProps.size() > 0 && listener == null) { - consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); - consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - // 设置监听器容器工厂 - containerFactory.setConsumerFactory(new DefaultKafkaConsumerFactory<>(consumerProps)); - // 获取监听类实例 - listener = context.getBean(MsKafkaListener.class); - } - } catch (Exception e) { - } + } /** @@ -61,17 +19,6 @@ public void create(@RequestBody Map consumerProps) { */ @GetMapping("/stop") public void stop() { - registry.getListenerContainers().forEach(container -> { - container.stop(); - }); - } - /** - * 获取监听类实例 - */ - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public MsKafkaListener listener() { - return new MsKafkaListener(); } } diff --git a/src/main/java/io/metersphere/api/service/MsKafkaListener.java b/src/main/java/io/metersphere/api/service/MsKafkaListener.java deleted file mode 100644 index 8425993..0000000 --- a/src/main/java/io/metersphere/api/service/MsKafkaListener.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.metersphere.api.service; -import com.alibaba.fastjson.JSON; -import io.metersphere.api.config.KafkaConfig; -import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; -import io.metersphere.node.util.LogUtil; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.kafka.support.Acknowledgment; - -public class MsKafkaListener { - - public static final String CONSUME_ID = "ms-api-automation-consume"; - - private JmeterExecuteService jmeterExecuteService; - - @KafkaListener(id = CONSUME_ID, topics = KafkaConfig.EXEC_TOPIC, groupId = "${spring.kafka.consumer.group-id}") - public void consume(ConsumerRecord record, Acknowledgment ack) { - LogUtil.info("接收到执行执行请求开始处理"); - if (jmeterExecuteService == null) { - jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - } - try { - if (record.value() != null) { - RunRequest request = JSON.parseObject(record.value(), RunRequest.class); - jmeterExecuteService.runStart(request); - } - } catch (Exception e) { - LogUtil.error(e.getMessage()); - } finally { - ack.acknowledge(); - } - LogUtil.info("执行执行请求处理结束"); - } -} diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java index 63b576c..41abbff 100644 --- a/src/main/java/io/metersphere/api/service/ProducerService.java +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -8,19 +8,28 @@ import org.springframework.stereotype.Service; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; @Service public class ProducerService { + // 初始化不同地址kafka,每个地址初始化一个线程 + private Map kafkaTemplateMap = new ConcurrentHashMap<>(); public KafkaTemplate init(Map producerProps) { try { - producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringSerializer"); - DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); - KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); - return kafkaTemplate; + Object serverUrl = producerProps.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG); + if (serverUrl != null && kafkaTemplateMap.containsKey(serverUrl.toString())) { + return kafkaTemplateMap.get(serverUrl); + } else { + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + "org.apache.kafka.common.serialization.StringSerializer"); + DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(producerProps); + KafkaTemplate kafkaTemplate = new KafkaTemplate(pf, true); + kafkaTemplateMap.put(serverUrl.toString(), kafkaTemplate); + return kafkaTemplate; + } } catch (Exception e) { LogUtil.error(e); return null; From dad66f42d0e66d4f150051e49af95a8f5dbec349 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 6 Dec 2021 10:33:35 +0800 Subject: [PATCH 137/157] =?UTF-8?q?fix=20(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E4=BF=AE=E5=A4=8D=E8=AF=81=E4=B9=A6=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E5=AF=BC=E8=87=B4=E9=80=89=E6=8B=A9=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E6=B1=A0=E6=89=A7=E8=A1=8C=E6=B5=8B=E8=AF=95=E6=97=B6=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E8=84=9A=E6=9C=AC=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --bug=1008571 --user=赵勇 证书问题导致选择资源池执行测试时下载脚本失败 https://www.tapd.cn/55049933/s/1077829 --- .../api/service/utils/ZipSpider.java | 138 ++++++------------ 1 file changed, 46 insertions(+), 92 deletions(-) diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index e77abbd..72d8c21 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -6,80 +6,60 @@ import org.apache.jmeter.protocol.http.util.HTTPFileArg; import org.apache.jorphan.collections.HashTree; +import javax.net.ssl.*; import java.io.*; import java.net.*; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class ZipSpider { - //根据网址返回网页源代码 - public static String getHtmlFromUrl(String url, String encoding) { - StringBuffer html = new StringBuffer(); - InputStreamReader isr = null; - BufferedReader buf = null; - String str = null; - try { - URL urlObj = new URL(url); - URLConnection con = urlObj.openConnection(); - isr = new InputStreamReader(con.getInputStream(), encoding); - buf = new BufferedReader(isr); - while ((str = buf.readLine()) != null) { - html.append(str + "\n"); - } - //sop(html.toString()); - } catch (Exception e) { - LogUtil.error(e); - } finally { - if (isr != null) { - try { - buf.close(); - isr.close(); - } catch (IOException e) { - LogUtil.error(e); + /** + * 覆盖java默认的证书验证 + */ + private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[]{}; + } - } - } + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { } - return html.toString(); - } - //根据网址下载网络文件到硬盘,包括图片,Gif图,以及压缩包 - public static void download(String url, String path) { - File file = null; - FileOutputStream fos = null; - String downloadName = url.substring(url.lastIndexOf("/") + 1); - HttpURLConnection httpCon = null; - URLConnection con = null; - URL urlObj = null; - InputStream in = null; - byte[] size = new byte[1024]; - int num = 0; + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + } + }}; + + /** + * 设置不验证主机 + */ + private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + /** + * 信任所有 + * + * @param connection + * @return + */ + private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) { + SSLSocketFactory oldFactory = connection.getSSLSocketFactory(); try { - file = new File(path + downloadName); - fos = new FileOutputStream(file); - if (url.startsWith("http")) { - urlObj = new URL(url); - con = urlObj.openConnection(); - httpCon = (HttpURLConnection) con; - in = httpCon.getInputStream(); - while ((num = in.read(size)) != -1) { - for (int i = 0; i < num; i++) - fos.write(size[i]); - } - } - + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + SSLSocketFactory newFactory = sc.getSocketFactory(); + connection.setSSLSocketFactory(newFactory); } catch (Exception e) { - LogUtil.error(e); - } finally { - try { - in.close(); - fos.close(); - } catch (Exception e) { - LogUtil.error(e); - } + e.printStackTrace(); } + return oldFactory; } //解压本地文件至目的文件路径 @@ -110,34 +90,6 @@ public static void unzip(String fromFile, String toFile) { } } - //从总目录下解压文件里所有的压缩包至目的文件路径 - public static void unzipFromLoc(String filePath) throws Exception { - File file = new File(filePath); - File[] list = file.listFiles(); - String from = ""; - String to = "E:\\myDownload\\unzipFileFromWeb\\"; - for (File f : list) { - boolean bool = f.isFile(); - if (bool) { - from = f.getAbsolutePath(); - from = from.replace("\\", "\\\\"); - sop(from); - unzip(from, to); - } - } - } - - public static void sop(Object obj) { - LogUtil.info(obj); - } - - public static void seperate(char c) { - for (int x = 0; x < 100; x++) { - System.out.print(c); - } - sop(""); - } - public static void getFiles(HashTree tree, List files) { for (Object key : tree.keySet()) { HashTree node = tree.get(key); @@ -174,6 +126,12 @@ public static File downloadFile(String urlPath, String downloadDir) { try { URL url = new URL(urlPath); URLConnection urlConnection = url.openConnection(); + boolean useHttps = urlPath.startsWith("https"); + if (useHttps) { + HttpsURLConnection https = (HttpsURLConnection) urlConnection; + trustAllHosts(https); + https.setHostnameVerifier(DO_NOT_VERIFY); + } httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET @@ -204,13 +162,9 @@ public static File downloadFile(String urlPath, String downloadDir) { LogUtil.info("文件下载成功!"); return file; } catch (MalformedURLException e) { - // TODO Auto-generated catch block LogUtil.error(e); - } catch (IOException e) { - // TODO Auto-generated catch block LogUtil.error(e); - LogUtil.info("文件下载失败!"); } finally { try { From 5c76cd40f2f72980edc4b9af5924df5121d9bfc4 Mon Sep 17 00:00:00 2001 From: BugKing Date: Mon, 6 Dec 2021 17:28:48 +0800 Subject: [PATCH 138/157] docs: update license --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From fc6b661429ba1fda97f041aefaade87e8b48cc6b Mon Sep 17 00:00:00 2001 From: CaptainB Date: Thu, 9 Dec 2021 13:41:17 +0800 Subject: [PATCH 139/157] build: spring boot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index edf9946..14094fb 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.5.4 + 2.6.1 io.metersphere From 6db52e28cf21b6bcf3a20b1cb14e3b1512a284e5 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 9 Dec 2021 18:38:43 +0800 Subject: [PATCH 140/157] =?UTF-8?q?refactor=20(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E5=AE=8C=E6=88=90=E6=89=A7=E8=A1=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 208 +-- .../api/config/AppStartListener.java | 6 +- .../controller/JmeterExecuteController.java | 15 +- .../api/controller/request/RunRequest.java | 24 - .../api/jmeter/APIBackendListenerClient.java | 306 --- .../api/jmeter/APIBackendListenerHandler.java | 57 + .../api/jmeter/GroovyLoadJarService.java | 32 + .../metersphere/api/jmeter/JMeterService.java | 64 +- .../io/metersphere/api/jmeter/JMeterVars.java | 115 -- .../metersphere/api/jmeter/LocalRunner.java | 48 - .../api/jmeter/constants/ApiRunMode.java | 5 - .../api/jmeter/constants/RequestType.java | 12 - .../api/jmeter/utils/Condition.java | 18 - .../api/jmeter/utils/DocumentUtils.java | 130 -- .../api/jmeter/utils/ElementCondition.java | 24 - .../api/jmeter/utils/FileUtils.java | 4 +- .../api/jmeter/utils/MessageCache.java | 10 - .../api/jmeter/utils/RunModeConfig.java | 11 - .../metersphere/api/module/RequestResult.java | 51 - .../api/module/ResponseAssertionResult.java | 13 - .../api/module/ResponseResult.java | 32 - .../api/module/ScenarioResult.java | 50 - .../io/metersphere/api/module/TestResult.java | 63 - .../api/service/JmeterExecuteService.java | 146 +- .../api/service/ProducerService.java | 4 +- .../api/service/utils/ZipSpider.java | 19 +- .../handler/ResultResponseBodyAdvice.java | 4 +- .../node/service/JmeterOperateService.java | 22 +- .../metersphere/node/util/CompressUtils.java | 6 +- .../io/metersphere/node/util/LogUtil.java | 261 --- .../java/org/apache/jmeter/NewDriver.java | 378 ---- .../jmeter/assertions/JSONPathAssertion.java | 267 --- .../jmeter/assertions/XMLAssertion.java | 175 -- .../org/apache/jmeter/config/CSVDataSet.java | 346 ---- .../apache/jmeter/config/KeystoreConfig.java | 174 -- .../http/sampler/HTTPSamplerProxy.java | 152 -- .../apache/jmeter/reporters/ResultAction.java | 98 - .../apache/jmeter/samplers/SampleResult.java | 1644 ----------------- .../jmeter/util/JSR223BeanInfoSupport.java | 93 - .../apache/jmeter/util/JSR223TestElement.java | 389 ---- .../org/apache/jmeter/util/SSLManager.java | 378 ---- .../apache/jorphan/collections/HashTree.java | 1095 ----------- 42 files changed, 198 insertions(+), 6751 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/controller/request/RunRequest.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java create mode 100644 src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java create mode 100644 src/main/java/io/metersphere/api/jmeter/GroovyLoadJarService.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/JMeterVars.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/LocalRunner.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/constants/RequestType.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/utils/Condition.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java delete mode 100644 src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java delete mode 100644 src/main/java/io/metersphere/api/module/RequestResult.java delete mode 100644 src/main/java/io/metersphere/api/module/ResponseAssertionResult.java delete mode 100644 src/main/java/io/metersphere/api/module/ResponseResult.java delete mode 100644 src/main/java/io/metersphere/api/module/ScenarioResult.java delete mode 100644 src/main/java/io/metersphere/api/module/TestResult.java delete mode 100644 src/main/java/io/metersphere/node/util/LogUtil.java delete mode 100644 src/main/java/org/apache/jmeter/NewDriver.java delete mode 100644 src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java delete mode 100644 src/main/java/org/apache/jmeter/assertions/XMLAssertion.java delete mode 100644 src/main/java/org/apache/jmeter/config/CSVDataSet.java delete mode 100644 src/main/java/org/apache/jmeter/config/KeystoreConfig.java delete mode 100644 src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java delete mode 100644 src/main/java/org/apache/jmeter/reporters/ResultAction.java delete mode 100644 src/main/java/org/apache/jmeter/samplers/SampleResult.java delete mode 100644 src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java delete mode 100644 src/main/java/org/apache/jmeter/util/JSR223TestElement.java delete mode 100644 src/main/java/org/apache/jmeter/util/SSLManager.java delete mode 100644 src/main/java/org/apache/jorphan/collections/HashTree.java diff --git a/pom.xml b/pom.xml index edf9946..8358f7e 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,12 @@ + + io.metersphere + metersphere-jmeter-core + 1.0.0 + + org.flywaydb flyway-core @@ -84,11 +90,6 @@ 5.0.3 - - org.apache.commons - commons-collections4 - 4.1 - org.apache.commons commons-text @@ -111,22 +112,7 @@ springdoc-openapi-ui 1.5.6 - - - org.apache.jmeter - ApacheJMeter_http - ${jmeter.version} - - - org.apache.logging.log4j - log4j-slf4j-impl - - - xstream - com.thoughtworks.xstream - - - + com.thoughtworks.xstream @@ -134,103 +120,6 @@ 1.4.17 - - org.python - jython-standalone - 2.7.0 - - - - org.apache.jmeter - ApacheJMeter_functions - ${jmeter.version} - - - - org.apache.jmeter - ApacheJMeter_jdbc - ${jmeter.version} - - - - org.apache.jmeter - ApacheJMeter_tcp - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_java - ${jmeter.version} - - - - com.microsoft.sqlserver - mssql-jdbc - 7.4.1.jre8 - - - - org.postgresql - postgresql - 42.2.14 - - - - com.oracle.database.jdbc - ojdbc8 - 19.7.0.0 - - - - - org.apache.dubbo - dubbo - ${dubbo.version} - - - spring-context - org.springframework - - - - - org.apache.zookeeper - zookeeper - 3.4.14 - - - slf4j-log4j12 - org.slf4j - - - - - org.apache.curator - curator-framework - 4.0.1 - - - org.apache.curator - curator-recipes - 4.0.1 - - - - - org.apache.dubbo - dubbo-registry-nacos - ${dubbo.version} - - - com.alibaba.nacos - nacos-api - ${nacos.version} - - - com.alibaba.nacos - nacos-client - ${nacos.version} - com.alibaba @@ -238,12 +127,6 @@ 2.1.7 - - io.metersphere - jmeter-plugins-dubbo - 2.7.12 - - org.springframework.boot @@ -257,12 +140,6 @@ 2.0.24 - - - org.codehaus.groovy - groovy-jsr223 - - org.mozilla @@ -305,17 +182,6 @@ 2.24 - - org.json - json - 20171018 - - - - com.aliyun - alibaba-dingtalk-service-sdk - 1.0.1 - org.apache.httpcomponents httpclient @@ -337,76 +203,18 @@ json-schema-validator 2.2.6 - - - org.apache.jmeter - ApacheJMeter_bolt - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_jms - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_ftp - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_junit - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_ldap - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_mail - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_components - ${jmeter.version} - - - org.apache.jmeter - ApacheJMeter_native - ${jmeter.version} - - - io.metersphere - metersphere-jmeter-functions - ${metersphere-jmeter-functions.version} - - - - com.jayway.jsonpath - json-path - 2.4.0 - org.aspectj aspectjweaver 1.9.6 - - commons-collections - commons-collections - 3.2.2 - + org.springframework.kafka spring-kafka - - diff --git a/src/main/java/io/metersphere/api/config/AppStartListener.java b/src/main/java/io/metersphere/api/config/AppStartListener.java index 6d2e6db..8c3c044 100644 --- a/src/main/java/io/metersphere/api/config/AppStartListener.java +++ b/src/main/java/io/metersphere/api/config/AppStartListener.java @@ -3,7 +3,7 @@ import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.apache.jmeter.NewDriver; import org.python.core.Options; import org.python.util.PythonInterpreter; @@ -47,7 +47,7 @@ private void initPythonEnv() { interp.exec("sys.path.append(\"" + path + "\")"); } catch (Exception e) { e.printStackTrace(); - LogUtil.error(e.getMessage(), e); + LoggerUtil.error(e.getMessage(), e); } } @@ -58,7 +58,7 @@ private void loadJars() { try { NewDriver.addPath(FileUtils.JAR_FILE_DIR); } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); + LoggerUtil.error(e.getMessage(), e); MSException.throwException(e.getMessage()); } } diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index ffcdab3..a3a7674 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -1,14 +1,12 @@ package io.metersphere.api.controller; import com.alibaba.fastjson.JSON; -import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.LocalRunner; import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.JvmService; -import io.metersphere.node.util.LogUtil; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.jmeter.LocalRunner; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.List; @@ -20,15 +18,8 @@ public class JmeterExecuteController { @Resource private JmeterExecuteService jmeterExecuteService; - @PostMapping(value = "/api/run", consumes = {"multipart/form-data"}) - public String apiRun(@RequestParam(value = "files") MultipartFile[] bodyFiles, @RequestParam(value = "jarFiles") MultipartFile[] jarFiles, String request) { - LogUtil.info("接收到测试请求 start "); - RunRequest runRequest = JSON.parseObject(request, RunRequest.class); - return jmeterExecuteService.run(runRequest, bodyFiles, jarFiles); - } - @PostMapping(value = "/api/start") - public String apiStartRun(@RequestBody RunRequest runRequest) { + public String apiStartRun(@RequestBody JmeterRunRequestDTO runRequest) { System.out.println("接收到测试请求: " + JSON.toJSONString(runRequest)); return jmeterExecuteService.runStart(runRequest); } diff --git a/src/main/java/io/metersphere/api/controller/request/RunRequest.java b/src/main/java/io/metersphere/api/controller/request/RunRequest.java deleted file mode 100644 index 11b49b1..0000000 --- a/src/main/java/io/metersphere/api/controller/request/RunRequest.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.metersphere.api.controller.request; - -import io.metersphere.api.jmeter.utils.RunModeConfig; -import lombok.Data; - -import java.util.Map; - -@Data -public class RunRequest { - private String testId; - // api / case 或有这个属性值 - private String reportId; - private String url; - private String userId; - private boolean isDebug; - private String runMode; - private String jmx; - // 集成报告ID - private String amassReport; - private RunModeConfig config; - - private Map kafka; - -} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java deleted file mode 100644 index 1cc1ed7..0000000 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ /dev/null @@ -1,306 +0,0 @@ -package io.metersphere.api.jmeter; - -import com.alibaba.fastjson.JSON; -import io.metersphere.api.jmeter.constants.ApiRunMode; -import io.metersphere.api.jmeter.constants.RequestType; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; -import io.metersphere.api.jmeter.utils.MessageCache; -import io.metersphere.api.module.*; -import io.metersphere.api.service.JmeterExecuteService; -import io.metersphere.api.service.ProducerService; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.jmeter.assertions.AssertionResult; -import org.apache.jmeter.protocol.http.sampler.HTTPSampleResult; -import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; -import org.apache.jmeter.visualizers.backend.BackendListenerContext; -import org.springframework.http.HttpMethod; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.io.Serializable; -import java.util.*; - -/** - * JMeter BackendListener扩展, jmx脚本中使用 - */ -public class APIBackendListenerClient extends AbstractBackendListenerClient implements Serializable { - - public final static String TEST_ID = "ms.test.id"; - - public final static String KAFKA_CONFIG = "ms.kafka.config"; - - public final static String TEST_REPORT_ID = "ms.test.report.name"; - - public final static String REPORT_ID = "ms.test.report.id"; - - public final static String AMASS_REPORT = "ms.test.amass.report.id"; - - private final static String THREAD_SPLIT = " "; - - private final static String ID_SPLIT = "-"; - - private final List queue = new ArrayList<>(); - - private String runMode = ApiRunMode.RUN.name(); - - private String userId; - - private boolean isDebug; - - private JmeterExecuteService jmeterExecuteService; - private ProducerService producerServer; - /** - * 测试ID - */ - private String testId; - - /** - * 只有合并报告是这个有值 - */ - private String setReportId; - - private String amassReport; - - private String reportId; - - private Map producerProps; - - /** - * 获得控制台内容 - */ - private PrintStream oldPrintStream = System.out; - private ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - private void setConsole() { - // 设置新的out - System.setOut(new PrintStream(bos)); - } - - private String getConsole() { - System.setOut(oldPrintStream); - return bos.toString(); - } - - @Override - public void setupTest(BackendListenerContext context) throws Exception { - setConsole(); - setParam(context); - super.setupTest(context); - } - - - @Override - public void handleSampleResults(List sampleResults, BackendListenerContext context) { - queue.addAll(sampleResults); - } - - @Override - public void teardownTest(BackendListenerContext context) throws Exception { - TestResult testResult = new TestResult(); - testResult.setTestId(testId); - testResult.setTotal(queue.size()); - testResult.setSetReportId(this.amassReport); - testResult.setDebug(this.isDebug); - testResult.setUserId(this.userId); - testResult.setConsole(getConsole()); - jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - producerServer = CommonBeanFactory.getBean(ProducerService.class); - try { - if (StringUtils.isNotEmpty(testId) && !MessageCache.runningEngine.isEmpty()) { - MessageCache.runningEngine.remove(testId); - } - if (StringUtils.isNotEmpty(setReportId) && !MessageCache.runningEngine.isEmpty()) { - MessageCache.runningEngine.remove(setReportId); - } - if (StringUtils.isNotEmpty(reportId) && !MessageCache.runningEngine.isEmpty()) { - MessageCache.runningEngine.remove(reportId); - } - // 一个脚本里可能包含多个场景(ThreadGroup),所以要区分开,key: 场景Id - final Map scenarios = new LinkedHashMap<>(); - queue.forEach(result -> { - // 线程名称: <场景名> <场景Index>-<请求Index>, 例如:Scenario 2-1 - if (StringUtils.equals(result.getSampleLabel(), "RunningDebugSampler")) { - testResult.setRunningDebugSampler(result.getResponseDataAsString()); - } else { - String scenarioName = StringUtils.substringBeforeLast(result.getThreadName(), THREAD_SPLIT); - String index = StringUtils.substringAfterLast(result.getThreadName(), THREAD_SPLIT); - String scenarioId = StringUtils.substringBefore(index, ID_SPLIT); - ScenarioResult scenarioResult; - if (!scenarios.containsKey(scenarioId)) { - scenarioResult = new ScenarioResult(); - try { - scenarioResult.setId(Integer.parseInt(scenarioId)); - } catch (Exception e) { - scenarioResult.setId(0); - LogUtil.error("场景ID转换异常: " + e.getMessage()); - } - scenarioResult.setName(scenarioName); - scenarios.put(scenarioId, scenarioResult); - } else { - scenarioResult = scenarios.get(scenarioId); - } - - if (result.isSuccessful()) { - scenarioResult.addSuccess(); - testResult.addSuccess(); - } else { - scenarioResult.addError(result.getErrorCount()); - testResult.addError(result.getErrorCount()); - } - - RequestResult requestResult = getRequestResult(result); - scenarioResult.getRequestResults().add(requestResult); - scenarioResult.addResponseTime(result.getTime()); - - testResult.addPassAssertions(requestResult.getPassAssertions()); - testResult.addTotalAssertions(requestResult.getTotalAssertions()); - - scenarioResult.addPassAssertions(requestResult.getPassAssertions()); - scenarioResult.addTotalAssertions(requestResult.getTotalAssertions()); - } - }); - testResult.getScenarios().addAll(scenarios.values()); - testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); - testResult.setRunMode(this.runMode); - } catch (Exception e) { - LogUtil.error("处理执行数据异常:" + e.getMessage()); - } - // 推送执行结果 - try { - LogUtil.info("执行完成开始同步发送KAFKA【" + testResult.getTestId() + "】"); - producerServer.send(JSON.toJSONString(testResult), producerProps); - LogUtil.info("同步发送报告信息到KAFKA完成【" + testResult.getTestId() + "】"); - - } catch (Exception ex) { - LogUtil.error("KAFKA 推送结果异常:[" + testId + "]" + ex.getMessage()); - // 补偿一个结果防止持续Running - if (testResult != null && testResult.getScenarios().size() > 0) { - for (ScenarioResult scenario : testResult.getScenarios()) { - if (scenario.getRequestResults() != null) { - scenario.getRequestResults().clear(); - } - } - } - producerServer.send(JSON.toJSONString(testResult), producerProps); - } - LogUtil.info("接口收到集合报告ID:" + amassReport); - - if (StringUtils.isNotEmpty(amassReport)) { - jmeterExecuteService.remove(amassReport, testId); - LogUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); - LogUtil.info("正在执行中的场景[" + amassReport + "]的数量:" + jmeterExecuteService.getRunningTasks(amassReport)); - LogUtil.info("正在执行中的场景[" + amassReport + "]的内容:" + jmeterExecuteService.getRunningList(amassReport)); - } - queue.clear(); - super.teardownTest(context); - } - - private RequestResult getRequestResult(SampleResult result) { - RequestResult requestResult = new RequestResult(); - requestResult.setId(result.getSamplerId()); - requestResult.setResourceId(result.getResourceId()); - requestResult.setName(result.getSampleLabel()); - requestResult.setUrl(result.getUrlAsString()); - requestResult.setMethod(getMethod(result)); - requestResult.setBody(result.getSamplerData()); - requestResult.setHeaders(result.getRequestHeaders()); - requestResult.setRequestSize(result.getSentBytes()); - requestResult.setStartTime(result.getStartTime()); - requestResult.setEndTime(result.getEndTime()); - requestResult.setTotalAssertions(result.getAssertionResults().length); - requestResult.setSuccess(result.isSuccessful()); - requestResult.setError(result.getErrorCount()); - requestResult.setScenario(result.getScenario()); - if (result instanceof HTTPSampleResult) { - HTTPSampleResult res = (HTTPSampleResult) result; - requestResult.setCookies(res.getCookies()); - } - - for (SampleResult subResult : result.getSubResults()) { - requestResult.getSubRequestResults().add(getRequestResult(subResult)); - } - ResponseResult responseResult = requestResult.getResponseResult(); - responseResult.setBody(result.getResponseDataAsString()); - responseResult.setHeaders(result.getResponseHeaders()); - responseResult.setLatency(result.getLatency()); - responseResult.setResponseCode(result.getResponseCode()); - responseResult.setResponseSize(result.getResponseData().length); - responseResult.setResponseTime(result.getTime()); - responseResult.setResponseMessage(result.getResponseMessage()); - if (JMeterVars.get(result.hashCode()) != null && CollectionUtils.isNotEmpty(JMeterVars.get(result.hashCode()).entrySet())) { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : JMeterVars.get(result.hashCode()).entrySet()) { - builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n"); - } - if (StringUtils.isNotEmpty(builder)) { - responseResult.setVars(builder.toString()); - } - JMeterVars.remove(result.hashCode()); - } - for (AssertionResult assertionResult : result.getAssertionResults()) { - ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult); - if (responseAssertionResult.isPass()) { - requestResult.addPassAssertions(); - } - //xpath 提取错误会添加断言错误 - if (StringUtils.isBlank(responseAssertionResult.getMessage()) || - (StringUtils.isNotBlank(responseAssertionResult.getName()) && !responseAssertionResult.getName().endsWith("XPath2Extractor"))) { - responseResult.getAssertions().add(responseAssertionResult); - } - } - return requestResult; - } - - private String getMethod(SampleResult result) { - String body = result.getSamplerData(); - // Dubbo Protocol - String start = "RPC Protocol: "; - String end = "://"; - if (StringUtils.contains(body, start)) { - String protocol = StringUtils.substringBetween(body, start, end); - if (StringUtils.isNotEmpty(protocol)) { - return protocol.toUpperCase(); - } - return RequestType.DUBBO; - } else if (StringUtils.contains(result.getResponseHeaders(), "url:jdbc")) { - return "SQL"; - } else { - // Http Method - String method = StringUtils.substringBefore(body, " "); - for (HttpMethod value : HttpMethod.values()) { - if (StringUtils.equals(method, value.name())) { - return method; - } - } - return "Request"; - } - } - - private void setParam(BackendListenerContext context) { - this.testId = context.getParameter(TEST_ID); - this.setReportId = context.getParameter(TEST_REPORT_ID); - this.amassReport = context.getParameter(AMASS_REPORT); - this.reportId = context.getParameter(REPORT_ID); - this.runMode = context.getParameter("runMode"); - this.isDebug = StringUtils.equals(context.getParameter("DEBUG"), "DEBUG") ? true : false; - this.userId = context.getParameter("USER_ID"); - this.producerProps = JSON.parseObject(context.getParameter(KAFKA_CONFIG), Map.class); - if (StringUtils.isBlank(this.runMode)) { - this.runMode = ApiRunMode.RUN.name(); - } - } - - private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) { - ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult(); - responseAssertionResult.setName(assertionResult.getName()); - responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError()); - if (!responseAssertionResult.isPass()) { - responseAssertionResult.setMessage(assertionResult.getFailureMessage()); - } - return responseAssertionResult; - } - -} diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java new file mode 100644 index 0000000..a0ac981 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java @@ -0,0 +1,57 @@ +package io.metersphere.api.jmeter; + + +import com.alibaba.fastjson.JSON; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.service.JmeterExecuteService; +import io.metersphere.api.service.ProducerService; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.ResultDTO; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Map; + +public class APIBackendListenerHandler { + + private PrintStream oldPrintStream = System.out; + + private static ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + public static void setConsole() { + System.setOut(new PrintStream(bos)); + } + + private String getConsole() { + System.setOut(oldPrintStream); + return bos.toString(); + } + + public void handleTeardownTest(ResultDTO dto, Map kafkaConfig) { + ProducerService producerServer = CommonBeanFactory.getBean(ProducerService.class); + try { + dto.setConsole(getConsole()); + LoggerUtil.info("执行完成开始同步发送KAFKA【" + dto.getTestId() + "】"); + producerServer.send(JSON.toJSONString(dto), kafkaConfig); + LoggerUtil.info("同步发送报告信息到KAFKA完成【" + dto.getTestId() + "】"); + } catch (Exception ex) { + LoggerUtil.error("KAFKA 推送结果异常:[" + dto.getTestId() + "]" + ex.getMessage()); + // 补偿一个结果防止持续Running + if (dto != null && dto.getRequestResults().size() > 0) { + dto.getRequestResults().clear(); + } + producerServer.send(JSON.toJSONString(dto), kafkaConfig); + } + + if (StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.name())) { + LoggerUtil.info("接口收到集合报告ID:" + dto.getReportId()); + JmeterExecuteService jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + jmeterExecuteService.remove(dto.getReportId(), dto.getTestId()); + LoggerUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); + LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的数量:" + jmeterExecuteService.getRunningTasks(dto.getReportId())); + LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的内容:" + jmeterExecuteService.getRunningList(dto.getReportId())); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/GroovyLoadJarService.java b/src/main/java/io/metersphere/api/jmeter/GroovyLoadJarService.java new file mode 100644 index 0000000..bc5c409 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/GroovyLoadJarService.java @@ -0,0 +1,32 @@ +package io.metersphere.api.jmeter; + +import groovy.lang.GroovyClassLoader; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.utils.LoggerUtil; +import org.springframework.stereotype.Service; + +import java.io.File; + +@Service +public class GroovyLoadJarService { + + public void loadGroovyJar(GroovyClassLoader classLoader) { + try { + File file = new File(FileUtils.JAR_FILE_DIR); + if (file.isFile()) { + classLoader.addURL(file.toURI().toURL()); + } else { + File[] files = file.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + classLoader.addURL(f.toURI().toURL()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + LoggerUtil.error(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 5a06bf6..18a6408 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,15 +1,12 @@ package io.metersphere.api.jmeter; -import com.alibaba.fastjson.JSON; -import io.metersphere.api.controller.request.RunRequest; -import io.metersphere.api.jmeter.constants.ApiRunMode; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.config.Arguments; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.jmeter.JMeterBase; +import io.metersphere.jmeter.LocalRunner; +import io.metersphere.utils.LoggerUtil; import org.apache.jmeter.util.JMeterUtils; -import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; @@ -17,8 +14,6 @@ import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.io.File; -import java.lang.reflect.Field; -import java.util.Map; @Service public class JMeterService { @@ -34,6 +29,8 @@ public void init() { JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES); JMeterUtils.setJMeterHome(JMETER_HOME); JMeterUtils.setLocale(LocaleContextHolder.getLocale()); + + APIBackendListenerHandler.setConsole(); } public String getJmeterHome() { @@ -50,54 +47,15 @@ public String getJmeterHome() { } } - public static HashTree getHashTree(Object scriptWrapper) throws Exception { - Field field = scriptWrapper.getClass().getDeclaredField("testPlan"); - field.setAccessible(true); - return (HashTree) field.get(scriptWrapper); - } - - - private void addBackendListener(HashTree testPlan, RunRequest request, Map producerProps) { - BackendListener backendListener = new BackendListener(); - if (StringUtils.isNotEmpty(request.getReportId())) { - backendListener.setName(request.getReportId()); - } else { - backendListener.setName(request.getTestId()); - } - Arguments arguments = new Arguments(); - if (StringUtils.isNotEmpty(request.getAmassReport())) { - arguments.addArgument(APIBackendListenerClient.AMASS_REPORT, request.getAmassReport()); - } - if (request.getConfig() != null && request.getConfig().getMode().equals("serial") && request.getConfig().getReportType().equals("setReport")) { - arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, request.getConfig().getReportName()); - } - if (StringUtils.isNotEmpty(request.getReportId()) && ApiRunMode.API_PLAN.name().equals(request.getRunMode())) { - arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getReportId()); - } else { - arguments.addArgument(APIBackendListenerClient.TEST_ID, request.getTestId()); - } - arguments.addArgument(APIBackendListenerClient.REPORT_ID, request.getReportId()); - - if (StringUtils.isNotBlank(request.getRunMode())) { - arguments.addArgument("runMode", request.getRunMode()); - } - arguments.addArgument(APIBackendListenerClient.KAFKA_CONFIG, JSON.toJSONString(producerProps)); - - arguments.addArgument("DEBUG", request.isDebug() ? "DEBUG" : "RUN"); - arguments.addArgument("USER_ID", request.getUserId()); - backendListener.setArguments(arguments); - backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); - testPlan.add(testPlan.getArray()[0], backendListener); - } - - public void run(RunRequest request, HashTree testPlan) { + public void run(JmeterRunRequestDTO runRequest, HashTree testPlan) { try { init(); - addBackendListener(testPlan, request, request.getKafka()); + runRequest.setHashTree(testPlan); + JMeterBase.addBackendListener(runRequest); LocalRunner runner = new LocalRunner(testPlan); - runner.run(request.getReportId()); + runner.run(runRequest.getReportId()); } catch (Exception e) { - LogUtil.error(e.getMessage(), e); + LoggerUtil.error(e.getMessage(), e); MSException.throwException("读取脚本失败"); } } diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterVars.java b/src/main/java/io/metersphere/api/jmeter/JMeterVars.java deleted file mode 100644 index ae468d3..0000000 --- a/src/main/java/io/metersphere/api/jmeter/JMeterVars.java +++ /dev/null @@ -1,115 +0,0 @@ -package io.metersphere.api.jmeter; - -import com.alibaba.fastjson.JSON; -import io.github.ningyu.jmeter.plugin.dubbo.sample.DubboSample; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.jmeter.extractor.JSR223PostProcessor; -import org.apache.jmeter.extractor.RegexExtractor; -import org.apache.jmeter.extractor.XPath2Extractor; -import org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor; -import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; -import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler; -import org.apache.jmeter.threads.JMeterVariables; -import org.apache.jorphan.collections.HashTree; -import org.springframework.util.StringUtils; - -import java.util.*; - -public class JMeterVars { - - private JMeterVars() { - } - - /** - * 数据和线程变量保持一致 - */ - private static Map variables = new HashMap<>(); - - /** - * 线程执行过程调用提取变量值 - * - * @param testId - * @param vars - * @param extract - */ - public static void addVars(Integer testId, JMeterVariables vars, String extract) { - JMeterVariables vs = variables.get(testId); - if (vs == null) { - vs = new JMeterVariables(); - } - if (!StringUtils.isEmpty(extract) && vars != null) { - List extracts = Arrays.asList(extract.split(";")); - if (CollectionUtils.isNotEmpty(extracts)) { - for (String item : extracts) { - String nrKey = item + "_matchNr"; - Object nr = vars.get(nrKey); - if (nr != null) { - int nrv = 0; - try { - nrv = Integer.valueOf(String.valueOf(nr)); - } catch (Exception e) { - } - if (nrv > 0) { - List data = new ArrayList<>(); - for (int i = 1; i < nrv + 1; i++) { - data.add(vars.get(item + "_" + i)); - } - String array = JSON.toJSONString(data); - vars.put(item, array); - } - } - vs.put(item, vars.get(item) == null ? "" : vars.get(item)); - } - vs.remove("TESTSTART.MS"); // 标示变量移除 - } - } - - variables.put(testId, vs); - } - - /** - * 处理所有请求,有提取变量的请求增加后置脚本提取变量值 - * - * @param tree - */ - public static void addJSR223PostProcessor(HashTree tree) { - for (Object key : tree.keySet()) { - HashTree node = tree.get(key); - if (key instanceof HTTPSamplerProxy || key instanceof DubboSample || key instanceof JDBCSampler) { - StringJoiner extract = new StringJoiner(";"); - for (Object child : node.keySet()) { - if (child instanceof RegexExtractor) { - RegexExtractor regexExtractor = (RegexExtractor) child; - extract.add(regexExtractor.getRefName()); - } else if (child instanceof XPath2Extractor) { - XPath2Extractor regexExtractor = (XPath2Extractor) child; - extract.add(regexExtractor.getRefName()); - } else if (child instanceof JSONPostProcessor) { - JSONPostProcessor regexExtractor = (JSONPostProcessor) child; - extract.add(regexExtractor.getRefNames()); - } - } - - if (Optional.ofNullable(extract).orElse(extract).length() > 0) { - JSR223PostProcessor shell = new JSR223PostProcessor(); - shell.setEnabled(true); - shell.setProperty("script", "io.metersphere.api.jmeter.JMeterVars.addVars(prev.hashCode(),vars," + "\"" + extract.toString() + "\"" + ");"); - node.add(shell); - } - } - - if (node != null) { - addJSR223PostProcessor(node); - } - } - } - - public static JMeterVariables get(Integer key) { - return variables.get(key); - } - - public static void remove(Integer key) { - variables.remove(key); - } - -} diff --git a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java b/src/main/java/io/metersphere/api/jmeter/LocalRunner.java deleted file mode 100644 index 60ab90a..0000000 --- a/src/main/java/io/metersphere/api/jmeter/LocalRunner.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.metersphere.api.jmeter; - -import io.metersphere.api.jmeter.utils.MessageCache; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.collections.CollectionUtils; -import org.apache.jmeter.engine.JMeterEngineException; -import org.apache.jmeter.engine.StandardJMeterEngine; -import org.apache.jorphan.collections.HashTree; - -import java.util.List; - -public class LocalRunner { - private HashTree jmxTree; - - public LocalRunner(HashTree jmxTree) { - this.jmxTree = jmxTree; - } - - public LocalRunner() { - } - - public void run(String report) { - StandardJMeterEngine engine = new StandardJMeterEngine(); - engine.configure(jmxTree); - try { - engine.runTest(); - MessageCache.runningEngine.put(report, engine); - } catch (JMeterEngineException e) { - engine.stopTest(true); - } - } - - public void stop(List reports) { - try { - if (CollectionUtils.isNotEmpty(reports)) { - for (String report : reports) { - StandardJMeterEngine engine = MessageCache.runningEngine.get(report); - if (engine != null) { - engine.stopTest(); - MessageCache.runningEngine.remove(report); - } - } - } - } catch (Exception e) { - LogUtil.error(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java b/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java deleted file mode 100644 index 2fcccaf..0000000 --- a/src/main/java/io/metersphere/api/jmeter/constants/ApiRunMode.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.metersphere.api.jmeter.constants; - -public enum ApiRunMode { - RUN, DEBUG, DEFINITION, SCENARIO, API_PLAN, JENKINS_API_PLAN, JENKINS, SCENARIO_PLAN, API, SCHEDULE_API_PLAN, SCHEDULE_SCENARIO,SCHEDULE_SCENARIO_PLAN, SCHEDULE_PERFORMANCE_TEST -} diff --git a/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java b/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java deleted file mode 100644 index 377746b..0000000 --- a/src/main/java/io/metersphere/api/jmeter/constants/RequestType.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.metersphere.api.jmeter.constants; - -public class RequestType { - - public static final String HTTP = "HTTP"; - - public static final String DUBBO = "DUBBO"; - - public static final String SQL = "SQL"; - - public static final String TCP = "TCP"; -} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/Condition.java b/src/main/java/io/metersphere/api/jmeter/utils/Condition.java deleted file mode 100644 index e6e4828..0000000 --- a/src/main/java/io/metersphere/api/jmeter/utils/Condition.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.metersphere.api.jmeter.utils; - -import lombok.Data; - -@Data -public class Condition { - private String key; - private Object value; - - public Condition() { - - } - - public Condition(String key, Object value) { - this.key = key; - this.value = value; - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java deleted file mode 100644 index c8cad89..0000000 --- a/src/main/java/io/metersphere/api/jmeter/utils/DocumentUtils.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.metersphere.api.jmeter.utils; - -import com.alibaba.fastjson.JSON; -import com.google.gson.Gson; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.oro.text.regex.Pattern; - -import java.text.DecimalFormat; -import java.util.List; -import java.util.Map; - -public class DocumentUtils { - - public static boolean documentChecked(Object subj, String condition, ThreadLocal decimalFormatter) { - if (StringUtils.isNotEmpty(condition)) { - ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class); - boolean isTrue = true; - if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) { - for (Condition item : elementCondition.getConditions()) { - String expectedValue = item.getValue() != null ? item.getValue().toString() : ""; - String resValue = objectToString(subj, decimalFormatter); - switch (item.getKey()) { - case "value_eq": - isTrue = StringUtils.equals(resValue, expectedValue); - break; - case "value_not_eq": - isTrue = !StringUtils.equals(resValue, expectedValue); - break; - case "value_in": - isTrue = StringUtils.contains(resValue, expectedValue); - break; - case "length_eq": - isTrue = getLength(subj, decimalFormatter) == numberOf(item.getValue()); - break; - case "length_not_eq": - isTrue = getLength(subj, decimalFormatter) != numberOf(item.getValue()); - break; - case "length_gt": - isTrue = getLength(subj, decimalFormatter) > numberOf(item.getValue()); - break; - case "length_lt": - isTrue = getLength(subj, decimalFormatter) < numberOf(item.getValue()); - break; - case "regular": - Pattern pattern = JMeterUtils.getPatternCache().getPattern(expectedValue); - isTrue = JMeterUtils.getMatcher().matches(resValue, pattern); - break; - } - if (!isTrue) { - break; - } - } - } - return isTrue; - } - return true; - } - - public static String objectToString(Object subj, ThreadLocal decimalFormatter) { - String str; - if (subj == null) { - str = "null"; - } else if (subj instanceof Map) { - str = new Gson().toJson(subj); - } else if (!(subj instanceof Double) && !(subj instanceof Float)) { - str = subj.toString(); - } else { - str = ((DecimalFormat) decimalFormatter.get()).format(subj); - } - return str; - } - - - private static int getLength(Object value) { - if (value != null) { - if (value instanceof List) { - return ((List) value).size(); - } - return value.toString().length(); - } - return 0; - } - - private static int getLength(Object value, ThreadLocal decimalFormatter) { - if (value != null) { - if (value instanceof Map) { - return ((Map) value).size(); - } else if (value instanceof List) { - return ((List) value).size(); - } else if (!(value instanceof Double) && !(value instanceof Float)) { - return value.toString().length(); - } else { - return ((DecimalFormat) decimalFormatter.get()).format(value).length(); - } - } - return 0; - } - - private static long numberOf(Object value) { - if (value != null) { - try { - return Long.parseLong(value.toString()); - } catch (Exception e) { - return 0; - } - } - return 0; - } - - public static String documentMsg(Object resValue, String condition) { - String msg = ""; - if (StringUtils.isNotEmpty(condition)) { - ElementCondition elementCondition = JSON.parseObject(condition, ElementCondition.class); - if (CollectionUtils.isNotEmpty(elementCondition.getConditions())) { - for (Condition item : elementCondition.getConditions()) { - if (StringUtils.equalsAny(item.getKey(), "value_eq", "value_not_eq", "value_in")) { - msg = resValue != null ? resValue.toString() : ""; - } else if (StringUtils.equalsAny(item.getKey(), "length_eq", "length_not_eq", "length_gt", "length_lt")) { - msg = "长度是:" + getLength(resValue) + ""; - } else { - msg = resValue != null ? resValue.toString() : ""; - } - } - } - } - return msg; - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java b/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java deleted file mode 100644 index f6a7044..0000000 --- a/src/main/java/io/metersphere/api/jmeter/utils/ElementCondition.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.metersphere.api.jmeter.utils; - -import lombok.Data; - -import java.util.List; - -@Data -public class ElementCondition { - private boolean include; - private boolean typeVerification; - private boolean arrayVerification; - List conditions; - - public ElementCondition() { - - } - - public ElementCondition(boolean include, boolean typeVerification, boolean arrayVerification, List conditions) { - this.include = include; - this.typeVerification = typeVerification; - this.arrayVerification = arrayVerification; - this.conditions = conditions; - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java index 4fd5378..99b4b11 100644 --- a/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -1,6 +1,6 @@ package io.metersphere.api.jmeter.utils; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.aspectj.util.FileUtil; import org.springframework.web.multipart.MultipartFile; @@ -24,7 +24,7 @@ public static void createFiles(MultipartFile[] bodyFiles, String path) { file.createNewFile(); FileUtil.copyStream(in, out); } catch (IOException e) { - LogUtil.error(e); + LoggerUtil.error(e); MSException.throwException("文件处理异常"); } } diff --git a/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java b/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java deleted file mode 100644 index ee0cd9d..0000000 --- a/src/main/java/io/metersphere/api/jmeter/utils/MessageCache.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.metersphere.api.jmeter.utils; - -import org.apache.jmeter.engine.StandardJMeterEngine; - -import java.util.concurrent.ConcurrentHashMap; - -public class MessageCache { - public static ConcurrentHashMap runningEngine = new ConcurrentHashMap<>(); - -} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java b/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java deleted file mode 100644 index 8330e8a..0000000 --- a/src/main/java/io/metersphere/api/jmeter/utils/RunModeConfig.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.metersphere.api.jmeter.utils; - -import lombok.Data; - -@Data -public class RunModeConfig { - private String mode; - private String reportType; - private String reportName; - private boolean onSampleError; -} diff --git a/src/main/java/io/metersphere/api/module/RequestResult.java b/src/main/java/io/metersphere/api/module/RequestResult.java deleted file mode 100644 index 3732394..0000000 --- a/src/main/java/io/metersphere/api/module/RequestResult.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.metersphere.api.module; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class RequestResult { - // 请求ID - private String id; - - private String resourceId; - - private String name; - - private String url; - - private String method; - - private String scenario; - - private long requestSize; - - private long startTime; - - private long endTime; - - private int error; - - private boolean success; - - private String headers; - - private String cookies; - - private String body; - - private int totalAssertions = 0; - - private int passAssertions = 0; - - private final List subRequestResults = new ArrayList<>(); - - private final ResponseResult responseResult = new ResponseResult(); - - public void addPassAssertions() { - this.passAssertions++; - } - -} diff --git a/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java b/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java deleted file mode 100644 index f75904c..0000000 --- a/src/main/java/io/metersphere/api/module/ResponseAssertionResult.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.metersphere.api.module; - -import lombok.Data; - -@Data -public class ResponseAssertionResult { - - private String name; - - private String message; - - private boolean pass; -} diff --git a/src/main/java/io/metersphere/api/module/ResponseResult.java b/src/main/java/io/metersphere/api/module/ResponseResult.java deleted file mode 100644 index f1ecd30..0000000 --- a/src/main/java/io/metersphere/api/module/ResponseResult.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.metersphere.api.module; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - - -@Data -public class ResponseResult { - - private String responseCode; - - private String responseMessage; - - private long responseTime; - - private long latency; - - private long responseSize; - - private String headers; - - private String body; - - private String vars; - - private String console; - - private final List assertions = new ArrayList<>(); - -} diff --git a/src/main/java/io/metersphere/api/module/ScenarioResult.java b/src/main/java/io/metersphere/api/module/ScenarioResult.java deleted file mode 100644 index d388e3f..0000000 --- a/src/main/java/io/metersphere/api/module/ScenarioResult.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.metersphere.api.module; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class ScenarioResult { - - private Integer id; - - private String name; - - private long responseTime; - - private int error = 0; - - private int success = 0; - - private int totalAssertions = 0; - - private int passAssertions = 0; - - private List requestResults = new ArrayList<>(); - - public void addResponseTime(long time) { - this.responseTime += time; - } - - public void addError(int count) { - this.error += count; - } - - public void addSuccess() { - this.success++; - } - - public void addTotalAssertions(int count) { - this.totalAssertions += count; - } - - public void addPassAssertions(int count) { - this.passAssertions += count; - } - - public int getTotal() { - return error + success; - } -} diff --git a/src/main/java/io/metersphere/api/module/TestResult.java b/src/main/java/io/metersphere/api/module/TestResult.java deleted file mode 100644 index c0d48e4..0000000 --- a/src/main/java/io/metersphere/api/module/TestResult.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.metersphere.api.module; - -import lombok.Data; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class TestResult { - - private String testId; - - private String setReportId; - - private int scenarioTotal; - - private int scenarioSuccess; - - private int scenarioError; - - private String userId; - - private boolean isDebug; - - private String runMode; - - private int success = 0; - - private int error = 0; - - private int total = 0; - - private int totalAssertions = 0; - - private int passAssertions = 0; - - private String console; - - private String runningDebugSampler; - - private List scenarios = new ArrayList<>(); - - public void addError(int count) { - this.error += count; - } - - public void addSuccess() { - this.success++; - } - - public void addTotalAssertions(int count) { - this.totalAssertions += count; - } - - public void addPassAssertions(int count) { - this.passAssertions += count; - } - - private static final String SEPARATOR = "<->"; - -} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index a267072..6f53475 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -1,24 +1,22 @@ package io.metersphere.api.service; import com.alibaba.fastjson.JSON; -import io.metersphere.api.controller.request.RunRequest; import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.api.service.utils.ZipSpider; -import io.metersphere.node.util.LogUtil; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.utils.LoggerUtil; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.NewDriver; import org.apache.jmeter.save.SaveService; import org.apache.jorphan.collections.HashTree; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.InputStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; @@ -32,8 +30,6 @@ public class JmeterExecuteService { @Resource private JMeterService jMeterService; - @Resource - private ProducerService producerService; private static String url = null; private static String plugUrl = null; @@ -41,24 +37,63 @@ public class JmeterExecuteService { // 记录所以执行中的请求/场景 private Map> runningTasks = new HashMap<>(); - private static InputStream getStrToStream(String sInputString) { - if (StringUtils.isNotEmpty(sInputString)) { - try { - ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); - return tInputStringStream; - } catch (Exception ex) { - ex.printStackTrace(); - MSException.throwException("生成脚本异常"); + public String runStart(JmeterRunRequestDTO runRequest) { + try { + if (runRequest != null && StringUtils.equals(runRequest.getReportType(), RunModeConstants.SET_REPORT.name())) { + this.putRunningTasks(runRequest.getReportId(), runRequest.getTestId()); + } + if (runRequest.getKafkaConfig() == null) { + return "KAFKA 初始化失败,请检查配置"; + } + // 生成附件/JAR文件 + URL urlObject = new URL(runRequest.getPlatformUrl()); + String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; + String plugJarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/plug/jar"; + + if (StringUtils.isEmpty(url)) { + LoggerUtil.info("开始同步上传的JAR:" + jarUrl); + File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); + if (file != null) { + ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); + this.loadJar(FileUtils.JAR_FILE_DIR); + } + } + if (StringUtils.isEmpty(plugUrl)) { + LoggerUtil.info("开始同步插件JAR:" + plugJarUrl); + File plugFile = ZipSpider.downloadFile(plugJarUrl, FileUtils.JAR_PLUG_FILE_DIR); + if (plugFile != null) { + ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); + this.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); + } } + url = jarUrl; + plugUrl = plugJarUrl; + LoggerUtil.info("开始拉取脚本和脚本附件:" + runRequest.getPlatformUrl()); + + File bodyFile = ZipSpider.downloadFile(runRequest.getPlatformUrl(), FileUtils.BODY_FILE_DIR); + if (bodyFile != null) { + ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + // 开始执行 + jMeterService.run(runRequest, testPlan); + FileUtils.deleteFile(bodyFile.getPath()); + } else { + MSException.throwException("未找到执行的JMX文件"); + } + } catch (Exception e) { + LoggerUtil.error(e.getMessage()); + return e.getMessage(); } - return null; + return "SUCCESS"; } private void loadJar(String path) { try { NewDriver.addPath(path); } catch (MalformedURLException e) { - LogUtil.error(e.getMessage(), e); + LoggerUtil.error(e.getMessage(), e); MSException.throwException(e.getMessage()); } } @@ -107,81 +142,6 @@ private void loadPlugJar(String jarPath) { } } - public String run(RunRequest request, MultipartFile[] bodyFiles, MultipartFile[] jarFiles) { - if (request == null || request.getJmx() == null) { - return "执行文件为空,无法执行!"; - } - LogUtil.info(request.getJmx()); - // 生成附件/JAR文件 - FileUtils.createFiles(bodyFiles, FileUtils.BODY_FILE_DIR); - FileUtils.createFiles(jarFiles, FileUtils.JAR_FILE_DIR); - try { - this.loadJar(FileUtils.JAR_FILE_DIR); - // 生成执行脚本 - InputStream inputSource = getStrToStream(request.getJmx()); - Object scriptWrapper = SaveService.loadElement(inputSource); - HashTree testPlan = JMeterService.getHashTree(scriptWrapper); - // 开始执行 - jMeterService.run(request, testPlan); - } catch (Exception e) { - LogUtil.error(e.getMessage()); - return e.getMessage(); - } - return "SUCCESS"; - } - - public String runStart(RunRequest runRequest) { - try { - if (runRequest != null && StringUtils.isNotEmpty(runRequest.getAmassReport())) { - this.putRunningTasks(runRequest.getAmassReport(), runRequest.getTestId()); - } - if (runRequest.getKafka() == null) { - return "KAFKA 初始化失败,请检查配置"; - } - // 生成附件/JAR文件 - URL urlObject = new URL(runRequest.getUrl()); - String jarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/jar"; - String plugJarUrl = urlObject.getProtocol() + "://" + urlObject.getHost() + (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : "") + "/api/jmeter/download/plug/jar"; - - if (StringUtils.isEmpty(url)) { - LogUtil.info("开始同步上传的JAR:" + jarUrl); - File file = ZipSpider.downloadFile(jarUrl, FileUtils.JAR_FILE_DIR); - if (file != null) { - ZipSpider.unzip(file.getPath(), FileUtils.JAR_FILE_DIR); - this.loadJar(FileUtils.JAR_FILE_DIR); - } - } - if (StringUtils.isEmpty(plugUrl)) { - LogUtil.info("开始同步插件JAR:" + plugJarUrl); - File plugFile = ZipSpider.downloadFile(plugJarUrl, FileUtils.JAR_PLUG_FILE_DIR); - if (plugFile != null) { - ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); - this.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); - } - } - url = jarUrl; - plugUrl = plugJarUrl; - LogUtil.info("开始拉取脚本和脚本附件:" + runRequest.getUrl()); - - File bodyFile = ZipSpider.downloadFile(runRequest.getUrl(), FileUtils.BODY_FILE_DIR); - if (bodyFile != null) { - ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); - // 生成执行脚本 - HashTree testPlan = SaveService.loadTree(jmxFile); - // 开始执行 - jMeterService.run(runRequest, testPlan); - FileUtils.deleteFile(bodyFile.getPath()); - } else { - MSException.throwException("未找到执行的JMX文件"); - } - } catch (Exception e) { - LogUtil.error(e.getMessage()); - return e.getMessage(); - } - return "SUCCESS"; - } - public void putRunningTasks(String key, String value) { List list = new ArrayList<>(); if (this.runningTasks.containsKey(key)) { @@ -227,7 +187,7 @@ public void execute() { } // 清理历史jar FileUtils.deletePath(FileUtils.JAR_PLUG_FILE_DIR); - LogUtil.info("开始同步插件JAR:" + plugUrl); + LoggerUtil.info("开始同步插件JAR:" + plugUrl); File plugFile = ZipSpider.downloadFile(plugUrl, FileUtils.JAR_PLUG_FILE_DIR); if (plugFile != null) { ZipSpider.unzip(plugFile.getPath(), FileUtils.JAR_PLUG_FILE_DIR); diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java index 41abbff..95d21df 100644 --- a/src/main/java/io/metersphere/api/service/ProducerService.java +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -1,7 +1,7 @@ package io.metersphere.api.service; import io.metersphere.api.config.KafkaConfig; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.apache.kafka.clients.producer.ProducerConfig; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -31,7 +31,7 @@ public KafkaTemplate init(Map producerProps) { return kafkaTemplate; } } catch (Exception e) { - LogUtil.error(e); + LoggerUtil.error(e); return null; } } diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java index 72d8c21..962850b 100644 --- a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -1,6 +1,6 @@ package io.metersphere.api.service.utils; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.apache.jmeter.config.CSVDataSet; import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; import org.apache.jmeter.protocol.http.util.HTTPFileArg; @@ -78,15 +78,15 @@ public static void unzip(String fromFile, String toFile) { while ((b = bin.read()) != -1) { bout.write(b); } - LogUtil.info(fout + "解压成功"); + LoggerUtil.info(fout + "解压成功"); } catch (Exception e) { - LogUtil.error(e); + LoggerUtil.error(e); } } } catch (FileNotFoundException e) { - LogUtil.error(e); + LoggerUtil.error(e); } catch (IOException e) { - LogUtil.error(e); + LoggerUtil.error(e); } } @@ -128,6 +128,7 @@ public static File downloadFile(String urlPath, String downloadDir) { URLConnection urlConnection = url.openConnection(); boolean useHttps = urlPath.startsWith("https"); if (useHttps) { + LoggerUtil.info("进入HTTPS协议处理方法"); HttpsURLConnection https = (HttpsURLConnection) urlConnection; trustAllHosts(https); https.setHostnameVerifier(DO_NOT_VERIFY); @@ -159,13 +160,13 @@ public static File downloadFile(String urlPath, String downloadDir) { // 关闭资源 bin.close(); out.close(); - LogUtil.info("文件下载成功!"); + LoggerUtil.info("文件下载成功!"); return file; } catch (MalformedURLException e) { - LogUtil.error(e); + LoggerUtil.error(e); } catch (IOException e) { - LogUtil.error(e); - LogUtil.info("文件下载失败!"); + LoggerUtil.error(e); + LoggerUtil.info("文件下载失败!"); } finally { try { if (bin != null) { diff --git a/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java index e784219..368b085 100644 --- a/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java +++ b/src/main/java/io/metersphere/node/controller/handler/ResultResponseBodyAdvice.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -38,7 +38,7 @@ public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaTy try { return objectMapper.writeValueAsString(success); } catch (JsonProcessingException e) { - LogUtil.error(e); + LoggerUtil.error(e); return o; } } diff --git a/src/main/java/io/metersphere/node/service/JmeterOperateService.java b/src/main/java/io/metersphere/node/service/JmeterOperateService.java index 1ece425..72540d8 100644 --- a/src/main/java/io/metersphere/node/service/JmeterOperateService.java +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -10,7 +10,7 @@ import io.metersphere.node.controller.request.TestRequest; import io.metersphere.node.util.CompressUtils; import io.metersphere.node.util.DockerClientService; -import io.metersphere.node.util.LogUtil; +import io.metersphere.utils.LoggerUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Service; @@ -39,7 +39,7 @@ public class JmeterOperateService { public void startContainer(TestRequest testRequest) { Map env = testRequest.getEnv(); String testId = env.get("TEST_ID"); - LogUtil.info("Receive start container request, test id: {}", testId); + LoggerUtil.info("Receive start container request, test id: {}", testId); String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); // 检查kafka连通性 checkKafka(bootstrapServers); @@ -66,7 +66,7 @@ private void startContainer(TestRequest testRequest, DockerClient dockerClient, String containerId = DockerClientService.createContainers(dockerClient, testId, containerImage, hostConfig, envs).getId(); DockerClientService.startContainer(dockerClient, containerId); - LogUtil.info("Container create started containerId: " + containerId); + LoggerUtil.info("Container create started containerId: " + containerId); String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); String reportId = testRequest.getEnv().get("REPORT_ID"); @@ -81,11 +81,11 @@ public void onComplete() { // copyTestResources(dockerClient, containerId, reportId, resourceIndex); DockerClientService.removeContainer(dockerClient, containerId); } - LogUtil.info("Remove container completed: " + containerId); + LoggerUtil.info("Remove container completed: " + containerId); } catch (Exception e) { - LogUtil.error("Remove container error: ", e); + LoggerUtil.error("Remove container error: ", e); } - LogUtil.info("completed...."); + LoggerUtil.info("completed...."); } }); @@ -105,7 +105,7 @@ public void onNext(Frame item) { String message = StringUtils.join(contents, " "); kafkaProducerService.sendMessage(topic, message); } - LogUtil.info(log); + LoggerUtil.info(log); } }); } @@ -178,7 +178,7 @@ private void checkKafka(String bootstrapServers) { } } } catch (Exception e) { - LogUtil.error(e); + LoggerUtil.error(e); throw new RuntimeException("Failed to connect to Kafka"); } } @@ -214,7 +214,7 @@ private void searchImage(DockerClient dockerClient, String imageName) { public void stopContainer(String testId) { - LogUtil.info("Receive stop container request, test: {}", testId); + LoggerUtil.info("Receive stop container request, test: {}", testId); DockerClient dockerClient = DockerClientService.connectDocker(); // container filter @@ -238,7 +238,7 @@ public List taskStatus(String testId) { } public String logContainer(String testId) { - LogUtil.info("Receive logs container request, test: {}", testId); + LoggerUtil.info("Receive logs container request, test: {}", testId); DockerClient dockerClient = DockerClientService.connectDocker(); // container filter @@ -263,7 +263,7 @@ public void onNext(Frame item) { } }).awaitCompletion(100, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - LogUtil.error(e); + LoggerUtil.error(e); } } return sb.toString(); diff --git a/src/main/java/io/metersphere/node/util/CompressUtils.java b/src/main/java/io/metersphere/node/util/CompressUtils.java index 04bcd95..e847793 100644 --- a/src/main/java/io/metersphere/node/util/CompressUtils.java +++ b/src/main/java/io/metersphere/node/util/CompressUtils.java @@ -1,5 +1,7 @@ package io.metersphere.node.util; +import io.metersphere.utils.LoggerUtil; + import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -32,7 +34,7 @@ public static Object zip(Object data) { b = bos.toByteArray(); bos.close(); } catch (Exception ex) { - LogUtil.error(ex); + LoggerUtil.error(ex); } return b; @@ -67,7 +69,7 @@ public static Object unzip(Object data) { zip.close(); bis.close(); } catch (Exception ex) { - LogUtil.error(ex); + LoggerUtil.error(ex); } return b; } diff --git a/src/main/java/io/metersphere/node/util/LogUtil.java b/src/main/java/io/metersphere/node/util/LogUtil.java deleted file mode 100644 index df77159..0000000 --- a/src/main/java/io/metersphere/node/util/LogUtil.java +++ /dev/null @@ -1,261 +0,0 @@ -package io.metersphere.node.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -public class LogUtil { - //日志工具类 -// public static final Log Logger = LogFactory.getLog(LogUtil.class); - - private static final String DEBUG = "DEBUG"; - private static final String INFO = "INFO"; - private static final String WARN = "WARN"; - private static final String ERROR = "ERROR"; - - /** - * 初始化日志 - * - * @return - */ - public static Logger getLogger() { - return LoggerFactory.getLogger(LogUtil.getLogClass()); - } - - public static void writeLog(Object msg, String level) { - Logger logger = LogUtil.getLogger(); - - if (DEBUG.equals(level)) { - if (logger != null && logger.isDebugEnabled()) { - logger.debug(LogUtil.getMsg(msg)); - } - } else if (INFO.equals(level)) { - if (logger != null && logger.isInfoEnabled()) { - logger.info(LogUtil.getMsg(msg)); - } - } else if (WARN.equals(level)) { - if (logger != null && logger.isWarnEnabled()) { - logger.warn(LogUtil.getMsg(msg)); - } - } else if (ERROR.equals(level)) { - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg)); - } - } else { - if (logger != null && logger.isErrorEnabled()) { - logger.error(""); - } - } - } - - public static void info(Object msg) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isInfoEnabled()) { - logger.info(LogUtil.getMsg(msg)); - } - } - - public static void info(Object msg, Object o1) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isInfoEnabled()) { - logger.info(LogUtil.getMsg(msg), o1); - } - } - - public static void info(Object msg, Object o1, Object o2) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isInfoEnabled()) { - logger.info(LogUtil.getMsg(msg), o1, o2); - } - } - - public static void info(Object msg, Object[] obj) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isInfoEnabled()) { - logger.info(LogUtil.getMsg(msg), obj); - } - } - - public static void debug(Object msg) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isDebugEnabled()) { - logger.debug(LogUtil.getMsg(msg)); - } - } - - public static void debug(Object msg, Object o) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isDebugEnabled()) { - logger.debug(LogUtil.getMsg(msg), o); - } - } - - public static void debug(Object msg, Object o1, Object o2) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isDebugEnabled()) { - logger.debug(LogUtil.getMsg(msg), o1, o2); - } - } - - public static void debug(Object msg, Object[] obj) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isDebugEnabled()) { - logger.debug(LogUtil.getMsg(msg), obj); - } - } - - public static void warn(Object msg) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isWarnEnabled()) { - logger.warn(LogUtil.getMsg(msg)); - } - } - - public static void warn(Object msg, Object o) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isWarnEnabled()) { - logger.warn(LogUtil.getMsg(msg), o); - } - } - - public static void warn(Object msg, Object o1, Object o2) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isWarnEnabled()) { - logger.warn(LogUtil.getMsg(msg), o1, o2); - } - } - - public static void warn(Object msg, Object[] obj) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isWarnEnabled()) { - logger.warn(LogUtil.getMsg(msg), obj); - } - } - - public static void error(Object msg) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg));// 并追加方法名称 - } - } - - public static void error(Object msg, Object o) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg), o); - } - } - - public static void error(Object msg, Object o1, Object o2) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg), o1, o2); - } - } - - public static void error(Object msg, Object[] obj) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg), obj); - } - } - - public static void error(Object msg, Throwable ex) { - Logger logger = LogUtil.getLogger(); - if (logger != null && logger.isErrorEnabled()) { - logger.error(LogUtil.getMsg(msg), ex); - } - } - - public static String getMsg(Object msg, Throwable ex) { - String str = ""; - - if (msg != null) { - str = LogUtil.getLogMethod() + "[" + msg.toString() + "]"; - } else { - str = LogUtil.getLogMethod() + "[null]"; - } - if (ex != null) { - str += "[" + ex.getMessage() + "]"; - } - - return str; - } - - public static String getMsg(Object msg) { - return LogUtil.getMsg(msg, null); - } - - /** - * 得到调用类名称 - * - * @return - */ - private static String getLogClass() { - String str = ""; - - StackTraceElement[] stack = (new Throwable()).getStackTrace(); - if (stack.length > 3) { - StackTraceElement ste = stack[3]; - str = ste.getClassName();// 类名称 - } - - return str; - } - - /** - * 得到调用方法名称 - * - * @return - */ - private static String getLogMethod() { - String str = ""; - - StackTraceElement[] stack = (new Throwable()).getStackTrace(); - if (stack.length > 4) { - StackTraceElement ste = stack[4]; - str = "Method[" + ste.getMethodName() + "]";// 方法名称 - } - - return str; - } - - public static String toString(Throwable e) { - StringWriter sw = null; - PrintWriter pw = null; - try { - sw = new StringWriter(); - pw = new PrintWriter(sw); - //将出错的栈信息输出到printWriter中 - e.printStackTrace(pw); - pw.flush(); - sw.flush(); - } finally { - if (sw != null) { - try { - sw.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - if (pw != null) { - pw.close(); - } - } - return sw.toString(); - } - - public static String getExceptionDetailsToStr(Exception e) { - StringBuilder sb = new StringBuilder(e.toString()); - StackTraceElement[] stackElements = e.getStackTrace(); - for (StackTraceElement stackTraceElement : stackElements) { - sb.append(stackTraceElement.toString()); - sb.append("\n"); - } - sb.append("\n"); - return sb.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/org/apache/jmeter/NewDriver.java b/src/main/java/org/apache/jmeter/NewDriver.java deleted file mode 100644 index 9b30948..0000000 --- a/src/main/java/org/apache/jmeter/NewDriver.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jmeter; - -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.text.SimpleDateFormat; -import java.util.*; - -/** - * Main class for JMeter - sets up initial classpath and the loader. - * - */ -public final class NewDriver { - - private static final String CLASSPATH_SEPARATOR = File.pathSeparator; - - private static final String OS_NAME = System.getProperty("os.name");// $NON-NLS-1$ - - private static final String OS_NAME_LC = OS_NAME.toLowerCase(java.util.Locale.ENGLISH); - - private static final String JAVA_CLASS_PATH = "java.class.path";// $NON-NLS-1$ - - private static final String JMETER_LOGFILE_SYSTEM_PROPERTY = "jmeter.logfile";// $NON-NLS-1$ - - private static final String HEADLESS_MODE_PROPERTY = "java.awt.headless";// $NON-NLS-1$ - /** The class loader to use for loading JMeter classes. */ - private static final DynamicClassLoader loader; - - /** The directory JMeter is installed in. */ - private static final String JMETER_INSTALLATION_DIRECTORY; - - private static final List EXCEPTIONS_IN_INIT = new ArrayList<>(); - - // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 - public static void setContextClassLoader() { - Thread.currentThread().setContextClassLoader(loader); - } - - public static void loaderClass(String name) { - try { - loader.loadClass(name); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } - - static { - final List jars = new LinkedList<>(); - final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH); - - // Find JMeter home dir from the initial classpath - String tmpDir; - - //只从 jmeter.home 加载 - tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ - if (tmpDir.length() == 0) { - File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ - tmpDir = userDir.getAbsoluteFile().getParent(); - } - - JMETER_INSTALLATION_DIRECTORY=tmpDir; - - /* - * Does the system support UNC paths? If so, may need to fix them up - * later - */ - boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$ - - // Add standard jar locations to initial classpath - StringBuilder classpath = new StringBuilder(); - File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$ - new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$ - new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$ - for (File libDir : libDirs) { - File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar")); - if (libJars == null) { - new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here - continue; - } - Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars - for (File libJar : libJars) { - try { - String s = libJar.getPath(); - - // Fix path to allow the use of UNC URLs - if (usesUNC) { - if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$ - s = "\\\\" + s;// $NON-NLS-1$ - } else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$ - s = "//" + s;// $NON-NLS-1$ - } - } // usesUNC - - jars.add(new File(s).toURI().toURL());// See Java bug 4496398 - classpath.append(CLASSPATH_SEPARATOR); - classpath.append(s); - } catch (MalformedURLException e) { // NOSONAR - EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e)); - } - } - } - - // ClassFinder needs the classpath - System.setProperty(JAVA_CLASS_PATH, initiaClasspath + classpath.toString()); - loader = AccessController.doPrivileged( - (PrivilegedAction) () -> - new DynamicClassLoader(jars.toArray(new URL[jars.size()]), Thread.currentThread().getContextClassLoader()) - ); - } - - /** - * Prevent instantiation. - */ - private NewDriver() { - } - - /** - * Generate an array of jar files located in a directory. - * Jar files located in sub directories will not be added. - * - * @param dir to search for the jar files. - */ - private static File[] listJars(File dir) { - if (dir.isDirectory()) { - return dir.listFiles((f, name) -> { - if (name.endsWith(".jar")) {// $NON-NLS-1$ - File jar = new File(f, name); - return jar.isFile() && jar.canRead(); - } - return false; - }); - } - return new File[0]; - } - - /** - * Add a URL to the loader classpath only; does not update the system classpath. - * - * @param path to be added. - * @throws MalformedURLException when path points to an invalid url - */ - public static void addURL(String path) throws MalformedURLException { - File furl = new File(path); - loader.addURL(furl.toURI().toURL()); // See Java bug 4496398 - File[] jars = listJars(furl); - for (File jar : jars) { - loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 - } - } - - /** - * Add a URL to the loader classpath only; does not update the system - * classpath. - * - * @param url - * The {@link URL} to add to the classpath - */ - public static void addURL(URL url) { - loader.addURL(url); - } - - /** - * Add a directory or jar to the loader and system classpaths. - * - * @param path - * to add to the loader and system classpath - * @throws MalformedURLException - * if path can not be transformed to a valid - * {@link URL} - */ - public static void addPath(String path) throws MalformedURLException { - File file = new File(path); - // Ensure that directory URLs end in "/" - if (file.isDirectory() && !path.endsWith("/")) {// $NON-NLS-1$ - file = new File(path + "/");// $NON-NLS-1$ - } - loader.addURL(file.toURI().toURL()); // See Java bug 4496398 - StringBuilder sb = new StringBuilder(System.getProperty(JAVA_CLASS_PATH)); - sb.append(CLASSPATH_SEPARATOR); - sb.append(path); - File[] jars = listJars(file); - for (File jar : jars) { - loader.addURL(jar.toURI().toURL()); // See Java bug 4496398 - sb.append(CLASSPATH_SEPARATOR); - sb.append(jar.getPath()); - } - - // ClassFinder needs this - System.setProperty(JAVA_CLASS_PATH,sb.toString()); - } - - /** - * Get the directory where JMeter is installed. This is the absolute path - * name. - * - * @return the directory where JMeter is installed. - */ - public static String getJMeterDir() { - return JMETER_INSTALLATION_DIRECTORY; - } - - /** - * The main program which actually runs JMeter. - * - * @param args - * the command line arguments - */ - public static void main(String[] args) { - if(!EXCEPTIONS_IN_INIT.isEmpty()) { - System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT)); // NOSONAR Intentional System.err use - } else { - Thread.currentThread().setContextClassLoader(loader); - - - setLoggingProperties(args); - - try { - // Only set property if it has not been set explicitely - if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) { - System.setProperty(HEADLESS_MODE_PROPERTY, "true"); - } - Class initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$ - Object instance = initialClass.getDeclaredConstructor().newInstance(); - Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$ - startup.invoke(instance, new Object[] { args }); - } catch(Throwable e){ // NOSONAR We want to log home directory in case of exception - e.printStackTrace(); // NOSONAR No logger at this step - System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use - } - } - } - - /** - * @param exceptionsInInit List of {@link Exception} - * @return String - */ - private static String exceptionsToString(List exceptionsInInit) { - StringBuilder builder = new StringBuilder(); - for (Exception exception : exceptionsInInit) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - exception.printStackTrace(printWriter); // NOSONAR - builder.append(stringWriter.toString()) - .append("\r\n"); - } - return builder.toString(); - } - - /* - * Set logging related system properties. - */ - private static void setLoggingProperties(String[] args) { - String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$ - - if (jmLogFile != null && !jmLogFile.isEmpty()) { - jmLogFile = replaceDateFormatInFileName(jmLogFile); - System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$ - } else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$ - System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$ - } - - String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$ - File logConfFile = null; - - if (jmLogConf != null && !jmLogConf.isEmpty()) { - logConfFile = new File(jmLogConf); - } else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$ - logConfFile = new File("log4j2.xml");// $NON-NLS-1$ - if (!logConfFile.isFile()) { - logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "jmeter.bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$ - } - } - - if (logConfFile != null) { - System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$ - } - } - - private static boolean shouldBeHeadless(String[] args) { - for (String arg : args) { - if("-n".equals(arg) || "-s".equals(arg) || "-g".equals(arg)) { - return true; - } - } - return false; - } - /* - * Find command line argument option value by the id and name. - */ - private static String getCommandLineArgument(String[] args, int id, String name) { - final String shortArgName = "-" + ((char) id);// $NON-NLS-1$ - final String longArgName = "--" + name;// $NON-NLS-1$ - - String value = null; - - for (int i = 0; i < args.length; i++) { - if ((shortArgName.equals(args[i]) && i < args.length - 1) - || longArgName.equals(args[i])) { - if (!args[i + 1].startsWith("-")) {// $NON-NLS-1$ - value = args[i + 1]; - } - break; - } else if (!shortArgName.equals(args[i]) && args[i].startsWith(shortArgName)) { - value = args[i].substring(shortArgName.length()); - break; - } - } - - return value; - } - - /* - * If the fileName contains at least one set of paired single-quotes, reformat using DateFormat - */ - private static String replaceDateFormatInFileName(String fileName) { - try { - StringBuilder builder = new StringBuilder(); - - final Date date = new Date(); - int fromIndex = 0; - int begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ - int end; - - String format; - SimpleDateFormat dateFormat; - - while (begin != -1) { - builder.append(fileName.substring(fromIndex, begin)); - - fromIndex = begin + 1; - end = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ - if (end == -1) { - throw new IllegalArgumentException("Invalid pairs of single-quotes in the file name: " + fileName);// $NON-NLS-1$ - } - - format = fileName.substring(begin + 1, end); - dateFormat = new SimpleDateFormat(format); - builder.append(dateFormat.format(date)); - - fromIndex = end + 1; - begin = fileName.indexOf('\'', fromIndex);// $NON-NLS-1$ - } - - if (fromIndex < fileName.length() - 1) { - builder.append(fileName.substring(fromIndex)); - } - - return builder.toString(); - } catch (Exception ex) { - System.err.println("Error replacing date format in file name:"+fileName+", error:"+ex.getMessage()); // NOSONAR - } - - return fileName; - } -} diff --git a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java deleted file mode 100644 index 8b569a3..0000000 --- a/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java +++ /dev/null @@ -1,267 +0,0 @@ -// -// Source code recreated from a .class file by IntelliJ IDEA -// (powered by Fernflower decompiler) -// - -package org.apache.jmeter.assertions; - -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.Predicate; -import io.metersphere.api.jmeter.utils.DocumentUtils; -import net.minidev.json.JSONArray; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.testelement.AbstractTestElement; -import org.apache.jmeter.testelement.ThreadListener; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.oro.text.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.text.DecimalFormat; - -public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { - private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class); - private static final long serialVersionUID = 2L; - public static final String JSONPATH = "JSON_PATH"; - public static final String EXPECTED_VALUE = "EXPECTED_VALUE"; - public static final String JSON_VALIDATION = "JSONVALIDATION"; - public static final String EXPECT_NULL = "EXPECT_NULL"; - public static final String INVERT = "INVERT"; - public static final String IS_REGEX = "ISREGEX"; - private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); - - public JSONPathAssertion() { - } - - private static DecimalFormat createDecimalFormat() { - DecimalFormat decimalFormatter = new DecimalFormat("#.#"); - decimalFormatter.setMaximumFractionDigits(340); - decimalFormatter.setMinimumFractionDigits(1); - return decimalFormatter; - } - - public String getOption() { - return getPropertyAsString("ASS_OPTION"); - } - - public String getElementCondition() { - return getPropertyAsString("ElementCondition"); - } - - public String getJsonPath() { - return this.getPropertyAsString("JSON_PATH"); - } - - public void setJsonPath(String jsonPath) { - this.setProperty("JSON_PATH", jsonPath); - } - - public String getExpectedValue() { - return this.getPropertyAsString("EXPECTED_VALUE"); - } - - public void setExpectedValue(String expectedValue) { - this.setProperty("EXPECTED_VALUE", expectedValue); - } - - public void setJsonValidationBool(boolean jsonValidation) { - this.setProperty(JSON_VALIDATION, jsonValidation); - } - - public void setExpectNull(boolean val) { - this.setProperty("EXPECT_NULL", val); - } - - public boolean isExpectNull() { - return this.getPropertyAsBoolean("EXPECT_NULL"); - } - - public boolean isJsonValidationBool() { - return this.getPropertyAsBoolean(JSON_VALIDATION); - } - - public void setInvert(boolean invert) { - this.setProperty("INVERT", invert); - } - - public boolean isInvert() { - return this.getPropertyAsBoolean("INVERT"); - } - - public void setIsRegex(boolean flag) { - this.setProperty(IS_REGEX, flag); - } - - public boolean isUseRegex() { - return this.getPropertyAsBoolean(IS_REGEX, true); - } - - private void doAssert(String jsonString) { - Object value = JsonPath.read(jsonString, this.getJsonPath(), new Predicate[0]); - if (this.isJsonValidationBool()) { - if (value instanceof JSONArray) { - if (this.arrayMatched((JSONArray) value)) { - return; - } - } else if (this.isExpectNull() && value == null || this.isEquals(value)) { - return; - } - - if (this.isExpectNull()) { - throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); - } else { - String msg = ""; - if (this.isUseRegex()) { - msg = "Value expected to match regexp '%s', but it did not match: '%s'"; - } else if (StringUtils.isNotEmpty(getOption()) && !this.isEquals(value)) { - switch (getOption()) { - case "CONTAINS": - msg = "Value contains to be '%s', but found '%s'"; - break; - case "NOT_CONTAINS": - msg = "Value not contains to be '%s', but found '%s'"; - break; - case "EQUALS": - msg = "Value equals to be '%s', but found '%s'"; - break; - case "NOT_EQUALS": - msg = "Value not equals to be '%s', but found '%s'"; - break; - case "GT": - msg = "Value > '%s', but found '%s'"; - break; - case "LT": - msg = "Value < '%s', but found '%s'"; - break; - case "DOCUMENT": - msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,实际返回:" + DocumentUtils.documentMsg(value, this.getElementCondition()); - break; - } - } else { - msg = "Value expected to be '%s', but found '%s'"; - } - throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter))); - } - } - } - - private boolean arrayMatched(JSONArray value) { - if (value.isEmpty() && "[]".equals(this.getExpectedValue())) { - return true; - } else { - Object[] var2 = value.toArray(); - int var3 = var2.length; - - for (int var4 = 0; var4 < var3; ++var4) { - Object subj = var2[var4]; - if (subj == null && this.isExpectNull() || this.isEquals(subj)) { - return true; - } - } - - return this.isEquals(value); - } - } - - private boolean isGt(String v1, String v2) { - try { - return Long.parseLong(v1) > Long.parseLong(v2); - } catch (Exception e) { - return false; - } - } - - private boolean isLt(String v1, String v2) { - try { - return Long.parseLong(v1) < Long.parseLong(v2); - } catch (Exception e) { - return false; - } - } - - private boolean isEquals(Object subj) { - String str = DocumentUtils.objectToString(subj, decimalFormatter); - if (this.isUseRegex()) { - Pattern pattern = JMeterUtils.getPatternCache().getPattern(this.getExpectedValue()); - return JMeterUtils.getMatcher().matches(str, pattern); - } else { - if (StringUtils.isNotEmpty(getOption())) { - boolean refFlag = false; - switch (getOption()) { - case "CONTAINS": - refFlag = str.contains(getExpectedValue()); - break; - case "NOT_CONTAINS": - refFlag = !str.contains(getExpectedValue()); - break; - case "EQUALS": - refFlag = str.equals(getExpectedValue()); - break; - case "NOT_EQUALS": - refFlag = !str.equals(getExpectedValue()); - break; - case "GT": - refFlag = isGt(str, getExpectedValue()); - break; - case "LT": - refFlag = isLt(str, getExpectedValue()); - break; - case "DOCUMENT": - refFlag = DocumentUtils.documentChecked(subj, this.getElementCondition(), decimalFormatter); - break; - } - return refFlag; - } - return str.equals(this.getExpectedValue()); - } - } - - - public AssertionResult getResult(SampleResult samplerResult) { - AssertionResult result = new AssertionResult(this.getName()); - String responseData = samplerResult.getResponseDataAsString(); - if (responseData.isEmpty()) { - return result.setResultForNull(); - } else { - result.setFailure(false); - result.setFailureMessage(""); - if (!this.isInvert()) { - try { - this.doAssert(responseData); - } catch (Exception var6) { - log.debug("Assertion failed", var6); - result.setFailure(true); - result.setFailureMessage(var6.getMessage()); - } - } else { - try { - this.doAssert(responseData); - result.setFailure(true); - if (this.isJsonValidationBool()) { - if (this.isExpectNull()) { - result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches null"); - } else { - result.setFailureMessage("Failed that JSONPath " + this.getJsonPath() + " not matches " + this.getExpectedValue()); - } - } else { - result.setFailureMessage("Failed that JSONPath not exists: " + this.getJsonPath()); - } - } catch (Exception var5) { - log.debug("Assertion failed, as expected", var5); - } - } - - return result; - } - } - - - public void threadStarted() { - } - - public void threadFinished() { - decimalFormatter.remove(); - } -} diff --git a/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java b/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java deleted file mode 100644 index 5db9ad9..0000000 --- a/src/main/java/org/apache/jmeter/assertions/XMLAssertion.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.assertions; - -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.Predicate; -import io.metersphere.api.jmeter.utils.DocumentUtils; -import net.minidev.json.JSONArray; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.testelement.AbstractTestElement; -import org.apache.jmeter.testelement.ThreadListener; -import org.json.JSONObject; -import org.json.XML; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParserFactory; -import java.io.IOException; -import java.io.Serializable; -import java.io.StringReader; -import java.text.DecimalFormat; - -/** - * Checks if the result is a well-formed XML content using {@link XMLReader} - */ -public class XMLAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { - private static final Logger log = LoggerFactory.getLogger(XMLAssertion.class); - private static ThreadLocal decimalFormatter = ThreadLocal.withInitial(XMLAssertion::createDecimalFormat); - - private static final long serialVersionUID = 242L; - - public String getXmlPath() { - return this.getPropertyAsString("XML_PATH"); - } - - public String getExpectedValue() { - return this.getPropertyAsString("EXPECTED_VALUE"); - } - - public String getCondition() { - return getPropertyAsString("ElementCondition"); - } - - private static DecimalFormat createDecimalFormat() { - DecimalFormat decimalFormatter = new DecimalFormat("#.#"); - decimalFormatter.setMaximumFractionDigits(340); - decimalFormatter.setMinimumFractionDigits(1); - return decimalFormatter; - } - - // one builder for all requests in a thread - private static final ThreadLocal XML_READER = new ThreadLocal() { - @Override - protected XMLReader initialValue() { - try { - XMLReader reader = SAXParserFactory.newInstance() - .newSAXParser() - .getXMLReader(); - reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - return reader; - } catch (SAXException | ParserConfigurationException e) { - log.error("Error initializing XMLReader in XMLAssertion", e); - return null; - } - } - }; - - /** - * Returns the result of the Assertion. - * Here it checks whether the Sample data is XML. - * If so an AssertionResult containing a FailureMessage will be returned. - * Otherwise the returned AssertionResult will reflect the success of the Sample. - */ - @Override - public AssertionResult getResult(SampleResult response) { - // no error as default - AssertionResult result = new AssertionResult(getName()); - String resultData = response.getResponseDataAsString(); - if (resultData.length() == 0) { - return result.setResultForNull(); - } - result.setFailure(false); - XMLReader builder = XML_READER.get(); - if (builder != null) { - try { - builder.setErrorHandler(new LogErrorHandler()); - builder.parse(new InputSource(new StringReader(resultData))); - try { - JSONObject xmlJSONObj = XML.toJSONObject(resultData); - String jsonPrettyPrintString = xmlJSONObj.toString(4); - doAssert(jsonPrettyPrintString); - } catch (Exception e) { - result.setError(true); - result.setFailure(true); - result.setFailureMessage(e.getMessage()); - } - } catch (SAXException | IOException e) { - result.setError(true); - result.setFailure(true); - result.setFailureMessage(e.getMessage()); - } - } else { - result.setError(true); - result.setFailureMessage("Cannot initialize XMLReader in element:" + getName() + ", check jmeter.log file"); - } - return result; - } - - - private void doAssert(String jsonString) { - Object value = JsonPath.read(jsonString, this.getXmlPath(), new Predicate[0]); - if (value instanceof JSONArray) { - if (this.arrayMatched((JSONArray) value)) { - return; - } - } - if (!this.isEquals(value)) { - String msg = (StringUtils.isNotEmpty(this.getName()) ? this.getName().split("==")[1] : "") + "校验失败,实际返回:" + DocumentUtils.documentMsg(value, this.getCondition()); - throw new IllegalStateException(String.format(msg, this.getExpectedValue(), DocumentUtils.objectToString(value, decimalFormatter))); - } - } - - - private boolean isEquals(Object subj) { - String str = DocumentUtils.objectToString(subj, decimalFormatter); - return DocumentUtils.documentChecked(str, this.getCondition(), decimalFormatter); - } - - private boolean arrayMatched(JSONArray value) { - if (value.isEmpty() && "[]".equals(this.getExpectedValue())) { - return true; - } else { - Object[] var2 = value.toArray(); - int var3 = var2.length; - - for (int var4 = 0; var4 < var3; ++var4) { - Object subj = var2[var4]; - if (subj == null || this.isEquals(subj)) { - return true; - } - } - - return this.isEquals(value); - } - } - - @Override - public void threadStarted() { - } - - @Override - public void threadFinished() { - XML_READER.remove(); - } -} diff --git a/src/main/java/org/apache/jmeter/config/CSVDataSet.java b/src/main/java/org/apache/jmeter/config/CSVDataSet.java deleted file mode 100644 index 7fd554f..0000000 --- a/src/main/java/org/apache/jmeter/config/CSVDataSet.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.config; - -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.engine.event.LoopIterationEvent; -import org.apache.jmeter.engine.event.LoopIterationListener; -import org.apache.jmeter.engine.util.NoConfigMerge; -import org.apache.jmeter.gui.GUIMenuSortOrder; -import org.apache.jmeter.gui.TestElementMetadata; -import org.apache.jmeter.save.CSVSaveService; -import org.apache.jmeter.services.FileServer; -import org.apache.jmeter.testbeans.TestBean; -import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer; -import org.apache.jmeter.testelement.property.JMeterProperty; -import org.apache.jmeter.testelement.property.StringProperty; -import org.apache.jmeter.threads.JMeterContext; -import org.apache.jmeter.threads.JMeterVariables; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JMeterStopThreadException; -import org.apache.jorphan.util.JOrphanUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.io.IOException; -import java.util.ResourceBundle; - -/** - * Read lines from a file and split int variables. - *

- * The iterationStart() method is used to set up each set of values. - *

- * By default, the same file is shared between all threads - * (and other thread groups, if they use the same file name). - *

- * The shareMode can be set to: - *

    - *
  • All threads - default, as described above
  • - *
  • Current thread group
  • - *
  • Current thread
  • - *
  • Identifier - all threads sharing the same identifier
  • - *
- *

- * The class uses the FileServer alias mechanism to provide the different share modes. - * For all threads, the file alias is set to the file name. - * Otherwise, a suffix is appended to the filename to make it unique within the required context. - * For current thread group, the thread group identityHashcode is used; - * for individual threads, the thread hashcode is used as the suffix. - * Or the user can provide their own suffix, in which case the file is shared between all - * threads with the same suffix. - */ -@GUIMenuSortOrder(1) -@TestElementMetadata(labelResource = "displayName") -public class CSVDataSet extends ConfigTestElement - implements TestBean, LoopIterationListener, NoConfigMerge { - private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class); - - private static final long serialVersionUID = 233L; - - private static final String EOFVALUE = // value to return at EOF - JMeterUtils.getPropDefault("csvdataset.eofstring", ""); //$NON-NLS-1$ //$NON-NLS-2$ - - private transient String filename; - - private transient String fileEncoding; - - private transient String variableNames; - - private transient String delimiter; - - private transient boolean quoted; - - private transient boolean recycle = true; - - private transient boolean stopThread; - - private transient String[] vars; - - private transient String alias; - - private transient String shareMode; - - private boolean firstLineIsNames = false; - - private boolean ignoreFirstLine = false; - - private Object readResolve() { - recycle = true; - return this; - } - - /** - * Override the setProperty method in order to convert - * the original String shareMode property. - * This used the locale-dependent display value, so caused - * problems when the language was changed. - * If the "shareMode" value matches a resource value then it is converted - * into the resource key. - * To reduce the need to look up resources, we only attempt to - * convert values with spaces in them, as these are almost certainly - * not variables (and they are definitely not resource keys). - */ - @Override - public void setProperty(JMeterProperty property) { - if (!(property instanceof StringProperty)) { - super.setProperty(property); - return; - } - - final String propName = property.getName(); - if (!"shareMode".equals(propName)) { - super.setProperty(property); - return; - } - - final String propValue = property.getStringValue(); - if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation - try { - final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass()); - final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE); - for (String resKey : CSVDataSetBeanInfo.getShareTags()) { - if (propValue.equals(rb.getString(resKey))) { - if (log.isDebugEnabled()) { - log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale()); - } - ((StringProperty) property).setValue(resKey); // reset the value - super.setProperty(property); - return; - } - } - // This could perhaps be a variable name - log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale()); - } catch (IntrospectionException e) { - log.error("Could not find BeanInfo; cannot translate shareMode entries", e); - } - } - super.setProperty(property); - } - - @Override - public void iterationStart(LoopIterationEvent iterEvent) { - FileServer server = FileServer.getFileServer(); - final JMeterContext context = getThreadContext(); - String delim = getDelimiter(); - if ("\\t".equals(delim)) { // $NON-NLS-1$ - delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$ - } else if (delim.isEmpty()) { - log.debug("Empty delimiter, will use ','"); - delim = ","; - } - //if (vars == null) { - initVars(server, context, delim); - //} - - // TODO: fetch this once as per vars above? - JMeterVariables threadVars = context.getVariables(); - String[] lineValues = {}; - try { - if (getQuotedData()) { - lineValues = server.getParsedLine(alias, recycle, - firstLineIsNames || ignoreFirstLine, delim.charAt(0)); - } else { - String line = server.readLine(alias, recycle, - firstLineIsNames || ignoreFirstLine); - lineValues = JOrphanUtils.split(line, delim, false); - } - for (int a = 0; a < vars.length && a < lineValues.length; a++) { - threadVars.put(vars[a], lineValues[a]); - } - } catch (IOException e) { // treat the same as EOF - log.error(e.toString()); - } - if (lineValues.length == 0) {// i.e. EOF - if (getStopThread()) { - throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:" - + getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle()); - } - for (String var : vars) { - threadVars.put(var, EOFVALUE); - } - } - } - - private void initVars(FileServer server, final JMeterContext context, String delim) { - String fileName = getFilename().trim(); - setAlias(context, fileName); - final String names = getVariableNames(); - if (StringUtils.isEmpty(names)) { - String header = server.reserveFile(fileName, getFileEncoding(), alias, true); - try { - vars = CSVSaveService.csvSplitString(header, delim.charAt(0)); - firstLineIsNames = true; - } catch (IOException e) { - throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e); - } - } else { - server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine); - vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$ - } - trimVarNames(vars); - } - - private void setAlias(final JMeterContext context, String alias) { - String mode = getShareMode(); - int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode); - switch (modeInt) { - case CSVDataSetBeanInfo.SHARE_ALL: - this.alias = alias; - break; - case CSVDataSetBeanInfo.SHARE_GROUP: - this.alias = alias + "@" + System.identityHashCode(context.getThreadGroup()); - break; - case CSVDataSetBeanInfo.SHARE_THREAD: - this.alias = alias + "@" + System.identityHashCode(context.getThread()); - break; - default: - this.alias = alias + "@" + mode; // user-specified key - break; - } - } - - /** - * trim content of array varNames - * - * @param varsNames - */ - private void trimVarNames(String[] varsNames) { - for (int i = 0; i < varsNames.length; i++) { - varsNames[i] = varsNames[i].trim(); - } - } - - /** - * @return Returns the filename. - */ - public String getFilename() { - return filename; - } - - /** - * @param filename The filename to set. - */ - public void setFilename(String filename) { - this.filename = filename; - } - - /** - * @return Returns the file encoding. - */ - public String getFileEncoding() { - return fileEncoding; - } - - /** - * @param fileEncoding The fileEncoding to set. - */ - public void setFileEncoding(String fileEncoding) { - this.fileEncoding = fileEncoding; - } - - /** - * @return Returns the variableNames. - */ - public String getVariableNames() { - return variableNames; - } - - /** - * @param variableNames The variableNames to set. - */ - public void setVariableNames(String variableNames) { - this.variableNames = variableNames; - } - - public String getDelimiter() { - return delimiter; - } - - public void setDelimiter(String delimiter) { - this.delimiter = delimiter; - } - - public boolean getQuotedData() { - return quoted; - } - - public void setQuotedData(boolean quoted) { - this.quoted = quoted; - } - - public boolean getRecycle() { - return recycle; - } - - public void setRecycle(boolean recycle) { - this.recycle = recycle; - } - - public boolean getStopThread() { - return stopThread; - } - - public void setStopThread(boolean value) { - this.stopThread = value; - } - - public String getShareMode() { - return shareMode; - } - - public void setShareMode(String value) { - this.shareMode = value; - } - - /** - * @return the ignoreFirstLine - */ - public boolean isIgnoreFirstLine() { - return ignoreFirstLine; - } - - /** - * @param ignoreFirstLine the ignoreFirstLine to set - */ - public void setIgnoreFirstLine(boolean ignoreFirstLine) { - this.ignoreFirstLine = ignoreFirstLine; - } -} diff --git a/src/main/java/org/apache/jmeter/config/KeystoreConfig.java b/src/main/java/org/apache/jmeter/config/KeystoreConfig.java deleted file mode 100644 index 16e4fcf..0000000 --- a/src/main/java/org/apache/jmeter/config/KeystoreConfig.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.config; - -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.gui.TestElementMetadata; -import org.apache.jmeter.testbeans.TestBean; -import org.apache.jmeter.testelement.TestStateListener; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jmeter.util.SSLManager; -import org.apache.jorphan.util.JMeterStopTestException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Configure Keystore - */ -@TestElementMetadata(labelResource = "displayName") -public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener { - - private static final long serialVersionUID = 1L; - private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class); - - private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$ - private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$ - - private String startIndex; - private String endIndex; - private String preload; - private String clientCertAliasVarName; - - public KeystoreConfig() { - super(); - } - - @Override - public void testEnded() { - testEnded(null); - } - - @Override - public void testEnded(String host) { - log.info("Destroying Keystore"); - SSLManager.getInstance().destroyKeystore(); - } - - @Override - public void testStarted() { - testStarted(null); - } - - @Override - public void testStarted(String host) { - String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context"); - if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) { - log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used"); - } - int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0); - int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1); - - if (!StringUtils.isEmpty(this.startIndex)) { - try { - startIndexAsInt = Integer.parseInt(this.startIndex); - } catch (NumberFormatException e) { - log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex, - startIndexAsInt, e, e); - } - } - - if (!StringUtils.isEmpty(this.endIndex)) { - try { - endIndexAsInt = Integer.parseInt(this.endIndex); - } catch (NumberFormatException e) { - log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex, - endIndexAsInt, e, e); - } - } - if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) { - throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index"); - } - log.info( - "Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')", - preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName); - // 加载认证文件 - String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH"); - String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD"); - InputStream in = null; - try { - in = new FileInputStream(new File(path)); - } catch (IOException e) { - log.error(e.getMessage()); - } - SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), - startIndexAsInt, - endIndexAsInt, - clientCertAliasVarName, in, password); - } - - /** - * @return the endIndex - */ - public String getEndIndex() { - return endIndex; - } - - /** - * @param endIndex the endIndex to set - */ - public void setEndIndex(String endIndex) { - this.endIndex = endIndex; - } - - /** - * @return the startIndex - */ - public String getStartIndex() { - return startIndex; - } - - /** - * @param startIndex the startIndex to set - */ - public void setStartIndex(String startIndex) { - this.startIndex = startIndex; - } - - /** - * @return the preload - */ - public String getPreload() { - return preload; - } - - /** - * @param preload the preload to set - */ - public void setPreload(String preload) { - this.preload = preload; - } - - /** - * @return the clientCertAliasVarName - */ - public String getClientCertAliasVarName() { - return clientCertAliasVarName; - } - - /** - * @param clientCertAliasVarName the clientCertAliasVarName to set - */ - public void setClientCertAliasVarName(String clientCertAliasVarName) { - this.clientCertAliasVarName = clientCertAliasVarName; - } -} diff --git a/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java b/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java deleted file mode 100644 index 86feab0..0000000 --- a/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPSamplerProxy.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.protocol.http.sampler; - -import io.metersphere.node.util.LogUtil; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.engine.event.LoopIterationEvent; -import org.apache.jmeter.samplers.Interruptible; - -import java.net.URL; - -/** - * Proxy class that dispatches to the appropriate HTTP sampler. - *

- * This class is stored in the test plan, and holds all the configuration settings. - * The actual implementation is created at run-time, and is passed a reference to this class - * so it can get access to all the settings stored by HTTPSamplerProxy. - */ -public final class HTTPSamplerProxy extends HTTPSamplerBase implements Interruptible { - - private static final long serialVersionUID = 1L; - - private transient HTTPAbstractImpl impl; - - public HTTPSamplerProxy() { - super(); - } - - /** - * Convenience method used to initialise the implementation. - * - * @param impl the implementation to use. - */ - public HTTPSamplerProxy(String impl) { - super(); - setImplementation(impl); - } - - protected String toExternalForm(URL u) { - int len = u.getProtocol().length() + 1; - if (u.getAuthority() != null && u.getAuthority().length() > 0) - len += 2 + u.getAuthority().length(); - if (u.getPath() != null) { - len += u.getPath().length(); - } - if (u.getQuery() != null) { - len += 1 + u.getQuery().length(); - } - if (u.getRef() != null) - len += 1 + u.getRef().length(); - - StringBuffer result = new StringBuffer(len); - result.append(u.getProtocol()); - result.append(":"); - if (u.getAuthority() != null && u.getAuthority().length() > 0) { - result.append("//"); - result.append(u.getAuthority()); - } - if (StringUtils.isNotEmpty(u.getPath())) { - int index = 0; - for (int i = 0; i < u.getPath().length(); i++) { - char ch = u.getPath().charAt(i); - if (String.valueOf(ch).equals("/")) { - index++; - } else { - break; - } - } - result.append("/" + u.getPath().substring(index)); - } - if (u.getQuery() != null) { - result.append('?'); - result.append(u.getQuery()); - } - if (u.getRef() != null) { - result.append("#"); - result.append(u.getRef()); - } - return result.toString(); - } - - /** - * {@inheritDoc} - */ - @Override - protected HTTPSampleResult sample(URL u, String method, boolean areFollowingRedirect, int depth) { - // When Retrieve Embedded resources + Concurrent Pool is used - // as the instance of Proxy is cloned, we end up with impl being null - // testIterationStart will not be executed but it's not a problem for 51380 as it's download of resources - // so SSL context is to be reused - if (impl == null) { // Not called from multiple threads, so this is OK - try { - impl = HTTPSamplerFactory.getImplementation(getImplementation(), this); - } catch (Exception ex) { - return errorResult(ex, new HTTPSampleResult()); - } - } - try { - String url = toExternalForm(u); - if (StringUtils.isNotEmpty(url) && url.startsWith("http:/http")) { - url = url.substring(6); - } - u = new URL(url); - } catch (Exception ex) { - LogUtil.error(ex); - } - return impl.sample(u, method, areFollowingRedirect, depth); - } - - // N.B. It's not possible to forward threadStarted() to the implementation class. - // This is because Config items are not processed until later, and HTTPDefaults may define the implementation - - @Override - public void threadFinished() { - if (impl != null) { - impl.threadFinished(); // Forward to sampler - } - } - - @Override - public boolean interrupt() { - if (impl != null) { - return impl.interrupt(); // Forward to sampler - } - return false; - } - - /* (non-Javadoc) - * @see org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase#testIterationStart(org.apache.jmeter.engine.event.LoopIterationEvent) - */ - @Override - public void testIterationStart(LoopIterationEvent event) { - if (impl != null) { - impl.notifyFirstSampleAfterLoopRestart(); - } - } -} diff --git a/src/main/java/org/apache/jmeter/reporters/ResultAction.java b/src/main/java/org/apache/jmeter/reporters/ResultAction.java deleted file mode 100644 index 10e611e..0000000 --- a/src/main/java/org/apache/jmeter/reporters/ResultAction.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jmeter.reporters; - -import org.apache.jmeter.samplers.SampleEvent; -import org.apache.jmeter.samplers.SampleListener; -import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.testelement.OnErrorTestElement; -import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; - -/** - * ResultAction - take action based on the status of the last Result - */ -public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener { - - private static final long serialVersionUID = 242L; - - private static final Logger log = LoggerFactory.getLogger(ResultAction.class); - - /** - * Constructor is initially called once for each occurrence in the test plan - * For GUI, several more instances are created Then clear is called at start - * of test Called several times during test startup The name will not - * necessarily have been set at this point. - */ - public ResultAction() { - super(); - } - - /** - * Examine the sample(s) and take appropriate action - * - * @see SampleListener#sampleOccurred(SampleEvent) - */ - @Override - public void sampleOccurred(SampleEvent e) { - SampleResult s = e.getResult(); - if (log.isDebugEnabled()) { - log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful()); - } - if (!s.isSuccessful()) { - if (isStopTestNow()) { - s.setStopTestNow(true); - } else if (isStopTest()) { - s.setStopTest(true); - } else if (isStopThread()) { - s.setStopThread(true); - } else if (isStartNextThreadLoop()) { - s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD); - } else if (isStartNextIterationOfCurrentLoop()) { - s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP); - } else if (isBreakCurrentLoop()) { - s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); - } - } else { - if (getErrorAction() == 1000) { - s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public void sampleStarted(SampleEvent e) { - // not used - } - - /** - * {@inheritDoc} - */ - @Override - public void sampleStopped(SampleEvent e) { - // not used - } - -} diff --git a/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/src/main/java/org/apache/jmeter/samplers/SampleResult.java deleted file mode 100644 index 529ab10..0000000 --- a/src/main/java/org/apache/jmeter/samplers/SampleResult.java +++ /dev/null @@ -1,1644 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jmeter.samplers; - -import org.apache.jmeter.assertions.AssertionResult; -import org.apache.jmeter.gui.Searchable; -import org.apache.jmeter.testelement.TestPlan; -import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; -import org.apache.jmeter.threads.JMeterContextService; -import org.apache.jmeter.util.JMeterUtils; -import org.apache.jorphan.util.JOrphanUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -// For unit tests, @see TestSampleResult - -/** - * This is a nice packaging for the various information returned from taking a - * sample of an entry. - */ -public class SampleResult implements Serializable, Cloneable, Searchable { - - private static final long serialVersionUID = 241L; - - // Needs to be accessible from Test code - static Logger log = LoggerFactory.getLogger(SampleResult.class); - - /** - * The default encoding to be used if not overridden. - * The value is ISO-8859-1. - */ - public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name(); - - private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); - private static final String OK_MSG = "OK"; // $NON-NLS-1$ - private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ - - - // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries - // However the suggested System.getProperty("file.encoding") is Cp1252 on - // Windows - // So use a new property with the original value as default - // needs to be accessible from test code - /** - * The default encoding to be used to decode the responseData byte array. - * The value is defined by the property "sampleresult.default.encoding" - * with a default of DEFAULT_HTTP_ENCODING if that is not defined. - */ - protected static final String DEFAULT_ENCODING - = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ - DEFAULT_HTTP_ENCODING); - - /** - * The default used by {@link #setResponseData(String, String)} - */ - private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); - - /** - * Data type value ({@value}) indicating that the response data is text. - * - * @see #getDataType - * @see #setDataType(String) - */ - public static final String TEXT = "text"; // $NON-NLS-1$ - - /** - * Data type value ({@value}) indicating that the response data is binary. - * - * @see #getDataType - * @see #setDataType(String) - */ - public static final String BINARY = "bin"; // $NON-NLS-1$ - - private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); - - // List of types that are known to be binary - private static final String[] BINARY_TYPES = { - "image/", //$NON-NLS-1$ - "audio/", //$NON-NLS-1$ - "video/", //$NON-NLS-1$ - }; - - // List of types that are known to be ascii, although they may appear to be binary - private static final String[] NON_BINARY_TYPES = { - "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) - "audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest) - "video/f4m", //$NON-NLS-1$ (Flash Media Manifest) - "image/svg+xml" //$NON-NLS-1$ (SVG is xml) - }; - - - /** - * empty array which can be returned instead of null - */ - private static final byte[] EMPTY_BA = new byte[0]; - - private static final SampleResult[] EMPTY_SR = new SampleResult[0]; - - private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; - - private static final boolean START_TIMESTAMP = - JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ - - /** - * Allow read-only access from test code - */ - private static final boolean USE_NANO_TIME = - JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ - - /** - * How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread - */ - private static final long NANOTHREAD_SLEEP = - JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$ - - private static final String NULL_FILENAME = "NULL"; - - static { - if (START_TIMESTAMP) { - log.info("Note: Sample TimeStamps are START times"); - } else { - log.info("Note: Sample TimeStamps are END times"); - } - log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING); - log.info("sampleresult.useNanoTime={}", USE_NANO_TIME); - log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP); - - if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) { - // Make sure we start with a reasonable value - NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); - NanoOffset nanoOffset = new NanoOffset(); - nanoOffset.setDaemon(true); - nanoOffset.setName("NanoOffset"); - nanoOffset.start(); - } - } - - /** - * 定制自定义添加 ================= - */ - private String samplerId; - - private String resourceId; - - public String getSamplerId() { - return this.samplerId; - } - - public String getResourceId() { - return this.resourceId; - } - - // 数据格式 List 多层父级按照同级统计 - private String scenario; - - public String getScenario() { - return this.scenario; - } - - /** - * 定制自定义添加 ================= - */ - private SampleSaveConfiguration saveConfig; - - private SampleResult parent; - - private byte[] responseData = EMPTY_BA; - - private String responseCode = "";// Never return null - - private String label = "";// Never return null - - /** - * Filename used by ResultSaver - */ - private String resultFileName = ""; - - /** - * The data used by the sampler - */ - private String samplerData; - - private String threadName = ""; // Never return null - - private String responseMessage = ""; - - private String responseHeaders = ""; // Never return null - - private String requestHeaders = ""; - - /** - * timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) - * the time stamp - can be start or end - */ - private long timeStamp = 0; - - private long startTime = 0; - - private long endTime = 0; - - private long idleTime = 0;// Allow for non-sample time - - /** - * Start of pause (if any) - */ - private long pauseTime = 0; - - private List assertionResults; - - private List subResults; - - /** - * The data type of the sample - * - * @see #getDataType() - * @see #setDataType(String) - * @see #TEXT - * @see #BINARY - */ - private String dataType = ""; // Don't return null if not set - - private boolean success; - - /** - * Files that this sample has been saved in. - * In Non GUI mode and when best config is used, size never exceeds 1, - * but as a compromise set it to 2 - */ - private final Set files = ConcurrentHashMap.newKeySet(2); - - // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? - private String dataEncoding;// (is this really the character set?) e.g. - // ISO-8895-1, UTF-8 - - private String contentType = ""; // e.g. text/html; charset=utf-8 - - /** - * elapsed time - */ - private long elapsedTime = 0; - - /** - * time to first response - */ - private long latency = 0; - - /** - * time to end connecting - */ - private long connectTime = 0; - - /** - * Way to signal what to do on Test - */ - private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE; - - /** - * Should thread terminate? - */ - private boolean stopThread = false; - - /** - * Should test terminate? - */ - private boolean stopTest = false; - - /** - * Should test terminate abruptly? - */ - private boolean stopTestNow = false; - - private int sampleCount = 1; - - private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data - - private int headersSize = 0; - - private long bodySize = 0; - - /** - * Currently active threads in this thread group - */ - private volatile int groupThreads = 0; - - /** - * Currently active threads in all thread groups - */ - private volatile int allThreads = 0; - - private final long nanoTimeOffset; - - // Allow testcode access to the settings - final boolean useNanoTime; - - final long nanoThreadSleep; - - private long sentBytes; - - private URL location; - - private transient boolean ignore; - - private transient int subResultIndex; - - /** - * Cache for responseData as string to avoid multiple computations - */ - private transient volatile String responseDataAsString; - - public SampleResult() { - this(USE_NANO_TIME, NANOTHREAD_SLEEP); - } - - // Allow test code to change the default useNanoTime setting - SampleResult(boolean nanoTime) { - this(nanoTime, NANOTHREAD_SLEEP); - } - - // Allow test code to change the default useNanoTime and nanoThreadSleep settings - SampleResult(boolean nanoTime, long nanoThreadSleep) { - this.elapsedTime = 0; - this.useNanoTime = nanoTime; - this.nanoThreadSleep = nanoThreadSleep; - this.nanoTimeOffset = initOffset(); - // 增加请求ID的获取 - Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); - if (sampler != null) { - this.samplerId = sampler.getPropertyAsString("MS-ID"); - this.resourceId = sampler.getPropertyAsString("MS-RESOURCE-ID"); - this.scenario = sampler.getPropertyAsString("MS-SCENARIO"); - } - - } - - /** - * Copy constructor. - * - * @param res existing sample result - */ - public SampleResult(SampleResult res) { - this(); - allThreads = res.allThreads;//OK - assertionResults = res.assertionResults; - bytes = res.bytes; - headersSize = res.headersSize; - bodySize = res.bodySize; - contentType = res.contentType;//OK - dataEncoding = res.dataEncoding;//OK - dataType = res.dataType;//OK - endTime = res.endTime;//OK - // files is created automatically, and applies per instance - groupThreads = res.groupThreads;//OK - idleTime = res.idleTime; - label = res.label;//OK - latency = res.latency; - connectTime = res.connectTime; - location = res.location;//OK - parent = res.parent; - pauseTime = res.pauseTime; - requestHeaders = res.requestHeaders;//OK - responseCode = res.responseCode;//OK - responseData = res.responseData;//OK - responseDataAsString = null; - responseHeaders = res.responseHeaders;//OK - responseMessage = res.responseMessage;//OK - - // Don't copy this; it is per instance resultFileName = res.resultFileName; - - sampleCount = res.sampleCount; - samplerData = res.samplerData; - saveConfig = res.saveConfig; - sentBytes = res.sentBytes; - startTime = res.startTime;//OK - stopTest = res.stopTest; - stopTestNow = res.stopTestNow; - stopThread = res.stopThread; - testLogicalAction = res.testLogicalAction; - subResults = res.subResults; - success = res.success;//OK - threadName = res.threadName;//OK - elapsedTime = res.elapsedTime; - timeStamp = res.timeStamp; - } - - /** - * Create a sample with a specific elapsed time but don't allow the times to - * be changed later - *

- * (only used by HTTPSampleResult) - * - * @param elapsed time - * @param atend create the sample finishing now, else starting now - */ - protected SampleResult(long elapsed, boolean atend) { - this(); - long now = currentTimeInMillis(); - if (atend) { - setTimes(now - elapsed, now); - } else { - setTimes(now, now + elapsed); - } - } - - /** - * Allow users to create a sample with specific timestamp and elapsed times - * for cloning purposes, but don't allow the times to be changed later - *

- * Currently used by CSVSaveService and - * StatisticalSampleResult - * - * @param stamp this may be a start time or an end time (both in - * milliseconds) - * @param elapsed time in milliseconds - */ - public SampleResult(long stamp, long elapsed) { - this(); - stampAndTime(stamp, elapsed); - } - - private long initOffset() { - if (useNanoTime) { - return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); - } else { - return Long.MIN_VALUE; - } - } - - /** - * @param propertiesToSave The propertiesToSave to set. - */ - public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { - this.saveConfig = propertiesToSave; - } - - public SampleSaveConfiguration getSaveConfig() { - return saveConfig; - } - - public boolean isStampedAtStart() { - return START_TIMESTAMP; - } - - /** - * Create a sample with specific start and end times for test purposes, but - * don't allow the times to be changed later - *

- * (used by StatVisualizerModel.Test) - * - * @param start start time in milliseconds since unix epoch - * @param end end time in milliseconds since unix epoch - * @return sample with given start and end time - */ - public static SampleResult createTestSample(long start, long end) { - SampleResult res = new SampleResult(); - res.setStartTime(start); - res.setEndTime(end); - return res; - } - - /** - * Create a sample with a specific elapsed time for test purposes, but don't - * allow the times to be changed later - * - * @param elapsed - desired elapsed time in milliseconds - * @return sample that starts 'now' and ends elapsed milliseconds later - */ - public static SampleResult createTestSample(long elapsed) { - long now = System.currentTimeMillis(); - return createTestSample(now, now + elapsed); - } - - private static long sampleNsClockInMs() { - return System.nanoTime() / 1000000; - } - - /** - * Helper method to get 1 ms resolution timing. - * - * @return the current time in milliseconds - * @throws RuntimeException when useNanoTime is true but - * nanoTimeOffset is not set - */ - public long currentTimeInMillis() { - if (useNanoTime) { - if (nanoTimeOffset == Long.MIN_VALUE) { - throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set"); - } - return sampleNsClockInMs() + nanoTimeOffset; - } - return System.currentTimeMillis(); - } - - // Helper method to maintain timestamp relationships - private void stampAndTime(long stamp, long elapsed) { - if (START_TIMESTAMP) { - startTime = stamp; - endTime = stamp + elapsed; - } else { - startTime = stamp - elapsed; - endTime = stamp; - } - timeStamp = stamp; - elapsedTime = elapsed; - } - - /** - * For use by SaveService only. - * - * @param stamp this may be a start time or an end time (both in milliseconds) - * @param elapsed time in milliseconds - * @throws RuntimeException when startTime or endTime has been - * set already - */ - public void setStampAndTime(long stamp, long elapsed) { - if (startTime != 0 || endTime != 0) { - throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set"); - } - stampAndTime(stamp, elapsed); - } - - /** - * Set the "marked" flag to show that the result has been written to the file. - * - * @param filename the name of the file - * @return true if the result was previously marked - */ - public boolean markFile(String filename) { - return !files.add(filename != null ? filename : NULL_FILENAME); - } - - public String getResponseCode() { - return responseCode; - } - - /** - * Set response code to OK, i.e. "200" - */ - public void setResponseCodeOK() { - responseCode = OK_CODE; - } - - public void setResponseCode(String code) { - responseCode = code; - } - - public boolean isResponseCodeOK() { - return responseCode.equals(OK_CODE); - } - - public String getResponseMessage() { - return responseMessage; - } - - public void setResponseMessage(String msg) { - responseMessage = msg; - } - - public void setResponseMessageOK() { - responseMessage = OK_MSG; - } - - /** - * Set result statuses OK - shorthand method to set: - *

    - *
  • ResponseCode
  • - *
  • ResponseMessage
  • - *
  • Successful status
  • - *
- */ - public void setResponseOK() { - setResponseCodeOK(); - setResponseMessageOK(); - setSuccessful(true); - } - - public String getThreadName() { - return threadName; - } - - public void setThreadName(String threadName) { - this.threadName = threadName; - } - - /** - * Get the sample timestamp, which may be either the start time or the end time. - * - * @return timeStamp in milliseconds - * @see #getStartTime() - * @see #getEndTime() - */ - public long getTimeStamp() { - return timeStamp; - } - - public String getSampleLabel() { - return label; - } - - /** - * Get the sample label for use in summary reports etc. - * - * @param includeGroup whether to include the thread group name - * @return the label - */ - public String getSampleLabel(boolean includeGroup) { - if (includeGroup) { - return threadName.substring(0, threadName.lastIndexOf(' ')) + ":" + label; - } - return label; - } - - public void setSampleLabel(String label) { - this.label = label; - } - - public void addAssertionResult(AssertionResult assertResult) { - if (assertionResults == null) { - assertionResults = new ArrayList<>(); - } - assertionResults.add(assertResult); - } - - /** - * Gets the assertion results associated with this sample. - * - * @return an array containing the assertion results for this sample. - * Returns empty array if there are no assertion results. - */ - public AssertionResult[] getAssertionResults() { - if (assertionResults == null) { - return EMPTY_AR; - } - return assertionResults.toArray(new AssertionResult[assertionResults.size()]); - } - - /** - * Add a subresult and adjust the parent byte count and end-time. - * - * @param subResult the {@link SampleResult} to be added - */ - public void addSubResult(SampleResult subResult) { - addSubResult(subResult, isRenameSampleLabel()); - } - - /** - * see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055 - * - * @return true if TestPlan is in functional mode or property subresults.disable_renaming is true - */ - public static boolean isRenameSampleLabel() { - return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING); - } - - /** - * Add a subresult and adjust the parent byte count and end-time. - * - * @param subResult the {@link SampleResult} to be added - * @param renameSubResults boolean do we rename subResults based on position - */ - public void addSubResult(SampleResult subResult, boolean renameSubResults) { - if (subResult == null) { - // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 - return; - } - String tn = getThreadName(); - if (tn.length() == 0) { - tn = Thread.currentThread().getName(); - this.setThreadName(tn); - } - subResult.setThreadName(tn); - - // Extend the time to the end of the added sample - setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 - // Include the byte count for the added sample - setBytes(getBytesAsLong() + subResult.getBytesAsLong()); - setSentBytes(getSentBytes() + subResult.getSentBytes()); - setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); - setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong()); - addRawSubResult(subResult, renameSubResults); - } - - /** - * Add a subresult to the collection without updating any parent fields. - * - * @param subResult the {@link SampleResult} to be added - */ - public void addRawSubResult(SampleResult subResult) { - storeSubResult(subResult, isRenameSampleLabel()); - } - - /** - * Add a subresult to the collection without updating any parent fields. - * - * @param subResult the {@link SampleResult} to be added - */ - private void addRawSubResult(SampleResult subResult, boolean renameSubResults) { - storeSubResult(subResult, renameSubResults); - } - - /** - * Add a subresult read from a results file. - *

- * As for {@link SampleResult#addSubResult(SampleResult) - * addSubResult(SampleResult)}, except that the fields don't need to be - * accumulated - * - * @param subResult the {@link SampleResult} to be added - */ - public void storeSubResult(SampleResult subResult) { - storeSubResult(subResult, isRenameSampleLabel()); - } - - /** - * Add a subresult read from a results file. - *

- * As for {@link SampleResult#addSubResult(SampleResult) - * addSubResult(SampleResult)}, except that the fields don't need to be - * accumulated - * - * @param subResult the {@link SampleResult} to be added - * @param renameSubResults boolean do we rename subResults based on position - */ - private void storeSubResult(SampleResult subResult, boolean renameSubResults) { - if (subResults == null) { - subResults = new ArrayList<>(); - } - if (renameSubResults) { - subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++); - } - subResults.add(subResult); - subResult.setParent(this); - } - - /** - * Gets the subresults associated with this sample. - * - * @return an array containing the subresults for this sample. Returns an - * empty array if there are no subresults. - */ - public SampleResult[] getSubResults() { - if (subResults == null) { - return EMPTY_SR; - } - return subResults.toArray(new SampleResult[subResults.size()]); - } - - /** - * Sets the responseData attribute of the SampleResult object. - *

- * If the parameter is null, then the responseData is set to an empty byte array. - * This ensures that getResponseData() can never be null. - * - * @param response the new responseData value - */ - public void setResponseData(byte[] response) { - responseDataAsString = null; - responseData = response == null ? EMPTY_BA : response; - } - - /** - * Sets the responseData attribute of the SampleResult object. - * Should only be called after setting the dataEncoding (if necessary) - * - * @param response the new responseData value (String) - * @deprecated - only intended for use from BeanShell code - */ - @Deprecated - public void setResponseData(String response) { - responseDataAsString = null; - try { - responseData = response.getBytes(getDataEncodingWithDefault()); - } catch (UnsupportedEncodingException e) { - log.warn("Could not convert string, using default encoding. " + e.getLocalizedMessage()); - responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here - } - } - - /** - * Sets the encoding and responseData attributes of the SampleResult object. - * - * @param response the new responseData value (String) - * @param encoding the encoding to set and then use (if null, use platform default) - */ - public void setResponseData(final String response, final String encoding) { - responseDataAsString = null; - String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET; - try { - responseData = response.getBytes(encodeUsing); - setDataEncoding(encodeUsing); - } catch (UnsupportedEncodingException e) { - log.warn("Could not convert string using '" + encodeUsing + - "', using default encoding: " + DEFAULT_CHARSET, e); - responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here - setDataEncoding(DEFAULT_CHARSET); - } - } - - /** - * Gets the responseData attribute of the SampleResult object. - *

- * Note that some samplers may not store all the data, in which case - * getResponseData().length will be incorrect. - *

- * Instead, always use {@link #getBytes()} to obtain the sample result byte count. - *

- * - * @return the responseData value (cannot be null) - */ - public byte[] getResponseData() { - return responseData; - } - - /** - * Gets the responseData of the SampleResult object as a String - * - * @return the responseData value as a String, converted according to the encoding - */ - public String getResponseDataAsString() { - try { - if (responseDataAsString == null) { - responseDataAsString = new String(responseData, getDataEncodingWithDefault()); - } - return responseDataAsString; - } catch (UnsupportedEncodingException e) { - log.warn("Using platform default as " + getDataEncodingWithDefault() + " caused " + e); - return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here - } - } - - public void setSamplerData(String s) { - samplerData = s; - } - - public String getSamplerData() { - return samplerData; - } - - /** - * Get the time it took this sample to occur. - * - * @return elapsed time in milliseconds - */ - public long getTime() { - return elapsedTime; - } - - public boolean isSuccessful() { - return success; - } - - /** - * Sets the data type of the sample. - * - * @param dataType String containing {@link #BINARY} or {@link #TEXT} - * @see #BINARY - * @see #TEXT - */ - public void setDataType(String dataType) { - this.dataType = dataType; - } - - /** - * Returns the data type of the sample. - * - * @return String containing {@link #BINARY} or {@link #TEXT} or the empty string - * @see #BINARY - * @see #TEXT - */ - public String getDataType() { - return dataType; - } - - /** - * Extract and save the DataEncoding and DataType from the parameter provided. - * Does not save the full content Type. - * - * @param ct - content type (may be null) - * @see #setContentType(String) which should be used to save the full content-type string - */ - public void setEncodingAndType(String ct) { - if (ct != null) { - // Extract charset and store as DataEncoding - // N.B. The meta tag: - // - // is now processed by HTTPSampleResult#getDataEncodingWithDefault - final String charsetPrefix = "charset="; // $NON-NLS-1$ - int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix); - if (cset >= 0) { - String charSet = ct.substring(cset + charsetPrefix.length()); - // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed - int semiColon = charSet.indexOf(';'); - if (semiColon >= 0) { - charSet = charSet.substring(0, semiColon); - } - // Check for quoted string - if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$ - setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes - } else { - setDataEncoding(charSet); - } - } - if (isBinaryType(ct)) { - setDataType(BINARY); - } else { - setDataType(TEXT); - } - } - } - - /* - * Determine if content-type is known to be binary, i.e. not displayable as text. - * - * @param ct content type - * @return true if content-type is of type binary. - */ - public static boolean isBinaryType(String ct) { - for (String entry : NON_BINARY_TYPES) { - if (ct.startsWith(entry)) { - return false; - } - } - for (String binaryType : BINARY_TYPES) { - if (ct.startsWith(binaryType)) { - return true; - } - } - return false; - } - - /** - * Sets the successful attribute of the SampleResult object. - * - * @param success the new successful value - */ - public void setSuccessful(boolean success) { - this.success = success; - } - - /** - * Returns the display name. - * - * @return display name of this sample result - */ - @Override - public String toString() { - return getSampleLabel(); - } - - /** - * Returns the dataEncoding or the default if no dataEncoding was provided. - * - * @return the value of the dataEncoding or DEFAULT_ENCODING - */ - public String getDataEncodingWithDefault() { - return getDataEncodingWithDefault(DEFAULT_ENCODING); - } - - /** - * Returns the dataEncoding or the default if no dataEncoding was provided. - * - * @param defaultEncoding the default to be applied - * @return the value of the dataEncoding or the provided default - */ - protected String getDataEncodingWithDefault(String defaultEncoding) { - if (dataEncoding != null && dataEncoding.length() > 0) { - return dataEncoding; - } - return defaultEncoding; - } - - /** - * Returns the dataEncoding. May be null or the empty String. - * - * @return the value of the dataEncoding - */ - public String getDataEncodingNoDefault() { - return dataEncoding; - } - - /** - * Sets the dataEncoding. - * - * @param dataEncoding the dataEncoding to set, e.g. ISO-8895-1, UTF-8 - */ - public void setDataEncoding(String dataEncoding) { - this.dataEncoding = dataEncoding; - } - - /** - * @return whether to stop the test waiting for current running Sampler to end - */ - public boolean isStopTest() { - return stopTest; - } - - /** - * @return whether to stop the test now interrupting current running samplers - */ - public boolean isStopTestNow() { - return stopTestNow; - } - - /** - * @return whether to stop this thread - */ - public boolean isStopThread() { - return stopThread; - } - - public void setStopTest(boolean b) { - stopTest = b; - } - - public void setStopTestNow(boolean b) { - stopTestNow = b; - } - - public void setStopThread(boolean b) { - stopThread = b; - } - - /** - * @return the request headers - */ - public String getRequestHeaders() { - return requestHeaders; - } - - /** - * @return the response headers - */ - public String getResponseHeaders() { - return responseHeaders; - } - - /** - * @param string - - * request headers - */ - public void setRequestHeaders(String string) { - requestHeaders = string; - } - - /** - * @param string - - * response headers - */ - public void setResponseHeaders(String string) { - responseHeaders = string; - } - - /** - * @return the full content type - e.g. text/html [;charset=utf-8 ] - */ - public String getContentType() { - return contentType; - } - - /** - * Get the media type from the Content Type - * - * @return the media type - e.g. text/html (without charset, if any) - */ - public String getMediaType() { - return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH); - } - - /** - * Stores the content-type string, e.g. text/xml; charset=utf-8 - * - * @param string the content-type to be set - * @see #setEncodingAndType(String) which can be used to extract the charset. - */ - public void setContentType(String string) { - contentType = string; - } - - /** - * @return idleTime - */ - public long getIdleTime() { - return idleTime; - } - - /** - * @return the end time - */ - public long getEndTime() { - return endTime; - } - - /** - * @return the start time - */ - public long getStartTime() { - return startTime; - } - - /* - * Helper methods N.B. setStartTime must be called before setEndTime - * - * setStartTime is used by HTTPSampleResult to clone the parent sampler and - * allow the original start time to be kept - */ - protected final void setStartTime(long start) { - startTime = start; - if (START_TIMESTAMP) { - timeStamp = startTime; - } - } - - public void setEndTime(long end) { - endTime = end; - if (!START_TIMESTAMP) { - timeStamp = endTime; - } - if (startTime == 0) { - log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } else { - elapsedTime = endTime - startTime - idleTime; - } - } - - /** - * Set idle time pause. - * For use by SampleResultConverter/CSVSaveService. - * - * @param idle long - */ - public void setIdleTime(long idle) { - idleTime = idle; - } - - private void setTimes(long start, long end) { - setStartTime(start); - setEndTime(end); - } - - /** - * Record the start time of a sample - */ - public void sampleStart() { - if (startTime == 0) { - setStartTime(currentTimeInMillis()); - } else { - log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - } - - /** - * Record the end time of a sample and calculate the elapsed time - */ - public void sampleEnd() { - if (endTime == 0) { - setEndTime(currentTimeInMillis()); - } else { - log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - } - - /** - * Pause a sample - */ - public void samplePause() { - if (pauseTime != 0) { - log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - pauseTime = currentTimeInMillis(); - } - - /** - * Resume a sample - */ - public void sampleResume() { - if (pauseTime == 0) { - log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG)); - } - idleTime += currentTimeInMillis() - pauseTime; - pauseTime = 0; - } - - /** - * When a Sampler is working as a monitor - * - * @param monitor flag whether this sampler is working as a monitor - * @deprecated since 3.2 NOOP - */ - @Deprecated - public void setMonitor(boolean monitor) { - // NOOP - } - - /** - * If the sampler is a monitor, method will return true. - * - * @return true if the sampler is a monitor - * @deprecated since 3.2 always return false - */ - @Deprecated - public boolean isMonitor() { - return false; - } - - /** - * The statistical sample sender aggregates several samples to save on - * transmission costs. - * - * @param count number of samples represented by this instance - */ - public void setSampleCount(int count) { - sampleCount = count; - } - - /** - * return the sample count. by default, the value is 1. - * - * @return the sample count - */ - public int getSampleCount() { - return sampleCount; - } - - /** - * Returns the count of errors. - * - * @return 0 - or 1 if the sample failed - *

- * TODO do we need allow for nested samples? - */ - public int getErrorCount() { - return success ? 0 : 1; - } - - public void setErrorCount(int i) {// for reading from CSV files - // ignored currently - } - - /* - * TODO: error counting needs to be sorted out. - * - * At present the Statistical Sampler tracks errors separately - * It would make sense to move the error count here, but this would - * mean lots of changes. - * It's also tricky maintaining the count - it can't just be incremented/decremented - * when the success flag is set as this may be done multiple times. - * The work-round for now is to do the work in the StatisticalSampleResult, - * which overrides this method. - * Note that some JMS samplers also create samples with > 1 sample count - * Also the Transaction Controller probably needs to be changed to do - * proper sample and error accounting. - * The purpose of this work-round is to allow at least minimal support for - * errors in remote statistical batch mode. - * - */ - - /** - * In the event the sampler does want to pass back the actual contents, we - * still want to calculate the throughput. The bytes are the bytes of the - * response data. - * - * @param length the number of bytes of the response data for this sample - */ - public void setBytes(long length) { - bytes = length; - } - - /** - * In the event the sampler does want to pass back the actual contents, we - * still want to calculate the throughput. The bytes are the bytes of the - * response data. - * - * @param length the number of bytes of the response data for this sample - * @deprecated use setBytes(long) - */ - @Deprecated - public void setBytes(int length) { - setBytes((long) length); - } - - /** - * @param sentBytesCount long sent bytes - */ - public void setSentBytes(long sentBytesCount) { - sentBytes = sentBytesCount; - } - - /** - * @return the sentBytes - */ - public long getSentBytes() { - return sentBytes; - } - - /** - * return the bytes returned by the response. - * - * @return byte count - * @deprecated use getBytesAsLong - */ - @Deprecated - public int getBytes() { - return (int) getBytesAsLong(); - } - - /** - * return the bytes returned by the response. - * - * @return byte count - */ - public long getBytesAsLong() { - long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong(); - return tmpSum == 0 ? bytes : tmpSum; - } - - /** - * @return Returns the latency. - */ - public long getLatency() { - return latency; - } - - /** - * Set the time to the first response - */ - public void latencyEnd() { - latency = currentTimeInMillis() - startTime - idleTime; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param latency The latency to set. - */ - public void setLatency(long latency) { - this.latency = latency; - } - - /** - * @return Returns the connect time. - */ - public long getConnectTime() { - return connectTime; - } - - /** - * Set the time to the end of connecting - */ - public void connectEnd() { - connectTime = currentTimeInMillis() - startTime - idleTime; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param time The connect time to set. - */ - public void setConnectTime(long time) { - this.connectTime = time; - } - - /** - * This is only intended for use by SampleResultConverter! - * - * @param timeStamp The timeStamp to set. - */ - public void setTimeStamp(long timeStamp) { - this.timeStamp = timeStamp; - } - - - public void setURL(URL location) { - this.location = location; - } - - public URL getURL() { - return location; - } - - /** - * Get a String representation of the URL (if defined). - * - * @return ExternalForm of URL, or empty string if url is null - */ - public String getUrlAsString() { - return location == null ? "" : location.toExternalForm(); - } - - /** - * @return Returns the parent. - */ - public SampleResult getParent() { - return parent; - } - - /** - * @param parent The parent to set. - */ - public void setParent(SampleResult parent) { - this.parent = parent; - } - - public String getResultFileName() { - return resultFileName; - } - - public void setResultFileName(String resultFileName) { - this.resultFileName = resultFileName; - } - - public int getGroupThreads() { - return groupThreads; - } - - public void setGroupThreads(int n) { - this.groupThreads = n; - } - - public int getAllThreads() { - return allThreads; - } - - public void setAllThreads(int n) { - this.allThreads = n; - } - - // Bug 47394 - - /** - * Allow custom SampleSenders to drop unwanted assertionResults - */ - public void removeAssertionResults() { - this.assertionResults = null; - } - - /** - * Allow custom SampleSenders to drop unwanted subResults - */ - public void removeSubResults() { - this.subResults = null; - } - - /** - * Set the headers size in bytes - * - * @param size the number of bytes of the header - */ - public void setHeadersSize(int size) { - this.headersSize = size; - } - - /** - * Get the headers size in bytes - * - * @return the headers size - */ - public int getHeadersSize() { - return headersSize; - } - - /** - * @return the body size in bytes - * @deprecated replaced by getBodySizeAsLong() - */ - @Deprecated - public int getBodySize() { - return (int) getBodySizeAsLong(); - } - - /** - * @return the body size in bytes - */ - public long getBodySizeAsLong() { - return bodySize == 0 ? responseData.length : bodySize; - } - - /** - * @param bodySize the body size to set - */ - public void setBodySize(long bodySize) { - this.bodySize = bodySize; - } - - /** - * @param bodySize the body size to set - * @deprecated use setBodySize(long) - */ - @Deprecated - public void setBodySize(int bodySize) { - this.bodySize = bodySize; - } - - private static class NanoOffset extends Thread { - - private static volatile long nanoOffset; - - static long getNanoOffset() { - return nanoOffset; - } - - @Override - public void run() { - // Wait longer than a clock pulse (generally 10-15ms) - getOffset(30L); // Catch an early clock pulse to reduce slop. - while (true) { - getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks - } - } - - private static void getOffset(long wait) { - try { - TimeUnit.MILLISECONDS.sleep(wait); - long clock = System.currentTimeMillis(); - long nano = SampleResult.sampleNsClockInMs(); - nanoOffset = clock - nano; - } catch (InterruptedException ignore) { - // ignored - Thread.currentThread().interrupt(); - } - } - } - - /** - * @return the startNextThreadLoop - * @deprecated use {@link SampleResult#getTestLogicalAction()} - */ - @Deprecated - public boolean isStartNextThreadLoop() { - return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; - } - - /** - * @param startNextThreadLoop the startNextLoop to set - * @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction) - */ - @Deprecated - public void setStartNextThreadLoop(boolean startNextThreadLoop) { - if (startNextThreadLoop) { - testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; - } else { - testLogicalAction = TestLogicalAction.CONTINUE; - } - } - - /** - * Clean up cached data - */ - public void cleanAfterSample() { - this.responseDataAsString = null; - } - - @Override - public Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new IllegalStateException("This should not happen"); - } - } - - @Override - public List getSearchableTokens() throws Exception { - List datasToSearch = new ArrayList<>(4); - datasToSearch.add(getSampleLabel()); - datasToSearch.add(getResponseDataAsString()); - datasToSearch.add(getRequestHeaders()); - datasToSearch.add(getResponseHeaders()); - return datasToSearch; - } - - /** - * @return boolean true if this SampleResult should not be sent to Listeners - */ - public boolean isIgnore() { - return ignore; - } - - /** - * Call this method to tell JMeter to ignore this SampleResult by Listeners - */ - public void setIgnore() { - this.ignore = true; - } - - /** - * @return String first non null assertion failure message if assertionResults is not null, null otherwise - */ - public String getFirstAssertionFailureMessage() { - String message = null; - AssertionResult[] results = getAssertionResults(); - - if (results != null) { - // Find the first non-null message - for (AssertionResult result : results) { - message = result.getFailureMessage(); - if (message != null) { - break; - } - } - } - return message; - } - - /** - * @return the testLogicalAction - */ - public TestLogicalAction getTestLogicalAction() { - return testLogicalAction; - } - - /** - * @param testLogicalAction the testLogicalAction to set - */ - public void setTestLogicalAction(TestLogicalAction testLogicalAction) { - this.testLogicalAction = testLogicalAction; - } -} diff --git a/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java b/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java deleted file mode 100644 index 67568d3..0000000 --- a/src/main/java/org/apache/jmeter/util/JSR223BeanInfoSupport.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jmeter.util; - -import org.apache.jmeter.testbeans.TestBean; - -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; -import java.util.*; -import java.util.Map.Entry; - -/** - * 解决JSR233加载 ScriptEngineFactory 空指针问题 - */ -public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport { - - private static final String[] LANGUAGE_TAGS; - - /** - * Will be removed in next version following 3.2 - * @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()} - */ - @Deprecated - public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility - - private static final String[][] CONSTANT_LANGUAGE_NAMES; - - static { - Map nameMap = new HashMap<>(); - ScriptEngineManager sem = new ScriptEngineManager(); - final List engineFactories = sem.getEngineFactories(); - for(ScriptEngineFactory fact : engineFactories){ - List names = fact.getNames(); - for(String shortName : names) { - if (shortName != null) { - nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact); - } - } - } - LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]); - Arrays.sort(LANGUAGE_TAGS); - CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2]; - int i = 0; - for(Entry me : nameMap.entrySet()) { - final String key = me.getKey(); - CONSTANT_LANGUAGE_NAMES[i][0] = key; - final ScriptEngineFactory fact = me.getValue(); - CONSTANT_LANGUAGE_NAMES[i++][1] = key + - " (" // $NON-NLS-1$ - + fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$ - + " / " // $NON-NLS-1$ - + fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$ - + ")"; // $NON-NLS-1$ - } - - LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility - } - - private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() { - @Override - protected Object[][] getContents() { - return CONSTANT_LANGUAGE_NAMES; - } - }; - - protected JSR223BeanInfoSupport(Class beanClass) { - super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE); - } - - /** - * @return String array of 2 columns array containing Script engine short name / Script Language details - */ - public static final String[][] getLanguageNames() { - return CONSTANT_LANGUAGE_NAMES.clone(); - } - -} diff --git a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/main/java/org/apache/jmeter/util/JSR223TestElement.java deleted file mode 100644 index 2dd7645..0000000 --- a/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jmeter.util; - -import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.node.util.LogUtil; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.collections.map.LRUMap; -import org.apache.commons.lang3.StringUtils; -import org.apache.jmeter.NewDriver; -import org.apache.jmeter.samplers.SampleResult; -import org.apache.jmeter.samplers.Sampler; -import org.apache.jmeter.testelement.TestStateListener; -import org.apache.jmeter.threads.JMeterContext; -import org.apache.jmeter.threads.JMeterContextService; -import org.apache.jmeter.threads.JMeterVariables; -import org.apache.jorphan.util.JOrphanUtils; -import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.script.*; -import java.io.*; -import java.util.Collections; -import java.util.Map; -import java.util.Properties; - - -/** - * Base class for JSR223 Test elements - *

- * 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 - * 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 - */ -public abstract class JSR223TestElement extends ScriptingTestElement - implements Serializable, TestStateListener { - private static final long serialVersionUID = 232L; - - private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class); - /** - * Cache of compiled scripts - */ - @SuppressWarnings("unchecked") // LRUMap does not support generics (yet) - private static final Map compiledScriptsCache = - Collections.synchronizedMap( - new LRUMap(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100))); - - /** - * If not empty then script in ScriptText will be compiled and cached - */ - private String cacheKey = ""; - - /** - * md5 of the script, used as an unique key for the cache - */ - private String scriptMd5 = null; - - /** - * Initialization On Demand Holder pattern - */ - private static class LazyHolder { - private LazyHolder() { - super(); - } - - public static final ScriptEngineManager INSTANCE = new ScriptEngineManager(); - } - - /** - * @return ScriptEngineManager singleton - */ - public static ScriptEngineManager getInstance() { - return LazyHolder.INSTANCE; - } - - public JSR223TestElement() { - super(); - } - - /** - * @return {@link ScriptEngine} for language defaulting to groovy if language is not set - * @throws ScriptException when no {@link ScriptEngine} could be found - */ - protected ScriptEngine getScriptEngine() throws ScriptException { - String lang = getScriptLanguageWithDefault(); - ScriptEngine scriptEngine = getInstance().getEngineByName(lang); - if (scriptEngine == null) { - throw new ScriptException("Cannot find engine named: '" + lang + "', ensure you set language field in JSR223 Test Element: " + getName()); - } - - return scriptEngine; - } - - /** - * @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set - */ - private String getScriptLanguageWithDefault() { - String lang = getScriptLanguage(); - if (StringUtils.isNotEmpty(lang)) { - return lang; - } - return DEFAULT_SCRIPT_LANGUAGE; - } - - /** - * Populate variables to be passed to scripts - * - * @param bindings Bindings - */ - protected void populateBindings(Bindings bindings) { - final String label = getName(); - final String fileName = getFilename(); - final String scriptParameters = getParameters(); - // Use actual class name for log - final Logger elementLogger = LoggerFactory.getLogger(getClass().getName() + "." + getName()); - bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed) - bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed) - bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed) - bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed) - String[] args = JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$ - bindings.put("args", args); // $NON-NLS-1$ (this name is fixed) - // Add variables for access to context and variables - JMeterContext jmctx = JMeterContextService.getContext(); - bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed) - JMeterVariables vars = jmctx.getVariables(); - bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed) - Properties props = JMeterUtils.getJMeterProperties(); - bindings.put("props", props); // $NON-NLS-1$ (this name is fixed) - // For use in debugging: - bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed) - - // Most subclasses will need these: - Sampler sampler = jmctx.getCurrentSampler(); - bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed) - SampleResult prev = jmctx.getPreviousResult(); - bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed) - } - - - /** - * This method will run inline script or file script with special behaviour for file script: - * - If ScriptEngine implements Compilable script will be compiled and cached - * - If not if will be run - * - * @param scriptEngine ScriptEngine - * @param pBindings {@link Bindings} might be null - * @return Object returned by script - * @throws IOException when reading the script fails - * @throws ScriptException when compiling or evaluation of the script fails - */ - protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings) - throws IOException, ScriptException { - - this.loadGroovyJar(scriptEngine); - - Bindings bindings = pBindings; - if (bindings == null) { - bindings = scriptEngine.createBindings(); - } - populateBindings(bindings); - File scriptFile = new File(getFilename()); - // Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws - // "java.lang.Error: unimplemented" - boolean supportsCompilable = scriptEngine instanceof Compilable - && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ - - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 - // 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 - NewDriver.setContextClassLoader(); - - try { - if (!StringUtils.isEmpty(getFilename())) { - if (scriptFile.exists() && scriptFile.canRead()) { - if (supportsCompilable) { - String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$ - scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$ - scriptFile.lastModified(); - CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey); - if (compiledScript == null) { - synchronized (compiledScriptsCache) { - compiledScript = compiledScriptsCache.get(newCacheKey); - if (compiledScript == null) { - // TODO Charset ? - try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), - (int) scriptFile.length())) { - compiledScript = ((Compilable) scriptEngine).compile(fileReader); - compiledScriptsCache.put(newCacheKey, compiledScript); - } - } - } - } - return compiledScript.eval(bindings); - } else { - // TODO Charset ? - try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), - (int) scriptFile.length())) { - return scriptEngine.eval(fileReader, bindings); - } - } - } else { - throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() - + "' does not exist or is unreadable for element:" + getName()); - } - } else if (!StringUtils.isEmpty(getScript())) { - if (supportsCompilable && - !ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) { - computeScriptMD5(); - CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5); - if (compiledScript == null) { - synchronized (compiledScriptsCache) { - compiledScript = compiledScriptsCache.get(this.scriptMd5); - if (compiledScript == null) { - compiledScript = ((Compilable) scriptEngine).compile(getScript()); - compiledScriptsCache.put(this.scriptMd5, compiledScript); - } - } - } - - return compiledScript.eval(bindings); - } else { - return scriptEngine.eval(getScript(), bindings); - } - } else { - throw new ScriptException("Both script file and script text are empty for element:" + getName()); - } - } catch (ScriptException ex) { - Throwable rootCause = ex.getCause(); - if (isStopCondition(rootCause)) { - throw (RuntimeException) ex.getCause(); - } else { - throw ex; - } - } finally { - // 将当前类加载器设置回来,避免加载无法加载到系统类加载加载类 - Thread.currentThread().setContextClassLoader(contextClassLoader); - } - } - - - /** - * groovy 使用的是自己的类加载器, - * 这里再执行脚本前,使用 groovy的加载器加载jar包, - * 解决groovy脚本无法使用jar包的问题 - * - * @param scriptEngine - * @Auth jianxing - */ - public static void loadGroovyJar(ScriptEngine scriptEngine) { - if (scriptEngine instanceof GroovyScriptEngineImpl) { - GroovyScriptEngineImpl groovyScriptEngine = (GroovyScriptEngineImpl) scriptEngine; - - try { - File file = new File(FileUtils.JAR_FILE_DIR); - if (file.isFile()) { - groovyScriptEngine.getClassLoader().addURL(file.toURI().toURL()); - } else { - File[] files = file.listFiles(); - if(files!= null && files.length > 0){ - for (File f : files) { - groovyScriptEngine.getClassLoader().addURL(f.toURI().toURL()); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - LogUtil.error(e.getMessage(), e); - } - } - } - - /** - * @return boolean true if element is not compilable or if compilation succeeds - * @throws IOException if script is missing - * @throws ScriptException if compilation fails - */ - public boolean compile() - throws ScriptException, IOException { - String lang = getScriptLanguageWithDefault(); - ScriptEngine scriptEngine = getInstance().getEngineByName(lang); - boolean supportsCompilable = scriptEngine instanceof Compilable - && !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$ - if (!supportsCompilable) { - return true; - } - if (!StringUtils.isEmpty(getScript())) { - try { - ((Compilable) scriptEngine).compile(getScript()); - return true; - } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); - return false; - } - } else { - File scriptFile = new File(getFilename()); - try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile), - (int) scriptFile.length())) { - try { - ((Compilable) scriptEngine).compile(fileReader); - return true; - } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); - return false; - } - } - } - } - - /** - * compute MD5 if it is null - */ - private void computeScriptMD5() { - // compute the md5 of the script if needed - if (scriptMd5 == null) { - scriptMd5 = DigestUtils.md5Hex(getScript()); - } - } - - /** - * @return the cacheKey - */ - public String getCacheKey() { - return cacheKey; - } - - /** - * @param cacheKey the cacheKey to set - */ - public void setCacheKey(String cacheKey) { - this.cacheKey = cacheKey; - } - - /** - * @see org.apache.jmeter.testelement.TestStateListener#testStarted() - */ - @Override - public void testStarted() { - // NOOP - } - - /** - * @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String) - */ - @Override - public void testStarted(String host) { - // NOOP - } - - /** - * @see org.apache.jmeter.testelement.TestStateListener#testEnded() - */ - @Override - public void testEnded() { - testEnded(""); - } - - /** - * @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String) - */ - @Override - public void testEnded(String host) { - compiledScriptsCache.clear(); - this.scriptMd5 = null; - } - - public String getScriptLanguage() { - return scriptLanguage; - } - - public void setScriptLanguage(String s) { - scriptLanguage = s; - } -} diff --git a/src/main/java/org/apache/jmeter/util/SSLManager.java b/src/main/java/org/apache/jmeter/util/SSLManager.java deleted file mode 100644 index 93363ed..0000000 --- a/src/main/java/org/apache/jmeter/util/SSLManager.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jmeter.util; - -import org.apache.commons.lang3.Validate; -import org.apache.jmeter.gui.GuiPackage; -import org.apache.jmeter.util.keystore.JmeterKeyStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.security.*; -import java.security.cert.CertificateException; -import java.util.Locale; - -/** - * The SSLManager handles the KeyStore information for JMeter. Basically, it - * handles all the logic for loading and initializing all the JSSE parameters - * and selecting the alias to authenticate against if it is available. - * SSLManager will try to automatically select the client certificate for you, - * but if it can't make a decision, it will pop open a dialog asking you for - * more information. - *

- * TODO? - N.B. does not currently allow the selection of a client certificate. - */ -public abstract class SSLManager { - private static final Logger log = LoggerFactory.getLogger(SSLManager.class); - - private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$ - - private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password - - public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$ - - private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$ - - private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ - - /** - * Singleton instance of the manager - */ - private static SSLManager manager; - - private static final boolean IS_SSL_SUPPORTED = true; - - /** - * Cache the KeyStore instance - */ - private JmeterKeyStore keyStore; - - /** - * Cache the TrustStore instance - null if no truststore name was provided - */ - private KeyStore trustStore = null; - // Have we yet tried to load the truststore? - private volatile boolean truststoreLoaded = false; - - /** - * Have the password available - */ - protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD); - - private int keystoreAliasStartIndex; - - private int keystoreAliasEndIndex; - - private String clientCertAliasVarName; - - /** - * Resets the SSLManager so that we can create a new one with a new keystore - */ - public static synchronized void reset() { - SSLManager.manager = null; - } - - public abstract void setContext(HttpURLConnection conn); - - /** - * Default implementation of setting the Provider - * - * @param provider the provider to use - */ - protected void setProvider(Provider provider) { - if (null != provider) { - Security.addProvider(provider); - } - } - - protected synchronized JmeterKeyStore getKeyStore() { - if (null == this.keyStore) { - String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided - String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type - fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name - log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); - try { - this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); - log.info("KeyStore created OK"); - } catch (Exception e) { - this.keyStore = null; - throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); - } - - try { - - // The string 'NONE' is used for the keystore location when using PKCS11 - // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE - if ("NONE".equalsIgnoreCase(fileName)) { - retryLoadKeys(null, false); - log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); - } else { - File initStore = new File(fileName); - if (fileName.length() > 0 && initStore.exists()) { - retryLoadKeys(initStore, true); - if (log.isInfoEnabled()) { - log.info("Total of {} aliases loaded OK from keystore {}", - keyStore.getAliasCount(), fileName); - } - } else { - log.warn("Keystore file not found, loading empty keystore"); - this.defaultpw = ""; // Ensure not null - this.keyStore.load(null, ""); - } - } - } catch (Exception e) { - log.error("Problem loading keystore: {}", e.getMessage(), e); - } - - if (log.isDebugEnabled()) { - log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); - } - } - - return this.keyStore; - } - - /** - * Opens and initializes the KeyStore. If the password for the KeyStore is - * not set, this method will prompt you to enter it. Unfortunately, there is - * no PasswordEntryField available from JOptionPane. - * - * @return the configured {@link JmeterKeyStore} - */ - protected synchronized JmeterKeyStore getKeyStore(InputStream is, String password) { - if (null == this.keyStore) { - String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided - String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type - fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name - log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); - try { - this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName); - log.info("KeyStore created OK"); - } catch (Exception e) { - this.keyStore = null; - throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e); - } - - try { - - // The string 'NONE' is used for the keystore location when using PKCS11 - // https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE - if ("NONE".equalsIgnoreCase(fileName)) { - retryLoadKeys(null, false); - log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount()); - } else { - File initStore = new File(fileName); - if (fileName.length() > 0 && initStore.exists()) { - retryLoadKeys(initStore, true); - if (log.isInfoEnabled()) { - log.info("Total of {} aliases loaded OK from keystore {}", - keyStore.getAliasCount(), fileName); - } - } else { - log.warn("Keystore file not found, loading empty keystore"); - this.defaultpw = ""; // Ensure not null - this.keyStore.load(is, password); - } - } - } catch (Exception e) { - log.error("Problem loading keystore: {}", e.getMessage(), e); - } - - if (log.isDebugEnabled()) { - log.debug("JmeterKeyStore type: {}", this.keyStore.getClass()); - } - } - - return this.keyStore; - } - - private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException, - CertificateException, IOException, KeyStoreException, UnrecoverableKeyException { - for (int i = 0; i < 3; i++) { - String password = getPassword(); - if (!allowEmptyPassword) { - Validate.notNull(password, "Password for keystore must not be null"); - } - try { - if (initStore == null) { - this.keyStore.load(null, password); - } else { - try (InputStream fis = new FileInputStream(initStore)) { - this.keyStore.load(fis, password); - } - } - return; - } catch (IOException e) { - log.debug("Could not load keystore. Wrong password for keystore?", e); - } - this.defaultpw = null; - } - } - - /* - * The password can be defined as a property; this dialogue is provided to allow it - * to be entered at run-time. - */ - private String getPassword() { - String password = this.defaultpw; - if (null == password) { - final GuiPackage guiInstance = GuiPackage.getInstance(); -// if (guiInstance != null) { -// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]")); -// JLabel passwordLabel = new JLabel("Password: "); -// JPasswordField pwf = new JPasswordField(64); -// pwf.setEchoChar('*'); -// passwordLabel.setLabelFor(pwf); -// panel.add(passwordLabel); -// panel.add(pwf); -// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel, -// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION, -// JOptionPane.PLAIN_MESSAGE); -// if (choice == JOptionPane.OK_OPTION) { -// char[] pwchars = pwf.getPassword(); -// this.defaultpw = new String(pwchars); -// Arrays.fill(pwchars, '*'); -// } -// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw); -// password = this.defaultpw; -// } - } else { - log.warn("No password provided, and no GUI present so cannot prompt"); - } - return password; - } - - /** - * Opens and initializes the TrustStore. - *

- * There are 3 possibilities: - *

    - *
  • no truststore name provided, in which case the default Java truststore - * should be used
  • - *
  • truststore name is provided, and loads OK
  • - *
  • truststore name is provided, but is not found or does not load OK, in - * which case an empty - * truststore is created
  • - *
- * If the KeyStore object cannot be created, then this is currently treated the - * same as if no truststore name was provided. - * - * @return {@code null} when Java truststore should be used. - * Otherwise the truststore, which may be empty if the file could not be - * loaded. - */ - protected KeyStore getTrustStore() { - if (!truststoreLoaded) { - - truststoreLoaded = true;// we've tried ... - - String fileName = System.getProperty(SSL_TRUST_STORE); - if (fileName == null) { - return null; - } - log.info("TrustStore Location: {}", fileName); - - try { - this.trustStore = KeyStore.getInstance("JKS"); - log.info("TrustStore created OK, Type: JKS"); - } catch (Exception e) { - this.trustStore = null; - throw new RuntimeException("Problem creating truststore: " + e.getMessage(), e); - } - - try { - File initStore = new File(fileName); - - if (initStore.exists()) { - try (InputStream fis = new FileInputStream(initStore)) { - this.trustStore.load(fis, null); - log.info("Truststore loaded OK from file"); - } - } else { - log.warn("Truststore file not found, loading empty truststore"); - this.trustStore.load(null, null); - } - } catch (Exception e) { - throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e); - } - } - - return this.trustStore; - } - - /** - * Protected Constructor to remove the possibility of directly instantiating - * this object. Create the SSLContext, and wrap all the X509KeyManagers with - * our X509KeyManager so that we can choose our alias. - */ - protected SSLManager() { - } - - /** - * Static accessor for the SSLManager object. The SSLManager is a singleton. - * - * @return the singleton {@link SSLManager} - */ - public static synchronized SSLManager getInstance() { - if (null == SSLManager.manager) { - SSLManager.manager = new JsseSSLManager(null); - } - - return SSLManager.manager; - } - - /** - * Test whether SSL is supported or not. - * - * @return flag whether SSL is supported - */ - public static boolean isSSLSupported() { - return SSLManager.IS_SSL_SUPPORTED; - } - - /** - * Configure Keystore - * - * @param preload flag whether the keystore should be opened within this method, - * or the opening should be delayed - * @param startIndex first index to consider for a key - * @param endIndex last index to consider for a key - * @param clientCertAliasVarName name of the default key, if empty the first key will be used - * as default key - */ - public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName, InputStream is, String password) { - this.keystoreAliasStartIndex = startIndex; - this.keystoreAliasEndIndex = endIndex; - this.clientCertAliasVarName = clientCertAliasVarName; - if (preload) { - keyStore = getKeyStore(is, password); - } - } - - /** - * Destroy Keystore - */ - public synchronized void destroyKeystore() { - keyStore = null; - } -} diff --git a/src/main/java/org/apache/jorphan/collections/HashTree.java b/src/main/java/org/apache/jorphan/collections/HashTree.java deleted file mode 100644 index 828ab58..0000000 --- a/src/main/java/org/apache/jorphan/collections/HashTree.java +++ /dev/null @@ -1,1095 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.apache.jorphan.collections; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.*; - -/** - * This class is used to create a tree structure of objects. Each element in the - * tree is also a key to the next node down in the tree. It provides many ways - * to add objects and branches, as well as many ways to retrieve. - *

- * HashTree implements the Map interface for convenience reasons. The main - * difference between a Map and a HashTree is that the HashTree organizes the - * data into a recursive tree structure, and provides the means to manipulate - * that structure. - *

- * Of special interest is the {@link #traverse(HashTreeTraverser)} method, which - * provides an expedient way to traverse any HashTree by implementing the - * {@link HashTreeTraverser} interface in order to perform some operation on the - * tree, or to extract information from the tree. - * - * @see HashTreeTraverser - * @see SearchByClass - */ -public class HashTree implements Serializable, Map, Cloneable { - - private static final long serialVersionUID = 240L; - - // Used for the RuntimeException to short-circuit the traversal - private static final String FOUND = "found"; // $NON-NLS-1$ - - // N.B. The keys can be either JMeterTreeNode or TestElement - protected final Map data; - - /** - * Creates an empty new HashTree. - */ - public HashTree() { - this(null, null); - } - - /** - * Allow subclasses to provide their own Map. - * @param _map {@link Map} to use - */ - protected HashTree(Map _map) { - this(_map, null); - } - - /** - * Creates a new HashTree and adds the given object as a top-level node. - * - * @param key - * name of the new top-level node - */ - public HashTree(Object key) { - this(new LinkedHashMap(), key); - } - - /** - * Uses the new HashTree if not null and adds the given object as a - * top-level node if not null - * - * @param _map - * the map to be used. If null a new {@link LinkedHashMap} - * will be created - * @param key - * the object to be used as the key for the root node (may be - * null, in which case no root node will be created) - */ - private HashTree(Map _map, Object key) { - if(_map != null) { - data = _map; - } else { - data = new LinkedHashMap<>(); - } - if(key != null) { - data.put(key, new HashTree()); - } - } - - /** - * The Map given must also be a HashTree, otherwise an - * UnsupportedOperationException is thrown. If it is a HashTree, this is - * like calling the add(HashTree) method. - * - * @see #add(HashTree) - * @see Map#putAll(Map) - */ - @Override - public void putAll(Map map) { - if (map instanceof HashTree) { - this.add((HashTree) map); - } else { - throw new UnsupportedOperationException("can only putAll other HashTree objects"); - } - } - - /** - * Exists to satisfy the Map interface. - * - * @see Map#entrySet() - */ - @Override - public Set> entrySet() { - return data.entrySet(); - } - - /** - * Implemented as required by the Map interface, but is not very useful - * here. All 'values' in a HashTree are HashTree's themselves. - * - * @param value - * Object to be tested as a value. - * @return True if the HashTree contains the value, false otherwise. - * @see Map#containsValue(Object) - */ - @Override - public boolean containsValue(Object value) { - return data.containsValue(value); - } - - /** - * This is the same as calling HashTree.add(key,value). - * - * @param key - * to use - * @param value - * to store against key - * @see Map#put(Object, Object) - */ - @Override - public HashTree put(Object key, HashTree value) { - HashTree previous = data.get(key); - add(key, value); - return previous; - } - - /** - * Clears the HashTree of all contents. - * - * @see Map#clear() - */ - @Override - public void clear() { - data.clear(); - } - - /** - * Returns a collection of all the sub-trees of the current tree. - * - * @see Map#values() - */ - @Override - public Collection values() { - return data.values(); - } - - /** - * Adds a key as a node at the current level and then adds the given - * HashTree to that new node. - * - * @param key - * key to create in this tree - * @param subTree - * sub tree to add to the node created for the first argument. - */ - public void add(Object key, HashTree subTree) { - add(key).add(subTree); - } - - /** - * Adds all the nodes and branches of the given tree to this tree. Is like - * merging two trees. Duplicates are ignored. - * - * @param newTree the tree to be added - */ - public void add(HashTree newTree) { - for (Object item : newTree.list()) { - add(item).add(newTree.getTree(item)); - } - } - - /** - * Creates a new HashTree and adds all the objects in the given collection - * as top-level nodes in the tree. - * - * @param keys - * a collection of objects to be added to the created HashTree. - */ - public HashTree(Collection keys) { - data = new LinkedHashMap<>(); - for (Object o : keys) { - data.put(o, new HashTree()); - } - } - - /** - * Creates a new HashTree and adds all the objects in the given array as - * top-level nodes in the tree. - * - * @param keys - * array with names for the new top-level nodes - */ - public HashTree(Object[] keys) { - data = new LinkedHashMap<>(); - for (Object key : keys) { - data.put(key, new HashTree()); - } - } - - /** - * If the HashTree contains the given object as a key at the top level, then - * a true result is returned, otherwise false. - * - * @param o - * Object to be tested as a key. - * @return True if the HashTree contains the key, false otherwise. - * @see Map#containsKey(Object) - */ - @Override - public boolean containsKey(Object o) { - return data.containsKey(o); - } - - /** - * If the HashTree is empty, true is returned, false otherwise. - * - * @return True if HashTree is empty, false otherwise. - */ - @Override - public boolean isEmpty() { - return data.isEmpty(); - } - - /** - * Sets a key and it's value in the HashTree. It actually sets up a key, and - * then creates a node for the key and sets the value to the new node, as a - * key. Any previous nodes that existed under the given key are lost. - * - * @param key - * key to be set up - * @param value - * value to be set up as a key in the secondary node - */ - public void set(Object key, Object value) { - data.put(key, createNewTree(value)); - } - - /** - * Sets a key into the current tree and assigns it a HashTree as its - * subtree. Any previous entries under the given key are removed. - * - * @param key - * key to be set up - * @param t - * HashTree that the key maps to - */ - public void set(Object key, HashTree t) { - data.put(key, t); - } - - /** - * Sets a key and its values in the HashTree. It sets up a key in the - * current node, and then creates a node for that key, and sets all the - * values in the array as keys in the new node. Any keys previously held - * under the given key are lost. - * - * @param key - * Key to be set up - * @param values - * Array of objects to be added as keys in the secondary node - */ - public void set(Object key, Object[] values) { - data.put(key, createNewTree(Arrays.asList(values))); - } - - /** - * Sets a key and its values in the HashTree. It sets up a key in the - * current node, and then creates a node for that key, and set all the - * values in the array as keys in the new node. Any keys previously held - * under the given key are removed. - * - * @param key - * key to be set up - * @param values - * Collection of objects to be added as keys in the secondary - * node - */ - public void set(Object key, Collection values) { - data.put(key, createNewTree(values)); - } - - /** - * Sets a series of keys into the HashTree. It sets up the first object in - * the key array as a key in the current node, recurses into the next - * HashTree node through that key and adds the second object in the array. - * Continues recursing in this manner until the end of the first array is - * reached, at which point all the values of the second array are set as - * keys to the bottom-most node. All previous keys of that bottom-most node - * are removed. - * - * @param treePath - * array of keys to put into HashTree - * @param values - * array of values to be added as keys to bottom-most node - */ - public void set(Object[] treePath, Object[] values) { - if (treePath != null && values != null) { - set(Arrays.asList(treePath), Arrays.asList(values)); - } - } - - /** - * Sets a series of keys into the HashTree. It sets up the first object in - * the key array as a key in the current node, recurses into the next - * HashTree node through that key and adds the second object in the array. - * Continues recursing in this manner until the end of the first array is - * reached, at which point all the values of the Collection of values are - * set as keys to the bottom-most node. Any keys previously held by the - * bottom-most node are lost. - * - * @param treePath - * array of keys to put into HashTree - * @param values - * Collection of values to be added as keys to bottom-most node - */ - public void set(Object[] treePath, Collection values) { - if (treePath != null) { - set(Arrays.asList(treePath), values); - } - } - - /** - * Sets a series of keys into the HashTree. It sets up the first object in - * the key list as a key in the current node, recurses into the next - * HashTree node through that key and adds the second object in the list. - * Continues recursing in this manner until the end of the first list is - * reached, at which point all the values of the array of values are set as - * keys to the bottom-most node. Any previously existing keys of that bottom - * node are removed. - * - * @param treePath - * collection of keys to put into HashTree - * @param values - * array of values to be added as keys to bottom-most node - */ - public void set(Collection treePath, Object[] values) { - HashTree tree = addTreePath(treePath); - tree.set(Arrays.asList(values)); - } - - /** - * Sets the nodes of the current tree to be the objects of the given - * collection. Any nodes previously in the tree are removed. - * - * @param values - * Collection of objects to set as nodes. - */ - public void set(Collection values) { - clear(); - this.add(values); - } - - /** - * Sets a series of keys into the HashTree. It sets up the first object in - * the key list as a key in the current node, recurses into the next - * HashTree node through that key and adds the second object in the list. - * Continues recursing in this manner until the end of the first list is - * reached, at which point all the values of the Collection of values are - * set as keys to the bottom-most node. Any previously existing keys of that - * bottom node are lost. - * - * @param treePath - * list of keys to put into HashTree - * @param values - * collection of values to be added as keys to bottom-most node - */ - public void set(Collection treePath, Collection values) { - HashTree tree = addTreePath(treePath); - tree.set(values); - } - - /** - * Adds an key into the HashTree at the current level. If a HashTree exists - * for the key already, no new tree will be added - * - * @param key - * key to be added to HashTree - * @return newly generated tree, if no tree was found for the given key; - * existing key otherwise - */ - public HashTree add(Object key) { - if (!data.containsKey(key)) { - HashTree newTree = createNewTree(); - data.put(key, newTree); - return newTree; - } - return getTree(key); - } - - /** - * Adds all the given objects as nodes at the current level. - * - * @param keys - * Array of Keys to be added to HashTree. - */ - public void add(Object[] keys) { - for (Object key : keys) { - add(key); - } - } - - /** - * Adds a bunch of keys into the HashTree at the current level. - * - * @param keys - * Collection of Keys to be added to HashTree. - */ - public void add(Collection keys) { - for (Object o : keys) { - add(o); - } - } - - /** - * Adds a key and it's value in the HashTree. The first argument becomes a - * node at the current level, and the second argument becomes a node of it. - * - * @param key - * key to be added - * @param value - * value to be added as a key in the secondary node - * @return HashTree for which value is the key - */ - public HashTree add(Object key, Object value) { - return add(key).add(value); - } - - /** - * Adds a key and it's values in the HashTree. The first argument becomes a - * node at the current level, and adds all the values in the array to the - * new node. - * - * @param key - * key to be added - * @param values - * array of objects to be added as keys in the secondary node - */ - public void add(Object key, Object[] values) { - add(key).add(values); - } - - /** - * Adds a key as a node at the current level and then adds all the objects - * in the second argument as nodes of the new node. - * - * @param key - * key to be added - * @param values - * Collection of objects to be added as keys in the secondary - * node - */ - public void add(Object key, Collection values) { - add(key).add(values); - } - - /** - * Adds a series of nodes into the HashTree using the given path. The first - * argument is an array that represents a path to a specific node in the - * tree. If the path doesn't already exist, it is created (the objects are - * added along the way). At the path, all the objects in the second argument - * are added as nodes. - * - * @param treePath - * an array of objects representing a path - * @param values - * array of values to be added as keys to bottom-most node - */ - public void add(Object[] treePath, Object[] values) { - if (treePath != null) { - add(Arrays.asList(treePath), Arrays.asList(values)); - } - } - - /** - * Adds a series of nodes into the HashTree using the given path. The first - * argument is an array that represents a path to a specific node in the - * tree. If the path doesn't already exist, it is created (the objects are - * added along the way). At the path, all the objects in the second argument - * are added as nodes. - * - * @param treePath - * an array of objects representing a path - * @param values - * collection of values to be added as keys to bottom-most node - */ - public void add(Object[] treePath, Collection values) { - if (treePath != null) { - add(Arrays.asList(treePath), values); - } - } - - public HashTree add(Object[] treePath, Object value) { - return add(Arrays.asList(treePath), value); - } - - /** - * Adds a series of nodes into the HashTree using the given path. The first - * argument is a List that represents a path to a specific node in the tree. - * If the path doesn't already exist, it is created (the objects are added - * along the way). At the path, all the objects in the second argument are - * added as nodes. - * - * @param treePath - * a list of objects representing a path - * @param values - * array of values to be added as keys to bottom-most node - */ - public void add(Collection treePath, Object[] values) { - HashTree tree = addTreePath(treePath); - tree.add(Arrays.asList(values)); - } - - /** - * Adds a series of nodes into the HashTree using the given path. The first - * argument is a List that represents a path to a specific node in the tree. - * If the path doesn't already exist, it is created (the objects are added - * along the way). At the path, the object in the second argument is added - * as a node. - * - * @param treePath - * a list of objects representing a path - * @param value - * Object to add as a node to bottom-most node - * @return HashTree for which value is the key - */ - public HashTree add(Collection treePath, Object value) { - HashTree tree = addTreePath(treePath); - return tree.add(value); - } - - /** - * Adds a series of nodes into the HashTree using the given path. The first - * argument is a SortedSet that represents a path to a specific node in the - * tree. If the path doesn't already exist, it is created (the objects are - * added along the way). At the path, all the objects in the second argument - * are added as nodes. - * - * @param treePath - * a SortedSet of objects representing a path - * @param values - * Collection of values to be added as keys to bottom-most node - */ - public void add(Collection treePath, Collection values) { - HashTree tree = addTreePath(treePath); - tree.add(values); - } - - protected HashTree addTreePath(Collection treePath) { - HashTree tree = this; - for (Object temp : treePath) { - tree = tree.add(temp); - } - return tree; - } - - /** - * Gets the HashTree mapped to the given key. - * - * @param key - * Key used to find appropriate HashTree() - * @return the HashTree for key - */ - public HashTree getTree(Object key) { - return data.get(key); - } - - /** - * Returns the HashTree object associated with the given key. Same as - * calling {@link #getTree(Object)}. - * - * @see Map#get(Object) - */ - @Override - public HashTree get(Object key) { - return getTree(key); - } - - /** - * Gets the HashTree object mapped to the last key in the array by recursing - * through the HashTree structure one key at a time. - * - * @param treePath - * array of keys. - * @return HashTree at the end of the recursion. - */ - public HashTree getTree(Object[] treePath) { - if (treePath != null) { - return getTree(Arrays.asList(treePath)); - } - return this; - } - - /** - * Create a clone of this HashTree. This is not a deep clone (i.e., the - * contents of the tree are not cloned). - * - */ - @Override - public Object clone() { - HashTree newTree = new HashTree(); - cloneTree(newTree); - return newTree; - } - - protected void cloneTree(HashTree newTree) { - for (Object key : list()) { - newTree.set(key, (HashTree) getTree(key).clone()); - } - } - - /** - * Creates a new tree. This method exists to allow inheriting classes to - * generate the appropriate types of nodes. For instance, when a node is - * added, it's value is a HashTree. Rather than directly calling the - * HashTree() constructor, the createNewTree() method is called. Inheriting - * classes should override these methods and create the appropriate subclass - * of HashTree. - * - * @return HashTree - */ - protected HashTree createNewTree() { - return new HashTree(); - } - - /** - * Creates a new tree. This method exists to allow inheriting classes to - * generate the appropriate types of nodes. For instance, when a node is - * added, it's value is a HashTree. Rather than directly calling the - * HashTree() constructor, the createNewTree() method is called. Inheriting - * classes should override these methods and create the appropriate subclass - * of HashTree. - * - * @param key - * object to use as the key for the top level - * - * @return newly created {@link HashTree} - */ - protected HashTree createNewTree(Object key) { - return new HashTree(key); - } - - /** - * Creates a new tree. This method exists to allow inheriting classes to - * generate the appropriate types of nodes. For instance, when a node is - * added, it's value is a HashTree. Rather than directly calling the - * HashTree() constructor, the createNewTree() method is called. Inheriting - * classes should override these methods and create the appropriate subclass - * of HashTree. - * - * @param values objects to be added to the new {@link HashTree} - * - * @return newly created {@link HashTree} - */ - protected HashTree createNewTree(Collection values) { - return new HashTree(values); - } - - /** - * Gets the HashTree object mapped to the last key in the SortedSet by - * recursing through the HashTree structure one key at a time. - * - * @param treePath - * Collection of keys - * @return HashTree at the end of the recursion - */ - public HashTree getTree(Collection treePath) { - return getTreePath(treePath); - } - - /** - * Gets a Collection of all keys in the current HashTree node. If the - * HashTree represented a file system, this would be like getting a - * collection of all the files in the current folder. - * - * @return Set of all keys in this HashTree - */ - public Collection list() { - return data.keySet(); - } - - /** - * Gets a Set of all keys in the HashTree mapped to the given key of the - * current HashTree object (in other words, one level down. If the HashTree - * represented a file system, this would like getting a list of all files in - * a sub-directory (of the current directory) specified by the key argument. - * - * @param key - * key used to find HashTree to get list of - * @return Set of all keys in found HashTree. - */ - public Collection list(Object key) { - HashTree temp = data.get(key); - if (temp != null) { - return temp.list(); - } - return new HashSet<>(); - } - - /** - * Removes the entire branch specified by the given key. - * - * @see Map#remove(Object) - */ - @Override - public HashTree remove(Object key) { - return data.remove(key); - } - - /** - * Recurses down into the HashTree structure using each subsequent key in the - * array of keys, and returns the Set of keys of the HashTree object at the - * end of the recursion. If the HashTree represented a file system, this - * would be like getting a list of all the files in a directory specified by - * the treePath, relative from the current directory. - * - * @param treePath - * Array of keys used to recurse into HashTree structure - * @return Set of all keys found in end HashTree - */ - public Collection list(Object[] treePath) { - if (treePath != null) { - return list(Arrays.asList(treePath)); - } - return list(); - } - - /** - * Recurses down into the HashTree structure using each subsequent key in the - * List of keys, and returns the Set of keys of the HashTree object at the - * end of the recursion. If the HashTree represented a file system, this - * would be like getting a list of all the files in a directory specified by - * the treePath, relative from the current directory. - * - * @param treePath - * List of keys used to recurse into HashTree structure - * @return Set of all keys found in end HashTree - */ - public Collection list(Collection treePath) { - HashTree tree = getTreePath(treePath); - if (tree != null) { - return tree.list(); - } - return new HashSet<>(); - } - - /** - * Finds the given current key, and replaces it with the given new key. Any - * tree structure found under the original key is moved to the new key. - * - * @param currentKey name of the key to be replaced - * @param newKey name of the new key - */ - public void replaceKey(Object currentKey, Object newKey) { - HashTree tree = getTree(currentKey); - data.remove(currentKey); - data.put(newKey, tree); - } - - /** - * Gets an array of all keys in the current HashTree node. If the HashTree - * represented a file system, this would be like getting an array of all the - * files in the current folder. - * - * @return array of all keys in this HashTree. - */ - public Object[] getArray() { - return data.keySet().toArray(); - } - - /** - * Gets an array of all keys in the HashTree mapped to the given key of the - * current HashTree object (in other words, one level down). If the HashTree - * represented a file system, this would like getting a list of all files in - * a sub-directory (of the current directory) specified by the key argument. - * - * @param key - * key used to find HashTree to get list of - * @return array of all keys in found HashTree - */ - public Object[] getArray(Object key) { - HashTree t = getTree(key); - if (t != null) { - return t.getArray(); - } - return null; - } - - /** - * Recurses down into the HashTree structure using each subsequent key in the - * array of keys, and returns an array of keys of the HashTree object at the - * end of the recursion. If the HashTree represented a file system, this - * would be like getting a list of all the files in a directory specified by - * the treePath, relative from the current directory. - * - * @param treePath - * array of keys used to recurse into HashTree structure - * @return array of all keys found in end HashTree - */ - public Object[] getArray(Object[] treePath) { - if (treePath != null) { - return getArray(Arrays.asList(treePath)); - } - return getArray(); - } - - /** - * Recurses down into the HashTree structure using each subsequent key in the - * treePath argument, and returns an array of keys of the HashTree object at - * the end of the recursion. If the HashTree represented a file system, this - * would be like getting a list of all the files in a directory specified by - * the treePath, relative from the current directory. - * - * @param treePath - * list of keys used to recurse into HashTree structure - * @return array of all keys found in end HashTree - */ - public Object[] getArray(Collection treePath) { - HashTree tree = getTreePath(treePath); - return (tree != null) ? tree.getArray() : null; - } - - protected HashTree getTreePath(Collection treePath) { - HashTree tree = this; - for (Object aTreePath : treePath) { - tree = tree.getTree(aTreePath); - if (tree == null) { - return null; - } - } - return tree; - } - - /** - * Returns a hashcode for this HashTree. - * - * @see Object#hashCode() - */ - @Override - public int hashCode() { - return data.hashCode() * 7; - } - - /** - * Compares all objects in the tree and verifies that the two trees contain - * the same objects at the same tree levels. Returns true if they do, false - * otherwise. - * - * @param o - * Object to be compared against - * @see Object#equals(Object) - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof HashTree)) { - return false; - } - HashTree oo = (HashTree) o; - if (oo.size() != this.size()) { - return false; - } - return data.equals(oo.data); - } - - /** - * Returns a Set of all the keys in the top-level of this HashTree. - * - * @see Map#keySet() - */ - @Override - public Set keySet() { - return data.keySet(); - } - - /** - * Searches the HashTree structure for the given key. If it finds the key, - * it returns the HashTree mapped to the key. If it finds nothing, it - * returns null. - * - * @param key - * Key to search for - * @return HashTree mapped to key, if found, otherwise null - */ - public HashTree search(Object key) { - HashTree result = getTree(key); - if (result != null) { - return result; - } - TreeSearcher searcher = new TreeSearcher(key); - try { - traverse(searcher); - } catch (RuntimeException e) { - if (!e.getMessage().equals(FOUND)){ - throw e; - } - // do nothing - means object is found - } - return searcher.getResult(); - } - - /** - * Method readObject. - * - * @param ois - * the stream to read the objects from - * @throws ClassNotFoundException - * when the class for the deserialization can not be found - * @throws IOException - * when I/O error occurs - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - } - - private void writeObject(ObjectOutputStream oos) throws IOException { - oos.defaultWriteObject(); - } - - /** - * Returns the number of top-level entries in the HashTree. - * - * @see Map#size() - */ - @Override - public int size() { - return data.size(); - } - - /** - * Allows any implementation of the HashTreeTraverser interface to easily - * traverse (depth-first) all the nodes of the HashTree. The Traverser - * implementation will be given notification of each node visited. - * - * @see HashTreeTraverser - * @param visitor - * the visitor that wants to traverse the tree - */ - public void traverse(HashTreeTraverser visitor) { - for (Object item : list()) { - visitor.addNode(item, getTree(item)); - getTree(item).traverseInto(visitor); - } - } - - /** - * The recursive method that accomplishes the tree-traversal and performs - * the callbacks to the HashTreeTraverser. - * - * @param visitor - * the {@link HashTreeTraverser} to be notified - */ - private void traverseInto(HashTreeTraverser visitor) { - if (list().isEmpty()) { - visitor.processPath(); - } else { - for (Object item : list()) { - final HashTree treeItem = getTree(item); - visitor.addNode(item, treeItem); - treeItem.traverseInto(visitor); - } - } - visitor.subtractNode(); - } - - /** - * Generate a printable representation of the tree. - * - * @return a representation of the tree - */ - @Override - public String toString() { - ConvertToString converter = new ConvertToString(); - try { - traverse(converter); - } catch (Exception e) { // Just in case - converter.reportError(e); - } - return converter.toString(); - } - - private static class TreeSearcher implements HashTreeTraverser { - - private final Object target; - - private HashTree result; - - public TreeSearcher(Object t) { - target = t; - } - - public HashTree getResult() { - return result; - } - - /** {@inheritDoc} */ - @Override - public void addNode(Object node, HashTree subTree) { - result = subTree.getTree(target); - if (result != null) { - // short circuit traversal when found - throw new RuntimeException(FOUND); - } - } - - /** {@inheritDoc} */ - @Override - public void processPath() { - // Not used - } - - /** {@inheritDoc} */ - @Override - public void subtractNode() { - // Not used - } - } - - private static class ConvertToString implements HashTreeTraverser { - private final StringBuilder string = new StringBuilder(getClass().getName() + "{"); - - private final StringBuilder spaces = new StringBuilder(); - - private int depth = 0; - - @Override - public void addNode(Object key, HashTree subTree) { - depth++; - string.append("\n").append(getSpaces()).append(key); - string.append(" {"); - } - - @Override - public void subtractNode() { - string.append("\n" + getSpaces() + "}"); - depth--; - } - - @Override - public void processPath() { - // NOOP - } - - @Override - public String toString() { - string.append("\n}"); - return string.toString(); - } - - void reportError(Throwable t){ - string.append("Error: ").append(t.toString()); - } - - private String getSpaces() { - if (spaces.length() < depth * 2) { - while (spaces.length() < depth * 2) { - spaces.append(" "); - } - } else if (spaces.length() > depth * 2) { - spaces.setLength(depth * 2); - } - return spaces.toString(); - } - } -} From be42eef84eba35f158b1522cb2a57beb640ac0f2 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Thu, 16 Dec 2021 19:29:11 +0800 Subject: [PATCH 141/157] =?UTF-8?q?refactor=20(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E5=A2=9E=E5=8A=A0=E8=B0=83=E7=94=A8=E9=94=81?= =?UTF-8?q?=EF=BC=8C=E9=98=B2=E6=AD=A2=E5=90=8C=E4=B8=80=E4=B8=AA=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E9=87=8D=E5=A4=8D=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/JmeterExecuteController.java | 6 +++- .../api/jmeter/APISingleResultListener.java | 2 ++ .../api/jmeter/queue/BlockingQueueUtil.java | 36 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/BlockingQueueUtil.java diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index a3a7674..9cf4ced 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -1,6 +1,7 @@ package io.metersphere.api.controller; import com.alibaba.fastjson.JSON; +import io.metersphere.api.jmeter.queue.BlockingQueueUtil; import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.JvmService; @@ -21,7 +22,10 @@ public class JmeterExecuteController { @PostMapping(value = "/api/start") public String apiStartRun(@RequestBody JmeterRunRequestDTO runRequest) { System.out.println("接收到测试请求: " + JSON.toJSONString(runRequest)); - return jmeterExecuteService.runStart(runRequest); + if (BlockingQueueUtil.add(runRequest.getReportId())) { + return jmeterExecuteService.runStart(runRequest); + } + return "当前报告 " + runRequest.getReportId() + " 正在执行中"; } @GetMapping("/status") diff --git a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java index 5ac70f9..3fb2d1a 100644 --- a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java +++ b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java @@ -1,6 +1,7 @@ package io.metersphere.api.jmeter; import com.alibaba.fastjson.JSON; +import io.metersphere.api.jmeter.queue.BlockingQueueUtil; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.ProducerService; @@ -69,6 +70,7 @@ public void handleTeardownTest(ResultDTO dto, Map kafkaConfig) { @Override public void testEnded(ResultDTO dto, Map kafkaConfig) { LoggerUtil.info("报告【" + dto.getReportId() + " 】执行完成"); + BlockingQueueUtil.remove(dto.getReportId()); dto.setConsole(getConsole()); if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { dto.setArbitraryData(new HashMap() {{ diff --git a/src/main/java/io/metersphere/api/jmeter/queue/BlockingQueueUtil.java b/src/main/java/io/metersphere/api/jmeter/queue/BlockingQueueUtil.java new file mode 100644 index 0000000..8ca8a18 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/BlockingQueueUtil.java @@ -0,0 +1,36 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BlockingQueueUtil { + final static List queue = Collections.synchronizedList(new ArrayList()); + + public static boolean add(String key) { + if (StringUtils.isNotEmpty(key) && !queue.contains(key)) { + try { + queue.add(key); + LoggerUtil.info("执行任务入列:" + key + " 剩余:" + queue.size()); + return true; + } catch (Exception e) { + LoggerUtil.error(e); + } + } + return false; + } + + public static void remove(String key) { + try { + if (StringUtils.isNotEmpty(key)) { + LoggerUtil.info("执行任务出列:" + key); + queue.remove(key); + } + } catch (Exception e) { + LoggerUtil.error("获取队列失败:" + e.getMessage()); + } + } +} From 2c09edf937b9b25ac9fd2444a481c8064cc97057 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Fri, 17 Dec 2021 15:36:55 +0800 Subject: [PATCH 142/157] =?UTF-8?q?refactor=20(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E6=89=A7=E8=A1=8C=E8=84=9A=E6=9C=AC=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/controller/JmeterExecuteController.java | 6 +++++- .../io/metersphere/api/jmeter/APISingleResultListener.java | 3 +++ .../io/metersphere/api/service/JmeterExecuteService.java | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java index 9cf4ced..5b4ffd9 100644 --- a/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -5,8 +5,10 @@ import io.metersphere.api.module.JvmInfo; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.JvmService; +import io.metersphere.constants.RunModeConstants; import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.jmeter.LocalRunner; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -22,7 +24,9 @@ public class JmeterExecuteController { @PostMapping(value = "/api/start") public String apiStartRun(@RequestBody JmeterRunRequestDTO runRequest) { System.out.println("接收到测试请求: " + JSON.toJSONString(runRequest)); - if (BlockingQueueUtil.add(runRequest.getReportId())) { + if (StringUtils.equals(runRequest.getReportType(), RunModeConstants.SET_REPORT.toString())) { + return jmeterExecuteService.runStart(runRequest); + } else if (BlockingQueueUtil.add(runRequest.getReportId())) { return jmeterExecuteService.runStart(runRequest); } return "当前报告 " + runRequest.getReportId() + " 正在执行中"; diff --git a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java index 3fb2d1a..b67f63a 100644 --- a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java +++ b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.queue.BlockingQueueUtil; import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.FileUtils; import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.ProducerService; import io.metersphere.constants.RunModeConstants; @@ -79,6 +80,8 @@ public void testEnded(ResultDTO dto, Map kafkaConfig) { } else { dto.getArbitraryData().put("TEST_END", true); } + + FileUtils.deleteFile(FileUtils.BODY_FILE_DIR + "/" + dto.getReportId() + "_" + dto.getTestId() + ".jmx"); // 存储结果 this.send(dto, kafkaConfig); } diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index 6f53475..b47964f 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -73,7 +73,7 @@ public String runStart(JmeterRunRequestDTO runRequest) { File bodyFile = ZipSpider.downloadFile(runRequest.getPlatformUrl(), FileUtils.BODY_FILE_DIR); if (bodyFile != null) { ZipSpider.unzip(bodyFile.getPath(), FileUtils.BODY_FILE_DIR); - File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getTestId() + ".jmx"); + File jmxFile = new File(FileUtils.BODY_FILE_DIR + "/" + runRequest.getReportId() + "_" + runRequest.getTestId() + ".jmx"); // 生成执行脚本 HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 From 6de24ef99a87bd4d24dc2feed541659ee3f35414 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Sat, 18 Dec 2021 14:43:20 +0800 Subject: [PATCH 143/157] build: jmeter version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c6df55b..ca11ccd 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 1.8 - 5.4.1 + 5.4.2 1.1.3 2.7.8 3.0.8 From e6aea761502077f33b4dc7bf83699ad178fc6a19 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Sat, 18 Dec 2021 15:09:08 +0800 Subject: [PATCH 144/157] build: jmeter version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ca11ccd..5da7e8d 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ io.metersphere ms-jmeter-core - 1.0.0 + 1.0.1 From 3f13a4ba4aebfbe4350219997a6c7174a5698f8c Mon Sep 17 00:00:00 2001 From: CaptainB Date: Mon, 20 Dec 2021 19:01:14 +0800 Subject: [PATCH 145/157] =?UTF-8?q?build:=20dockerfile=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4abaf45..22518c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,10 +16,11 @@ COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app +RUN mv /app/lib/ms-jmeter-core-*.jar /app/lib/ms-jmeter-core.jar RUN mv /app/jmeter /opt/ RUN mkdir -p /opt/jmeter/lib/junit -ENV JAVA_CLASSPATH=/app:/app/lib/* +ENV JAVA_CLASSPATH=/app:/app/lib/ms-jmeter-core.jar:/app/lib/* ENV JAVA_MAIN_CLASS=io.metersphere.Application ENV AB_OFF=true ENV MS_VERSION=${MS_VERSION} From 33e95f135b0d1dca5729a3452752571518a312af Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Tue, 21 Dec 2021 12:10:45 +0800 Subject: [PATCH 146/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E6=94=AF=E6=8C=81=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E9=98=9F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../api/jmeter/APIBackendListenerHandler.java | 57 ------ .../api/jmeter/APISingleResultListener.java | 44 +---- .../metersphere/api/jmeter/JMeterService.java | 17 +- .../jmeter/queue/ExecThreadPoolExecutor.java | 177 ++++++++++++++++++ .../queue/MsRejectedExecutionHandler.java | 28 +++ .../api/jmeter/queue/NamedThreadFactory.java | 20 ++ .../queue/PoolExecBlockingQueueUtil.java | 50 +++++ .../api/jmeter/queue/SystemExecTask.java | 50 +++++ .../api/service/JmeterExecuteService.java | 3 +- .../api/service/ProducerService.java | 34 ++++ 11 files changed, 384 insertions(+), 98 deletions(-) delete mode 100644 src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/ExecThreadPoolExecutor.java create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/MsRejectedExecutionHandler.java create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/NamedThreadFactory.java create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/PoolExecBlockingQueueUtil.java create mode 100644 src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java diff --git a/pom.xml b/pom.xml index 5da7e8d..b04e139 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ io.metersphere ms-jmeter-core - 1.0.1 + 1.0.2 diff --git a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java b/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java deleted file mode 100644 index a0ac981..0000000 --- a/src/main/java/io/metersphere/api/jmeter/APIBackendListenerHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -package io.metersphere.api.jmeter; - - -import com.alibaba.fastjson.JSON; -import io.metersphere.api.jmeter.utils.CommonBeanFactory; -import io.metersphere.api.service.JmeterExecuteService; -import io.metersphere.api.service.ProducerService; -import io.metersphere.constants.RunModeConstants; -import io.metersphere.dto.ResultDTO; -import io.metersphere.utils.LoggerUtil; -import org.apache.commons.lang3.StringUtils; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.Map; - -public class APIBackendListenerHandler { - - private PrintStream oldPrintStream = System.out; - - private static ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - public static void setConsole() { - System.setOut(new PrintStream(bos)); - } - - private String getConsole() { - System.setOut(oldPrintStream); - return bos.toString(); - } - - public void handleTeardownTest(ResultDTO dto, Map kafkaConfig) { - ProducerService producerServer = CommonBeanFactory.getBean(ProducerService.class); - try { - dto.setConsole(getConsole()); - LoggerUtil.info("执行完成开始同步发送KAFKA【" + dto.getTestId() + "】"); - producerServer.send(JSON.toJSONString(dto), kafkaConfig); - LoggerUtil.info("同步发送报告信息到KAFKA完成【" + dto.getTestId() + "】"); - } catch (Exception ex) { - LoggerUtil.error("KAFKA 推送结果异常:[" + dto.getTestId() + "]" + ex.getMessage()); - // 补偿一个结果防止持续Running - if (dto != null && dto.getRequestResults().size() > 0) { - dto.getRequestResults().clear(); - } - producerServer.send(JSON.toJSONString(dto), kafkaConfig); - } - - if (StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.name())) { - LoggerUtil.info("接口收到集合报告ID:" + dto.getReportId()); - JmeterExecuteService jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - jmeterExecuteService.remove(dto.getReportId(), dto.getTestId()); - LoggerUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); - LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的数量:" + jmeterExecuteService.getRunningTasks(dto.getReportId())); - LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的内容:" + jmeterExecuteService.getRunningList(dto.getReportId())); - } - } -} diff --git a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java index b67f63a..808d7c2 100644 --- a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java +++ b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java @@ -1,12 +1,10 @@ package io.metersphere.api.jmeter; -import com.alibaba.fastjson.JSON; import io.metersphere.api.jmeter.queue.BlockingQueueUtil; +import io.metersphere.api.jmeter.queue.PoolExecBlockingQueueUtil; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.FileUtils; -import io.metersphere.api.service.JmeterExecuteService; import io.metersphere.api.service.ProducerService; -import io.metersphere.constants.RunModeConstants; import io.metersphere.dto.ResultDTO; import io.metersphere.jmeter.MsExecListener; import io.metersphere.utils.LoggerUtil; @@ -31,47 +29,20 @@ private String getConsole() { return bos.toString(); } - private void send(ResultDTO dto, Map kafkaConfig) { - ProducerService producerServer = CommonBeanFactory.getBean(ProducerService.class); - try { - if (producerServer != null) { - dto.setConsole(getConsole()); - LoggerUtil.info("执行完成开始同步发送KAFKA【" + dto.getTestId() + "】"); - producerServer.send(JSON.toJSONString(dto), kafkaConfig); - LoggerUtil.info("同步发送报告信息到KAFKA完成【" + dto.getTestId() + "】"); - } - } catch (Exception ex) { - LoggerUtil.error("KAFKA 推送结果异常:[" + dto.getTestId() + "]" + ex.getMessage()); - // 补偿一个结果防止持续Running - if (dto != null && dto.getRequestResults().size() > 0) { - dto.getRequestResults().clear(); - } - producerServer.send(JSON.toJSONString(dto), kafkaConfig); - } - - if (dto != null && StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.name())) { - LoggerUtil.info("处理接口集合报告ID:" + dto.getReportId()); - JmeterExecuteService jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); - if (jmeterExecuteService != null) { - jmeterExecuteService.remove(dto.getReportId(), dto.getTestId()); - LoggerUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); - LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的数量:" + jmeterExecuteService.getRunningTasks(dto.getReportId())); - LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的内容:" + jmeterExecuteService.getRunningList(dto.getReportId())); - } - } - } - @Override public void handleTeardownTest(ResultDTO dto, Map kafkaConfig) { LoggerUtil.info("开始处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + " 】"); dto.setConsole(getConsole()); - this.send(dto, kafkaConfig); + CommonBeanFactory.getBean(ProducerService.class).send(dto, kafkaConfig); } @Override public void testEnded(ResultDTO dto, Map kafkaConfig) { + PoolExecBlockingQueueUtil.offer(dto.getReportId()); LoggerUtil.info("报告【" + dto.getReportId() + " 】执行完成"); - BlockingQueueUtil.remove(dto.getReportId()); + if (StringUtils.isNotEmpty(dto.getReportId())) { + BlockingQueueUtil.remove(dto.getReportId()); + } dto.setConsole(getConsole()); if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { dto.setArbitraryData(new HashMap() {{ @@ -80,9 +51,8 @@ public void testEnded(ResultDTO dto, Map kafkaConfig) { } else { dto.getArbitraryData().put("TEST_END", true); } - FileUtils.deleteFile(FileUtils.BODY_FILE_DIR + "/" + dto.getReportId() + "_" + dto.getTestId() + ".jmx"); // 存储结果 - this.send(dto, kafkaConfig); + CommonBeanFactory.getBean(ProducerService.class).send(dto, kafkaConfig); } } diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index c5efe18..d14eccd 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -1,5 +1,7 @@ package io.metersphere.api.jmeter; +import io.metersphere.api.jmeter.queue.ExecThreadPoolExecutor; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.dto.JmeterRunRequestDTO; @@ -47,11 +49,11 @@ public String getJmeterHome() { } } - public void run(JmeterRunRequestDTO runRequest, HashTree testPlan) { + public void runLocal(JmeterRunRequestDTO runRequest, HashTree testPlan) { try { init(); runRequest.setHashTree(testPlan); - JMeterBase.addSyncListener(runRequest,APISingleResultListener.class.getCanonicalName()); + JMeterBase.addSyncListener(runRequest, runRequest.getHashTree(), APISingleResultListener.class.getCanonicalName()); LocalRunner runner = new LocalRunner(testPlan); runner.run(runRequest.getReportId()); } catch (Exception e) { @@ -59,4 +61,15 @@ public void run(JmeterRunRequestDTO runRequest, HashTree testPlan) { MSException.throwException("读取脚本失败"); } } + + public void run(JmeterRunRequestDTO request) { + if (request.getCorePoolSize() > 0) { + CommonBeanFactory.getBean(ExecThreadPoolExecutor.class).setCorePoolSize(request.getCorePoolSize()); + } + CommonBeanFactory.getBean(ExecThreadPoolExecutor.class).addTask(request); + } + + public void addQueue(JmeterRunRequestDTO request) { + this.runLocal(request, request.getHashTree()); + } } diff --git a/src/main/java/io/metersphere/api/jmeter/queue/ExecThreadPoolExecutor.java b/src/main/java/io/metersphere/api/jmeter/queue/ExecThreadPoolExecutor.java new file mode 100644 index 0000000..b337bdd --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/ExecThreadPoolExecutor.java @@ -0,0 +1,177 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.Queue; +import java.util.concurrent.*; + +@Service +public class ExecThreadPoolExecutor { + // 线程池维护线程的最少数量 + private final static int CORE_POOL_SIZE = 10; + // 线程池维护线程的最大数量 + private final static int MAX_POOL_SIZE = 10; + // 线程池维护线程所允许的空闲时间 + private final static int KEEP_ALIVE_TIME = 1; + // 线程池所使用的缓冲队列大小 + private final static int WORK_QUEUE_SIZE = 10000; + + private MsRejectedExecutionHandler msRejectedExecutionHandler = new MsRejectedExecutionHandler(); + /** + * 创建线程池 + */ + private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor( + CORE_POOL_SIZE, + MAX_POOL_SIZE, + KEEP_ALIVE_TIME, + TimeUnit.SECONDS, + new ArrayBlockingQueue(WORK_QUEUE_SIZE), + new NamedThreadFactory("MS-JMETER-RUN-TASK"), + msRejectedExecutionHandler); + /** + * 缓冲区调度线程池 + */ + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("MS-BUFFER-SCHEDULED")); + + public void addTask(JmeterRunRequestDTO requestDTO) { + outApiThreadPoolExecutorLogger(); + SystemExecTask task = new SystemExecTask(requestDTO); + threadPool.execute(task); + } + + /** + * 调度线程池,检查缓冲区 + */ + final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + //判断缓冲队列是否存在记录 + if (CollectionUtils.isNotEmpty(msRejectedExecutionHandler.getBufferQueue())) { + //当线程池的队列容量少于WORK_QUEUE_SIZE,则开始把缓冲队列的任务 加入到 线程池 + if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) { + JmeterRunRequestDTO requestDTO = msRejectedExecutionHandler.getBufferQueue().poll(); + SystemExecTask task = new SystemExecTask(requestDTO); + threadPool.submit(task); + LoggerUtil.info("把缓冲区任务重新添加到线程池,报告ID:" + requestDTO.getReportId()); + } + } + } + }, 0, 2, TimeUnit.SECONDS); + + + /** + * 终止线程池和调度线程池 + */ + public void shutdown() { + //true表示如果定时任务在执行,立即中止,false则等待任务结束后再停止 + LoggerUtil.info("终止执行线程池和调度线程池:" + scheduledFuture.cancel(true)); + scheduler.shutdown(); + threadPool.shutdown(); + } + + /** + * 保留两位小数 + */ + private String divide(int num1, int num2) { + return String.format("%1.2f%%", Double.parseDouble(num1 + "") / Double.parseDouble(num2 + "") * 100); + } + + public void outApiThreadPoolExecutorLogger() { + ArrayBlockingQueue queue = (ArrayBlockingQueue) threadPool.getQueue(); + StringBuffer buffer = new StringBuffer("API 并发队列详情:\n"); + buffer.append(" 核心线程数:" + threadPool.getCorePoolSize()).append("\n"); + buffer.append(" 活动线程数:" + threadPool.getActiveCount()).append("\n"); + buffer.append(" 最大线程数:" + threadPool.getMaximumPoolSize()).append("\n"); + buffer.append(" 线程池活跃度:" + divide(threadPool.getActiveCount(), threadPool.getMaximumPoolSize())).append("\n"); + buffer.append(" 任务完成数:" + threadPool.getCompletedTaskCount()).append("\n"); + buffer.append(" 队列大小:" + (queue.size() + queue.remainingCapacity())).append("\n"); + buffer.append(" 当前排队线程数:" + (msRejectedExecutionHandler.getBufferQueue().size() + queue.size())).append("\n"); + buffer.append(" 队列剩余大小:" + queue.remainingCapacity()).append("\n"); + buffer.append(" 队列使用度:" + divide(queue.size(), queue.size() + queue.remainingCapacity())); + + LoggerUtil.info(buffer.toString()); + + if (queue.size() > 0 && LoggerUtil.getLogger().isDebugEnabled()) { + LoggerUtil.debug(this.getWorkerQueue()); + } + } + + public void setCorePoolSize(int corePoolSize) { + try { + if (corePoolSize != threadPool.getCorePoolSize()) { + threadPool.setCorePoolSize(corePoolSize); + threadPool.setMaximumPoolSize(corePoolSize); + threadPool.allowCoreThreadTimeOut(true); + LoggerUtil.info("AllCoreThreads: " + threadPool.prestartAllCoreThreads()); + } + } catch (Exception e) { + LoggerUtil.error("设置线程参数异常:" + e); + } + } + + public void removeQueue(String reportId) { + // 检查缓冲区 + Queue bufferQueue = msRejectedExecutionHandler.getBufferQueue(); + if (CollectionUtils.isNotEmpty(bufferQueue)) { + bufferQueue.forEach(item -> { + if (item != null && StringUtils.equals(item.getReportId(), reportId)) { + bufferQueue.remove(item); + } + }); + } + // 检查等待队列 + BlockingQueue workerQueue = threadPool.getQueue(); + workerQueue.forEach(item -> { + SystemExecTask task = (SystemExecTask) item; + if (task.getRequest() != null && StringUtils.equals(task.getRequest().getReportId(), reportId)) { + workerQueue.remove(item); + } + }); + } + + public void removeAllQueue() { + // 检查缓冲区 + msRejectedExecutionHandler.getBufferQueue().clear(); + // 检查等待队列 + threadPool.getQueue().clear(); + } + + public boolean check(String reportId) { + // 检查缓冲区 + Queue bufferQueue = msRejectedExecutionHandler.getBufferQueue(); + if (CollectionUtils.isNotEmpty(bufferQueue)) { + return bufferQueue.stream().filter(task -> StringUtils.equals(task.getReportId(), reportId)).count() > 0; + } + // 检查等待队列 + BlockingQueue workerQueue = threadPool.getQueue(); + return workerQueue.stream().filter(task -> StringUtils.equals(((SystemExecTask) task).getRequest().getReportId(), reportId)).count() > 0; + } + + public boolean checkPlanReport(String planReportId) { + // 检查缓冲区 + Queue bufferQueue = msRejectedExecutionHandler.getBufferQueue(); + if (CollectionUtils.isNotEmpty(bufferQueue)) { + return bufferQueue.stream().filter(task -> StringUtils.equals(task.getTestPlanReportId(), planReportId)).count() > 0; + } + // 检查等待队列 + BlockingQueue workerQueue = threadPool.getQueue(); + return workerQueue.stream().filter(task -> StringUtils.equals(((SystemExecTask) task).getRequest().getTestPlanReportId(), planReportId)).count() > 0; + } + + public String getWorkerQueue() { + StringBuffer buffer = new StringBuffer(); + BlockingQueue workerQueue = threadPool.getQueue(); + workerQueue.forEach(item -> { + SystemExecTask task = (SystemExecTask) item; + if (task.getRequest() != null) { + buffer.append("等待队列报告:【 " + task.getRequest().getReportId() + "】资源:【 " + task.getRequest().getTestId() + "】").append("\n"); + } + }); + + return buffer.toString(); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/queue/MsRejectedExecutionHandler.java b/src/main/java/io/metersphere/api/jmeter/queue/MsRejectedExecutionHandler.java new file mode 100644 index 0000000..172fcfa --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/MsRejectedExecutionHandler.java @@ -0,0 +1,28 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.utils.LoggerUtil; + +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; + +public class MsRejectedExecutionHandler implements RejectedExecutionHandler { + /** + * 执行任务缓冲队列,当线程池满了,则将任务存入到此缓冲队列 + * 这里是否搞个redis/写到磁盘? + */ + private Queue bufferQueue = new LinkedBlockingQueue<>(); + + public Queue getBufferQueue() { + return bufferQueue; + } + + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + //任务加入到缓冲队列 + bufferQueue.offer(((SystemExecTask) r).getRequest()); + LoggerUtil.info("执行任务过多,任务加入缓冲区:" + ((SystemExecTask) r).getRequest().getReportId()); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/queue/NamedThreadFactory.java b/src/main/java/io/metersphere/api/jmeter/queue/NamedThreadFactory.java new file mode 100644 index 0000000..367da67 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/NamedThreadFactory.java @@ -0,0 +1,20 @@ +package io.metersphere.api.jmeter.queue; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class NamedThreadFactory implements ThreadFactory { + private static AtomicInteger tag = new AtomicInteger(0); + private String name; + + public NamedThreadFactory(String name) { + this.name = name; + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName(this.name + ":" + tag.getAndIncrement()); + return thread; + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/queue/PoolExecBlockingQueueUtil.java b/src/main/java/io/metersphere/api/jmeter/queue/PoolExecBlockingQueueUtil.java new file mode 100644 index 0000000..672e30b --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/PoolExecBlockingQueueUtil.java @@ -0,0 +1,50 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class PoolExecBlockingQueueUtil { + // 系统级队列控制整体并发数量 + public static Map> queue = new ConcurrentHashMap<>(); + + private static final String END_SIGN = "RUN-END"; + private static final int QUEUE_SIZE = 1; + + public static void offer(String key) { + if (StringUtils.isNotEmpty(key) && queue.containsKey(key)) { + try { + queue.get(key).offer(END_SIGN); + } catch (Exception e) { + LoggerUtil.error(e); + } finally { + queue.remove(key); + } + } + } + + public static Object take(String key) { + try { + if (StringUtils.isNotEmpty(key) && !queue.containsKey(key)) { + BlockingQueue blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE); + queue.put(key, blockingQueue); + return blockingQueue.poll(30, TimeUnit.MINUTES); + } + } catch (Exception e) { + LoggerUtil.error("初始化队列失败:" + e.getMessage()); + } + return null; + } + + public static void remove(String key) { + if (StringUtils.isNotEmpty(key) && queue.containsKey(key)) { + queue.get(key).offer(END_SIGN); + queue.remove(key); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java b/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java new file mode 100644 index 0000000..a792e39 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java @@ -0,0 +1,50 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.service.ProducerService; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.dto.ResultDTO; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; + +import java.util.HashMap; + +public class SystemExecTask implements Runnable { + private JmeterRunRequestDTO request; + + public SystemExecTask(JmeterRunRequestDTO request) { + this.request = request; + } + + public JmeterRunRequestDTO getRequest() { + return this.request; + } + + @Override + public void run() { + LoggerUtil.info("开始执行报告ID:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】"); + JMeterService jMeterService = CommonBeanFactory.getBean(JMeterService.class); + jMeterService.addQueue(request); + if (StringUtils.isNotEmpty(request.getReportId())) { + Object res = PoolExecBlockingQueueUtil.take(request.getReportId()); + if (res == null) { + LoggerUtil.info("执行报告:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】执行超时"); + ResultDTO dto = new ResultDTO(); + BeanUtils.copyProperties(dto, request); + if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { + dto.setArbitraryData(new HashMap() {{ + this.put("TEST_END", true); + this.put("TIMEOUT", true); + }}); + } else { + dto.getArbitraryData().put("TEST_END", true); + dto.getArbitraryData().put("TIMEOUT", true); + } + CommonBeanFactory.getBean(ProducerService.class).send(dto, request.getKafkaConfig()); + } + } + LoggerUtil.info("任务:【 " + request.getReportId() + " 】执行完成"); + } +} diff --git a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java index b47964f..d546f20 100755 --- a/src/main/java/io/metersphere/api/service/JmeterExecuteService.java +++ b/src/main/java/io/metersphere/api/service/JmeterExecuteService.java @@ -77,7 +77,8 @@ public String runStart(JmeterRunRequestDTO runRequest) { // 生成执行脚本 HashTree testPlan = SaveService.loadTree(jmxFile); // 开始执行 - jMeterService.run(runRequest, testPlan); + runRequest.setHashTree(testPlan); + jMeterService.run(runRequest); FileUtils.deleteFile(bodyFile.getPath()); } else { MSException.throwException("未找到执行的JMX文件"); diff --git a/src/main/java/io/metersphere/api/service/ProducerService.java b/src/main/java/io/metersphere/api/service/ProducerService.java index 95d21df..004f85e 100644 --- a/src/main/java/io/metersphere/api/service/ProducerService.java +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -1,7 +1,12 @@ package io.metersphere.api.service; +import com.alibaba.fastjson.JSON; import io.metersphere.api.config.KafkaConfig; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.ResultDTO; import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.producer.ProducerConfig; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -43,4 +48,33 @@ public void send(String message, Map producerProps) { kafkaTemplate.flush(); } } + + public void send(ResultDTO dto, Map kafkaConfig) { + ProducerService producerServer = CommonBeanFactory.getBean(ProducerService.class); + try { + if (producerServer != null) { + LoggerUtil.info("执行完成开始同步发送KAFKA【" + dto.getTestId() + "】"); + producerServer.send(JSON.toJSONString(dto), kafkaConfig); + LoggerUtil.info("同步发送报告信息到KAFKA完成【" + dto.getTestId() + "】"); + } + } catch (Exception ex) { + LoggerUtil.error("KAFKA 推送结果异常:[" + dto.getTestId() + "]" + ex.getMessage()); + // 补偿一个结果防止持续Running + if (dto != null && dto.getRequestResults().size() > 0) { + dto.getRequestResults().clear(); + } + producerServer.send(JSON.toJSONString(dto), kafkaConfig); + } + + if (dto != null && StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.name())) { + LoggerUtil.info("处理接口集合报告ID:" + dto.getReportId()); + JmeterExecuteService jmeterExecuteService = CommonBeanFactory.getBean(JmeterExecuteService.class); + if (jmeterExecuteService != null) { + jmeterExecuteService.remove(dto.getReportId(), dto.getTestId()); + LoggerUtil.info("正在执行中的并发报告数量:" + jmeterExecuteService.getRunningSize()); + LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的数量:" + jmeterExecuteService.getRunningTasks(dto.getReportId())); + LoggerUtil.info("正在执行中的场景[" + dto.getReportId() + "]的内容:" + jmeterExecuteService.getRunningList(dto.getReportId())); + } + } + } } From 0ac5b9af14307a7425b428ff9d7416f280a7c9cc Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 22 Dec 2021 11:40:54 +0800 Subject: [PATCH 147/157] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b04e139..97830c5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.metersphere node-controller - dev + 1.16 node-controller node-controller From 5782eb37e98bad086682502b86d35e64d56f1945 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 22 Dec 2021 14:43:19 +0800 Subject: [PATCH 148/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E5=8A=A0=E5=BC=BA=E9=98=9F=E5=88=97=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=9C=BA=E5=88=B6=E7=A1=AE=E8=AE=A4=E6=97=A0=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=BA=BF=E7=A8=8B=E5=90=8E=E5=9C=A8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/queue/SystemExecTask.java | 3 ++- .../api/jmeter/utils/JmeterThreadUtils.java | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/JmeterThreadUtils.java diff --git a/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java b/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java index a792e39..4a6c56e 100644 --- a/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java +++ b/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java @@ -2,6 +2,7 @@ import io.metersphere.api.jmeter.JMeterService; import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.JmeterThreadUtils; import io.metersphere.api.service.ProducerService; import io.metersphere.dto.JmeterRunRequestDTO; import io.metersphere.dto.ResultDTO; @@ -29,7 +30,7 @@ public void run() { jMeterService.addQueue(request); if (StringUtils.isNotEmpty(request.getReportId())) { Object res = PoolExecBlockingQueueUtil.take(request.getReportId()); - if (res == null) { + if (res == null && !JmeterThreadUtils.isRunning(request.getReportId(), request.getTestId())) { LoggerUtil.info("执行报告:【 " + request.getReportId() + " 】,资源ID【 " + request.getTestId() + " 】执行超时"); ResultDTO dto = new ResultDTO(); BeanUtils.copyProperties(dto, request); diff --git a/src/main/java/io/metersphere/api/jmeter/utils/JmeterThreadUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/JmeterThreadUtils.java new file mode 100644 index 0000000..c7ce51c --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/JmeterThreadUtils.java @@ -0,0 +1,20 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.commons.lang3.StringUtils; + +public class JmeterThreadUtils { + public static boolean isRunning(String reportId, String testId) { + ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); + int noThreads = currentGroup.activeCount(); + Thread[] lstThreads = new Thread[noThreads]; + currentGroup.enumerate(lstThreads); + for (int i = 0; i < noThreads; i++) { + if (StringUtils.isNotEmpty(reportId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(reportId)) { + return true; + } else if (StringUtils.isNotEmpty(testId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(testId)) { + return true; + } + } + return false; + } +} From 7243152310db1e2a2f156e4c97b62fc2988a07e6 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 22 Dec 2021 21:19:30 +0800 Subject: [PATCH 149/157] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0ms-jmeter?= =?UTF-8?q?-core=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b04e139..f0490fa 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ io.metersphere ms-jmeter-core - 1.0.2 + 1.0.3 From 45be5b0f75261840721d13ead6b83427cb8d54a3 Mon Sep 17 00:00:00 2001 From: BugKing Date: Wed, 22 Dec 2021 21:19:30 +0800 Subject: [PATCH 150/157] =?UTF-8?q?refactor:=20=E6=9B=B4=E6=96=B0ms-jmeter?= =?UTF-8?q?-core=E4=BE=9D=E8=B5=96=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 97830c5..c4830a0 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ io.metersphere ms-jmeter-core - 1.0.2 + 1.0.3 From f45a88bb9e4938373940b56cfbdcb799d1cacec1 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Sun, 26 Dec 2021 10:43:54 +0800 Subject: [PATCH 151/157] =?UTF-8?q?build:=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BC=98=E5=85=88=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/io/metersphere/Application.java | 5 ++++- .../resources/{application.properties => base.properties} | 0 2 files changed, 4 insertions(+), 1 deletion(-) rename src/main/resources/{application.properties => base.properties} (100%) diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java index f40f17e..4c3d4a2 100644 --- a/src/main/java/io/metersphere/Application.java +++ b/src/main/java/io/metersphere/Application.java @@ -15,7 +15,10 @@ @EnableConfigurationProperties({ JmeterProperties.class, }) -@PropertySource(value = {"file:/opt/metersphere/conf/metersphere.properties"}, encoding = "UTF-8", ignoreResourceNotFound = true) +@PropertySource(value = { + "classpath:/base.properties", + "file:/opt/metersphere/conf/metersphere.properties", +}, encoding = "UTF-8", ignoreResourceNotFound = true) @SpringBootApplication @EnableScheduling public class Application { diff --git a/src/main/resources/application.properties b/src/main/resources/base.properties similarity index 100% rename from src/main/resources/application.properties rename to src/main/resources/base.properties From b329e246c4e386137b63008de5f13de19db5ae0b Mon Sep 17 00:00:00 2001 From: CaptainB Date: Sun, 26 Dec 2021 11:55:22 +0800 Subject: [PATCH 152/157] =?UTF-8?q?build:=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BC=98=E5=85=88=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ca42773..ea40d8f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + %d %5p %40.40c:%4L - %m%n From ccc6451c9de015425e4e1a5b3f8e58d62902fbae Mon Sep 17 00:00:00 2001 From: CaptainB Date: Mon, 27 Dec 2021 15:29:41 +0800 Subject: [PATCH 153/157] build: spring boot version, jmeter version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f0490fa..19bfdf3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.5.4 + 2.6.2 io.metersphere @@ -16,7 +16,7 @@ 1.8 - 5.4.2 + 5.4.3 1.1.3 2.7.8 3.0.8 From 6b2b1bd00beda030ff70869fec2928cb3355db41 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Mon, 27 Dec 2021 15:42:16 +0800 Subject: [PATCH 154/157] build: core version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 19bfdf3..25f8c5c 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ io.metersphere ms-jmeter-core - 1.0.3 + 1.0.4 From 826c413538065fec37a01602a7ddbdd63a17eac5 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 27 Dec 2021 18:49:13 +0800 Subject: [PATCH 155/157] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20=E6=97=A5=E5=BF=97=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E6=8F=90=E5=8F=96=E6=9C=89=E6=95=88=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/jmeter/APISingleResultListener.java | 37 +++-- .../metersphere/api/jmeter/JMeterService.java | 7 +- .../api/jmeter/JmeterLoggerAppender.java | 42 ++++++ .../api/jmeter/utils/DateUtils.java | 130 ++++++++++++++++++ .../api/jmeter/utils/FixedCapacityUtils.java | 33 +++++ src/main/resources/logback.xml | 12 ++ 6 files changed, 243 insertions(+), 18 deletions(-) create mode 100644 src/main/java/io/metersphere/api/jmeter/JmeterLoggerAppender.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/DateUtils.java create mode 100644 src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtils.java diff --git a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java index 808d7c2..20f28af 100644 --- a/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java +++ b/src/main/java/io/metersphere/api/jmeter/APISingleResultListener.java @@ -4,35 +4,42 @@ import io.metersphere.api.jmeter.queue.PoolExecBlockingQueueUtil; import io.metersphere.api.jmeter.utils.CommonBeanFactory; import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.FixedCapacityUtils; import io.metersphere.api.service.ProducerService; import io.metersphere.dto.ResultDTO; import io.metersphere.jmeter.MsExecListener; import io.metersphere.utils.LoggerUtil; import org.apache.commons.lang3.StringUtils; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; public class APISingleResultListener extends MsExecListener { - private PrintStream oldPrintStream = System.out; - - private static ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - public static void setConsole() { - System.setOut(new PrintStream(bos)); - } - - private String getConsole() { - System.setOut(oldPrintStream); - return bos.toString(); + private String getJmeterLogger(String testId) { + try { + Long startTime = FixedCapacityUtils.jmeterLogTask.get(testId); + if (startTime == null) { + startTime = FixedCapacityUtils.jmeterLogTask.get("[" + testId + "]"); + } + if (startTime == null) { + startTime = System.currentTimeMillis(); + } + Long endTime = System.currentTimeMillis(); + Long finalStartTime = startTime; + String logMessage = FixedCapacityUtils.fixedCapacityCache.entrySet().stream() + .filter(map -> map.getKey() > finalStartTime && map.getKey() < endTime) + .map(map -> map.getValue()).collect(Collectors.joining()); + return logMessage; + } catch (Exception e) { + return ""; + } } @Override public void handleTeardownTest(ResultDTO dto, Map kafkaConfig) { LoggerUtil.info("开始处理单条执行结果报告【" + dto.getReportId() + " 】,资源【 " + dto.getTestId() + " 】"); - dto.setConsole(getConsole()); + dto.setConsole(getJmeterLogger(dto.getReportId())); CommonBeanFactory.getBean(ProducerService.class).send(dto, kafkaConfig); } @@ -43,7 +50,7 @@ public void testEnded(ResultDTO dto, Map kafkaConfig) { if (StringUtils.isNotEmpty(dto.getReportId())) { BlockingQueueUtil.remove(dto.getReportId()); } - dto.setConsole(getConsole()); + dto.setConsole(getJmeterLogger(dto.getReportId())); if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { dto.setArbitraryData(new HashMap() {{ this.put("TEST_END", true); diff --git a/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/src/main/java/io/metersphere/api/jmeter/JMeterService.java index d14eccd..6b11016 100644 --- a/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -2,6 +2,7 @@ import io.metersphere.api.jmeter.queue.ExecThreadPoolExecutor; import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.FixedCapacityUtils; import io.metersphere.api.jmeter.utils.JmeterProperties; import io.metersphere.api.jmeter.utils.MSException; import io.metersphere.dto.JmeterRunRequestDTO; @@ -26,13 +27,10 @@ public class JMeterService { @PostConstruct public void init() { String JMETER_HOME = getJmeterHome(); - String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties"; JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES); JMeterUtils.setJMeterHome(JMETER_HOME); JMeterUtils.setLocale(LocaleContextHolder.getLocale()); - - APISingleResultListener.setConsole(); } public String getJmeterHome() { @@ -52,6 +50,9 @@ public String getJmeterHome() { public void runLocal(JmeterRunRequestDTO runRequest, HashTree testPlan) { try { init(); + if (!FixedCapacityUtils.jmeterLogTask.containsKey(runRequest.getReportId())) { + FixedCapacityUtils.jmeterLogTask.put(runRequest.getReportId(), System.currentTimeMillis()); + } runRequest.setHashTree(testPlan); JMeterBase.addSyncListener(runRequest, runRequest.getHashTree(), APISingleResultListener.class.getCanonicalName()); LocalRunner runner = new LocalRunner(testPlan); diff --git a/src/main/java/io/metersphere/api/jmeter/JmeterLoggerAppender.java b/src/main/java/io/metersphere/api/jmeter/JmeterLoggerAppender.java new file mode 100644 index 0000000..3f6fb50 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JmeterLoggerAppender.java @@ -0,0 +1,42 @@ +package io.metersphere.api.jmeter; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.StackTraceElementProxy; +import ch.qos.logback.core.UnsynchronizedAppenderBase; +import io.metersphere.api.jmeter.utils.DateUtils; +import io.metersphere.api.jmeter.utils.FixedCapacityUtils; +import io.metersphere.utils.LoggerUtil; + +public class JmeterLoggerAppender extends UnsynchronizedAppenderBase { + @Override + public void append(ILoggingEvent event) { + try { + if (!event.getLevel().levelStr.equals(LoggerUtil.DEBUG)) { + StringBuffer message = new StringBuffer(); + message.append(DateUtils.getTimeStr(event.getTimeStamp())).append(" ") + .append(event.getLevel()).append(" ") + .append(event.getThreadName()).append(" ") + .append(event.getFormattedMessage()).append("\n"); + + if (event.getThrowableProxy() != null) { + message.append(event.getThrowableProxy().getMessage()).append("\n"); + message.append(event.getThrowableProxy().getClassName()).append("\n"); + if (event.getThrowableProxy().getStackTraceElementProxyArray() != null) { + for (StackTraceElementProxy stackTraceElementProxy : event.getThrowableProxy().getStackTraceElementProxyArray()) { + message.append(" ").append(stackTraceElementProxy.getSTEAsString()).append("\n"); + } + } + } + if (message != null && !message.toString().contains("java.net.UnknownHostException")) { + if (FixedCapacityUtils.fixedCapacityCache.containsKey(event.getTimeStamp())) { + FixedCapacityUtils.fixedCapacityCache.get(event.getTimeStamp()).append(message); + } else { + FixedCapacityUtils.fixedCapacityCache.put(event.getTimeStamp(), message); + } + } + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/jmeter/utils/DateUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/DateUtils.java new file mode 100644 index 0000000..ec02489 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/DateUtils.java @@ -0,0 +1,130 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.utils.LoggerUtil; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + + +public class DateUtils { + public static final String DATE_PATTERM = "yyyy-MM-dd"; + public static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + + public static Date getDate(String dateString) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM); + return dateFormat.parse(dateString); + } + public static Date getTime(String timeString) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_PATTERN); + return dateFormat.parse(timeString); + } + + public static String getDateString(Date date) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM); + return dateFormat.format(date); + } + + public static String getDateString(long timeStamp) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM); + return dateFormat.format(timeStamp); + } + + public static String getTimeString(Date date) throws Exception { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_PATTERN); + return dateFormat.format(date); + } + + public static String getTimeString(long timeStamp) { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_PATTERN); + return dateFormat.format(timeStamp); + } + + public static String getTimeStr(long timeStamp) { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_PATTERN); + return dateFormat.format(timeStamp); + } + public static String getDataStr(long timeStamp) { + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM); + return dateFormat.format(timeStamp); + } + + + public static Date dateSum (Date date,int countDays){ + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.DAY_OF_MONTH,countDays); + + return calendar.getTime(); + } + + public static Date dateSum (Date date,int countUnit,int calendarType){ + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(calendarType,countUnit); + + return calendar.getTime(); + } + + /** + * 获取入参日期所在周的周一周末日期。 日期对应的时间为当日的零点 + * + * @return Map(2); key取值范围:firstTime/lastTime + */ + public static Map getWeedFirstTimeAndLastTime(Date date) { + Map returnMap = new HashMap<>(); + Calendar calendar = Calendar.getInstance(); + + //Calendar默认一周的开始是周日。业务需求从周一开始算,所以要"+1" + int weekDayAdd = 1; + + try { + calendar.setTime(date); + calendar.set(Calendar.DAY_OF_WEEK, calendar.getActualMinimum(Calendar.DAY_OF_WEEK)); + calendar.add(Calendar.DAY_OF_MONTH,weekDayAdd); + + //第一天的时分秒是 00:00:00 这里直接取日期,默认就是零点零分 + Date thisWeekFirstTime = getDate(getDateString(calendar.getTime())); + + calendar.clear(); + calendar.setTime(date); + calendar.set(Calendar.DAY_OF_WEEK, calendar.getActualMaximum(Calendar.DAY_OF_WEEK)); + calendar.add(Calendar.DAY_OF_MONTH,weekDayAdd); + + //最后一天的时分秒应当是23:59:59。 处理方式是增加一天计算日期再-1 + calendar.add(Calendar.DAY_OF_MONTH,1); + Date nextWeekFirstDay = getDate(getDateString(calendar.getTime())); + Date thisWeekLastTime = getTime(getTimeString(nextWeekFirstDay.getTime()-1)); + + returnMap.put("firstTime", thisWeekFirstTime); + returnMap.put("lastTime", thisWeekLastTime); + } catch (Exception e) { + LoggerUtil.error(e); + } + return returnMap; + + } + + /** + * 获取当前时间或者当前时间+- 任意天数 时间的时间戳 + * @param countDays + * @return + */ + public static Long getTimestamp(int countDays){ + Date now = new Date(); + return dateSum (now,countDays).getTime()/1000*1000; + } + + /** + * 获取当天的起始时间Date + * @param time 指定日期 例: 2020-12-13 06:12:42 + * @return 当天起始时间 例: 2020-12-13 00:00:00 + * @throws Exception + */ + public static Date getDayStartTime(Date time) throws Exception { + return getDate(getDateString(time)); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtils.java new file mode 100644 index 0000000..4d3fe87 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtils.java @@ -0,0 +1,33 @@ +package io.metersphere.api.jmeter.utils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class FixedCapacityUtils { + public static Map fixedCapacityCache = Collections.synchronizedMap(new LRUHashMap<>()); + public final static Map jmeterLogTask = new HashMap<>(); + + public static StringBuffer get(Long key) { + return fixedCapacityCache.get(key); + } + + public static void put(Long key, StringBuffer value) { + fixedCapacityCache.put(key, value); + } + + public static int size() { + return fixedCapacityCache.size(); + } + + + static class LRUHashMap extends LinkedHashMap { + private int capacity = 100; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ca42773..af748a2 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -140,6 +140,18 @@ + + + + + + + + + + + + From 28e163a3be5771beede49ca8e8a227174eccda23 Mon Sep 17 00:00:00 2001 From: BugKing Date: Tue, 28 Dec 2021 11:47:55 +0800 Subject: [PATCH 156/157] =?UTF-8?q?build:=20=E6=9E=84=E5=BB=BA=E5=90=8E?= =?UTF-8?q?=E5=88=A0=E9=99=A4docker=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 1106dc0..01d2971 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,6 +22,7 @@ pipeline { sh "docker build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-\${GIT_COMMIT:0:8} -t ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ." sh "docker tag ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" sh "docker push ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" + sh "docker rmi ${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME}" } } stage('Notification') { From d41f8e64b0ed308876fce8430a11c78cdbec52f1 Mon Sep 17 00:00:00 2001 From: BugKing Date: Tue, 28 Dec 2021 13:44:03 +0800 Subject: [PATCH 157/157] =?UTF-8?q?build:=20=E6=9E=84=E5=BB=BA=E5=90=8E?= =?UTF-8?q?=E6=B8=85=E7=90=86workspace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 01d2971..145f1ce 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,6 +39,10 @@ pipeline { withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { qyWechatNotification failSend: true, mentionedId: '', mentionedMobile: '', webhookUrl: "$WEBHOOK" } + cleanWs(cleanWhenNotBuilt: false, + deleteDirs: true, + disableDeferredWipeout: true, + notFailBuild: true) } } }