diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index 2bd7fd240d..697f8f0f5c 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -3,9 +3,7 @@ package io.metersphere.api.jmeter; import com.alibaba.fastjson.JSON; import io.metersphere.api.dto.RunRequest; import io.metersphere.api.dto.automation.RunModeConfig; -import io.metersphere.api.dto.scenario.request.BodyFile; import io.metersphere.api.service.ApiScenarioReportService; -import io.metersphere.base.domain.JarConfig; import io.metersphere.base.domain.TestResource; import io.metersphere.base.domain.TestResourcePool; import io.metersphere.base.mapper.TestResourcePoolMapper; @@ -13,7 +11,6 @@ import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.constants.ResourcePoolTypeEnum; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.commons.utils.CompressUtils; import io.metersphere.commons.utils.LogUtil; import io.metersphere.config.JmeterProperties; import io.metersphere.dto.BaseSystemConfigDTO; @@ -21,36 +18,23 @@ import io.metersphere.dto.NodeDTO; import io.metersphere.i18n.Translator; import io.metersphere.performance.engine.Engine; import io.metersphere.performance.engine.EngineFactory; -import io.metersphere.service.JarConfigService; import io.metersphere.service.SystemParameterService; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.config.Arguments; -import org.apache.jmeter.config.CSVDataSet; -import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; -import org.apache.jmeter.protocol.http.util.HTTPFileArg; import org.apache.jmeter.save.SaveService; import org.apache.jmeter.util.JMeterUtils; import org.apache.jmeter.visualizers.backend.BackendListener; import org.apache.jorphan.collections.HashTree; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.core.io.FileSystemResource; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.PostConstruct; import javax.annotation.Resource; -import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; @Service public class JMeterService { @@ -74,7 +58,7 @@ public class JMeterService { JMeterUtils.setLocale(LocaleContextHolder.getLocale()); } - public void run(String testId, String debugReportId, InputStream is) { + public void runOld(String testId, String debugReportId, InputStream is) { init(); try { Object scriptWrapper = SaveService.loadElement(is); @@ -126,195 +110,11 @@ public class JMeterService { testPlan.add(testPlan.getArray()[0], backendListener); } - private void addBackendListener(String testId, String debugReportId, String runMode, HashTree testPlan, RunModeConfig config) { - BackendListener backendListener = new BackendListener(); - backendListener.setName(testId); - Arguments arguments = new Arguments(); - arguments.addArgument(APIBackendListenerClient.TEST_ID, testId); - if (StringUtils.isNotBlank(runMode)) { - arguments.addArgument("runMode", runMode); - } - if (StringUtils.isNotBlank(debugReportId)) { - arguments.addArgument("debugReportId", debugReportId); - } - backendListener.setArguments(arguments); - backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); - testPlan.add(testPlan.getArray()[0], backendListener); - } - - public void addBackendListener(String testId, HashTree testPlan) { - BackendListener backendListener = new BackendListener(); - backendListener.setName(testId); - Arguments arguments = new Arguments(); - arguments.addArgument(APIBackendListenerClient.TEST_ID, testId); - backendListener.setArguments(arguments); - backendListener.setClassname(APIBackendListenerClient.class.getCanonicalName()); - testPlan.add(testPlan.getArray()[0], backendListener); - } - - public void runDefinition(String testId, HashTree testPlan, String debugReportId, String runMode) { - try { - init(); - addBackendListener(testId, debugReportId, runMode, testPlan); - LocalRunner runner = new LocalRunner(testPlan); - runner.run(); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(Translator.get("api_load_script_error")); - } - } - - public void runSerial(String testId, HashTree testPlan, String debugReportId, String runMode, RunModeConfig config) { - try { - init(); - addBackendListener(testId, debugReportId, runMode, testPlan, config); - LocalRunner runner = new LocalRunner(testPlan); - runner.run(); - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - MSException.throwException(Translator.get("api_load_script_error")); - } - } - - /** - * 获取当前jmx 涉及到的文件 - * - * @param tree - */ - public void getFiles(HashTree tree, List files) { - for (Object key : tree.keySet()) { - HashTree node = tree.get(key); - if (key instanceof HTTPSamplerProxy) { - HTTPSamplerProxy source = (HTTPSamplerProxy) key; - if (source != null && source.getHTTPFiles().length > 0) { - for (HTTPFileArg arg : source.getHTTPFiles()) { - BodyFile file = new BodyFile(); - file.setId(arg.getParamName()); - file.setName(arg.getPath()); - files.add(file); - } - } - } else if (key instanceof CSVDataSet) { - CSVDataSet source = (CSVDataSet) key; - if (source != null && source.getFilename() != null) { - BodyFile file = new BodyFile(); - file.setId(source.getFilename()); - file.setName(source.getFilename()); - files.add(file); - } - } - if (node != null) { - getFiles(node, files); - } - } - } - - public byte[] fileToByte(File tradeFile) { - byte[] buffer = null; - try (FileInputStream fis = new FileInputStream(tradeFile); - ByteArrayOutputStream bos = new ByteArrayOutputStream();) { - byte[] b = new byte[1024]; - int n; - while ((n = fis.read(b)) != -1) { - bos.write(b, 0, n); - } - buffer = bos.toByteArray(); - } catch (Exception e) { - } - return buffer; - } - - public List getZipJar() { - List jarFiles = new LinkedList<>(); - // jar 包 - JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class); - List jars = jarConfigService.list(); - List files = new ArrayList<>(); - - jars.forEach(jarConfig -> { - String path = jarConfig.getPath(); - File file = new File(path); - if (file.isDirectory() && !path.endsWith("/")) { - file = new File(path + "/"); - } - files.add(file); - }); - - try { - File file = CompressUtils.zipFiles(UUID.randomUUID().toString() + ".zip", files); - FileSystemResource resource = new FileSystemResource(file); - byte[] fileByte = this.fileToByte(file); - if (fileByte != null) { - ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { - @Override - public String getFilename() throws IllegalStateException { - return resource.getFilename(); - } - }; - jarFiles.add(byteArrayResource); - } - } catch (Exception e) { - e.printStackTrace(); - } - - return jarFiles; - } - - public List getJar() { - List jarFiles = new LinkedList<>(); - // jar 包 - JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class); - List jars = jarConfigService.list(); - jars.forEach(jarConfig -> { - try { - String path = jarConfig.getPath(); - File file = new File(path); - if (file.isDirectory() && !path.endsWith("/")) { - file = new File(path + "/"); - } - FileSystemResource resource = new FileSystemResource(file); - byte[] fileByte = this.fileToByte(file); - if (fileByte != null) { - ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { - @Override - public String getFilename() throws IllegalStateException { - return resource.getFilename(); - } - }; - jarFiles.add(byteArrayResource); - } - - } catch (Exception e) { - LogUtil.error(e.getMessage(), e); - } - }); - return jarFiles; - } - - public List getMultipartFiles(HashTree hashTree) { - List multipartFiles = new LinkedList<>(); - // 获取附件 - List files = new LinkedList<>(); - getFiles(hashTree, files); - if (CollectionUtils.isNotEmpty(files)) { - for (BodyFile bodyFile : files) { - File file = new File(bodyFile.getName()); - if (file != null && !file.exists()) { - FileSystemResource resource = new FileSystemResource(file); - byte[] fileByte = this.fileToByte(file); - if (fileByte != null) { - ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { - @Override - public String getFilename() throws IllegalStateException { - return resource.getFilename(); - } - }; - multipartFiles.add(byteArrayResource); - } - } - } - } - return multipartFiles; + public void runLocal(String testId, HashTree testPlan, String debugReportId, String runMode) { + init(); + addBackendListener(testId, debugReportId, runMode, testPlan); + LocalRunner runner = new LocalRunner(testPlan); + runner.run(); } public void runTest(String testId, String reportId, String runMode, String testPlanScenarioId, RunModeConfig config) { diff --git a/backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java b/backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java new file mode 100644 index 0000000000..4132a21180 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/jmeter/MsResultCollector.java @@ -0,0 +1,568 @@ +/* + * 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 org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.reporters.AbstractListenerElement; +import org.apache.jmeter.reporters.Summariser; +import org.apache.jmeter.samplers.*; +import org.apache.jmeter.save.CSVSaveService; +import org.apache.jmeter.save.SaveService; +import org.apache.jmeter.services.FileServer; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.BooleanProperty; +import org.apache.jmeter.testelement.property.ObjectProperty; +import org.apache.jmeter.util.JMeterUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * This class handles all saving of samples. + * The class must be thread-safe because it is shared between threads (NoThreadClone). + */ +public class MsResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, + TestStateListener, Remoteable, NoThreadClone { + /** + * Keep track of the file writer and the configuration, + * as the instance used to close them is not the same as the instance that creates + * them. This means one cannot use the saved PrintWriter or use getSaveConfig() + */ + private static class FileEntry { + final PrintWriter pw; + final SampleSaveConfiguration config; + + FileEntry(PrintWriter printWriter, SampleSaveConfiguration sampleSaveConfiguration) { + this.pw = printWriter; + this.config = sampleSaveConfiguration; + } + } + + private static final class ShutdownHook implements Runnable { + + @Override + public void run() { + log.info("Shutdown hook started"); + synchronized (LOCK) { + finalizeFileOutput(); + } + log.info("Shutdown hook ended"); + } + } + + private static final Logger log = LoggerFactory.getLogger(MsResultCollector.class); + + private static final long serialVersionUID = 234L; + + // This string is used to identify local test runs, so must not be a valid host name + private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$ + + private static final String TESTRESULTS_START = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_START_V1_1_PREVER = ""; // $NON-NLS-1$ + + private static final String TESTRESULTS_END = ""; // $NON-NLS-1$ + + // we have to use version 1.0, see bug 59973 + private static final String XML_HEADER = ""; // $NON-NLS-1$ + + private static final int MIN_XML_FILE_LEN = XML_HEADER.length() + TESTRESULTS_START.length() + + TESTRESULTS_END.length(); + + public static final String FILENAME = "filename"; // $NON-NLS-1$ + + private static final String SAVE_CONFIG = "saveConfig"; // $NON-NLS-1$ + + 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$ + + /** + * AutoFlush on each line + */ + private static final boolean SAVING_AUTOFLUSH = JMeterUtils.getPropDefault("jmeter.save.saveservice.autoflush", false); //$NON-NLS-1$ + + // Static variables + + // Lock used to guard static mutable variables + private static final Object LOCK = new Object(); + + private static final Map files = new HashMap<>(); + + /** + * Shutdown Hook that ensures PrintWriter is flushed is CTRL+C or kill is called during a test + */ + private static Thread shutdownHook; + + /** + * The instance count is used to keep track of whether any tests are currently running. + * It's not possible to use the constructor or threadStarted etc as tests may overlap + * e.g. a remote test may be started, + * and then a local test started whilst the remote test is still running. + */ + private static int instanceCount; // Keep track of how many instances are active + + // Instance variables (guarded by volatile) + private transient volatile PrintWriter out; + + /** + * Is a test running ? + */ + private volatile boolean inTest = false; + + private volatile boolean isStats = false; + + /** + * the summarizer to which this result collector will forward the samples + */ + private volatile Summariser summariser; + + /** + * No-arg constructor. + */ + public MsResultCollector() { + this(null); + } + + /** + * Constructor which sets the used {@link Summariser} + * + * @param summer The {@link Summariser} to use + */ + public MsResultCollector(Summariser summer) { + setErrorLogging(false); + setSuccessOnlyLogging(false); + setProperty(new ObjectProperty(SAVE_CONFIG, new SampleSaveConfiguration())); + summariser = summer; + } + + // Ensure that the sample save config is not shared between copied nodes + // N.B. clone only seems to be used for client-server tests + @Override + public Object clone() { + MsResultCollector clone = (MsResultCollector) super.clone(); + clone.setSaveConfig((SampleSaveConfiguration) clone.getSaveConfig().clone()); + // Unfortunately AbstractTestElement does not call super.clone() + clone.summariser = this.summariser; + return clone; + } + + private void setFilenameProperty(String f) { + setProperty(FILENAME, f); + } + + /** + * Get the filename of the file this collector uses + * + * @return The name of the file + */ + public String getFilename() { + return getPropertyAsString(FILENAME); + } + + /** + * Get the state of error logging + * + * @return Flag whether errors should be logged + */ + public boolean isErrorLogging() { + return getPropertyAsBoolean(ERROR_LOGGING); + } + + /** + * Sets error logging flag + * + * @param errorLogging The flag whether errors should be logged + */ + public final void setErrorLogging(boolean errorLogging) { + setProperty(new BooleanProperty(ERROR_LOGGING, errorLogging)); + } + + /** + * Sets the flag whether only successful samples should be logged + * + * @param value The flag whether only successful samples should be logged + */ + public final void setSuccessOnlyLogging(boolean value) { + if (value) { + setProperty(new BooleanProperty(SUCCESS_ONLY_LOGGING, true)); + } else { + removeProperty(SUCCESS_ONLY_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); + } + + /** + * Decides whether or not to a sample is wanted based on: + *
    + *
  • errorOnly
  • + *
  • successOnly
  • + *
  • sample success
  • + *
+ * Should only be called for single samples. + * + * @param success is sample successful + * @return whether to log/display the sample + */ + public boolean isSampleWanted(boolean success) { + boolean errorOnly = isErrorLogging(); + boolean successOnly = isSuccessOnlyLogging(); + return isSampleWanted(success, errorOnly, successOnly); + } + + /** + * Decides whether or not to a sample is wanted based on: + *
    + *
  • errorOnly
  • + *
  • successOnly
  • + *
  • sample success
  • + *
+ * This version is intended to be called by code that loops over many samples; + * it is cheaper than fetching the settings each time. + * + * @param success status of sample + * @param errorOnly if errors only wanted + * @param successOnly if success only wanted + * @return whether to log/display the sample + */ + public static boolean isSampleWanted(boolean success, boolean errorOnly, + boolean successOnly) { + return (!errorOnly && !successOnly) || + (success && successOnly) || + (!success && errorOnly); + // successOnly and errorOnly cannot both be set + } + + /** + * Sets the filename attribute of the MsResultCollector object. + * + * @param f the new filename value + */ + public void setFilename(String f) { + if (inTest) { + return; + } + setFilenameProperty(f); + } + + @Override + public void testEnded(String host) { + synchronized (LOCK) { + instanceCount--; + if (instanceCount <= 0) { + // No need for the hook now + // Bug 57088 - prevent (im?)possible NPE + if (shutdownHook != null) { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } else { + log.warn("Should not happen: shutdownHook==null, instanceCount={}", instanceCount); + } + finalizeFileOutput(); + out = null; + inTest = false; + } + } + + if (summariser != null) { + summariser.testEnded(host); + } + } + + @Override + public void testStarted(String host) { + synchronized (LOCK) { + if (instanceCount == 0) { // Only add the hook once + shutdownHook = new Thread(new ShutdownHook()); + Runtime.getRuntime().addShutdownHook(shutdownHook); + } + instanceCount++; + try { + if (out == null) { + try { + // Note: getFileWriter ignores a null filename + out = getFileWriter(getFilename(), getSaveConfig()); + } catch (FileNotFoundException e) { + out = null; + } + } + if (getVisualizer() != null) { + this.isStats = getVisualizer().isStats(); + } + } catch (Exception e) { + log.error("Exception occurred while initializing file output.", e); + } + } + inTest = true; + + if (summariser != null) { + summariser.testStarted(host); + } + } + + @Override + public void testEnded() { + testEnded(TEST_IS_LOCAL); + } + + @Override + public void testStarted() { + testStarted(TEST_IS_LOCAL); + } + + private static void writeFileStart(PrintWriter writer, SampleSaveConfiguration saveConfig) { + if (saveConfig.saveAsXml()) { + writer.print(XML_HEADER); + // Write the EOL separately so we generate LF line ends on Unix and Windows + writer.print("\n"); // $NON-NLS-1$ + String pi = saveConfig.getXmlPi(); + if (pi.length() > 0) { + writer.println(pi); + } + // Can't do it as a static initialisation, because SaveService + // is being constructed when this is called + writer.print(TESTRESULTS_START_V1_1_PREVER); + writer.print(SaveService.getVERSION()); + writer.print(TESTRESULTS_START_V1_1_POSTVER); + // Write the EOL separately so we generate LF line ends on Unix and Windows + writer.print("\n"); // $NON-NLS-1$ + } else if (saveConfig.saveFieldNames()) { + writer.println(CSVSaveService.printableFieldNamesToString(saveConfig)); + } + } + + private static void writeFileEnd(PrintWriter pw, SampleSaveConfiguration saveConfig) { + if (saveConfig.saveAsXml()) { + pw.print("\n"); // $NON-NLS-1$ + pw.print(TESTRESULTS_END); + pw.print("\n");// Added in version 1.1 // $NON-NLS-1$ + } + } + + private static PrintWriter getFileWriter(final String pFilename, SampleSaveConfiguration saveConfig) + throws IOException { + if (pFilename == null || pFilename.length() == 0) { + return null; + } + if (log.isDebugEnabled()) { + log.debug("Getting file: {} in thread {}", pFilename, Thread.currentThread().getName()); + } + String filename = FileServer.resolveBaseRelativeName(pFilename); + filename = new File(filename).getCanonicalPath(); // try to ensure uniqueness (Bug 60822) + FileEntry fe = files.get(filename); + PrintWriter writer = null; + boolean trimmed = true; + + if (fe == null) { + if (saveConfig.saveAsXml()) { + trimmed = trimLastLine(filename); + } else { + trimmed = new File(filename).exists(); + } + // Find the name of the directory containing the file + // and create it - if there is one + File pdir = new File(filename).getParentFile(); + if (pdir != null) { + // returns false if directory already exists, so need to check again + if (pdir.mkdirs()) { + if (log.isInfoEnabled()) { + log.info("Folder at {} was created", pdir.getAbsolutePath()); + } + } // else if might have been created by another process so not a problem + if (!pdir.exists()) { + log.warn("Error creating directories for {}", pdir); + } + } + writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(filename, + trimmed)), SaveService.getFileEncoding(StandardCharsets.UTF_8.name())), SAVING_AUTOFLUSH); + if (log.isDebugEnabled()) { + log.debug("Opened file: {} in thread {}", filename, Thread.currentThread().getName()); + } + files.put(filename, new FileEntry(writer, saveConfig)); + } else { + writer = fe.pw; + } + if (!trimmed) { + log.debug("Writing header to file: {}", filename); + writeFileStart(writer, saveConfig); + } + return writer; + } + + // returns false if the file did not contain the terminator + private static boolean trimLastLine(String filename) { + try (RandomAccessFile raf = new RandomAccessFile(filename, "rw")) { // $NON-NLS-1$ + long len = raf.length(); + if (len < MIN_XML_FILE_LEN) { + return false; + } + raf.seek(len - TESTRESULTS_END.length() - 10); + String line; + long pos = raf.getFilePointer(); + int end = 0; + while ((line = raf.readLine()) != null)// reads to end of line OR end of file + { + end = line.indexOf(TESTRESULTS_END); + if (end >= 0) // found the string + { + break; + } + pos = raf.getFilePointer(); + } + if (line == null) { + log.warn("Unexpected EOF trying to find XML end marker in {}", filename); + return false; + } + raf.setLength(pos + end);// Truncate the file + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + if (log.isWarnEnabled()) { + log.warn("Error trying to find XML terminator. {}", e.toString()); + } + return false; + } + return true; + } + + @Override + public void sampleStarted(SampleEvent e) { + // NOOP + } + + @Override + public void sampleStopped(SampleEvent e) { + // NOOP + } + + /** + * When a test result is received, display it and save it. + * + * @param event the sample event that was received + */ + @Override + public void sampleOccurred(SampleEvent event) { + SampleResult result = event.getResult(); + log.info("*****************************" + result.getThreadName() + " == " + result.getSampleLabel()); + if (isSampleWanted(result.isSuccessful())) { + sendToVisualizer(result); + if (out != null && !isResultMarked(result) && !this.isStats) { + SampleSaveConfiguration config = getSaveConfig(); + result.setSaveConfig(config); + try { + if (config.saveAsXml()) { + SaveService.saveSampleResult(event, out); + } else { // !saveAsXml + CSVSaveService.saveSampleResult(event, out); + } + } catch (Exception err) { + log.error("Error trying to record a sample", err); // should throw exception back to caller + } + } + } + + if (summariser != null) { + summariser.sampleOccurred(event); + } + } + + protected final void sendToVisualizer(SampleResult r) { + if (getVisualizer() != null) { + getVisualizer().add(r); + } + } + + /** + * Checks if the sample result is marked or not, and marks it + * + * @param res - the sample result to check + * @return true if the result was marked + */ + private boolean isResultMarked(SampleResult res) { + String filename = getFilename(); + return res.markFile(filename); + } + + /** + * Flush PrintWriter to synchronize file contents + */ + public void flushFile() { + if (out != null) { + log.info("forced flush through MsResultCollector#flushFile"); + out.flush(); + } + } + + private static void finalizeFileOutput() { + for (Map.Entry me : files.entrySet()) { + String key = me.getKey(); + MsResultCollector.FileEntry value = me.getValue(); + try { + log.debug("Closing: {}", key); + writeFileEnd(value.pw, value.config); + value.pw.close(); + if (value.pw.checkError()) { + log.warn("Problem detected during use of {}", key); + } + } catch (Exception ex) { + log.error("Error closing file {}", key, ex); + } + } + files.clear(); + } + + /** + * @return Returns the saveConfig. + */ + public SampleSaveConfiguration getSaveConfig() { + try { + return (SampleSaveConfiguration) getProperty(SAVE_CONFIG).getObjectValue(); + } catch (ClassCastException e) { + setSaveConfig(new SampleSaveConfiguration()); + return getSaveConfig(); + } + } + + /** + * @param saveConfig The saveConfig to set. + */ + public void setSaveConfig(SampleSaveConfiguration saveConfig) { + getProperty(SAVE_CONFIG).setObjectValue(saveConfig); + } + + // This is required so that + // @see org.apache.jmeter.gui.tree.JMeterTreeModel.getNodesOfType() + // can find the Clearable nodes - the userObject has to implement the interface. + @Override + public void clearData() { + // NOOP + } +} diff --git a/backend/src/main/java/io/metersphere/api/service/APITestService.java b/backend/src/main/java/io/metersphere/api/service/APITestService.java index 667771d09e..7d1b2d23fb 100644 --- a/backend/src/main/java/io/metersphere/api/service/APITestService.java +++ b/backend/src/main/java/io/metersphere/api/service/APITestService.java @@ -236,7 +236,7 @@ public class APITestService { mailService.sendHtml(reportId,notice,"api"); }*/ changeStatus(request.getId(), APITestStatus.Running); - jMeterService.run(request.getId(), null, is); + jMeterService.runOld(request.getId(), null, is); return reportId; } @@ -445,7 +445,7 @@ public class APITestService { LogUtil.error(e.getMessage(), e); } - jMeterService.run(request.getId(), reportId, is); + jMeterService.runOld(request.getId(), reportId, is); return reportId; } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index d8d24cfcde..251017d99e 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -1233,7 +1233,7 @@ public class ApiAutomationService { List reportIds = new LinkedList<>(); try { HashTree hashTree = generateHashTree(apiScenarios, request, reportIds); - jMeterService.runSerial(JSON.toJSONString(reportIds), hashTree, request.getReportId(), runMode, request.getConfig()); + jMeterService.runLocal(JSON.toJSONString(reportIds), hashTree, request.getReportId(), runMode); } catch (Exception e) { LogUtil.error(e.getMessage()); MSException.throwException(e.getMessage()); @@ -1334,7 +1334,7 @@ public class ApiAutomationService { FileUtils.createBodyFiles(request.getScenarioFileIds(), scenarioFiles); // 调用执行方法 - jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name()); + jMeterService.runLocal(request.getId(), hashTree, request.getReportId(), ApiRunMode.SCENARIO.name()); return request.getId(); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java index dd76449b9a..570c5bac46 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java @@ -593,7 +593,7 @@ public class ApiDefinitionService { if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) { jMeterService.runTest(request.getId(), request.getId(), runMode, null, request.getConfig()); } else { - jMeterService.runDefinition(request.getId(), hashTree, request.getReportId(), runMode); + jMeterService.runLocal(request.getId(), hashTree, request.getReportId(), runMode); } return request.getId(); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiJmeterFileService.java b/backend/src/main/java/io/metersphere/api/service/ApiJmeterFileService.java index 424cd55757..c6197fdb8b 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiJmeterFileService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiJmeterFileService.java @@ -12,6 +12,7 @@ import io.metersphere.base.mapper.TestPlanApiScenarioMapper; import io.metersphere.commons.constants.ApiRunMode; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.LogUtil; import io.metersphere.service.JarConfigService; import io.metersphere.track.service.TestPlanApiCaseService; @@ -91,7 +92,7 @@ public class ApiJmeterFileService { if (file.isDirectory() && !path.endsWith("/")) { file = new File(path + "/"); } - byte[] fileByte = jMeterService.fileToByte(file); + byte[] fileByte = FileUtils.fileToByte(file); if (fileByte != null) { jarFiles.put(file.getName(), fileByte); } @@ -106,12 +107,12 @@ public class ApiJmeterFileService { Map multipartFiles = new LinkedHashMap<>(); // 获取附件 List files = new LinkedList<>(); - jMeterService.getFiles(hashTree, files); + FileUtils.getFiles(hashTree, files); if (CollectionUtils.isNotEmpty(files)) { for (BodyFile bodyFile : files) { File file = new File(bodyFile.getName()); if (file != null && !file.exists()) { - byte[] fileByte = jMeterService.fileToByte(file); + byte[] fileByte = FileUtils.fileToByte(file); if (fileByte != null) { multipartFiles.put(file.getName(), fileByte); } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java index 8a9f88b317..0a643228b9 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java @@ -606,7 +606,7 @@ public class ApiTestCaseService { String runMode = ApiRunMode.JENKINS.name(); */ // 调用执行方法 - jMeterService.runDefinition(request.getCaseId(), jmeterHashTree, request.getReportId(), request.getRunMode()); + jMeterService.runLocal(request.getCaseId(), jmeterHashTree, request.getReportId(), request.getRunMode()); } catch (Exception ex) { LogUtil.error(ex.getMessage()); @@ -625,7 +625,7 @@ public class ApiTestCaseService { request.setTestPlanId(testPlanID); HashTree jmeterHashTree = this.generateHashTree(request, apiCaseBolbs); // 调用执行方法 - jMeterService.runDefinition(id, jmeterHashTree, debugReportId, runMode); + jMeterService.runLocal(id, jmeterHashTree, debugReportId, runMode); } catch (Exception ex) { LogUtil.error(ex.getMessage()); } diff --git a/backend/src/main/java/io/metersphere/api/service/task/ParallelScenarioExecTask.java b/backend/src/main/java/io/metersphere/api/service/task/ParallelScenarioExecTask.java index 9112c110c5..39f47dfb17 100644 --- a/backend/src/main/java/io/metersphere/api/service/task/ParallelScenarioExecTask.java +++ b/backend/src/main/java/io/metersphere/api/service/task/ParallelScenarioExecTask.java @@ -29,7 +29,7 @@ public class ParallelScenarioExecTask implements Callable { if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) { jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), request.getRunMode(), request.getPlanScenarioId(), request.getConfig()); } else { - jMeterService.runSerial(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode(), request.getConfig()); + jMeterService.runLocal(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode()); } return null; } catch (Exception ex) { diff --git a/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java b/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java index 10ed0a5e82..f60c8f8002 100644 --- a/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java +++ b/backend/src/main/java/io/metersphere/api/service/task/SerialScenarioExecTask.java @@ -35,7 +35,7 @@ public class SerialScenarioExecTask implements Callable { if (request.getConfig() != null && StringUtils.isNotBlank(request.getConfig().getResourcePoolId())) { jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), request.getRunMode(), request.getPlanScenarioId(), request.getConfig()); } else { - jMeterService.runSerial(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode(), request.getConfig()); + jMeterService.runLocal(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), request.getReportId(), request.getRunMode()); } // 轮询查看报告状态,最多200次,防止死循环 int index = 1; diff --git a/backend/src/main/java/io/metersphere/commons/utils/FileUtils.java b/backend/src/main/java/io/metersphere/commons/utils/FileUtils.java index 99a55fd985..3ce6d869f3 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/FileUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/FileUtils.java @@ -1,13 +1,24 @@ package io.metersphere.commons.utils; +import io.metersphere.api.dto.scenario.request.BodyFile; +import io.metersphere.base.domain.JarConfig; import io.metersphere.commons.exception.MSException; import io.metersphere.i18n.Translator; +import io.metersphere.service.JarConfigService; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.CSVDataSet; +import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy; +import org.apache.jmeter.protocol.http.util.HTTPFileArg; +import org.apache.jorphan.collections.HashTree; import org.aspectj.util.FileUtil; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.FileSystemResource; import org.springframework.web.multipart.MultipartFile; import java.io.*; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -135,4 +146,146 @@ public class FileUtils { } } + + + /** + * 获取当前jmx 涉及到的文件 + * + * @param tree + */ + public static void getFiles(HashTree tree, List files) { + for (Object key : tree.keySet()) { + HashTree node = tree.get(key); + if (key instanceof HTTPSamplerProxy) { + HTTPSamplerProxy source = (HTTPSamplerProxy) key; + if (source != null && source.getHTTPFiles().length > 0) { + for (HTTPFileArg arg : source.getHTTPFiles()) { + BodyFile file = new BodyFile(); + file.setId(arg.getParamName()); + file.setName(arg.getPath()); + files.add(file); + } + } + } else if (key instanceof CSVDataSet) { + CSVDataSet source = (CSVDataSet) key; + if (source != null && source.getFilename() != null) { + BodyFile file = new BodyFile(); + file.setId(source.getFilename()); + file.setName(source.getFilename()); + files.add(file); + } + } + if (node != null) { + getFiles(node, files); + } + } + } + + public static byte[] fileToByte(File tradeFile) { + byte[] buffer = null; + try (FileInputStream fis = new FileInputStream(tradeFile); + ByteArrayOutputStream bos = new ByteArrayOutputStream();) { + byte[] b = new byte[1024]; + int n; + while ((n = fis.read(b)) != -1) { + bos.write(b, 0, n); + } + buffer = bos.toByteArray(); + } catch (Exception e) { + } + return buffer; + } + + public List getZipJar() { + List jarFiles = new LinkedList<>(); + // jar 包 + JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class); + List jars = jarConfigService.list(); + List files = new ArrayList<>(); + + jars.forEach(jarConfig -> { + String path = jarConfig.getPath(); + File file = new File(path); + if (file.isDirectory() && !path.endsWith("/")) { + file = new File(path + "/"); + } + files.add(file); + }); + + try { + File file = CompressUtils.zipFiles(UUID.randomUUID().toString() + ".zip", files); + FileSystemResource resource = new FileSystemResource(file); + byte[] fileByte = this.fileToByte(file); + if (fileByte != null) { + ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { + @Override + public String getFilename() throws IllegalStateException { + return resource.getFilename(); + } + }; + jarFiles.add(byteArrayResource); + } + } catch (Exception e) { + e.printStackTrace(); + } + + return jarFiles; + } + + public List getJar() { + List jarFiles = new LinkedList<>(); + // jar 包 + JarConfigService jarConfigService = CommonBeanFactory.getBean(JarConfigService.class); + List jars = jarConfigService.list(); + jars.forEach(jarConfig -> { + try { + String path = jarConfig.getPath(); + File file = new File(path); + if (file.isDirectory() && !path.endsWith("/")) { + file = new File(path + "/"); + } + FileSystemResource resource = new FileSystemResource(file); + byte[] fileByte = this.fileToByte(file); + if (fileByte != null) { + ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { + @Override + public String getFilename() throws IllegalStateException { + return resource.getFilename(); + } + }; + jarFiles.add(byteArrayResource); + } + + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + } + }); + return jarFiles; + } + + public List getMultipartFiles(HashTree hashTree) { + List multipartFiles = new LinkedList<>(); + // 获取附件 + List files = new LinkedList<>(); + getFiles(hashTree, files); + if (CollectionUtils.isNotEmpty(files)) { + for (BodyFile bodyFile : files) { + File file = new File(bodyFile.getName()); + if (file != null && !file.exists()) { + FileSystemResource resource = new FileSystemResource(file); + byte[] fileByte = this.fileToByte(file); + if (fileByte != null) { + ByteArrayResource byteArrayResource = new ByteArrayResource(fileByte) { + @Override + public String getFilename() throws IllegalStateException { + return resource.getFilename(); + } + }; + multipartFiles.add(byteArrayResource); + } + } + } + } + return multipartFiles; + } } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java index ce5aa69fc5..1f1cb5454a 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanService.java @@ -933,7 +933,7 @@ public class TestPlanService { testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig()); String runMode = ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(); // 调用执行方法 - jMeterService.runDefinition(request.getId(), jmeterHashTree, request.getReportId(), runMode); + jMeterService.runLocal(request.getId(), jmeterHashTree, request.getReportId(), runMode); } return returnId; diff --git a/backend/src/main/java/io/metersphere/track/service/task/ParallelApiExecTask.java b/backend/src/main/java/io/metersphere/track/service/task/ParallelApiExecTask.java index b13bd77c9e..30905f1683 100644 --- a/backend/src/main/java/io/metersphere/track/service/task/ParallelApiExecTask.java +++ b/backend/src/main/java/io/metersphere/track/service/task/ParallelApiExecTask.java @@ -34,7 +34,7 @@ public class ParallelApiExecTask implements Callable { if (config != null && StringUtils.isNotBlank(config.getResourcePoolId())) { jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), runMode, null, config); } else { - jMeterService.runDefinition(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode); + jMeterService.runLocal(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode); } return null; } catch (Exception ex) { diff --git a/backend/src/main/java/io/metersphere/track/service/task/SerialApiExecTask.java b/backend/src/main/java/io/metersphere/track/service/task/SerialApiExecTask.java index b14b873f18..871b603b01 100644 --- a/backend/src/main/java/io/metersphere/track/service/task/SerialApiExecTask.java +++ b/backend/src/main/java/io/metersphere/track/service/task/SerialApiExecTask.java @@ -36,7 +36,7 @@ public class SerialApiExecTask implements Callable { if (config != null && StringUtils.isNotBlank(config.getResourcePoolId())) { jMeterService.runTest(runModeDataDTO.getTestId(), runModeDataDTO.getReportId(), runMode, null, config); } else { - jMeterService.runDefinition(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode); + jMeterService.runLocal(runModeDataDTO.getReportId(), runModeDataDTO.getHashTree(), null, runMode); } // 轮询查看报告状态,最多200次,防止死循环 ApiDefinitionExecResult report = null;