diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml new file mode 100644 index 0000000..eedf488 --- /dev/null +++ b/.github/workflows/auto-pr.yml @@ -0,0 +1,17 @@ +on: [push, pull_request, release] + +name: MeterSphere pull request handler + +jobs: + generic_handler: + name: Generic handler for MeterSphere Repos + runs-on: ubuntu-latest + steps: + - name: Add labels + uses: jumpserver/action-generic-handler@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }} + - uses: actions/checkout@v2 + - uses: actions-ecosystem/action-add-labels@v1 + with: + labels: ${{ github.base_ref }} \ No newline at end of file diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml new file mode 100644 index 0000000..5487ae5 --- /dev/null +++ b/.github/workflows/build-push.yml @@ -0,0 +1,85 @@ +name: Build Docker Image and Push + +on: + push: + branches: + - master + - v1* + pull_request: + branches: + - master + - 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 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + 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: Unzip jar + run: mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) + + - name: Prepare + id: prepare + run: | + DOCKER_IMAGE=metersphere/node-controller + DOCKER_PLATFORMS=linux/amd64,linux/arm64 + TAG_NAME=${{ env.GITHUB_REF_SLUG }} + DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest" + echo ::set-output name=docker_image::${DOCKER_IMAGE} + echo ::set-output name=version::${TAG_NAME} + echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \ + --build-arg VERSION=${TAG_NAME} \ + --build-arg MS_VERSION=${{ env.GITHUB_REF_SLUG }}-${{ env.GITHUB_SHA_SHORT }} \ + --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ + --build-arg VCS_REF=${GITHUB_SHA::8} \ + ${DOCKER_IMAGE_TAGS} . + + - name: Set up Docker Buildx + uses: crazy-max/ghaction-docker-buildx@v3 + + - name: Docker Buildx (build) + run: | + docker buildx build --output "type=image,push=false" ${{ steps.prepare.outputs.buildx_args }} + + - name: Login to Registry + uses: docker/login-action@v1 + with: + username: metersphere + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Docker Buildx (push) + run: | + docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} diff --git a/.gitignore b/.gitignore index 179b07f..2b501ef 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ target/ *.iml *.ipr src/test/ +*.jar +.flattened-pom.xml +.mvn +!.mvn/maven-wrapper.properties \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..a9f1ef8 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/Dockerfile b/Dockerfile index 644a0c6..447a289 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,22 @@ -FROM registry.fit2cloud.com/fit2cloud2/fabric8-java-alpine-openjdk8-jre +FROM registry.cn-qingdao.aliyuncs.com/metersphere/alpine-openjdk17-jre -MAINTAINER FIT2CLOUD +LABEL maintainer="FIT2CLOUD " -RUN mkdir -p /opt/apps +ARG MS_VERSION=dev +ARG DEPENDENCY=target/dependency -ADD target/node-controller-1.0.jar /opt/apps +COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY ${DEPENDENCY}/META-INF /app/META-INF +COPY ${DEPENDENCY}/BOOT-INF/classes /app -ENV JAVA_APP_JAR=/opt/apps/node-controller-1.0.jar -ENV AB_OFF=true +RUN mv /app/lib/ms-jmeter-core-*.jar /app/lib/ms-jmeter-core.jar +RUN mv /app/jmeter /opt/ -ENV JAVA_OPTIONS=-Dfile.encoding=utf-8 +ENV JAVA_CLASSPATH=/app:/app/lib/ms-jmeter-core.jar:/opt/jmeter/lib/ext/*:/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 -Djava.awt.headless=true --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED" CMD ["/deployments/run-java.sh"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..6e09604 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,69 @@ + properties([ [ $class: 'ThrottleJobProperty', + categories: ['metersphere'], + limitOneJobWithMatchingParams: false, + maxConcurrentPerNode: 1, + maxConcurrentTotal: 1, + paramsToUseForLimit: '', + throttleEnabled: true, + throttleOption: 'category' ] ]) + +pipeline { + agent { + node { + label 'metersphere' + } + } + triggers { + pollSCM('0 * * * *') + } + environment { + JAVA_HOME = '/opt/jdk-17' + IMAGE_NAME = 'node-controller' + IMAGE_PREFIX = 'registry.cn-qingdao.aliyuncs.com/metersphere' + } + stages { + stage('Preparation') { + steps { + script { + REVISION = "" + if (env.BRANCH_NAME.startsWith("v") ) { + REVISION = env.BRANCH_NAME.substring(1) + } else { + REVISION = env.BRANCH_NAME + } + env.REVISION = "${REVISION}" + echo "REVISION=${REVISION}" + } + } + } + stage('Build/Test') { + steps { + configFileProvider([configFile(fileId: 'metersphere-maven', targetLocation: 'settings.xml')]) { + sh ''' + export JAVA_HOME=/opt/jdk-17 + export CLASSPATH=$JAVA_HOME/lib:$CLASSPATH + export PATH=$JAVA_HOME/bin:$PATH + java -version + ./mvnw clean package -Drevision=${REVISION} --settings ./settings.xml + mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) + ''' + } + } + } + stage('Docker build & push') { + steps { + sh '''#!/bin/bash -xe + docker --config /home/metersphere/.docker buildx build --build-arg MS_VERSION=\${TAG_NAME:-\$BRANCH_NAME}-\${GIT_COMMIT:0:8} -t ${IMAGE_PREFIX}/${IMAGE_NAME}:\${TAG_NAME:-\$BRANCH_NAME} --platform linux/amd64,linux/arm64 . --push + ''' + } + } + } + post('Notification') { + always { + sh "echo \$WEBHOOK\n" + withCredentials([string(credentialsId: 'wechat-bot-webhook', variable: 'WEBHOOK')]) { + qyWechatNotification failNotify: true, mentionedId: '', mentionedMobile: '', webhookUrl: "$WEBHOOK" + } + } + } +} 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 +. 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) diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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 +# +# https://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index eaabcaf..b9ff9bc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,22 +3,24 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework.boot - spring-boot-starter-parent - 2.2.6.RELEASE - + io.metersphere + metersphere + main - io.metersphere node-controller - 1.0 + ${revision} node-controller node-controller - 1.8 + 5.1.0 + + org.springframework.boot + spring-boot-configuration-processor + org.springframework.boot spring-boot-starter-web @@ -33,17 +35,39 @@ org.springframework.boot spring-boot-starter-jetty + + + org.eclipse.jetty + jetty-alpn-java-server + + + org.eclipse.jetty.http2 + http2-server + + + com.github.docker-java + docker-java-core + ${docker-java.version} + + + commons-io + commons-io + + + com.github.docker-java - docker-java - - 3.2.0 + docker-java-transport-httpclient5 + ${docker-java.version} org.projectlombok lombok - + + org.apache.commons + commons-lang3 + org.springframework.boot spring-boot-starter-test @@ -55,6 +79,186 @@ + + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + spring-context + org.springframework + + + + + + io.metersphere + ms-jmeter-core + ${revision} + + + netty + io.netty + + + mail + javax.mail + + + + + + org.flywaydb + flyway-core + + + com.mysql + mysql-connector-j + runtime + + + com.github.pagehelper + pagehelper + ${pagehelper.version} + + + + org.apache.commons + commons-text + ${commons-text.version} + + + commons-codec + commons-codec + + + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + + org.springframework.boot + spring-boot-starter-data-ldap + + + + + io.swagger.parser.v3 + swagger-parser + ${swagger-parser.version} + + + + + org.mozilla + rhino-engine + ${rhino.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + org.commonmark + commonmark + ${commonmark.version} + + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + + org.dom4j + dom4j + ${dom4j.version} + + + + jaxen + jaxen + + + net.sourceforge.htmlcleaner + htmlcleaner + ${htmlcleaner.version} + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + + io.fabric8 + kubernetes-client + + + + com.github.java-json-tools + json-schema-validator + ${json-schema-validator.version} + + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + org.springframework.kafka + spring-kafka + + + + + org.bouncycastle + bcprov-jdk15on + ${bcprov-jdk15on.version} + + + org.apache.poi + poi + ${poi.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + io.minio + minio + ${minio.version} + + + + + org.eclipse.jgit + org.eclipse.jgit + @@ -63,6 +267,53 @@ 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 + + + 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 + ${jython.version} + jar + true + src/main/resources/jmeter/lib/ext + jython-standalone.jar + + + ${project.build.directory}/wars + false + true + + diff --git a/src/main/java/io/metersphere/Application.java b/src/main/java/io/metersphere/Application.java index 010e94c..531cc53 100644 --- a/src/main/java/io/metersphere/Application.java +++ b/src/main/java/io/metersphere/Application.java @@ -1,13 +1,34 @@ package io.metersphere; +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; +import org.springframework.scheduling.annotation.EnableScheduling; +@ServletComponentScan +@EnableConfigurationProperties({ + JMeterProperties.class, +}) +@PropertySource(value = { + "classpath:/base.properties", + "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); } + @Bean + @ConditionalOnMissingBean + public CommonBeanFactory commonBeanFactory() { + return new CommonBeanFactory(); + } } 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..3fe8580 --- /dev/null +++ b/src/main/java/io/metersphere/api/controller/JmeterExecuteController.java @@ -0,0 +1,65 @@ +package io.metersphere.api.controller; + +import io.metersphere.api.jmeter.JMeterLoggerAppender; +import io.metersphere.api.jmeter.queue.BlockingQueueUtil; +import io.metersphere.api.jmeter.utils.JMeterThreadUtil; +import io.metersphere.api.service.JMeterExecuteService; +import io.metersphere.api.service.JvmService; +import io.metersphere.api.vo.JvmInfo; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +@RestController +@RequestMapping("/jmeter") +public class JmeterExecuteController { + + @Resource + private JMeterExecuteService jmeterExecuteService; + + @PostMapping(value = "/api/start") + public String apiStartRun(@RequestBody JmeterRunRequestDTO runRequest) { + if ((StringUtils.equals(runRequest.getReportType(), RunModeConstants.SET_REPORT.toString())) + || (BlockingQueueUtil.add(runRequest.getReportId()))) { + return jmeterExecuteService.runStart(runRequest); + } + return "当前报告 " + runRequest.getReportId() + " 正在执行中"; + } + + @PostMapping(value = "/debug") + public String apiDebug(@RequestBody JmeterRunRequestDTO runRequest) { + LoggerUtil.info("接收到测试请求 start "); + return jmeterExecuteService.debug(runRequest); + } + + + @GetMapping("/get/running/queue/{reportId}") + public boolean getRunningQueue(@PathVariable String reportId) { + return JMeterThreadUtil.isRunning(reportId, null); + } + + @GetMapping("/status") + public String getStatus() { + return "OK"; + } + + @GetMapping("/get-jvm-info") + public JvmInfo getJvmInfo() { + return JvmService.jvmInfo(); + } + + @GetMapping("/stop") + public void stop(@RequestBody List keys) { + JMeterThreadUtil.stop(keys); + } + + @GetMapping("/log/debug/{enable}") + public boolean debug(@PathVariable boolean enable) { + return JMeterLoggerAppender.enable = enable; + } +} diff --git a/src/main/java/io/metersphere/api/enums/StorageConstants.java b/src/main/java/io/metersphere/api/enums/StorageConstants.java new file mode 100644 index 0000000..e7f9c5c --- /dev/null +++ b/src/main/java/io/metersphere/api/enums/StorageConstants.java @@ -0,0 +1,5 @@ +package io.metersphere.api.enums; + +public enum StorageConstants { + LOCAL, MINIO, FILE_REF +} diff --git a/src/main/java/io/metersphere/api/jmeter/ExtendedParameter.java b/src/main/java/io/metersphere/api/jmeter/ExtendedParameter.java new file mode 100644 index 0000000..5f6ba46 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/ExtendedParameter.java @@ -0,0 +1,8 @@ +package io.metersphere.api.jmeter; + +public class ExtendedParameter { + public static final String TEST_END = "TEST_END"; + public static final String SAVE_RESULT = "SAVE_RESULT"; + public static final String REPORT_STATUS = "REPORT_STATUS"; + public static final String JAR_PATH = "JAR_PATH"; +} 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..0f3a06c --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterLoggerAppender.java @@ -0,0 +1,53 @@ +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.FixedCapacityUtil; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JMeterLoggerAppender extends UnsynchronizedAppenderBase { + public static Logger logger = LoggerFactory.getLogger("JMETER"); + public static boolean enable = false; + private final static String THREAD_SPLIT = " "; + + @Override + public void append(ILoggingEvent event) { + try { + if (!event.getLevel().levelStr.equals(LoggerUtil.DEBUG) && StringUtils.isNotEmpty(event.getThreadName())) { + StringBuffer message = new StringBuffer(); + String threadName = StringUtils.substringBeforeLast(event.getThreadName(), THREAD_SPLIT); + 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") + && FixedCapacityUtil.containsKey(threadName)) { + FixedCapacityUtil.get(threadName).append(message); + } + if (!enable) { + logger.info("JMETER-LOG ", message); + } + } + if (enable) { + logger.info("JMETER-DEBUG-LOG " + event.getFormattedMessage()); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } +} \ No newline at end of file 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..41c70f4 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -0,0 +1,146 @@ +package io.metersphere.api.jmeter; + +import io.metersphere.api.jmeter.queue.ExecThreadPoolExecutor; +import io.metersphere.api.jmeter.utils.FixedCapacityUtil; +import io.metersphere.api.jmeter.utils.JMeterProperties; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.constants.BackendListenerConstants; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.jmeter.JMeterBase; +import io.metersphere.jmeter.LocalRunner; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jmeter.threads.ThreadGroup; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.collections.HashTree; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import java.io.File; +import java.lang.reflect.Field; + +@Service +public class JMeterService { + + @Resource + private JMeterProperties jmeterProperties; + @Resource + private ExecThreadPoolExecutor execThreadPoolExecutor; + + @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(); + } + } + + private String getReportId(HashTree tree, JmeterRunRequestDTO runRequest) { + if (StringUtils.equals(runRequest.getReportType(), RunModeConstants.SET_REPORT.toString())) { + String reportId = StringUtils.join(runRequest.getReportId(), "_", runRequest.getTestId()); + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof ThreadGroup) { + ((ThreadGroup) key).setName(reportId); + break; + } else { + getReportId(node, runRequest); + } + } + return reportId; + } else { + return runRequest.getReportId(); + } + } + + public void runLocal(JmeterRunRequestDTO runRequest, HashTree testPlan) { + try { + init(); + String reportId = getReportId(runRequest.getHashTree(), runRequest); + if (!FixedCapacityUtil.containsKey(reportId)) { + FixedCapacityUtil.put(reportId, new StringBuffer("")); + } + runRequest.setHashTree(testPlan); + // 调试 + if (runRequest.isDebug()) { + addDebugListener(runRequest); + } + if ((runRequest.getExtendedParameters().containsKey(ExtendedParameter.SAVE_RESULT) + && Boolean.valueOf(runRequest.getExtendedParameters().get(ExtendedParameter.SAVE_RESULT).toString())) + || !runRequest.isDebug()) { + JMeterBase.addBackendListener(runRequest, runRequest.getHashTree(), + MsApiBackendListener.class.getCanonicalName()); + } + LocalRunner runner = new LocalRunner(testPlan); + runner.run(runRequest.getReportId()); + } catch (Exception e) { + LoggerUtil.error("Local执行异常", runRequest.getReportId(), e); + MSException.throwException("读取脚本失败"); + } + } + + /** + * 添加调试监听 + */ + private void addDebugListener(JmeterRunRequestDTO request) { + MsDebugListener resultCollector = new MsDebugListener(); + resultCollector.setName(request.getReportId()); + resultCollector.setTestId(request.getTestId()); + resultCollector.setReportId(request.getReportId()); + resultCollector.setKafkaConfig(request.getKafkaConfig()); + resultCollector.setProperty(TestElement.TEST_CLASS, MsDebugListener.class.getName()); + resultCollector.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ViewResultsFullVisualizer")); + resultCollector.setEnabled(true); + resultCollector.setClearLog(true); + resultCollector.setRunMode(request.getRunMode()); + if ((request.getExtendedParameters().containsKey(ExtendedParameter.SAVE_RESULT) + && Boolean.valueOf(request.getExtendedParameters().get(ExtendedParameter.SAVE_RESULT).toString()))) { + resultCollector.setClearLog(false); + } + // 添加DEBUG标示 + HashTree test = ArrayUtils.isNotEmpty(request.getHashTree().getArray()) ? request.getHashTree().getTree(request.getHashTree().getArray()[0]) : null; + if (test != null && ArrayUtils.isNotEmpty(test.getArray()) && test.getArray()[0] instanceof ThreadGroup) { + ThreadGroup group = (ThreadGroup) test.getArray()[0]; + group.setProperty(BackendListenerConstants.MS_DEBUG.name(), true); + } + request.getHashTree().add(request.getHashTree().getArray()[0], resultCollector); + } + + public void run(JmeterRunRequestDTO request) { + if (request.getCorePoolSize() > 0) { + execThreadPoolExecutor.setCorePoolSize(request.getCorePoolSize()); + } + execThreadPoolExecutor.addTask(request); + } + + public void addQueue(JmeterRunRequestDTO request) { + this.runLocal(request, request.getHashTree()); + } + + public static HashTree getHashTree(Object scriptWrapper) throws Exception { + Field field = scriptWrapper.getClass().getDeclaredField("testPlan"); + field.setAccessible(true); + return (HashTree) field.get(scriptWrapper); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java b/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java new file mode 100644 index 0000000..a6c9ad9 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/MsApiBackendListener.java @@ -0,0 +1,134 @@ +package io.metersphere.api.jmeter; + +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.jmeter.utils.FixedCapacityUtil; +import io.metersphere.api.service.JvmService; +import io.metersphere.api.service.ProducerService; +import io.metersphere.api.service.utils.JmxAttachmentFileUtil; +import io.metersphere.constants.BackendListenerConstants; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.ResultDTO; +import io.metersphere.jmeter.JMeterBase; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LoggerUtil; +import io.metersphere.utils.RetryResultUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; +import org.apache.jmeter.visualizers.backend.BackendListenerContext; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class MsApiBackendListener extends AbstractBackendListenerClient implements Serializable { + private List queues; + private JmxAttachmentFileUtil jmxAttachmentFileUtil; + // KAFKA 配置信息 + private Map producerProps; + private ResultDTO dto; + + @Override + public void setupTest(BackendListenerContext context) throws Exception { + queues = new LinkedList<>(); + jmxAttachmentFileUtil = CommonBeanFactory.getBean(JmxAttachmentFileUtil.class); + this.setParam(context); + super.setupTest(context); + } + + @Override + public void handleSampleResults(List sampleResults, BackendListenerContext context) { + LoggerUtil.info("开始处理单条执行结果报告", dto.getReportId()); + if (CollectionUtils.isNotEmpty(sampleResults)) { + RetryResultUtil.clearLoops(sampleResults); + queues.addAll(sampleResults); + } + } + + @Override + public void teardownTest(BackendListenerContext context) { + String executeReportId = null; + try { + super.teardownTest(context); + PoolExecBlockingQueueUtil.offer(dto.getReportId()); + if (StringUtils.isNotEmpty(dto.getReportId())) { + BlockingQueueUtil.remove(dto.getReportId()); + } + // 整理执行结果 + LoggerUtil.info("开始处理数据:" + queues.size(), dto.getReportId()); + JMeterBase.resultFormatting(queues, dto); + if (dto.isRetryEnable()) { + LoggerUtil.info("重试结果处理开始", dto.getReportId()); + RetryResultUtil.mergeRetryResults(dto.getRequestResults()); + LoggerUtil.info("重试结果处理结束", dto.getReportId()); + } + String reportId = dto.getReportId(); + executeReportId = reportId; + + if (StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.toString())) { + reportId = StringUtils.join(dto.getReportId(), "_", dto.getTestId()); + } + dto.setConsole(FixedCapacityUtil.getJmeterLogger(reportId, true)); + if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { + dto.setArbitraryData(new HashMap() {{ + this.put(ExtendedParameter.TEST_END, true); + }}); + } else { + dto.getArbitraryData().put(ExtendedParameter.TEST_END, true); + } + FileUtils.deleteFile(FileUtils.BODY_FILE_DIR + "/" + dto.getReportId() + "_" + dto.getTestId() + ".jmx"); + LoggerUtil.info("node整体执行完成", dto.getReportId()); + // 存储结果 + CommonBeanFactory.getBean(ProducerService.class).send(dto, producerProps); + LoggerUtil.info(JvmService.jvmInfo().toString(), dto.getReportId()); + } catch (Exception e) { + LoggerUtil.error("结果处理异常", dto.getReportId(), e); + } finally { + //删除执行过程中的文件和附件 + jmxAttachmentFileUtil.deleteTmpFiles(executeReportId); + + if (FileServer.getFileServer() != null) { + LoggerUtil.info("进入监听,开始关闭CSV", dto.getReportId()); + FileServer.getFileServer().closeCsv(dto.getReportId()); + } + PoolExecBlockingQueueUtil.offer(dto.getReportId()); + queues.clear(); + } + } + + /** + * 初始化参数 + * + * @param context + */ + private void setParam(BackendListenerContext context) { + dto = new ResultDTO(); + dto.setTestId(context.getParameter(BackendListenerConstants.TEST_ID.name())); + dto.setRunMode(context.getParameter(BackendListenerConstants.RUN_MODE.name())); + dto.setReportId(context.getParameter(BackendListenerConstants.REPORT_ID.name())); + dto.setReportType(context.getParameter(BackendListenerConstants.REPORT_TYPE.name())); + dto.setTestPlanReportId(context.getParameter(BackendListenerConstants.MS_TEST_PLAN_REPORT_ID.name())); + if (context.getParameter(BackendListenerConstants.RETRY_ENABLE.name()) != null) { + dto.setRetryEnable(Boolean.parseBoolean(context.getParameter(BackendListenerConstants.RETRY_ENABLE.name()))); + } + this.producerProps = new HashMap<>(); + + if (StringUtils.isNotEmpty(context.getParameter(BackendListenerConstants.KAFKA_CONFIG.name()))) { + this.producerProps = JsonUtils.parseObject(context.getParameter(BackendListenerConstants.KAFKA_CONFIG.name()), Map.class); + } + dto.setQueueId(context.getParameter(BackendListenerConstants.QUEUE_ID.name())); + dto.setRunType(context.getParameter(BackendListenerConstants.RUN_TYPE.name())); + + String ept = context.getParameter(BackendListenerConstants.EPT.name()); + if (StringUtils.isNotEmpty(ept)) { + dto.setExtendedParameters(JsonUtils.parseObject(ept, Map.class)); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java b/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java new file mode 100644 index 0000000..5aa94cb --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/MsDebugListener.java @@ -0,0 +1,226 @@ +/* + * 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 io.metersphere.api.jmeter; + +import io.metersphere.api.jmeter.dto.MsgDTO; +import io.metersphere.api.jmeter.queue.PoolExecBlockingQueueUtil; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.FixedCapacityUtil; +import io.metersphere.api.jmeter.utils.ResultParseUtil; +import io.metersphere.api.service.ProducerService; +import io.metersphere.api.service.utils.JmxAttachmentFileUtil; +import io.metersphere.dto.RequestResult; +import io.metersphere.jmeter.JMeterBase; +import io.metersphere.utils.JMeterVars; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.reporters.AbstractListenerElement; +import org.apache.jmeter.samplers.*; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.threads.JMeterVariables; + +import java.io.Serializable; +import java.util.Map; + +/** + * 实时结果监听 + */ +public class MsDebugListener extends AbstractListenerElement implements SampleListener, Clearable, Serializable, + TestStateListener, Remoteable, NoThreadClone { + private static final String ERROR_LOGGING = "MsResultCollector.error_logging"; // $NON-NLS-1$ + private static final String SUCCESS_ONLY_LOGGING = "MsResultCollector.success_only_logging"; // $NON-NLS-1$ + private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$ + private static final String RUNNING_DEBUG_SAMPLER_NAME = "RunningDebugSampler"; + public static final String TEST_END = "MS_TEST_END"; + private ProducerService producerService; + private JmxAttachmentFileUtil jmxAttachmentFileUtil; + private String testId; + private String reportId; + private Map kafkaConfig; + private boolean clearLog; + private String runMode; + + public String getTestId() { + return testId; + } + + public void setTestId(String testId) { + this.testId = testId; + } + + public void setClearLog(boolean clearLog) { + this.clearLog = clearLog; + } + + public String getReportId() { + return reportId; + } + + public void setReportId(String reportId) { + this.reportId = reportId; + } + + public void setKafkaConfig(Map kafkaConfig) { + this.kafkaConfig = kafkaConfig; + } + + public void setRunMode(String runMode) { + this.runMode = runMode; + } + + @Override + public Object clone() { + MsDebugListener clone = (MsDebugListener) super.clone(); + return clone; + } + + public boolean isErrorLogging() { + return getPropertyAsBoolean(ERROR_LOGGING); + } + + /** + * Get the state of successful only logging + * + * @return Flag whether only successful samples should be logged + */ + public boolean isSuccessOnlyLogging() { + return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING, false); + } + + public boolean isSampleWanted(boolean success, SampleResult result) { + boolean errorOnly = isErrorLogging(); + boolean successOnly = isSuccessOnlyLogging(); + return isSampleWanted(success, errorOnly, successOnly) && !StringUtils.containsIgnoreCase(result.getSampleLabel(), "MS_CLEAR_LOOPS_VAR_"); + } + + public static boolean isSampleWanted(boolean success, boolean errorOnly, + boolean successOnly) { + return (!errorOnly && !successOnly) || + (success && successOnly) || + (!success && errorOnly); + } + + @Override + public void testEnded(String host) { + LoggerUtil.info("Debug TestEnded " + this.getName()); + MsgDTO dto = new MsgDTO(); + dto.setExecEnd(false); + dto.setContent(TEST_END); + dto.setReportId("send." + this.getName()); + dto.setToReport(this.getName()); + dto.setRunMode(this.runMode); + LoggerUtil.debug("send. " + this.getName()); + producerService.sendDebug(this.reportId, dto, kafkaConfig); + PoolExecBlockingQueueUtil.offer(this.getName()); + jmxAttachmentFileUtil.deleteTmpFiles(this.reportId); + } + + @Override + public void testStarted(String host) { + producerService = CommonBeanFactory.getBean(ProducerService.class); + jmxAttachmentFileUtil = CommonBeanFactory.getBean(JmxAttachmentFileUtil.class); + LoggerUtil.debug("TestStarted " + this.getName()); + } + + @Override + public void testEnded() { + testEnded(TEST_IS_LOCAL); + } + + @Override + public void testStarted() { + testStarted(TEST_IS_LOCAL); + } + + @Override + public void sampleStarted(SampleEvent e) { + try { + MsgDTO dto = new MsgDTO(); + dto.setContent(e.getThreadGroup()); + dto.setReportId("send." + this.getName()); + dto.setToReport(this.getName()); + dto.setRunMode(this.runMode); + LoggerUtil.debug("send. " + this.getName()); + producerService.sendDebug(this.reportId, dto, kafkaConfig); + } catch (Exception ex) { + LoggerUtil.error("消息推送失败:", ex); + } + } + + @Override + public void sampleStopped(SampleEvent e) { + LoggerUtil.info("执行结束", this.getReportId()); + } + + @Override + public void sampleOccurred(SampleEvent event) { + SampleResult result = event.getResult(); + this.setVars(result); + if (isSampleWanted(result.isSuccessful(), result) && !StringUtils.equals(result.getSampleLabel(), RUNNING_DEBUG_SAMPLER_NAME)) { + RequestResult requestResult = JMeterBase.getRequestResult(result); + if (requestResult != null && ResultParseUtil.isNotAutoGenerateSampler(requestResult)) { + MsgDTO dto = new MsgDTO(); + dto.setExecEnd(false); + dto.setReportId("send." + this.getName()); + dto.setToReport(this.getName()); + dto.setRunMode(this.runMode); + + String console = FixedCapacityUtil.getJmeterLogger(this.getReportId(), clearLog); + if (StringUtils.isNotEmpty(requestResult.getName()) && requestResult.getName().startsWith("Transaction=")) { + requestResult.getSubRequestResults().forEach(transactionResult -> { + transactionResult.getResponseResult().setConsole(console); + dto.setContent("result_" + JsonUtils.toJSONString(transactionResult)); + producerService.sendDebug(this.reportId, dto, kafkaConfig); + }); + } else { + requestResult.getResponseResult().setConsole(console); + dto.setContent("result_" + JsonUtils.toJSONString(requestResult)); + producerService.sendDebug(this.reportId, dto, kafkaConfig); + } + LoggerUtil.debug("send. " + this.getName()); + } + } + } + + private void setVars(SampleResult result) { + if (StringUtils.isNotEmpty(result.getSampleLabel()) && result.getSampleLabel().startsWith("Transaction=")) { + for (int i = 0; i < result.getSubResults().length; i++) { + SampleResult subResult = result.getSubResults()[i]; + this.setVars(subResult); + } + } + JMeterVariables variables = JMeterVars.get(result.getResourceId()); + if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : variables.entrySet()) { + builder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n"); + } + if (StringUtils.isNotEmpty(builder)) { + result.setExtVars(builder.toString()); + } + } + } + + @Override + public void clearData() { + LoggerUtil.info("clear data"); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/MsDriverManager.java b/src/main/java/io/metersphere/api/jmeter/MsDriverManager.java new file mode 100644 index 0000000..33ace93 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/MsDriverManager.java @@ -0,0 +1,121 @@ +package io.metersphere.api.jmeter; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.metersphere.api.enums.StorageConstants; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.jmeter.utils.URLParserUtil; +import io.metersphere.api.repository.MinIORepositoryImpl; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.dto.ProjectJarConfig; +import io.metersphere.enums.JmxFileMetadataColumns; +import io.metersphere.jmeter.ProjectClassLoader; +import io.metersphere.utils.JarConfigUtils; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LocalPathUtil; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.testelement.property.JMeterProperty; +import org.apache.jorphan.collections.HashTree; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MsDriverManager { + + public static void downloadProjectJar(JmeterRunRequestDTO runRequest) { + if (MapUtils.isNotEmpty(runRequest.getCustomJarInfo())) { + Map> map = runRequest.getCustomJarInfo(); + //获取项目id + List projectIds = map.keySet().stream().collect(Collectors.toList()); + //获取需要下载的jar包 + Map> jarConfigs = JarConfigUtils.getJarConfigs(projectIds, map); + if (MapUtils.isNotEmpty(jarConfigs)) { + Map> historyDataMap = new HashMap<>(); + Map> minIOMap = new HashMap<>(); + List loaderProjectIds = new ArrayList<>(); + jarConfigs.forEach((key, value) -> { + loaderProjectIds.add(key); + if (CollectionUtils.isNotEmpty(value)) { + //Git下载或历史数据 + List otherRepositoryList = value.stream().distinct().filter(s -> !StringUtils.equals(StorageConstants.MINIO.name(), s.getStorage()) || s.isHasFile()).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(otherRepositoryList)) { + historyDataMap.put(key, otherRepositoryList); + } + //MinIO下载 + List minIOList = value.stream().distinct().filter(s -> StringUtils.equals(StorageConstants.MINIO.name(), s.getStorage())).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(minIOList)) { + minIOMap.put(key, minIOList); + } + } + }); + //下载MinIO jar包 + if (MapUtils.isNotEmpty(minIOMap)) { + MinIORepositoryImpl minIORepository = new MinIORepositoryImpl(); + minIOMap.forEach((key, value) -> { + value.stream().forEach(s -> { + try { + String path = StringUtils.join( + key, File.separator, s.getName()); + LoggerUtil.info("开始下载MinIO中的Jar包,文件名:" + s.getName() + ",路径:" + path); + byte[] bytes = minIORepository.getFileAsStream(path).readAllBytes(); + FileUtils.createFile(StringUtils.join(LocalPathUtil.JAR_PATH, + File.separator, + key, + File.separator, + s.getId(), + File.separator, + String.valueOf(s.getUpdateTime()), ".jar"), bytes); + } catch (Exception e) { + historyDataMap.put(key, value); + LoggerUtil.error(e.getMessage(), e); + LoggerUtil.error("Jar包下载失败,不存在MinIO中"); + } + }); + }); + } + //下载Git或本地jar包 + if (MapUtils.isNotEmpty(historyDataMap)) { + try { + // 生成附件/JAR文件 + String jarUrl = URLParserUtil.getJarURL(runRequest.getPlatformUrl()); + LoggerUtil.info("开始同步上传的JAR:" + jarUrl); + //下载历史jar包 + File file = ZipSpider.downloadJarDb(jarUrl, historyDataMap, LocalPathUtil.JAR_PATH); + if (file != null) { + ZipSpider.unzip(file.getPath(), LocalPathUtil.JAR_PATH); + FileUtils.deleteFile(file.getPath()); + } + } catch (Exception e) { + LoggerUtil.error(e.getMessage(), e); + MSException.throwException(e.getMessage()); + } + } + if (CollectionUtils.isNotEmpty(loaderProjectIds)) { + ProjectClassLoader.initClassLoader(loaderProjectIds); + } + } + } + } + + public static void batchLoadJar(JmeterRunRequestDTO runRequest, HashTree testPlan) { + // 批量执行时,loadjar在这里加载 + TestPlan testPlanElement = (TestPlan) testPlan.getArray()[0]; + JMeterProperty property = testPlanElement.getProperty(JmxFileMetadataColumns.JAR_PATH_CONFIG.name()); + if (StringUtils.isNotBlank(property.getStringValue())) { + Map> jarsMap = JsonUtils.parseObject(property.getStringValue(), new TypeReference>>() { + }); + runRequest.setCustomJarInfo(jarsMap); + MsDriverManager.downloadProjectJar(runRequest); + testPlanElement.removeProperty(JmxFileMetadataColumns.JAR_PATH_CONFIG.name()); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/jmeter/dto/MsgDTO.java b/src/main/java/io/metersphere/api/jmeter/dto/MsgDTO.java new file mode 100644 index 0000000..ac95718 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/dto/MsgDTO.java @@ -0,0 +1,21 @@ +package io.metersphere.api.jmeter.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * + */ +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data +public class MsgDTO { + private String reportId; + private String toReport; + private boolean execEnd; + private String content; + private String runMode; +} diff --git a/src/main/java/io/metersphere/api/jmeter/dto/RequestResultExpandDTO.java b/src/main/java/io/metersphere/api/jmeter/dto/RequestResultExpandDTO.java new file mode 100644 index 0000000..7a121e7 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/dto/RequestResultExpandDTO.java @@ -0,0 +1,16 @@ +package io.metersphere.api.jmeter.dto; +import io.metersphere.dto.RequestResult; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +public class RequestResultExpandDTO extends RequestResult { + private String status; + private String uiImg; + private String reportId; + private long time; + private Map attachInfoMap; +} 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..a0196ff --- /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("队列处理异常", key, 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("获取队列失败", key, e); + } + } +} 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..857736b --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/ExecThreadPoolExecutor.java @@ -0,0 +1,182 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.api.jmeter.utils.JMeterThreadUtil; +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 DEFAULT_MAX_POOL_SIZE = 10; + // 线程池维护线程所允许的空闲时间 + private final static int KEEP_ALIVE_TIME = 1; + // 线程池所使用的缓冲队列大小 + private final static int WORK_QUEUE_SIZE = 10000; + + private final static int MAX_POOL_SIZE = 1000; + + private MsRejectedExecutionHandler msRejectedExecutionHandler = new MsRejectedExecutionHandler(); + /** + * 创建线程池 + */ + private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor( + CORE_POOL_SIZE, + DEFAULT_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("把缓冲区任务重新添加到线程池", 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(" 系统当前线程数:" + JMeterThreadUtil.activeCount()).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(" 执行队列大小:" + PoolExecBlockingQueueUtil.queue.size()).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 maximumPoolSize) { + try { + if (maximumPoolSize <= MAX_POOL_SIZE && maximumPoolSize != threadPool.getMaximumPoolSize()) { + threadPool.setMaximumPoolSize(maximumPoolSize); + threadPool.setCorePoolSize(maximumPoolSize); + threadPool.allowCoreThreadTimeOut(true); + LoggerUtil.info("set successfully " + threadPool.prestartAllCoreThreads()); + } + LoggerUtil.info("The number of concurrent settings is too large: " + threadPool.getMaximumPoolSize()); + } 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..d7b917f --- /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("初始化队列失败", key, e); + } + 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..c7732a7 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/queue/SystemExecTask.java @@ -0,0 +1,51 @@ +package io.metersphere.api.jmeter.queue; + +import io.metersphere.api.jmeter.ExtendedParameter; +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.JMeterThreadUtil; +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("开始执行队列中任务", request.getReportId()); + CommonBeanFactory.getBean(JMeterService.class).addQueue(request); + if (StringUtils.isNotEmpty(request.getReportId())) { + Object res = PoolExecBlockingQueueUtil.take(request.getReportId()); + if (res == null && !JMeterThreadUtil.isRunning(request.getReportId(), request.getTestId())) { + LoggerUtil.info("任务执行超时", request.getReportId()); + ResultDTO dto = new ResultDTO(); + BeanUtils.copyProperties(dto, request); + if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { + dto.setArbitraryData(new HashMap() {{ + this.put(ExtendedParameter.TEST_END, true); + this.put("TIMEOUT", true); + }}); + } else { + dto.getArbitraryData().put(ExtendedParameter.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/jmeter/utils/ApiReportStatus.java b/src/main/java/io/metersphere/api/jmeter/utils/ApiReportStatus.java new file mode 100644 index 0000000..f069748 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/ApiReportStatus.java @@ -0,0 +1,5 @@ +package io.metersphere.api.jmeter.utils; + +public enum ApiReportStatus { + PENDING, RUNNING, RERUNNING, ERROR, SUCCESS, FAKE_ERROR, STOPPED, TIMEOUT +} 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/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/FileUtils.java b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java new file mode 100644 index 0000000..41494e2 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/FileUtils.java @@ -0,0 +1,81 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class FileUtils { + public static final String BODY_FILE_DIR = "/opt/metersphere/data/body"; + public static final String JAR_PLUG_FILE_DIR = "/opt/metersphere/data/node/plug/jar"; + public static final String IS_REF = "isRef"; + public static final String FILE_ID = "fileId"; + public static final String FILENAME = "filename"; + + public static final String FILE_PATH = "File.path"; + + public static final String KEYSTORE_FILE_PATH = "MS-KEYSTORE-FILE-PATH"; + public static final String BODY_PLUGIN_FILE_DIR = "/opt/metersphere/data/body/plugin/"; + + public static void deleteFile(String path) { + File file = new File(path); + if (file.exists()) { + file.delete(); + } + } + + public static void deleteDir(String path) { + try { + File file = new File(path); + if (file.isDirectory()) { + org.apache.commons.io.FileUtils.deleteDirectory(file); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } + + public static List getFileNames(String path, String targetPath) { + File f = new File(path); + if (!f.exists()) { + f.mkdirs(); + } + List fileNames = new ArrayList<>(); + File fa[] = f.listFiles(); + for (int i = 0; i < fa.length; i++) { + File fs = fa[i]; + if (fs.exists()) { + fileNames.add(StringUtils.join(targetPath, fs.getName())); + } + } + return fileNames; + } + + public static void createFile(String filePath, byte[] fileBytes) { + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + try { + File dir = file.getParentFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + file.createNewFile(); + } catch (Exception e) { + LoggerUtil.error(e); + } + + try (InputStream in = new ByteArrayInputStream(fileBytes); OutputStream out = new FileOutputStream(file)) { + final int MAX = 4096; + byte[] buf = new byte[MAX]; + for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) { + out.write(buf, 0, bytesRead); + } + } catch (IOException e) { + LoggerUtil.error(e); + } + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtil.java b/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtil.java new file mode 100644 index 0000000..051c674 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/FixedCapacityUtil.java @@ -0,0 +1,61 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +public class FixedCapacityUtil { + private static Map fixedCapacityCache = Collections.synchronizedMap(new LRUHashMap<>()); + + public static StringBuffer get(String key) { + if (containsKey(key)) { + return fixedCapacityCache.get(key); + } + return new StringBuffer(""); + } + + public static boolean containsKey(String key) { + if (StringUtils.isEmpty(key)) { + return false; + } + return fixedCapacityCache.containsKey(key); + } + + public static void put(String key, StringBuffer value) { + if (!fixedCapacityCache.containsKey(key)) { + fixedCapacityCache.put(key, value); + } + } + + public static void remove(String key) { + if (fixedCapacityCache.containsKey(key)) { + fixedCapacityCache.remove(key); + } + } + + public static int size() { + return fixedCapacityCache.size(); + } + + + static class LRUHashMap extends LinkedHashMap { + private int capacity = 3000; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } + } + + public static String getJmeterLogger(String reportId, boolean isRemove) { + try { + return get(reportId).toString(); + } catch (Exception e) { + return StringUtils.EMPTY; + } finally { + if (isRemove) { + remove(reportId); + } + } + } +} 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..47fde6c --- /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/JMeterThreadUtil.java b/src/main/java/io/metersphere/api/jmeter/utils/JMeterThreadUtil.java new file mode 100644 index 0000000..c6e6c11 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/JMeterThreadUtil.java @@ -0,0 +1,57 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.api.jmeter.queue.ExecThreadPoolExecutor; +import io.metersphere.api.jmeter.queue.PoolExecBlockingQueueUtil; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; + +public class JMeterThreadUtil { + public static String stop(List names) { + ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); + int noThreads = currentGroup.activeCount(); + Thread[] lstThreads = new Thread[noThreads]; + currentGroup.enumerate(lstThreads); + StringBuilder threadNames = new StringBuilder(); + for (String name : names) { + for (int i = 0; i < noThreads; i++) { + if (lstThreads[i] != null && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(name)) { + LoggerUtil.error("异常强制处理线程编号:" + i + " = " + lstThreads[i].getName()); + threadNames.append(lstThreads[i].getName()).append(";"); + lstThreads[i].interrupt(); + } + } + } + return threadNames.toString(); + } + + public static boolean isRunning(String reportId, String testId) { + if (StringUtils.isEmpty(reportId)) { + return false; + } + if (PoolExecBlockingQueueUtil.queue.containsKey(reportId)) { + return true; + } + if (CommonBeanFactory.getBean(ExecThreadPoolExecutor.class).check(reportId)) { + return true; + } + 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; + } + + public static Integer activeCount() { + ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); + return currentGroup.activeCount(); + } +} 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/ResponseFormatType.java b/src/main/java/io/metersphere/api/jmeter/utils/ResponseFormatType.java new file mode 100644 index 0000000..a03d2d1 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/ResponseFormatType.java @@ -0,0 +1,8 @@ +package io.metersphere.api.jmeter.utils; + +/** + * 请求体格式化类型 + */ +public enum ResponseFormatType { + XML, JSON, RAW +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/ResultParseUtil.java b/src/main/java/io/metersphere/api/jmeter/utils/ResultParseUtil.java new file mode 100644 index 0000000..f61557c --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/ResultParseUtil.java @@ -0,0 +1,14 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.dto.RequestResult; +import org.apache.commons.lang3.StringUtils; + +public class ResultParseUtil { + + public static final String PRE_PROCESS_SCRIPT = "PRE_PROCESSOR_ENV_"; + public static final String POST_PROCESS_SCRIPT = "POST_PROCESSOR_ENV_"; + + public static boolean isNotAutoGenerateSampler(RequestResult result) { + return !(StringUtils.equals(result.getMethod(), "Request") && StringUtils.startsWithAny(result.getName(), PRE_PROCESS_SCRIPT, POST_PROCESS_SCRIPT)); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/URLParserUtil.java b/src/main/java/io/metersphere/api/jmeter/utils/URLParserUtil.java new file mode 100644 index 0000000..e6f1d0f --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/URLParserUtil.java @@ -0,0 +1,31 @@ +package io.metersphere.api.jmeter.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.net.MalformedURLException; +import java.net.URL; + +public class URLParserUtil { + private static final String QUOTES = "://"; + + public static String getHost(String platformURL) throws MalformedURLException { + URL urlObject = new URL(platformURL); + return StringUtils.join(urlObject.getProtocol(), QUOTES, urlObject.getHost(), (urlObject.getPort() > 0 ? ":" + urlObject.getPort() : StringUtils.EMPTY)); + } + + public static String getPluginURL(String platformURL) throws MalformedURLException { + return StringUtils.join(getHost(platformURL), "/api/jmeter/download/plugin/jar"); + } + + public static String getJarURL(String platformURL) throws MalformedURLException { + return StringUtils.join(getHost(platformURL), "/api/jmeter/download/jar"); + } + + public static String getDownFileURL(String platformURL) throws MalformedURLException { + return StringUtils.join(getHost(platformURL), "/api/jmeter/download/files"); + } + + public static String getScriptURL(String platformURL) throws MalformedURLException { + return StringUtils.join(getHost(platformURL), "/api/jmeter/get-script"); + } +} diff --git a/src/main/java/io/metersphere/api/jmeter/utils/XMLUtil.java b/src/main/java/io/metersphere/api/jmeter/utils/XMLUtil.java new file mode 100644 index 0000000..0cb7c74 --- /dev/null +++ b/src/main/java/io/metersphere/api/jmeter/utils/XMLUtil.java @@ -0,0 +1,39 @@ +package io.metersphere.api.jmeter.utils; + +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; +import org.dom4j.Document; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; + +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +public class XMLUtil { + public static String formatXmlString(String xmlString) throws Exception { + xmlString = StringUtils.replace(xmlString, StringUtils.LF, StringUtils.EMPTY); + Document document = getDom4jDocumentByXmlString(xmlString); + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setEncoding(document.getXMLEncoding()); + StringWriter stringWriter = new StringWriter(); + XMLWriter writer = new XMLWriter(stringWriter, format); + writer.write(document); + writer.close(); + return stringWriter.toString(); + } + + public static Document getDom4jDocumentByXmlString(String xmlString) throws Exception { + SAXReader reader = new SAXReader(); + try { + reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + reader.setFeature("http://xml.org/sax/features/external-general-entities", false); + reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (Exception e) { + LoggerUtil.error(e); + } + return reader.read(new ByteArrayInputStream(xmlString.getBytes(StandardCharsets.UTF_8.name()))); + } +} diff --git a/src/main/java/io/metersphere/api/repository/FileCenter.java b/src/main/java/io/metersphere/api/repository/FileCenter.java new file mode 100644 index 0000000..1aafd1f --- /dev/null +++ b/src/main/java/io/metersphere/api/repository/FileCenter.java @@ -0,0 +1,39 @@ +package io.metersphere.api.repository; + +import io.metersphere.api.enums.StorageConstants; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.dto.AttachmentBodyFile; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class FileCenter { + public static FileRepository getRepository(String storage) { + if (StringUtils.equals(StorageConstants.MINIO.name(), storage)) { + LoggerUtil.info("NAS文件处理"); + return CommonBeanFactory.getBean(MinIORepositoryImpl.class); + } else { + return null; + } + } + + public static List downloadFiles(List downLoadFileList) { + + List downloadErrorList = new ArrayList<>(); + + if (CollectionUtils.isNotEmpty(downLoadFileList)) { + List minIODownloadFiles = new ArrayList<>(); + downLoadFileList.forEach(attachmentBodyFile -> { + if (StringUtils.equals(StorageConstants.MINIO.name(), attachmentBodyFile.getFileStorage())) { + minIODownloadFiles.add(attachmentBodyFile); + } + }); + downloadErrorList = Objects.requireNonNull(getRepository(StorageConstants.MINIO.name())).downloadFiles(minIODownloadFiles); + } + return downloadErrorList; + } +} diff --git a/src/main/java/io/metersphere/api/repository/FileRepository.java b/src/main/java/io/metersphere/api/repository/FileRepository.java new file mode 100644 index 0000000..d700518 --- /dev/null +++ b/src/main/java/io/metersphere/api/repository/FileRepository.java @@ -0,0 +1,10 @@ +package io.metersphere.api.repository; + + +import io.metersphere.dto.AttachmentBodyFile; + +import java.util.List; + +public interface FileRepository { + List downloadFiles(List requestList); +} diff --git a/src/main/java/io/metersphere/api/repository/MinIORepositoryImpl.java b/src/main/java/io/metersphere/api/repository/MinIORepositoryImpl.java new file mode 100644 index 0000000..511e473 --- /dev/null +++ b/src/main/java/io/metersphere/api/repository/MinIORepositoryImpl.java @@ -0,0 +1,108 @@ +package io.metersphere.api.repository; + +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.dto.AttachmentBodyFile; +import io.metersphere.enums.MinIOConfigEnum; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LoggerUtil; +import io.metersphere.utils.TemporaryFileUtil; +import io.minio.BucketExistsArgs; +import io.minio.GetObjectArgs; +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Component +public class MinIORepositoryImpl implements FileRepository { + + public static final String bucket = "metersphere"; + private static MinioClient minioClient = null; + + @Resource + private TemporaryFileUtil temporaryFileUtil; + + public File getFile(AttachmentBodyFile request) throws Exception { + if (temporaryFileUtil == null) { + temporaryFileUtil = CommonBeanFactory.getBean(TemporaryFileUtil.class); + } + assert temporaryFileUtil != null; + File file = temporaryFileUtil.getFile(request.getProjectId(), request.getFileMetadataId(), request.getFileUpdateTime(), request.getName(), request.getFileType()); + if (file == null) { + byte[] fileBytes = getFileAsStream(request.getRemotePath()).readAllBytes(); + //附件存储到缓存目录中 + temporaryFileUtil.saveFile(request.getProjectId(), request.getFileMetadataId(), request.getFileUpdateTime(), request.getName(), request.getFileType(), fileBytes); + return temporaryFileUtil.getFile(request.getProjectId(), request.getFileMetadataId(), request.getFileUpdateTime(), request.getName(), request.getFileType()); + } else { + return file; + } + } + + @Override + public List downloadFiles(List attachmentBodyFileList) { + if (temporaryFileUtil == null) { + temporaryFileUtil = CommonBeanFactory.getBean(TemporaryFileUtil.class); + } + List errorList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(attachmentBodyFileList)) { + attachmentBodyFileList.forEach(attachmentBodyFile -> { + try { + attachmentBodyFile.setRemotePath(attachmentBodyFile.getProjectId() + "/" + attachmentBodyFile.getName()); + File file = this.getFile(attachmentBodyFile); + if (file == null) { + errorList.add(attachmentBodyFile); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + }); + } + return errorList; + } + + public void initMinioClient(Map minioConfig) { + if (MapUtils.isEmpty(minioConfig)) { + LoggerUtil.info("初始化MinIO插件停止:参数[minioConfig]为空。"); + return; + } + try { + Object serverUrl = minioConfig.get(MinIOConfigEnum.ENDPOINT).toString(); + if (minioClient == null && serverUrl != null) { + LoggerUtil.info("开始初始化MinIO插件。配置:", JsonUtils.toJSONString(minioConfig)); + // 创建 MinioClient 客户端 + minioClient = MinioClient.builder() + .endpoint(minioConfig.get(MinIOConfigEnum.ENDPOINT).toString()) + .credentials(minioConfig.get(MinIOConfigEnum.ACCESS_KEY).toString(), minioConfig.get(MinIOConfigEnum.SECRET_KEY).toString()) + .build(); + boolean exist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build()); + if (!exist) { + minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); + } + LoggerUtil.info("初始化MinIO插件结束。"); + } else { + LoggerUtil.info("MinIOClient已初始化,无需再配置。"); + } + } catch (Exception e) { + LoggerUtil.error("MinIOClient已初始化失败!", e); + } + } + + public InputStream getFileAsStream(String path) throws Exception { + if (minioClient != null) { + return minioClient.getObject(GetObjectArgs.builder() + .bucket(bucket) + .object(path) + .build()); + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/api/service/DownloadPluginJarService.java b/src/main/java/io/metersphere/api/service/DownloadPluginJarService.java new file mode 100644 index 0000000..fbf94e7 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/DownloadPluginJarService.java @@ -0,0 +1,146 @@ +package io.metersphere.api.service; + +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.URLParserUtil; +import io.metersphere.api.repository.MinIORepositoryImpl; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.dto.AttachmentBodyFile; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.dto.PluginConfigDTO; +import io.metersphere.dto.PluginInfoDTO; +import io.metersphere.utils.LocalPathUtil; +import io.metersphere.utils.LoggerUtil; +import io.metersphere.utils.TemporaryFileUtil; +import jakarta.annotation.Resource; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class DownloadPluginJarService { + + @Resource + private MinIORepositoryImpl minIOConfigService; + + private TemporaryFileUtil temporaryFileUtil; + + public void downloadPlugin(JmeterRunRequestDTO runRequest) { + if (temporaryFileUtil == null) { + temporaryFileUtil = CommonBeanFactory.getBean(TemporaryFileUtil.class); + } + LoggerUtil.info("开始检查MinIO配置:", runRequest.getReportId()); + //Minio 初始化 + minIOConfigService.initMinioClient(runRequest.getPluginConfigDTO().getConfig()); + LoggerUtil.info("开始同步插件JAR:", runRequest.getReportId()); + + if (CollectionUtils.isEmpty(runRequest.getPluginConfigDTO().getPluginDTOS())) { + return; + } + List jarPluginIds = new ArrayList<>(); + try { + //获取本地已存在的jar信息 + List nodeFiles = FileUtils.getFileNames(LocalPathUtil.PLUGIN_PATH, FileUtils.BODY_PLUGIN_FILE_DIR); + //获取所有插件信息 + PluginConfigDTO pluginConfigDTO = runRequest.getPluginConfigDTO(); + + List pluginList = pluginConfigDTO.getPluginDTOS(); + //获取主服务插件jar信息 + List dbJars = pluginList + .stream() + .map(PluginInfoDTO::getSourcePath) + .toList(); + if (CollectionUtils.isNotEmpty(nodeFiles)) { + //node文件和主服务文件的差集,删除无用jar包 + List expiredJar = nodeFiles + .stream().filter(plugin -> !dbJars.contains(plugin)).collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(expiredJar)) { + expiredJar.forEach(expired -> { + FileUtils.deleteFile(StringUtils.join(LocalPathUtil.PLUGIN_PATH, File.separator, StringUtils.substringAfter(expired, FileUtils.BODY_PLUGIN_FILE_DIR))); + }); + } + } + + //需要从MinIO或者主服务工程获取的jar (优先从MinIO下载) + pluginList = pluginList.stream().filter(plugin -> !nodeFiles.contains(plugin.getSourcePath())).collect(Collectors.toList()); + pluginList.forEach(plugin -> { + try { + AttachmentBodyFile fileRequest = new AttachmentBodyFile(); + fileRequest.setRemotePath(StringUtils.join(LocalPathUtil.PLUGIN_PATH, plugin.getPluginId(), File.separator, plugin.getPluginId())); + File file = minIOConfigService.getFile(fileRequest); + if (file != null && file.exists()) { + FileUtils.createFile(StringUtils.join(LocalPathUtil.PLUGIN_PATH, File.separator, StringUtils.substringAfter(plugin.getSourcePath(), FileUtils.BODY_PLUGIN_FILE_DIR)), temporaryFileUtil.fileToByte(file)); + } + } catch (Exception e) { + jarPluginIds.add(plugin.getPluginId()); + } + }); + //兼容历史数据 + if (CollectionUtils.isNotEmpty(jarPluginIds)) { + String plugJarUrl = URLParserUtil.getPluginURL(runRequest.getPlatformUrl()); + LoggerUtil.info("下载插件jar:", plugJarUrl); + File plugFile = ZipSpider.downloadJarHistory(plugJarUrl, jarPluginIds, LocalPathUtil.PLUGIN_PATH); + if (plugFile != null) { + ZipSpider.unzip(plugFile.getPath(), LocalPathUtil.PLUGIN_PATH); + FileUtils.deleteFile(plugFile.getPath()); + } + } + //load所有jar + if (CollectionUtils.isNotEmpty(pluginList) || CollectionUtils.isNotEmpty(jarPluginIds)) { + this.loadPlugJar(LocalPathUtil.PLUGIN_PATH); + } + } catch (Exception e) { + LoggerUtil.error("node处理插件异常", runRequest.getReportId(), e); + } + } + + public void loadPlugJar(String jarPath) { + File file = new File(jarPath); + if (file.isDirectory() && !jarPath.endsWith("/")) { + file = new File(jarPath + "/"); + } + + File[] jars = listJars(file); + for (File jarFile : jars) { + try { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try { + // 从URLClassLoader类中获取类所在文件夹的方法,jar也可以认为是一个文件夹 + Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + // 获取系统类加载器 + method.invoke(classLoader, jarFile.toURI().toURL()); + } catch (Exception e) { + Method method = classLoader.getClass() + .getDeclaredMethod("appendToClassPathForInstrumentation", String.class); + method.setAccessible(true); + method.invoke(classLoader, jarFile.getPath()); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } + } + + 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]; + } + +} 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 100755 index 0000000..9056732 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JMeterExecuteService.java @@ -0,0 +1,143 @@ +package io.metersphere.api.service; + +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.MsDriverManager; +import io.metersphere.api.jmeter.queue.BlockingQueueUtil; +import io.metersphere.api.jmeter.queue.PoolExecBlockingQueueUtil; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.MSException; +import io.metersphere.api.jmeter.utils.URLParserUtil; +import io.metersphere.api.service.utils.JmxAttachmentFileUtil; +import io.metersphere.api.service.utils.ZipSpider; +import io.metersphere.api.vo.ScriptData; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.utils.LoggerUtil; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.save.SaveService; +import org.apache.jorphan.collections.HashTree; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +@Service +public class JMeterExecuteService { + @Resource + private JMeterService jMeterService; + @Resource + private ProducerService producerService; + @Resource + private RestTemplate restTemplate; + @Resource + private JmxAttachmentFileUtil jmxAttachmentFileUtil; + @Resource + private DownloadPluginJarService downloadPluginJarService; + + + public String runStart(JmeterRunRequestDTO runRequest) { + try { + LoggerUtil.info("进入node执行方法开始处理任务", runRequest.getReportId()); + if (runRequest.getKafkaConfig() == null) { + LoggerUtil.error("KAFKA配置为空无法执行", runRequest.getReportId()); + return "KAFKA 初始化失败,请检查配置"; + } + // 下载系统插件 + downloadPluginJarService.downloadPlugin(runRequest); + JMeterRunContext.getContext().setEnable(runRequest.isEnable()); + LoggerUtil.info("开始拉取脚本和脚本附件:" + runRequest.getPlatformUrl(), runRequest.getReportId()); + if (runRequest.getHashTree() != null) { + // 下载项目上传的自定义插件 + MsDriverManager.downloadProjectJar(runRequest); + jMeterService.run(runRequest); + return "SUCCESS"; + } + 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.getReportId() + "_" + runRequest.getTestId() + ".jmx"); + LoggerUtil.info("下载执行脚本完成:" + jmxFile.getName(), runRequest.getReportId()); + // 生成执行脚本 + HashTree testPlan = SaveService.loadTree(jmxFile); + MsDriverManager.batchLoadJar(runRequest, testPlan); + //检查是否需要附件进行下载,并替换JMX里的文件路径 + jmxAttachmentFileUtil.parseJmxAttachmentFile(testPlan, runRequest.getReportId(), runRequest.getPlatformUrl()); + + // 开始执行 + runRequest.setHashTree(testPlan); + LoggerUtil.info("开始加入队列执行", runRequest.getReportId()); + jMeterService.run(runRequest); + FileUtils.deleteFile(bodyFile.getPath()); + } else { + PoolExecBlockingQueueUtil.offer(runRequest.getReportId()); + MSException.throwException("未找到执行的JMX文件"); + } + } catch (Exception e) { + LoggerUtil.error("node处理任务异常", runRequest.getReportId(), e); + BlockingQueueUtil.remove(runRequest.getReportId()); + PoolExecBlockingQueueUtil.offer(runRequest.getReportId()); + LoggerUtil.info("node处理任务异常,补偿一条失败消息", runRequest.getReportId(), e); + producerService.send(runRequest, "node处理任务异常:" + e.getMessage()); + LoggerUtil.error("处理脚本异常" + runRequest.getReportId(), e); + } + return "SUCCESS"; + } + + public String debug(JmeterRunRequestDTO runRequest) { + try { + runRequest.getExtendedParameters().put(LoggerUtil.DEBUG, true); + Map params = new HashMap<>(); + params.put("reportId", runRequest.getReportId()); + params.put("testId", runRequest.getTestId()); + String script = this.getForObject(URLParserUtil.getScriptURL(runRequest.getPlatformUrl()), params); + InputStream inputSource = getStrToStream(script); + runRequest.setHashTree(JMeterService.getHashTree(SaveService.loadElement(inputSource))); + runRequest.setDebug(true); + + //检查是否需要附件进行下载,并替换JMX里的文件路径 + jmxAttachmentFileUtil.parseJmxAttachmentFile(runRequest.getHashTree(), runRequest.getReportId(), runRequest.getPlatformUrl()); + return this.runStart(runRequest); + } catch (Exception e) { + LoggerUtil.error(e); + return e.getMessage(); + } + } + + private String getForObject(String url, Object object) { + StringBuffer stringBuffer = new StringBuffer(url); + if (object instanceof Map) { + Iterator iterator = ((Map) object).entrySet().iterator(); + if (iterator.hasNext()) { + stringBuffer.append("?"); + Object element; + while (iterator.hasNext()) { + element = iterator.next(); + Map.Entry entry = (Map.Entry) element; + if (entry.getValue() != null) { + stringBuffer.append(element).append("&"); + } + url = stringBuffer.substring(0, stringBuffer.length() - 1); + } + } + } + return restTemplate.getForObject(url, ScriptData.class).getData(); + } + + private static InputStream getStrToStream(String sInputString) { + if (StringUtils.isNotEmpty(sInputString)) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes()); + return tInputStringStream; + } catch (Exception ex) { + LoggerUtil.error(ex); + MSException.throwException("生成脚本异常"); + } + } + return null; + } +} diff --git a/src/main/java/io/metersphere/api/service/JMeterRunContext.java b/src/main/java/io/metersphere/api/service/JMeterRunContext.java new file mode 100644 index 0000000..6081dc3 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/JMeterRunContext.java @@ -0,0 +1,56 @@ +package io.metersphere.api.service; + +import org.apache.commons.collections.MapUtils; + +import java.util.HashMap; +import java.util.Map; + +public class JMeterRunContext { + private boolean enable; + private String plugUrl; + private Map projectUrls; + private static JMeterRunContext context; + private String url; + + private JMeterRunContext() { + + } + + public Map getProjectUrls() { + if (MapUtils.isEmpty(projectUrls)) { + projectUrls = new HashMap<>(); + } + return projectUrls; + } + + public boolean isEnable() { + return enable; + } + + public void setEnable(boolean enable) { + this.enable = enable; + } + + public String getPlugUrl() { + return plugUrl; + } + + public void setPlugUrl(String plugUrl) { + this.plugUrl = plugUrl; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public static JMeterRunContext getContext() { + if (context == null) { + context = new JMeterRunContext(); + } + return context; + } +} 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..c6378ce --- /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.vo.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); + } +} 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..160a96b --- /dev/null +++ b/src/main/java/io/metersphere/api/service/ProducerService.java @@ -0,0 +1,114 @@ +package io.metersphere.api.service; + +import io.metersphere.api.jmeter.ExtendedParameter; +import io.metersphere.api.jmeter.dto.MsgDTO; +import io.metersphere.config.KafkaConfig; +import io.metersphere.constants.RunModeConstants; +import io.metersphere.dto.JmeterRunRequestDTO; +import io.metersphere.dto.RequestResult; +import io.metersphere.dto.ResultDTO; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.springframework.beans.BeanUtils; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class ProducerService { + // 初始化不同地址kafka,每个地址初始化一个线程 + private Map kafkaTemplateMap = new ConcurrentHashMap<>(); + public static final String DEBUG_TOPICS_KEY = "MS-API-DEBUG-KEY"; + + public KafkaTemplate init(Map producerProps) { + try { + 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) { + LoggerUtil.error(e); + return null; + } + } + + public void send(JmeterRunRequestDTO runRequest, String logMessage) { + ResultDTO dto = new ResultDTO(); + BeanUtils.copyProperties(runRequest, dto); + dto.setConsole(logMessage); + dto.setRequestResults(new LinkedList<>()); + if (dto.getArbitraryData() == null || dto.getArbitraryData().isEmpty()) { + dto.setArbitraryData(new HashMap() {{ + this.put(ExtendedParameter.TEST_END, true); + }}); + } else { + dto.getArbitraryData().put(ExtendedParameter.TEST_END, true); + } + this.send(dto, runRequest.getKafkaConfig()); + } + + public void send(String key, String message, Map producerProps) { + KafkaTemplate kafkaTemplate = this.init(producerProps); + if (kafkaTemplate != null) { + kafkaTemplate.send(KafkaConfig.TOPICS, key, message); + } + } + + public void sendDebug(String key, MsgDTO dto, Map producerProps) { + KafkaTemplate kafkaTemplate = this.init(producerProps); + if (kafkaTemplate != null && producerProps.containsKey(DEBUG_TOPICS_KEY)) { + kafkaTemplate.send(producerProps.get(DEBUG_TOPICS_KEY).toString(), key, JsonUtils.toJSONString(dto)); + } + } + + public void send(ResultDTO dto, Map kafkaConfig) { + try { + LoggerUtil.info("执行完成开始同步发送KAFKA" + dto.getRequestResults().size(), dto.getReportId()); + this.send(dto.getReportId(), JsonUtils.toJSONString(dto), kafkaConfig); + LoggerUtil.info("同步发送报告信息到KAFKA完成", dto.getReportId()); + } catch (Exception ex) { + LoggerUtil.error("KAFKA 推送结果异常", dto.getReportId(), ex); + // 尝试逐条发送 + if (dto != null && CollectionUtils.isNotEmpty(dto.getRequestResults())) { + dto.getArbitraryData().put(ExtendedParameter.REPORT_STATUS, dto); + StringBuffer logMsg = new StringBuffer(dto.getConsole()) + .append("\n") + .append("KAFKA推送结果异常:[" + dto.getReportId() + "]") + .append("\n") + .append(ex.getMessage()); + dto.setConsole(logMsg.toString()); + dto.getRequestResults().forEach(item -> { + if (item != null) { + ResultDTO resultDTO = new ResultDTO(); + BeanUtils.copyProperties(dto, resultDTO); + resultDTO.setRequestResults(new LinkedList() {{ + this.add(item); + }}); + this.send(dto.getReportId(), JsonUtils.toJSONString(resultDTO), kafkaConfig); + } + }); + } + } + + if (dto != null && StringUtils.equals(dto.getReportType(), RunModeConstants.SET_REPORT.name())) { + LoggerUtil.info("处理接口集合报告", dto.getReportId()); + } + } +} 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..e41b04a --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/BodyFile.java @@ -0,0 +1,12 @@ +package io.metersphere.api.service.utils; + +import lombok.Data; + +@Data +public class BodyFile { + private String id; + private String name; + private String storage; + // 调试附件处理 + private String refResourceId; +} diff --git a/src/main/java/io/metersphere/api/service/utils/BodyFileRequest.java b/src/main/java/io/metersphere/api/service/utils/BodyFileRequest.java new file mode 100644 index 0000000..d58b233 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/BodyFileRequest.java @@ -0,0 +1,13 @@ +package io.metersphere.api.service.utils; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +@Data +@AllArgsConstructor +public class BodyFileRequest { + private String reportId; + private List bodyFiles; +} diff --git a/src/main/java/io/metersphere/api/service/utils/JmxAttachmentFileUtil.java b/src/main/java/io/metersphere/api/service/utils/JmxAttachmentFileUtil.java new file mode 100644 index 0000000..c2861b0 --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/JmxAttachmentFileUtil.java @@ -0,0 +1,309 @@ +package io.metersphere.api.service.utils; + +import io.metersphere.api.enums.StorageConstants; +import io.metersphere.api.jmeter.utils.CommonBeanFactory; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.jmeter.utils.URLParserUtil; +import io.metersphere.api.repository.FileCenter; +import io.metersphere.dto.AttachmentBodyFile; +import io.metersphere.enums.JmxFileMetadataColumns; +import io.metersphere.utils.LoggerUtil; +import io.metersphere.utils.TemporaryFileUtil; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.CSVDataSet; +import org.apache.jmeter.config.KeystoreConfig; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.collections.HashTree; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JmxAttachmentFileUtil { + + private TemporaryFileUtil temporaryFileUtil; + + /** + * 解析JMX中的附件相关信息 + */ + public void parseJmxAttachmentFile(HashTree testPlan, String reportId, String platformUrl) { + if (temporaryFileUtil == null) { + temporaryFileUtil = CommonBeanFactory.getBean(TemporaryFileUtil.class); + } + /* + 1.解析jmx中需要的附件,并赋值为本地路径 + 2.获取jmx中附件对应的node节点附件路径 + 2.1 从本地获取 + 2.2 本地未找到的,在minio下载,然后缓存到本地 + 2.3 minio未找到的,在主工程下载。无法判断是否更新的文件不缓存 + */ + if (testPlan != null) { + List bodyFileList = parseAttachmentBodyFile(testPlan); + this.getAttachmentFile(bodyFileList, reportId, platformUrl); + } + } + + private List parseAttachmentBodyFile(HashTree jmxHashTree) { + List bodyFileList = new ArrayList<>(); + initAttachmentBodyFile(jmxHashTree, bodyFileList); + return bodyFileList; + } + + /** + * 获取附件对应的本地临时文件路径 + * 1 获取minio/git的文件。 (已下载过不会重复下载;未下载过的下载完之后会记录到执行文件夹内) + * 2 对于local(主工程硬盘)上面的,连接主工程下载并记录到执行文件夹内 + */ + private void getAttachmentFile(List bodyFileList, String reportId, String platformUrl) { + List downloadFromApiServer = new ArrayList<>(); + List downloadFromRepository = new ArrayList<>(); + //检查Local类型的文件在本地是否存在 + if (CollectionUtils.isNotEmpty(bodyFileList)) { + bodyFileList.forEach(bodyFile -> { + File executeFile; + if (StringUtils.isNotEmpty(bodyFile.getFileStorage()) && !StringUtils.equals(bodyFile.getFileStorage(), StorageConstants.LOCAL.name())) { + executeFile = temporaryFileUtil.getFile(bodyFile.getProjectId(), bodyFile.getFileMetadataId(), bodyFile.getFileUpdateTime(), bodyFile.getName(), bodyFile.getFileType()); + if (executeFile == null) { + LoggerUtil.info("本次执行[" + reportId + "]需要下载的[" + bodyFile.getFileStorage() + "]文件【" + bodyFile.getFileUpdateTime() + "_" + bodyFile.getName() + "】在当前机器节点未找到!"); + //区分MinIO下载还是api-server下载 + if (StringUtils.equals(bodyFile.getFileStorage(), StorageConstants.MINIO.name())) { + downloadFromRepository.add(bodyFile); + } else { + downloadFromApiServer.add(bodyFile); + } + //存在未找到的文件,还有一种可能是执行文件夹中存在过期文件。这时候执行一次删除判断。 + temporaryFileUtil.deleteOldFile(bodyFile.getProjectId(), bodyFile.getFileMetadataId(), bodyFile.getFileUpdateTime(), bodyFile.getName()); + } else { + LoggerUtil.info("本次执行[" + reportId + "]需要下载的[" + bodyFile.getFileStorage() + "]文件【" + bodyFile.getName() + "】在当前机器节点已找到,无需下载。"); + } + } else { + String filePath = this.substringBodyPath(bodyFile.getFilePath()); + executeFile = temporaryFileUtil.getLocalFile(filePath); + if (executeFile == null) { + LoggerUtil.info("本次执行[" + reportId + "]需要下载的[Local]文件【" + filePath + "】在当前机器节点未找到!"); + downloadFromApiServer.add(bodyFile); + } else { + LoggerUtil.info("本次执行[" + reportId + "]需要下载的[Local]文件【" + filePath + "】在当前机器节点已找到,无需下载。"); + } + } + }); + } + + //获取MinIO文件 + List downloadErrorList = FileCenter.downloadFiles(downloadFromRepository); + LoggerUtil.info("本次执行[" + reportId + "]在文件库中需要下载[" + downloadFromRepository.size() + "]个文件,已下载完毕。"); + if (CollectionUtils.isNotEmpty(downloadErrorList)) { + downloadFromApiServer.addAll(downloadErrorList); + } + //Minio没下载到的文件连接主工程下载 + for (AttachmentBodyFile minIOFile : downloadFromRepository) { + File executeFile = temporaryFileUtil.getFile(minIOFile.getProjectId(), minIOFile.getFileMetadataId(), minIOFile.getFileUpdateTime(), minIOFile.getName(), minIOFile.getFileType()); + if (executeFile == null) { + LoggerUtil.info("本次执行[" + reportId + "]需要下载的MinIO文件【" + minIOFile.getFileUpdateTime() + "_" + minIOFile.getName() + "】下载失败!需要连接主工程下载!"); + downloadFromApiServer.add(minIOFile); + } + } + + // API-TEST下载 + if (CollectionUtils.isNotEmpty(downloadFromApiServer)) { + try { + String uri = URLParserUtil.getDownFileURL(platformUrl); + List files = new ArrayList<>(); + downloadFromApiServer.forEach(attachmentBodyFile -> { + BodyFile bodyFile = new BodyFile(); + bodyFile.setRefResourceId(attachmentBodyFile.getFileMetadataId()); + bodyFile.setStorage(attachmentBodyFile.getFileStorage()); + if (StringUtils.isNotEmpty(attachmentBodyFile.getFilePath())) { + bodyFile.setName(attachmentBodyFile.getFilePath()); + } else { + bodyFile.setName(attachmentBodyFile.getName()); + } + files.add(bodyFile); + }); + BodyFileRequest request = new BodyFileRequest(reportId, files); + String downloadPath = temporaryFileUtil.fileFolder; + this.mkDir(downloadPath); + File bodyFile = ZipSpider.downloadFile(uri, request, downloadPath); + if (bodyFile != null) { + //解压文件直接到缓存目录中。 + ZipSpider.unzip(bodyFile.getPath(), downloadPath); + FileUtils.deleteFile(bodyFile.getPath()); + } + LoggerUtil.info("本次执行[" + reportId + "]连接主工程Metersphere需要下载[" + downloadFromApiServer.size() + "]个文件,下载完毕。"); + } catch (Exception e) { + LoggerUtil.error("连接[API-TEST]下载附件失败!", e); + } + } + } + + private void mkDir(String folderPath) { + File file = new File(folderPath); + if (!file.exists() || !file.isDirectory()) { + file.mkdirs(); + } + } + + private String substringBodyPath(String filePath) { + if (StringUtils.startsWith(filePath, FileUtils.BODY_FILE_DIR + "/")) { + filePath = StringUtils.substring(filePath, FileUtils.BODY_FILE_DIR.length() + 1); + } + return filePath; + } + + private void initAttachmentBodyFile(HashTree tree, List files) { + for (Object key : tree.keySet()) { + if (key == null) { + continue; + } + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy) { + dealWithHttp(key, files); + } else if (key instanceof CSVDataSet) { + dealWithCsv(key, files); + } else if (key instanceof KeystoreConfig) { + dealWithKeystoreConfig(key, files); + } + + if (node != null) { + initAttachmentBodyFile(node, files); + } + } + } + + public void dealWithKeystoreConfig(Object tree, List files) { + + if (tree != null) { + KeystoreConfig source = (KeystoreConfig) tree; + AttachmentBodyFile file = new AttachmentBodyFile(); + if (StringUtils.isNotBlank(source.getPropertyAsString(FileUtils.KEYSTORE_FILE_PATH))) { + String filePath = source.getPropertyAsString(FileUtils.KEYSTORE_FILE_PATH); + file.setFilePath(filePath); + } + + String localPath = temporaryFileUtil.generateLocalFilePath(this.substringBodyPath(file.getFilePath())); + //判断文本地件是否存在。如果存在则返回null。 文件库文件的本地校验在下载之前判断 + if (this.isFileExists(null, null, 0, this.substringBodyPath(file.getFilePath()))) { + file = null; + } + source.setProperty(FileUtils.KEYSTORE_FILE_PATH, localPath); + if (file != null) { + files.add(file); + } + } + } + + + private void dealWithHttp(Object key, List files) { + if (key == null) { + return; + } + HTTPSamplerProxy source = (HTTPSamplerProxy) key; + for (HTTPFileArg httpFileArg : source.getHTTPFiles()) { + AttachmentBodyFile file = getAttachmentBodyFileByTestElement(httpFileArg); + if (file != null) { + files.add(file); + } + } + } + + private void dealWithCsv(Object key, List files) { + CSVDataSet source = (CSVDataSet) key; + if (StringUtils.isNotEmpty(source.getPropertyAsString(FileUtils.FILENAME))) { + AttachmentBodyFile file = getAttachmentBodyFileByTestElement(source); + if (file != null) { + files.add(file); + } + } + } + + private AttachmentBodyFile getAttachmentBodyFileByTestElement(TestElement testElement) { + AttachmentBodyFile file = null; + if (testElement != null) { + + file = new AttachmentBodyFile(); + file.setId(testElement.getPropertyAsString(FileUtils.FILENAME)); + file.setName(testElement.getPropertyAsString(FileUtils.FILENAME)); + if (testElement.getPropertyAsBoolean(FileUtils.IS_REF)) { + file.setRef(true); + } + if (StringUtils.isNotBlank(testElement.getPropertyAsString(FileUtils.FILE_ID))) { + file.setFileMetadataId(testElement.getPropertyAsString(FileUtils.FILE_ID)); + } + if (StringUtils.isNotBlank(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_NAME.name()))) { + file.setName(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_NAME.name())); + } + if (StringUtils.isNotBlank(testElement.getPropertyAsString(FileUtils.FILE_PATH))) { + file.setFilePath(testElement.getPropertyAsString(FileUtils.FILE_PATH)); + } + if (testElement.getPropertyAsLong(JmxFileMetadataColumns.REF_FILE_UPDATE_TIME.name()) > 0) { + file.setFileUpdateTime(testElement.getPropertyAsLong(JmxFileMetadataColumns.REF_FILE_UPDATE_TIME.name())); + } + if (StringUtils.isNotBlank(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_STORAGE.name()))) { + file.setFileStorage(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_STORAGE.name())); + } + + if (StringUtils.isNotBlank(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_PROJECT_ID.name()))) { + file.setProjectId(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_PROJECT_ID.name())); + } + + if (StringUtils.isNotBlank(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_TYPE.name()))) { + file.setFileType(testElement.getPropertyAsString(JmxFileMetadataColumns.REF_FILE_TYPE.name())); + } + + String localPath; + if (StringUtils.isNotEmpty(file.getFileStorage()) && !StringUtils.equals(file.getFileStorage(), StorageConstants.LOCAL.name())) { + localPath = temporaryFileUtil.generateFilePath( + file.getProjectId(), + file.getFileMetadataId(), + file.getFileUpdateTime(), + file.getName(), + file.getFileType() + ); + } else { + String filePath = StringUtils.isBlank(file.getFilePath()) ? file.getName() : file.getFilePath(); + localPath = temporaryFileUtil.generateLocalFilePath(this.substringBodyPath(filePath)); + //判断文本地件是否存在。如果存在则返回null。 文件库文件的本地校验在下载之前判断 + if (this.isFileExists(null, null, 0, this.substringBodyPath(filePath))) { + file = null; + } + } + + if (StringUtils.isNotBlank(testElement.getPropertyAsString(FileUtils.FILE_PATH))) { + testElement.setProperty(FileUtils.FILE_PATH, localPath); + } else { + testElement.setProperty(FileUtils.FILENAME, localPath); + } + + if (testElement instanceof HTTPFileArg) { + ((HTTPFileArg) testElement).setPath(localPath); + } + } + return file; + } + + private boolean isFileExists(String folder, String fileMetadataId, long updateTime, String fileName) { + File localFile = temporaryFileUtil.getFile(folder, fileMetadataId, updateTime, fileName, null); + return localFile != null; + } + + public void deleteTmpFiles(String reportId) { + if (StringUtils.isNotEmpty(reportId)) { + String executeTmpFolder = StringUtils.join( + temporaryFileUtil.generateFileDir(null, null, 0), + File.separator, + "tmp", + File.separator, + reportId + ); + try { + FileUtils.deleteDir(executeTmpFolder); + } catch (Exception e) { + LoggerUtil.error("删除[" + reportId + "]执行中产生的临时文件失败!", e); + } + + } + } +} diff --git a/src/main/java/io/metersphere/api/service/utils/ZipSpider.java b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java new file mode 100644 index 0000000..b488c8f --- /dev/null +++ b/src/main/java/io/metersphere/api/service/utils/ZipSpider.java @@ -0,0 +1,309 @@ +package io.metersphere.api.service.utils; + +import io.metersphere.dto.ProjectJarConfig; +import io.metersphere.utils.JsonUtils; +import io.metersphere.utils.LoggerUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Consts; +import org.springframework.http.HttpMethod; + +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.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class ZipSpider { + + /** + * 覆盖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 { + } + + 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; + } + }; + + /** + * 信任所有 + */ + private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) { + SSLSocketFactory oldFactory = connection.getSSLSocketFactory(); + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + SSLSocketFactory newFactory = sc.getSocketFactory(); + connection.setSSLSocketFactory(newFactory); + } catch (Exception e) { + e.printStackTrace(); + } + return oldFactory; + } + + //解压本地文件至目的文件路径 + public static void unzip(String fromFile, String toFile) { + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fromFile)); BufferedInputStream bin = new BufferedInputStream(zin);) { + String parent = toFile; + ZipEntry entry; + while ((entry = zin.getNextEntry()) != null && !entry.isDirectory()) { + File fout = new File(parent, entry.getName()); + if (!fout.exists()) { + (new File(fout.getParent())).mkdirs(); + } + try (FileOutputStream out = new FileOutputStream(fout); + BufferedOutputStream bout = new BufferedOutputStream(out);) { + int b; + while ((b = bin.read()) != -1) { + bout.write(b); + } + LoggerUtil.info(fout + "解压成功"); + } catch (Exception e) { + LoggerUtil.error(e); + } + } + } catch (FileNotFoundException e) { + LoggerUtil.error(e); + } catch (IOException e) { + LoggerUtil.error(e); + } + } + + @SuppressWarnings("finally") + 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(); + boolean useHttps = urlPath.startsWith("https"); + if (useHttps) { + LoggerUtil.info("进入HTTPS协议处理方法"); + HttpsURLConnection https = (HttpsURLConnection) urlConnection; + trustAllHosts(https); + https.setHostnameVerifier(DO_NOT_VERIFY); + } + httpURLConnection = (HttpURLConnection) urlConnection;// http的连接类 + httpURLConnection.setConnectTimeout(1000 * 5);//设置超时 + httpURLConnection.setRequestMethod("GET");//设置请求方式,默认是GET + httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码 + httpURLConnection.connect();// 打开连接 + + bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + if (StringUtils.isEmpty(fileName)) { + LoggerUtil.info(urlPath + " 下载的文件名称为空", fileName); + fileName = ""; + } else { + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + } + String path = StringUtils.join(downloadDir, File.separatorChar, fileName);// 指定存放位置 + File file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (file != null && file.getParentFile() != null && !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(); + LoggerUtil.info("文件下载成功!"); + return file; + } catch (MalformedURLException e) { + LoggerUtil.error(e); + } catch (IOException e) { + LoggerUtil.error(e); + LoggerUtil.info("文件下载失败!"); + } finally { + try { + if (bin != null) { + bin.close(); + } + if (out != null) { + out.close(); + } + if (httpURLConnection != null) { + httpURLConnection.disconnect(); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } + return null; + } + + public static File downloadJarHistory(String urlPath, List request, String downloadDir) { + byte[] t = JsonUtils.toJSONString(request).getBytes(Consts.UTF_8); + File file = getFile(urlPath, downloadDir, t); + if (file != null) return file; + return null; + } + + public static File downloadJarDb(String urlPath, Map> request, String downloadDir) { + byte[] t = JsonUtils.toJSONString(request).getBytes(Consts.UTF_8); + File file = getFile(urlPath, downloadDir, t); + if (file != null) return file; + return null; + } + + private static File getFile(String urlPath, String downloadDir, byte[] t) { + OutputStream out = null; + BufferedInputStream bin = null; + HttpURLConnection httpURLConnection = null; + try { + URL url = new URL(urlPath); + 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); + } + httpURLConnection = (HttpURLConnection) urlConnection; + httpURLConnection.setRequestMethod(HttpMethod.POST.name()); + httpURLConnection.setDoOutput(true); + httpURLConnection.setDoInput(true); + httpURLConnection.setUseCaches(false);//POST请求不能使用缓存(POST不能被缓存) + httpURLConnection.setRequestProperty("Connection", "Keep-Alive"); + httpURLConnection.setInstanceFollowRedirects(true);//设置只作用于当前的实例 + httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + httpURLConnection.setConnectTimeout(20 * 1000);//设置连接主机超时(单位:毫秒) + httpURLConnection.setReadTimeout(20 * 1000);//设置从主机读取数据超时(单位:毫秒) + httpURLConnection.connect(); + + OutputStream outputStream = httpURLConnection.getOutputStream(); + outputStream.write(t); + outputStream.flush(); + outputStream.close(); + + bin = new BufferedInputStream(httpURLConnection.getInputStream()); + String fileName = httpURLConnection.getHeaderField("Content-Disposition"); + if (StringUtils.isEmpty(fileName)) { + LoggerUtil.info(urlPath + " 下载的文件名称为空", fileName); + fileName = ""; + } else { + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), "UTF-8"); + } + String path = StringUtils.join(downloadDir, File.separatorChar, fileName);// 指定存放位置 + File file = new File(path); + // 校验文件夹目录是否存在,不存在就创建一个目录 + if (file != null && file.getParentFile() != null && !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(); + LoggerUtil.info("文件下载成功!"); + return file; + } catch (MalformedURLException e) { + LoggerUtil.error(e); + } catch (IOException e) { + LoggerUtil.error(e); + LoggerUtil.info("文件下载失败!"); + } finally { + try { + if (bin != null) { + bin.close(); + } + if (out != null) { + out.close(); + } + if (httpURLConnection != null) { + httpURLConnection.disconnect(); + } + } catch (Exception e) { + LoggerUtil.error(e); + } + } + return null; + } + + public static File downloadFile(String urlPath, BodyFileRequest request, String downloadDir) { + File file = null; + try { + URL url = new URL(urlPath);//创建连接 + 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); + } + HttpURLConnection connection = (HttpURLConnection) urlConnection; + connection.setRequestMethod(HttpMethod.POST.name()); + connection.setDoOutput(true); + connection.setDoInput(true); + connection.setUseCaches(false);//POST请求不能使用缓存(POST不能被缓存) + connection.setRequestProperty("Connection", "Keep-Alive"); + connection.setInstanceFollowRedirects(true);//设置只作用于当前的实例 + connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + connection.setConnectTimeout(20 * 1000);//设置连接主机超时(单位:毫秒) + connection.setReadTimeout(20 * 1000);//设置从主机读取数据超时(单位:毫秒) + connection.connect(); + + OutputStream outputStream = connection.getOutputStream(); + byte[] t = JsonUtils.toJSONString(request).getBytes(Consts.UTF_8); + outputStream.write(t); + outputStream.flush(); + outputStream.close(); + // 写文件 + String fileName = connection.getHeaderField("Content-Disposition"); + fileName = URLDecoder.decode(fileName.substring(fileName.indexOf("filename") + 10, fileName.length() - 1), Consts.UTF_8); + String path = downloadDir + File.separatorChar + fileName;// 指定存放位置 + file = new File(path); + OutputStream out = new FileOutputStream(file); + BufferedInputStream bin = new BufferedInputStream(connection.getInputStream()); + int size = 0; + byte[] b = new byte[2048]; + //把输入流的文件读取到字节数据b中,然后输出到指定目录的文件 + while ((size = bin.read(b)) != -1) { + out.write(b, 0, size); + } + out.close(); + bin.close(); + } catch (Exception e) { + LoggerUtil.error(e); + } + return file; + } +} diff --git a/src/main/java/io/metersphere/api/vo/ApiScenarioReportVo.java b/src/main/java/io/metersphere/api/vo/ApiScenarioReportVo.java new file mode 100644 index 0000000..c2632a2 --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/ApiScenarioReportVo.java @@ -0,0 +1,16 @@ +package io.metersphere.api.vo; + +import lombok.Data; + +@Data +public class ApiScenarioReportVo { + private String reportId; + private String resourceId; + private String status; + private String errorCode; + + public ApiScenarioReportVo(String reportId, String resourceId) { + this.reportId = reportId; + this.resourceId = resourceId; + } +} diff --git a/src/main/java/io/metersphere/api/vo/ExecuteResultEnum.java b/src/main/java/io/metersphere/api/vo/ExecuteResultEnum.java new file mode 100644 index 0000000..a9b4e27 --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/ExecuteResultEnum.java @@ -0,0 +1,33 @@ +package io.metersphere.api.vo; + +public enum ExecuteResultEnum { + //误报状态 + ERROR_REPORT_RESULT("errorReportResult"), ERROR_REPORT("errorReport"), + //停止状态 + STOP("STOP"), + //接口执行状态(兼容旧数据) + API_SUCCESS("success"), API_ERROR("error"), + //未执行状态 + UN_EXECUTE("unexecute"), + //场景执行状态(兼容旧数据) + SCENARIO_SUCCESS("Success"), SCENARIO_ERROR("Error"), + //测试计划执行状态(兼容旧数据) + TEST_PLAN_PREPARE("PREPARE"), TEST_PLAN_RUNNING("RUNNING"), + + FAIL("fail"); + + private String value; + + ExecuteResultEnum(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/io/metersphere/api/vo/JvmInfo.java b/src/main/java/io/metersphere/api/vo/JvmInfo.java new file mode 100644 index 0000000..31668ae --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/JvmInfo.java @@ -0,0 +1,45 @@ +package io.metersphere.api.vo; + +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; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("当前JVM内存信息:").append("\n"); + builder.append("当前JVM最大内存:" + this.vmMax + " M").append("\n"); + builder.append("当前JVM占用的总内存:" + this.vmTotal + " M").append("\n"); + builder.append("当前JVM空闲内存为:" + this.vmFree + " M").append("\n"); + builder.append("当前JVM已用内存为:" + this.vmUse + " M").append("\n"); + return builder.toString(); + } +} diff --git a/src/main/java/io/metersphere/api/vo/RemoteFileAttachInfo.java b/src/main/java/io/metersphere/api/vo/RemoteFileAttachInfo.java new file mode 100644 index 0000000..fcb2e4a --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/RemoteFileAttachInfo.java @@ -0,0 +1,23 @@ +package io.metersphere.api.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class RemoteFileAttachInfo implements Serializable { + private String repositoryPath; + private String userName; + private String token; + private String branch; + private String commitId; + private String filePath; + private String commitMessage; + private long size; +} diff --git a/src/main/java/io/metersphere/api/vo/RepositoryRequest.java b/src/main/java/io/metersphere/api/vo/RepositoryRequest.java new file mode 100644 index 0000000..457b2de --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/RepositoryRequest.java @@ -0,0 +1,13 @@ +package io.metersphere.api.vo; + +import lombok.Data; + +@Data +public class RepositoryRequest { + private String fileMetadataId; + private String filePath; + private String commitId; + private String projectId; + private long updateTime; + private String fileName; +} diff --git a/src/main/java/io/metersphere/api/vo/ResultVO.java b/src/main/java/io/metersphere/api/vo/ResultVO.java new file mode 100644 index 0000000..4b16b09 --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/ResultVO.java @@ -0,0 +1,16 @@ +package io.metersphere.api.vo; + +import lombok.Data; + +import java.text.DecimalFormat; + +@Data +public class ResultVO { + private String status; + private long scenarioSuccess; + private int scenarioTotal; + +} + + + diff --git a/src/main/java/io/metersphere/api/vo/ScenarioStatusEnum.java b/src/main/java/io/metersphere/api/vo/ScenarioStatusEnum.java new file mode 100644 index 0000000..cccf07c --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/ScenarioStatusEnum.java @@ -0,0 +1,5 @@ +package io.metersphere.api.vo; + +public enum ScenarioStatusEnum { + Saved, Success, Error, Timeout, Fail, Trash, Underway +} diff --git a/src/main/java/io/metersphere/api/vo/ScriptData.java b/src/main/java/io/metersphere/api/vo/ScriptData.java new file mode 100644 index 0000000..44377d2 --- /dev/null +++ b/src/main/java/io/metersphere/api/vo/ScriptData.java @@ -0,0 +1,8 @@ +package io.metersphere.api.vo; + +import lombok.Data; + +@Data +public class ScriptData { + private String data; +} diff --git a/src/main/java/io/metersphere/config/AppStartListener.java b/src/main/java/io/metersphere/config/AppStartListener.java new file mode 100644 index 0000000..db89ee5 --- /dev/null +++ b/src/main/java/io/metersphere/config/AppStartListener.java @@ -0,0 +1,57 @@ +package io.metersphere.config; + +import io.metersphere.api.jmeter.JMeterService; +import io.metersphere.api.jmeter.utils.FileUtils; +import io.metersphere.api.service.DownloadPluginJarService; +import io.metersphere.jmeter.ProjectClassLoader; +import io.metersphere.utils.LoggerUtil; +import jakarta.annotation.Resource; +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; + +@Component +public class AppStartListener implements ApplicationListener { + + @Resource + private JMeterService jMeterService; + @Resource + private DownloadPluginJarService downloadPluginJarService; + + @Value("${jmeter.home}") + private String jmeterHome; + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + System.out.println("================= NODE 应用启动 START ================="); + LoggerUtil.info("jmeter.home", jmeterHome); + initPythonEnv(); + + ProjectClassLoader.initClassLoader(); + + LoggerUtil.info("init plugin", FileUtils.JAR_PLUG_FILE_DIR); + downloadPluginJarService.loadPlugJar(FileUtils.JAR_PLUG_FILE_DIR); + } + + /** + * 解决接口测试-无法导入内置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(); + LoggerUtil.error(e.getMessage(), e); + } + } +} diff --git a/src/main/java/io/metersphere/config/JettyFilter.java b/src/main/java/io/metersphere/config/JettyFilter.java new file mode 100644 index 0000000..5a078d9 --- /dev/null +++ b/src/main/java/io/metersphere/config/JettyFilter.java @@ -0,0 +1,25 @@ +package io.metersphere.config; + + +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +@WebFilter(urlPatterns = "/*", filterName = "jettyFilter") +public class JettyFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + if ("TRACE".equalsIgnoreCase(httpRequest.getMethod()) || "TRACK".equalsIgnoreCase(httpRequest.getMethod())) { + httpResponse.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/io/metersphere/config/KafkaConfig.java b/src/main/java/io/metersphere/config/KafkaConfig.java new file mode 100644 index 0000000..fff32ad --- /dev/null +++ b/src/main/java/io/metersphere/config/KafkaConfig.java @@ -0,0 +1,6 @@ +package io.metersphere.config; + +public class KafkaConfig { + //执行结果回传 + public static final String TOPICS = "ms-api-exec-topic"; +} diff --git a/src/main/java/io/metersphere/config/UtilConfig.java b/src/main/java/io/metersphere/config/UtilConfig.java new file mode 100644 index 0000000..ef858a0 --- /dev/null +++ b/src/main/java/io/metersphere/config/UtilConfig.java @@ -0,0 +1,26 @@ +package io.metersphere.config; + +import io.metersphere.api.service.utils.JmxAttachmentFileUtil; +import io.metersphere.utils.LocalPathUtil; +import io.metersphere.utils.TemporaryFileUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class UtilConfig { + + static { + LocalPathUtil.JAR_PATH += LocalPathUtil.NODE; + LocalPathUtil.PLUGIN_PATH += LocalPathUtil.NODE; + } + + @Bean + public TemporaryFileUtil temporaryFileUtil() { + return new TemporaryFileUtil(TemporaryFileUtil.NODE_FILE_FOLDER); + } + + @Bean + public JmxAttachmentFileUtil jmxAttachmentFileUtil() { + return new JmxAttachmentFileUtil(); + } +} diff --git a/src/main/java/io/metersphere/config/WebConfig.java b/src/main/java/io/metersphere/config/WebConfig.java new file mode 100644 index 0000000..613852e --- /dev/null +++ b/src/main/java/io/metersphere/config/WebConfig.java @@ -0,0 +1,117 @@ +package io.metersphere.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.metersphere.utils.LoggerUtil; +import org.apache.hc.client5.http.ConnectTimeoutException; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.socket.ConnectionSocketFactory; +import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.NoHttpResponseException; +import org.apache.hc.core5.http.config.Registry; +import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.http.protocol.HttpContext; +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 java.io.IOException; +import java.net.ConnectException; +import java.net.SocketException; +import java.util.concurrent.TimeUnit; + +@Configuration +public class WebConfig { + private final static int RETRY_COUNT = 3; + private final static long RETRY_INTERVAL_TIME = 1000L; + private final static int MAX_TOTAL = 1000; + private final static int MAX_PER_ROUTE = 500; + private final static int CONN_REQUEST_TIMEOUT = 5000; + private final static int CONNECT_TIMEOUT = 8000; + private final static int SOCKET_TIMEOUT = 20 * 1000; + + @Bean + public RestTemplate restTemplate() { + return setTemplate(); + } + + @Bean + public HttpClient httpClient() { + Registry registry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", SSLConnectionSocketFactory.getSocketFactory()) + .build(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); + //设置整个连接池最大连接数 + connectionManager.setMaxTotal(MAX_TOTAL); + //路由是对maxTotal的细分 + connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE); + //服务器返回数据(response)的时间,超过该时间抛出read timeout + connectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS).build()); + + + RequestConfig requestConfig = RequestConfig.custom() + //连接上服务器(握手成功)的时间,超出该时间抛出connect timeout + .setConnectTimeout(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) + //从连接池中获取连接的超时时间,超过该时间未拿到可用连接,Timeout waiting for connection from pool + .setConnectionRequestTimeout(CONN_REQUEST_TIMEOUT, TimeUnit.MILLISECONDS) + .build(); + // 重试次数 + HttpClientBuilder client = getHttpClientBuilder(); + client.setDefaultRequestConfig(requestConfig); + // 配置连接池 + client.setConnectionManager(connectionManager); + return client.build(); + } + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + private RestTemplate setTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient())); + return restTemplate; + } + + private HttpClientBuilder getHttpClientBuilder() { + HttpClientBuilder httpClientBuilder = HttpClients.custom(); + // 只有io异常才会触发重试 + httpClientBuilder.setRetryStrategy(new DefaultHttpRequestRetryStrategy() { + @Override + public boolean retryRequest(HttpRequest request, IOException exception, int execCount, HttpContext context) { + // curRetryCount 每一次都会递增,从1开始 + if (execCount > RETRY_COUNT) return false; + try { + //重试延迟 + Thread.sleep(execCount * RETRY_INTERVAL_TIME); + } catch (InterruptedException e) { + e.printStackTrace(); + LoggerUtil.error(e); + } + if (exception instanceof ConnectTimeoutException || + exception instanceof NoHttpResponseException || + exception instanceof ConnectException || + exception instanceof SocketException) { + LoggerUtil.info("重试次数: " + execCount); + return true; + } + return false; + } + }); + return httpClientBuilder; + } + +} diff --git a/src/main/java/io/metersphere/controller/request/TestRequest.java b/src/main/java/io/metersphere/controller/request/TestRequest.java deleted file mode 100644 index f790f43..0000000 --- a/src/main/java/io/metersphere/controller/request/TestRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.metersphere.controller.request; - -import lombok.Data; - -import java.util.HashMap; -import java.util.Map; - -@Data -public class TestRequest extends DockerLoginRequest { - - private int size; - private String fileString; - private String testId; - private String image; - private Map testData = new HashMap<>(); - private Map env = new HashMap<>(); -} 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 51% rename from src/main/java/io/metersphere/controller/JmeterOperateController.java rename to src/main/java/io/metersphere/node/controller/JmeterOperateController.java index 7c8d24c..08770c9 100644 --- a/src/main/java/io/metersphere/controller/JmeterOperateController.java +++ b/src/main/java/io/metersphere/node/controller/JmeterOperateController.java @@ -1,13 +1,12 @@ -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; -import java.io.IOException; +import jakarta.annotation.Resource; import java.util.List; @RestController @@ -20,32 +19,18 @@ public class JmeterOperateController { * 初始化测试任务,根据需求启动若干个 JMeter Engine 容器 */ @PostMapping("/container/start") - public void startContainer(@RequestBody TestRequest testRequest) throws IOException { + public String startContainer(@RequestBody TestRequest testRequest) { 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..848d841 --- /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 jakarta.servlet.http.HttpServletRequest; +import jakarta.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..368b085 --- /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.utils.LoggerUtil; +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) { + LoggerUtil.error(e); + return o; + } + } + return ResultHolder.success(o); + } + return o; + } + +} 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 53% rename from src/main/java/io/metersphere/controller/request/DockerLoginRequest.java rename to src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java index fa01205..52c854b 100644 --- a/src/main/java/io/metersphere/controller/request/DockerLoginRequest.java +++ b/src/main/java/io/metersphere/node/controller/request/DockerLoginRequest.java @@ -1,8 +1,10 @@ -package io.metersphere.controller.request; +package io.metersphere.node.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/node/controller/request/TestRequest.java b/src/main/java/io/metersphere/node/controller/request/TestRequest.java new file mode 100644 index 0000000..ccb1729 --- /dev/null +++ b/src/main/java/io/metersphere/node/controller/request/TestRequest.java @@ -0,0 +1,14 @@ +package io.metersphere.node.controller.request; + +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Setter +public class TestRequest extends DockerLoginRequest { + private String image; + 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 new file mode 100644 index 0000000..a7fdf70 --- /dev/null +++ b/src/main/java/io/metersphere/node/service/JmeterOperateService.java @@ -0,0 +1,278 @@ +package io.metersphere.node.service; + +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.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.utils.LoggerUtil; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import jakarta.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; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.zip.ZipOutputStream; + +@Service +public class JmeterOperateService { + @Resource + private KafkaProducerService kafkaProducerService; + @Resource + private DockerClientService dockerClientService; + + public void startContainer(TestRequest testRequest) { + Map env = testRequest.getEnv(); + String testId = env.get("TEST_ID"); + LoggerUtil.info("Receive start container request, test id: " + testId); + String bootstrapServers = env.get("BOOTSTRAP_SERVERS"); + // 检查kafka连通性 + checkKafka(bootstrapServers); + // 初始化kafka + kafkaProducerService.init(bootstrapServers); + + DockerClient dockerClient = dockerClientService.connectDocker(); + + String containerImage = testRequest.getImage(); + + // 查找镜像 + searchImage(dockerClient, testRequest.getImage()); + // 检查容器是否存在 + checkContainerExists(dockerClient, testId); + // 启动测试 + startContainer(testRequest, dockerClient, testId, containerImage); + } + + private void startContainer(TestRequest testRequest, DockerClient dockerClient, String testId, String containerImage) { + String topic = testRequest.getEnv().getOrDefault("LOG_TOPIC", "JMETER_LOGS"); + String reportId = testRequest.getEnv().get("REPORT_ID"); + + + String testContainerId = dockerClientService.createContainers(dockerClient, testRequest, testId, containerImage).getId(); + dockerClientService.startContainer(dockerClient, testContainerId); + LoggerUtil.info("Container create started testContainerId: " + testContainerId); + + String reportContainerId = dockerClientService.createReportContainers(dockerClient, testRequest, testId, containerImage).getId(); + dockerClientService.startContainer(dockerClient, reportContainerId); + LoggerUtil.info("Container create started reportContainerId: " + reportContainerId); + + + removeContainer(dockerClient, testContainerId); + + removeContainer(dockerClient, reportContainerId); + + dockerClient.logContainerCmd(testContainerId) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + String log = new String(item.getPayload()).trim(); + String oomMessage = "There is insufficient memory for the Java Runtime Environment to continue."; + String oomMessage2 = "java.lang.OutOfMemoryError"; + if (StringUtils.contains(log, oomMessage) || StringUtils.contains(log, oomMessage2)) { + LoggerUtil.info("handle out of memory error."); + // oom 退出 + String[] contents = new String[]{reportId, "none", "0", oomMessage}; + String message = StringUtils.join(contents, " "); + kafkaProducerService.sendMessage(topic, message); + stopContainer(testId); + } + LoggerUtil.info(log); + } + }); + } + + private void removeContainer(DockerClient dockerClient, String containerId) { + dockerClient.waitContainerCmd(containerId) + .exec(new WaitContainerResultCallback() { + @Override + public void onComplete() { + // 清理文件夹 + try { + if (dockerClientService.existContainer(dockerClient, containerId) > 0) { +// copyTestResources(dockerClient, testContainerId, reportId, resourceIndex); + dockerClientService.removeContainer(dockerClient, containerId); + } + LoggerUtil.info("Remove container completed: " + containerId); + } catch (Exception e) { + LoggerUtil.error("Remove container error: ", e); + } + LoggerUtil.info("completed...."); + } + }); + } + + 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 + "_" + resourceIndex + ".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) + .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) { + 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]); + try ( + Socket soc = new Socket() + ) { + soc.connect(new InetSocketAddress(ip, port), 1000); // 1s timeout + } + } + } catch (Exception e) { + LoggerUtil.error(e); + throw new RuntimeException("Failed to connect to Kafka"); + } + } + + + private void searchImage(DockerClient dockerClient, String imageName) { + // image + List imageList = dockerClient.listImagesCmd().exec(); + if (CollectionUtils.isEmpty(imageList)) { + throw new RuntimeException("Image List is empty"); + } + List collect = imageList.stream().filter(image -> { + String[] repoTags = image.getRepoTags(); + if (repoTags == null) { + return false; + } + for (String repoTag : repoTags) { + if (repoTag.equals(imageName)) { + return true; + } + } + return false; + }).collect(Collectors.toList()); + + if (collect.size() == 0) { + throw new RuntimeException("Image Not Found: " + imageName); + } + } + + + public void stopContainer(String testId) { + LoggerUtil.info("Receive stop container request, test: " + testId); + DockerClient dockerClient = dockerClientService.connectDocker(); + + // container filter + List list = dockerClient.listContainersCmd() + .withShowAll(true) + .withStatusFilter(Collections.singletonList("running")) + .withNameFilter(Collections.singletonList(testId)) + .exec(); + // container stop + list.forEach(container -> dockerClientService.removeContainer(dockerClient, container.getId())); + } + + public List taskStatus(String testId) { + DockerClient dockerClient = dockerClientService.connectDocker(); + List containerList = dockerClient.listContainersCmd() + .withStatusFilter(Arrays.asList("created", "restarting", "running", "paused", "exited")) + .withNameFilter(Collections.singletonList(testId)) + .exec(); + // 查询执行的状态 + return containerList; + } + + public String logContainer(String testId) { + LoggerUtil.info("Receive logs container request, test: " + testId); + DockerClient dockerClient = dockerClientService.connectDocker(); + + // container filter + List list = dockerClient.listContainersCmd() + .withShowAll(true) + .withStatusFilter(Collections.singletonList("running")) + .withNameFilter(Collections.singletonList(testId)) + .exec(); + + StringBuilder sb = new StringBuilder(); + if (list.size() > 0) { + try { + dockerClient.logContainerCmd(list.get(0).getId()) + .withFollowStream(true) + .withStdOut(true) + .withStdErr(true) + .withTailAll() + .exec(new InvocationBuilder.AsyncResultCallback() { + @Override + public void onNext(Frame item) { + sb.append(new String(item.getPayload()).trim()).append("\n"); + } + }).awaitCompletion(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LoggerUtil.error(e); + } + } + return sb.toString(); + } +} 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..21f7209 --- /dev/null +++ b/src/main/java/io/metersphere/node/service/KafkaProducerService.java @@ -0,0 +1,32 @@ +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 org.springframework.stereotype.Service; + +import java.util.Properties; + +@Service +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); + } +} 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..e847793 --- /dev/null +++ b/src/main/java/io/metersphere/node/util/CompressUtils.java @@ -0,0 +1,155 @@ +package io.metersphere.node.util; + +import io.metersphere.utils.LoggerUtil; + +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) { + LoggerUtil.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) { + LoggerUtil.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(); + } + } + } + + +} diff --git a/src/main/java/io/metersphere/node/util/DockerClientService.java b/src/main/java/io/metersphere/node/util/DockerClientService.java new file mode 100644 index 0000000..a07f491 --- /dev/null +++ b/src/main/java/io/metersphere/node/util/DockerClientService.java @@ -0,0 +1,157 @@ +package io.metersphere.node.util; + +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.Volume; +import com.github.dockerjava.api.model.VolumesFrom; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.DockerClientImpl; +import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; +import com.github.dockerjava.transport.DockerHttpClient; +import io.metersphere.node.controller.request.TestRequest; +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +@Service +public class DockerClientService { + + @Value("${jmeter.cpu.set:}") + private String jmeterCores; + @Value("${report.cpu.set:}") + private String reportCores; + @Value("${split.file.size:500000}") + private long splitFileSize; + + @PostConstruct + public void init() { + if (StringUtils.isBlank(jmeterCores) || StringUtils.isBlank(jmeterCores)) { + int cores = Runtime.getRuntime().availableProcessors(); + if (cores >= 4) { + jmeterCores = "1-" + (cores - 1); + reportCores = "0"; + } + if (cores >= 8) { + jmeterCores = "2-" + (cores - 1); + reportCores = "0-1"; + } + } + } + + /** + * 连接docker服务器 + * + * @return + */ + public DockerClient connectDocker() { + DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); + DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder() + .dockerHost(config.getDockerHost()) + .sslConfig(config.getSSLConfig()) + .maxConnections(100) + .connectionTimeout(Duration.ofSeconds(30)) + .responseTimeout(Duration.ofSeconds(45)) + .build(); + return DockerClientImpl.getInstance(config, httpClient); + } + + /** + * 创建容器 + * + * @param client + * @return + */ + public CreateContainerResponse createContainers(DockerClient client, TestRequest request, String testId, String imageName) { + + // 创建 hostConfig + HostConfig hostConfig = HostConfig.newHostConfig() + .withNetworkMode("host") + .withCpusetCpus(jmeterCores); + + CreateContainerResponse container = client.createContainerCmd(imageName) + .withName(testId) + .withHostConfig(hostConfig) + .withEnv(getEnvs(request)) + .withVolumes(new Volume("/test"), new Volume("/jmeter-log")) + .exec(); + return container; + } + + public CreateContainerResponse createReportContainers(DockerClient client, TestRequest request, String testId, String imageName) { + // 创建 hostConfig + HostConfig hostConfig = HostConfig.newHostConfig() + .withNetworkMode("host") + .withCpusetCpus(reportCores) + .withVolumesFrom(new VolumesFrom(testId)); + CreateContainerResponse container = client.createContainerCmd(imageName) + .withName(testId + "_report") + .withHostConfig(hostConfig) + .withEntrypoint("/bin/sh", "-c", "/generate-report.sh") + .withEnv(getEnvs(request)) + .exec(); + return container; + } + + + /** + * 启动容器 + * + * @param client + * @param containerId + */ + public void startContainer(DockerClient client, String containerId) { + client.startContainerCmd(containerId).exec(); + } + + /** + * 停止容器 + * + * @param client + * @param containerId + */ + public void stopContainer(DockerClient client, String containerId) { + client.stopContainerCmd(containerId).exec(); + } + + /** + * 删除容器 + * + * @param client + * @param containerId + */ + public void removeContainer(DockerClient client, String containerId) { + client.removeContainerCmd(containerId) + .withForce(true) + .withRemoveVolumes(true) + .exec(); + } + + /** + * 容器是否存在 + * + * @param client + * @param containerId + */ + public int existContainer(DockerClient client, String containerId) { + List list = client.listContainersCmd() + .withShowAll(true) + .withIdFilter(Collections.singleton(containerId)) + .exec(); + return list.size(); + } + + private String[] getEnvs(TestRequest testRequest) { + Map env = testRequest.getEnv(); + env.put("SPLIT_FILE_SIZE", String.valueOf(splitFileSize)); + return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); + } +} diff --git a/src/main/java/io/metersphere/service/JmeterOperateService.java b/src/main/java/io/metersphere/service/JmeterOperateService.java deleted file mode 100644 index 743a623..0000000 --- a/src/main/java/io/metersphere/service/JmeterOperateService.java +++ /dev/null @@ -1,176 +0,0 @@ -package io.metersphere.service; - -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.core.InvocationBuilder; -import io.metersphere.controller.request.TestRequest; -import io.metersphere.util.DockerClientService; -import io.metersphere.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 java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -@Service -public class JmeterOperateService { - - 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(); - String filePath = StringUtils.join(new String[]{"", "opt", "node-data", 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); - } - } - - // 查找镜像 - searchImage(dockerClient, testRequest.getImage()); - - 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); - } - - 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."); - DockerClientService.removeContainer(dockerClient, containerId); - LogUtil.info("Remove container completed: " + containerId); - } catch (IOException e) { - LogUtil.error("Remove dir error: ", e); - } - LogUtil.info("completed...."); - } - }); - }); - } - - private String[] getEnvs(TestRequest testRequest) { - Map env = testRequest.getEnv(); - return env.keySet().stream().map(k -> k + "=" + env.get(k)).toArray(String[]::new); - } - - private void searchImage(DockerClient dockerClient, String imageName) { - // image - List imageList = dockerClient.listImagesCmd().exec(); - if (CollectionUtils.isEmpty(imageList)) { - throw new RuntimeException("Image List is empty"); - } - List collect = imageList.stream().filter(image -> { - String[] repoTags = image.getRepoTags(); - if (repoTags == null) { - return false; - } - for (String repoTag : repoTags) { - if (repoTag.equals(imageName)) { - return true; - } - } - return false; - }).collect(Collectors.toList()); - - if (collect.size() == 0) { - throw new RuntimeException("Image Not Found."); - } - } - - - public void stopContainer(String testId) { - LogUtil.info("Receive stop container request, test: {}", testId); - DockerClient dockerClient = DockerClientService.connectDocker(); - - // container filter - List list = dockerClient.listContainersCmd() - .withShowAll(true) - .withStatusFilter(Collections.singletonList("running")) - .withNameFilter(Collections.singletonList(testId)) - .exec(); - // container stop - list.forEach(container -> DockerClientService.stopContainer(dockerClient, container.getId())); - } - - public List taskStatus(String testId) { - DockerClient dockerClient = DockerClientService.connectDocker(); - List containerList = dockerClient.listContainersCmd() - .withStatusFilter(Arrays.asList("created", "restarting", "running", "paused", "exited")) - .withNameFilter(Collections.singletonList(testId)) - .exec(); - // 查询执行的状态 - return containerList; - } - - public String logContainer(String testId) { - LogUtil.info("Receive logs container request, test: {}", testId); - DockerClient dockerClient = DockerClientService.connectDocker(); - - // container filter - List list = dockerClient.listContainersCmd() - .withShowAll(true) - .withStatusFilter(Collections.singletonList("running")) - .withNameFilter(Collections.singletonList(testId)) - .exec(); - - StringBuilder sb = new StringBuilder(); - if (list.size() > 0) { - try { - dockerClient.logContainerCmd(list.get(0).getId()) - .withFollowStream(true) - .withStdOut(true) - .withStdErr(true) - .withTailAll() - .exec(new InvocationBuilder.AsyncResultCallback() { - @Override - public void onNext(Frame item) { - sb.append(new String(item.getPayload()).trim()).append("\n"); - } - }).awaitCompletion(100, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - LogUtil.error(e); - } - } - return sb.toString(); - } -} diff --git a/src/main/java/io/metersphere/util/DockerClientService.java b/src/main/java/io/metersphere/util/DockerClientService.java deleted file mode 100644 index 7222232..0000000 --- a/src/main/java/io/metersphere/util/DockerClientService.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.metersphere.util; - -import com.github.dockerjava.api.DockerClient; -import com.github.dockerjava.api.command.CreateContainerResponse; -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; -import org.apache.commons.lang.StringUtils; - -public class DockerClientService { - - /** - * 连接docker服务器 - * - * @return - */ - public static DockerClient connectDocker() { - DockerClient dockerClient = DockerClientBuilder.getInstance().build(); - Info info = dockerClient.infoCmd().exec(); - LogUtil.info("docker的环境信息如下:================="); - LogUtil.info(info); - return dockerClient; - } - - public static DockerClient connectDocker(DockerLoginRequest request) { - if (StringUtils.isBlank(request.getRegistry()) || StringUtils.isBlank(request.getUsername()) || StringUtils.isBlank(request.getPassword())) { - return connectDocker(); - } - DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder() - .withRegistryUrl(request.getRegistry()) - .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; - } - - /** - * 创建容器 - * - * @param client - * @return - */ - public static CreateContainerResponse createContainers(DockerClient client, String containerName, String imageName, HostConfig hostConfig, String... env) { - CreateContainerResponse container = client.createContainerCmd(imageName) - .withName(containerName) - .withHostConfig(hostConfig) - .withEnv(env) - .exec(); - return container; - } - - - /** - * 启动容器 - * - * @param client - * @param containerId - */ - public static void startContainer(DockerClient client, String containerId) { - client.startContainerCmd(containerId).exec(); - } - - /** - * 停止容器 - * - * @param client - * @param containerId - */ - public static void stopContainer(DockerClient client, String containerId) { - client.stopContainerCmd(containerId).exec(); - } - - /** - * 删除容器 - * - * @param client - * @param containerId - */ - public static void removeContainer(DockerClient client, String containerId) { - client.removeContainerCmd(containerId).exec(); - } - -} diff --git a/src/main/java/io/metersphere/util/LogUtil.java b/src/main/java/io/metersphere/util/LogUtil.java deleted file mode 100644 index c955137..0000000 --- a/src/main/java/io/metersphere/util/LogUtil.java +++ /dev/null @@ -1,261 +0,0 @@ -package io.metersphere.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/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 6c51bae..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.application.name=node-controller -logging.file.path=/opt/fit2cloud/logs/${spring.application.name} -server.port=8082 - diff --git a/src/main/resources/base.properties b/src/main/resources/base.properties new file mode 100644 index 0000000..d780ee3 --- /dev/null +++ b/src/main/resources/base.properties @@ -0,0 +1,11 @@ +spring.application.name=node-controller +logging.file.path=/opt/metersphere/logs/${spring.application.name} +server.port=8082 +spring.kafka.consumer.group-id=metersphere_group_id +spring.kafka.listener.ack-mode=MANUAL_IMMEDIATE +# jmeter +jmeter.home=/opt/jmeter + +report.realtime=true +report.final=true +split.file.size=500000 \ No newline at end of file diff --git a/src/main/resources/jmeter/bin/jmeter.properties b/src/main/resources/jmeter/bin/jmeter.properties new file mode 100644 index 0000000..9de4732 --- /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 Dracula +#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 initialisation 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; default=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 diff --git a/src/main/resources/jmeter/lib/junit/.gitkeep b/src/main/resources/jmeter/lib/junit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index d5eae80..a024ab2 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,6 +1,6 @@ - + %d %5p %40.40c:%4L - %m%n @@ -103,7 +103,7 @@ DEBUG 10000 - + @@ -111,7 +111,7 @@ DEBUG 10000 - + @@ -119,7 +119,7 @@ INFO 10000 - + @@ -128,7 +128,7 @@ 10000 true - + @@ -137,24 +137,70 @@ 10000 true - + + + + + + + + + + + + + + + + + INFO + + ${logging.file.path}/ms-jmeter-run.log + + ${logging.file.path}/history/ms-jmeter-run.%d{yyyyMMdd}-%i.log + + 30 + + 50MB + + + + UTF-8 + %d %t %-5level %logger{36} %line - %msg%n + + + + + INFO + + 10000 + + + + + + + + - - + + - - - - - + + + + + - + \ No newline at end of file