fix(接口测试): 解决支持jar包后无法加载部分类的问题
This commit is contained in:
parent
133e1b2aea
commit
ee56ab2750
|
@ -150,10 +150,6 @@
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-slf4j-impl</artifactId>
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
<exclusion>
|
|
||||||
<groupId>org.apache-extras.beanshell</groupId>
|
|
||||||
<artifactId>bsh</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@ -520,15 +516,6 @@
|
||||||
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
|
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
|
||||||
<destFileName>jython-standalone.jar</destFileName>
|
<destFileName>jython-standalone.jar</destFileName>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
<artifactItem>
|
|
||||||
<groupId>org.apache-extras.beanshell</groupId>
|
|
||||||
<artifactId>bsh</artifactId>
|
|
||||||
<version>2.0b6</version>
|
|
||||||
<type>jar</type>
|
|
||||||
<overWrite>true</overWrite>
|
|
||||||
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
|
|
||||||
<destFileName>bsh-2.0b6.jar</destFileName>
|
|
||||||
</artifactItem>
|
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
<outputDirectory>${project.build.directory}/wars</outputDirectory>
|
<outputDirectory>${project.build.directory}/wars</outputDirectory>
|
||||||
<overWriteReleases>false</overWriteReleases>
|
<overWriteReleases>false</overWriteReleases>
|
||||||
|
|
|
@ -6,13 +6,11 @@ import io.metersphere.commons.utils.LogUtil;
|
||||||
import io.metersphere.config.JmeterProperties;
|
import io.metersphere.config.JmeterProperties;
|
||||||
import io.metersphere.i18n.Translator;
|
import io.metersphere.i18n.Translator;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.jmeter.NewDriver;
|
|
||||||
import org.apache.jmeter.config.Arguments;
|
import org.apache.jmeter.config.Arguments;
|
||||||
import org.apache.jmeter.save.SaveService;
|
import org.apache.jmeter.save.SaveService;
|
||||||
import org.apache.jmeter.util.JMeterUtils;
|
import org.apache.jmeter.util.JMeterUtils;
|
||||||
import org.apache.jmeter.visualizers.backend.BackendListener;
|
import org.apache.jmeter.visualizers.backend.BackendListener;
|
||||||
import org.apache.jorphan.collections.HashTree;
|
import org.apache.jorphan.collections.HashTree;
|
||||||
|
|
||||||
import org.springframework.context.i18n.LocaleContextHolder;
|
import org.springframework.context.i18n.LocaleContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@ -30,10 +28,6 @@ public class JMeterService {
|
||||||
public void run(String testId, String debugReportId, InputStream is) {
|
public void run(String testId, String debugReportId, InputStream is) {
|
||||||
String JMETER_HOME = getJmeterHome();
|
String JMETER_HOME = getJmeterHome();
|
||||||
|
|
||||||
// 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题
|
|
||||||
// 同时隔离在 beanshell 中访问由系统类加载器加载的其他类
|
|
||||||
NewDriver.setContextClassLoader();
|
|
||||||
|
|
||||||
String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties";
|
String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties";
|
||||||
JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES);
|
JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES);
|
||||||
JMeterUtils.setJMeterHome(JMETER_HOME);
|
JMeterUtils.setJMeterHome(JMETER_HOME);
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
package org.apache.jmeter;
|
package org.apache.jmeter;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -28,12 +27,7 @@ import java.net.URL;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main class for JMeter - sets up initial classpath and the loader.
|
* Main class for JMeter - sets up initial classpath and the loader.
|
||||||
|
@ -65,6 +59,14 @@ public final class NewDriver {
|
||||||
Thread.currentThread().setContextClassLoader(loader);
|
Thread.currentThread().setContextClassLoader(loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void loaderClass(String name) {
|
||||||
|
try {
|
||||||
|
loader.loadClass(name);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
final List<URL> jars = new LinkedList<>();
|
final List<URL> jars = new LinkedList<>();
|
||||||
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
|
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
|
||||||
|
@ -72,28 +74,13 @@ public final class NewDriver {
|
||||||
// Find JMeter home dir from the initial classpath
|
// Find JMeter home dir from the initial classpath
|
||||||
String tmpDir;
|
String tmpDir;
|
||||||
|
|
||||||
// StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator);
|
|
||||||
// if (tok.countTokens() == 1
|
|
||||||
// || (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath
|
|
||||||
// && OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$
|
|
||||||
// )
|
|
||||||
// ) {
|
|
||||||
// File jar = new File(tok.nextToken());
|
|
||||||
// try {
|
|
||||||
// tmpDir = jar.getCanonicalFile().getParentFile().getParent();
|
|
||||||
// System.out.println(tmpDir + "111");
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// tmpDir = null;
|
|
||||||
// }
|
|
||||||
// } else {// e.g. started from IDE with full classpath
|
|
||||||
|
|
||||||
//只从 jmeter.home 加载
|
//只从 jmeter.home 加载
|
||||||
tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$
|
tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$
|
||||||
if (tmpDir.length() == 0) {
|
if (tmpDir.length() == 0) {
|
||||||
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
|
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
|
||||||
tmpDir = userDir.getAbsoluteFile().getParent();
|
tmpDir = userDir.getAbsoluteFile().getParent();
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
JMETER_INSTALLATION_DIRECTORY=tmpDir;
|
JMETER_INSTALLATION_DIRECTORY=tmpDir;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -0,0 +1,354 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.jmeter.util;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.Compilable;
|
||||||
|
import javax.script.CompiledScript;
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.collections.map.LRUMap;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.jmeter.NewDriver;
|
||||||
|
import org.apache.jmeter.samplers.SampleResult;
|
||||||
|
import org.apache.jmeter.samplers.Sampler;
|
||||||
|
import org.apache.jmeter.testelement.TestStateListener;
|
||||||
|
import org.apache.jmeter.threads.JMeterContext;
|
||||||
|
import org.apache.jmeter.threads.JMeterContextService;
|
||||||
|
import org.apache.jmeter.threads.JMeterVariables;
|
||||||
|
import org.apache.jorphan.util.JOrphanUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for JSR223 Test elements
|
||||||
|
*
|
||||||
|
* 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题
|
||||||
|
* 同时隔离在 beanshell 中访问由系统类加载器加载的其他类
|
||||||
|
*/
|
||||||
|
public abstract class JSR223TestElement extends ScriptingTestElement
|
||||||
|
implements Serializable, TestStateListener
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 232L;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JSR223TestElement.class);
|
||||||
|
/**
|
||||||
|
* Cache of compiled scripts
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked") // LRUMap does not support generics (yet)
|
||||||
|
private static final Map<String, CompiledScript> compiledScriptsCache =
|
||||||
|
Collections.synchronizedMap(
|
||||||
|
new LRUMap(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100)));
|
||||||
|
|
||||||
|
/** If not empty then script in ScriptText will be compiled and cached */
|
||||||
|
private String cacheKey = "";
|
||||||
|
|
||||||
|
/** md5 of the script, used as an unique key for the cache */
|
||||||
|
private String scriptMd5 = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization On Demand Holder pattern
|
||||||
|
*/
|
||||||
|
private static class LazyHolder {
|
||||||
|
private LazyHolder() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
public static final ScriptEngineManager INSTANCE = new ScriptEngineManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ScriptEngineManager singleton
|
||||||
|
*/
|
||||||
|
public static ScriptEngineManager getInstance() {
|
||||||
|
return LazyHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSR223TestElement() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@link ScriptEngine} for language defaulting to groovy if language is not set
|
||||||
|
* @throws ScriptException when no {@link ScriptEngine} could be found
|
||||||
|
*/
|
||||||
|
protected ScriptEngine getScriptEngine() throws ScriptException {
|
||||||
|
String lang = getScriptLanguageWithDefault();
|
||||||
|
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
|
||||||
|
if (scriptEngine == null) {
|
||||||
|
throw new ScriptException("Cannot find engine named: '"+lang+"', ensure you set language field in JSR223 Test Element: "+getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return script language or DEFAULT_SCRIPT_LANGUAGE if none is set
|
||||||
|
*/
|
||||||
|
private String getScriptLanguageWithDefault() {
|
||||||
|
String lang = getScriptLanguage();
|
||||||
|
if (StringUtils.isNotEmpty(lang)) {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
return DEFAULT_SCRIPT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate variables to be passed to scripts
|
||||||
|
* @param bindings Bindings
|
||||||
|
*/
|
||||||
|
protected void populateBindings(Bindings bindings) {
|
||||||
|
final String label = getName();
|
||||||
|
final String fileName = getFilename();
|
||||||
|
final String scriptParameters = getParameters();
|
||||||
|
// Use actual class name for log
|
||||||
|
final Logger elementLogger = LoggerFactory.getLogger(getClass().getName()+"."+getName());
|
||||||
|
bindings.put("log", elementLogger); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
bindings.put("Label", label); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
bindings.put("FileName", fileName); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
bindings.put("Parameters", scriptParameters); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
String[] args=JOrphanUtils.split(scriptParameters, " ");//$NON-NLS-1$
|
||||||
|
bindings.put("args", args); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
// Add variables for access to context and variables
|
||||||
|
JMeterContext jmctx = JMeterContextService.getContext();
|
||||||
|
bindings.put("ctx", jmctx); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
JMeterVariables vars = jmctx.getVariables();
|
||||||
|
bindings.put("vars", vars); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
Properties props = JMeterUtils.getJMeterProperties();
|
||||||
|
bindings.put("props", props); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
// For use in debugging:
|
||||||
|
bindings.put("OUT", System.out); // NOSONAR $NON-NLS-1$ (this name is fixed)
|
||||||
|
|
||||||
|
// Most subclasses will need these:
|
||||||
|
Sampler sampler = jmctx.getCurrentSampler();
|
||||||
|
bindings.put("sampler", sampler); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
SampleResult prev = jmctx.getPreviousResult();
|
||||||
|
bindings.put("prev", prev); // $NON-NLS-1$ (this name is fixed)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will run inline script or file script with special behaviour for file script:
|
||||||
|
* - If ScriptEngine implements Compilable script will be compiled and cached
|
||||||
|
* - If not if will be run
|
||||||
|
* @param scriptEngine ScriptEngine
|
||||||
|
* @param pBindings {@link Bindings} might be null
|
||||||
|
* @return Object returned by script
|
||||||
|
* @throws IOException when reading the script fails
|
||||||
|
* @throws ScriptException when compiling or evaluation of the script fails
|
||||||
|
*/
|
||||||
|
protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings pBindings)
|
||||||
|
throws IOException, ScriptException {
|
||||||
|
Bindings bindings = pBindings;
|
||||||
|
if (bindings == null) {
|
||||||
|
bindings = scriptEngine.createBindings();
|
||||||
|
}
|
||||||
|
populateBindings(bindings);
|
||||||
|
File scriptFile = new File(getFilename());
|
||||||
|
// Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws
|
||||||
|
// "java.lang.Error: unimplemented"
|
||||||
|
boolean supportsCompilable = scriptEngine instanceof Compilable
|
||||||
|
&& !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$
|
||||||
|
|
||||||
|
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
// 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题
|
||||||
|
// 同时隔离在 beanshell 中访问由系统类加载器加载的其他类
|
||||||
|
NewDriver.setContextClassLoader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!StringUtils.isEmpty(getFilename())) {
|
||||||
|
if (scriptFile.exists() && scriptFile.canRead()) {
|
||||||
|
if (supportsCompilable) {
|
||||||
|
String newCacheKey = getScriptLanguage() + "#" + // $NON-NLS-1$
|
||||||
|
scriptFile.getAbsolutePath() + "#" + // $NON-NLS-1$
|
||||||
|
scriptFile.lastModified();
|
||||||
|
CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey);
|
||||||
|
if (compiledScript == null) {
|
||||||
|
synchronized (compiledScriptsCache) {
|
||||||
|
compiledScript = compiledScriptsCache.get(newCacheKey);
|
||||||
|
if (compiledScript == null) {
|
||||||
|
// TODO Charset ?
|
||||||
|
try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile),
|
||||||
|
(int) scriptFile.length())) {
|
||||||
|
compiledScript = ((Compilable) scriptEngine).compile(fileReader);
|
||||||
|
compiledScriptsCache.put(newCacheKey, compiledScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compiledScript.eval(bindings);
|
||||||
|
} else {
|
||||||
|
// TODO Charset ?
|
||||||
|
try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile),
|
||||||
|
(int) scriptFile.length())) {
|
||||||
|
return scriptEngine.eval(fileReader, bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ScriptException("Script file '" + scriptFile.getAbsolutePath()
|
||||||
|
+ "' does not exist or is unreadable for element:" + getName());
|
||||||
|
}
|
||||||
|
} else if (!StringUtils.isEmpty(getScript())) {
|
||||||
|
if (supportsCompilable &&
|
||||||
|
!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) {
|
||||||
|
computeScriptMD5();
|
||||||
|
CompiledScript compiledScript = compiledScriptsCache.get(this.scriptMd5);
|
||||||
|
if (compiledScript == null) {
|
||||||
|
synchronized (compiledScriptsCache) {
|
||||||
|
compiledScript = compiledScriptsCache.get(this.scriptMd5);
|
||||||
|
if (compiledScript == null) {
|
||||||
|
compiledScript = ((Compilable) scriptEngine).compile(getScript());
|
||||||
|
compiledScriptsCache.put(this.scriptMd5, compiledScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compiledScript.eval(bindings);
|
||||||
|
} else {
|
||||||
|
return scriptEngine.eval(getScript(), bindings);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ScriptException("Both script file and script text are empty for element:" + getName());
|
||||||
|
}
|
||||||
|
} catch (ScriptException ex) {
|
||||||
|
Throwable rootCause = ex.getCause();
|
||||||
|
if(isStopCondition(rootCause)) {
|
||||||
|
throw (RuntimeException) ex.getCause();
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// 将当前类加载器设置回来,避免加载无法加载到系统类加载加载类
|
||||||
|
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean true if element is not compilable or if compilation succeeds
|
||||||
|
* @throws IOException if script is missing
|
||||||
|
* @throws ScriptException if compilation fails
|
||||||
|
*/
|
||||||
|
public boolean compile()
|
||||||
|
throws ScriptException, IOException {
|
||||||
|
String lang = getScriptLanguageWithDefault();
|
||||||
|
ScriptEngine scriptEngine = getInstance().getEngineByName(lang);
|
||||||
|
boolean supportsCompilable = scriptEngine instanceof Compilable
|
||||||
|
&& !("bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName())); // NOSONAR // $NON-NLS-1$
|
||||||
|
if(!supportsCompilable) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!StringUtils.isEmpty(getScript())) {
|
||||||
|
try {
|
||||||
|
((Compilable) scriptEngine).compile(getScript());
|
||||||
|
return true;
|
||||||
|
} catch (ScriptException e) { // NOSONAR
|
||||||
|
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File scriptFile = new File(getFilename());
|
||||||
|
try (BufferedReader fileReader = new BufferedReader(new FileReader(scriptFile),
|
||||||
|
(int) scriptFile.length())) {
|
||||||
|
try {
|
||||||
|
((Compilable) scriptEngine).compile(fileReader);
|
||||||
|
return true;
|
||||||
|
} catch (ScriptException e) { // NOSONAR
|
||||||
|
logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compute MD5 if it is null
|
||||||
|
*/
|
||||||
|
private void computeScriptMD5() {
|
||||||
|
// compute the md5 of the script if needed
|
||||||
|
if(scriptMd5 == null) {
|
||||||
|
scriptMd5 = DigestUtils.md5Hex(getScript());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the cacheKey
|
||||||
|
*/
|
||||||
|
public String getCacheKey() {
|
||||||
|
return cacheKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cacheKey the cacheKey to set
|
||||||
|
*/
|
||||||
|
public void setCacheKey(String cacheKey) {
|
||||||
|
this.cacheKey = cacheKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.apache.jmeter.testelement.TestStateListener#testStarted()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void testStarted() {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.apache.jmeter.testelement.TestStateListener#testStarted(java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void testStarted(String host) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.apache.jmeter.testelement.TestStateListener#testEnded()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void testEnded() {
|
||||||
|
testEnded("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.apache.jmeter.testelement.TestStateListener#testEnded(java.lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void testEnded(String host) {
|
||||||
|
compiledScriptsCache.clear();
|
||||||
|
this.scriptMd5 = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScriptLanguage() {
|
||||||
|
return scriptLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScriptLanguage(String s) {
|
||||||
|
scriptLanguage = s;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,334 +0,0 @@
|
||||||
//
|
|
||||||
// Source code recreated from a .class file by IntelliJ IDEA
|
|
||||||
// (powered by Fernflower decompiler)
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.apache.jmeter.visualizers.backend;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.LongAdder;
|
|
||||||
import java.util.concurrent.locks.LockSupport;
|
|
||||||
import org.apache.jmeter.config.Arguments;
|
|
||||||
import org.apache.jmeter.engine.util.NoThreadClone;
|
|
||||||
import org.apache.jmeter.samplers.Remoteable;
|
|
||||||
import org.apache.jmeter.samplers.SampleEvent;
|
|
||||||
import org.apache.jmeter.samplers.SampleListener;
|
|
||||||
import org.apache.jmeter.samplers.SampleResult;
|
|
||||||
import org.apache.jmeter.testelement.AbstractTestElement;
|
|
||||||
import org.apache.jmeter.testelement.TestStateListener;
|
|
||||||
import org.apache.jmeter.testelement.property.TestElementProperty;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
// 当前上下文类加载为 NewDriver 的类加载器,这里使用系统类加载器加载 APIBackendListenerClient
|
|
||||||
|
|
||||||
public class BackendListener extends AbstractTestElement implements Backend, Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BackendListener.class);
|
|
||||||
public static final String CLASSNAME = "classname";
|
|
||||||
public static final String QUEUE_SIZE = "QUEUE_SIZE";
|
|
||||||
private static final Object LOCK = new Object();
|
|
||||||
public static final String ARGUMENTS = "arguments";
|
|
||||||
private Class<?> clientClass;
|
|
||||||
public static final String DEFAULT_QUEUE_SIZE = "5000";
|
|
||||||
private static final transient SampleResult FINAL_SAMPLE_RESULT = new SampleResult();
|
|
||||||
private static final Map<String, BackendListener.ListenerClientData> queuesByTestElementName = new ConcurrentHashMap();
|
|
||||||
private transient String myName;
|
|
||||||
private transient BackendListener.ListenerClientData listenerClientData;
|
|
||||||
|
|
||||||
public BackendListener() {
|
|
||||||
this.setArguments(new Arguments());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object clone() {
|
|
||||||
BackendListener clone = (BackendListener)super.clone();
|
|
||||||
clone.clientClass = this.clientClass;
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Class<?> initClass() {
|
|
||||||
String name = this.getClassname().trim();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 当前上下文类加载为 NewDriver 的类加载器,这里使用系统类加载器加载 APIBackendListenerClient
|
|
||||||
return Class.forName(name, false, this.getClass().getClassLoader());
|
|
||||||
} catch (Exception var3) {
|
|
||||||
log.error("{}\tException initialising: {}", new Object[]{this.whoAmI(), name, var3});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String whoAmI() {
|
|
||||||
return Thread.currentThread().getName() + "@" + Integer.toHexString(this.hashCode()) + "-" + this.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sampleOccurred(SampleEvent event) {
|
|
||||||
Arguments args = this.getArguments();
|
|
||||||
BackendListenerContext context = new BackendListenerContext(args);
|
|
||||||
SampleResult sr = this.listenerClientData.client.createSampleResult(context, event.getResult());
|
|
||||||
if (sr == null) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("{} => Dropping SampleResult: {}", this.getName(), event.getResult());
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (!this.listenerClientData.queue.offer(sr)) {
|
|
||||||
this.listenerClientData.queueWaits.add(1L);
|
|
||||||
long t1 = System.nanoTime();
|
|
||||||
this.listenerClientData.queue.put(sr);
|
|
||||||
long t2 = System.nanoTime();
|
|
||||||
this.listenerClientData.queueWaitTime.add(t2 - t1);
|
|
||||||
}
|
|
||||||
} catch (Exception var9) {
|
|
||||||
log.error("sampleOccurred, failed to queue the sample", var9);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void sendToListener(BackendListenerClient backendListenerClient, BackendListenerContext context, List<SampleResult> sampleResults) {
|
|
||||||
if (!sampleResults.isEmpty()) {
|
|
||||||
backendListenerClient.handleSampleResults(sampleResults, context);
|
|
||||||
sampleResults.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BackendListenerClient createBackendListenerClientImpl(Class<?> clientClass) {
|
|
||||||
if (clientClass == null) {
|
|
||||||
return new BackendListener.ErrorBackendListenerClient();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return (BackendListenerClient)clientClass.getDeclaredConstructor().newInstance();
|
|
||||||
} catch (Exception var2) {
|
|
||||||
log.error("Exception creating: {}", clientClass, var2);
|
|
||||||
return new BackendListener.ErrorBackendListenerClient();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStarted() {
|
|
||||||
this.testStarted("local");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStarted(String host) {
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("{}\ttestStarted({})", this.whoAmI(), host);
|
|
||||||
}
|
|
||||||
|
|
||||||
String size = this.getQueueSize();
|
|
||||||
|
|
||||||
int queueSize;
|
|
||||||
try {
|
|
||||||
queueSize = Integer.parseInt(size);
|
|
||||||
} catch (NumberFormatException var12) {
|
|
||||||
log.warn("Invalid queue size '{}' defaulting to {}", size, "5000");
|
|
||||||
queueSize = Integer.parseInt("5000");
|
|
||||||
}
|
|
||||||
|
|
||||||
Object var4 = LOCK;
|
|
||||||
synchronized(LOCK) {
|
|
||||||
this.myName = this.getName();
|
|
||||||
this.listenerClientData = (BackendListener.ListenerClientData)queuesByTestElementName.get(this.myName);
|
|
||||||
if (this.listenerClientData == null) {
|
|
||||||
this.clientClass = this.initClass();
|
|
||||||
BackendListenerClient backendListenerClient = createBackendListenerClientImpl(this.clientClass);
|
|
||||||
BackendListenerContext context = new BackendListenerContext((Arguments)this.getArguments().clone());
|
|
||||||
this.listenerClientData = new BackendListener.ListenerClientData();
|
|
||||||
this.listenerClientData.queue = new ArrayBlockingQueue(queueSize);
|
|
||||||
this.listenerClientData.queueWaits = new LongAdder();
|
|
||||||
this.listenerClientData.queueWaitTime = new LongAdder();
|
|
||||||
this.listenerClientData.latch = new CountDownLatch(1);
|
|
||||||
this.listenerClientData.client = backendListenerClient;
|
|
||||||
if (log.isInfoEnabled()) {
|
|
||||||
log.info("{}: Starting worker with class: {} and queue capacity: {}", new Object[]{this.getName(), this.clientClass, this.getQueueSize()});
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendListener.Worker worker = new BackendListener.Worker(backendListenerClient, (Arguments)this.getArguments().clone(), this.listenerClientData);
|
|
||||||
worker.setDaemon(true);
|
|
||||||
worker.start();
|
|
||||||
if (log.isInfoEnabled()) {
|
|
||||||
log.info("{}: Started worker with class: {}", this.getName(), this.clientClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
backendListenerClient.setupTest(context);
|
|
||||||
} catch (Exception var10) {
|
|
||||||
throw new IllegalStateException("Failed calling setupTest", var10);
|
|
||||||
}
|
|
||||||
|
|
||||||
queuesByTestElementName.put(this.myName, this.listenerClientData);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenerClientData.instanceCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEnded(String host) {
|
|
||||||
Object var2 = LOCK;
|
|
||||||
synchronized(LOCK) {
|
|
||||||
BackendListener.ListenerClientData listenerClientDataForName = (BackendListener.ListenerClientData)queuesByTestElementName.get(this.myName);
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("testEnded called on instance {}#{}", this.myName, listenerClientDataForName.instanceCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listenerClientDataForName != null) {
|
|
||||||
listenerClientDataForName.instanceCount--;
|
|
||||||
if (listenerClientDataForName.instanceCount > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
queuesByTestElementName.remove(this.myName);
|
|
||||||
} else {
|
|
||||||
log.error("No listener client data found for BackendListener {}", this.myName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.listenerClientData.queue.put(FINAL_SAMPLE_RESULT);
|
|
||||||
} catch (Exception var6) {
|
|
||||||
log.warn("testEnded() with exception: {}", var6.getMessage(), var6);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.listenerClientData.queueWaits.longValue() > 0L) {
|
|
||||||
log.warn("QueueWaits: {}; QueueWaitTime: {} (nanoseconds), you may need to increase queue capacity, see property 'backend_queue_capacity'", this.listenerClientData.queueWaits, this.listenerClientData.queueWaitTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.listenerClientData.latch.await();
|
|
||||||
BackendListenerContext context = new BackendListenerContext(this.getArguments());
|
|
||||||
this.listenerClientData.client.teardownTest(context);
|
|
||||||
} catch (Exception var5) {
|
|
||||||
throw new IllegalStateException("Failed calling teardownTest", var5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEnded() {
|
|
||||||
this.testEnded("local");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sampleStarted(SampleEvent e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sampleStopped(SampleEvent e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setArguments(Arguments args) {
|
|
||||||
args.removeArgument("useRegexpForSamplersList", "false");
|
|
||||||
this.setProperty(new TestElementProperty("arguments", args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Arguments getArguments() {
|
|
||||||
return (Arguments)this.getProperty("arguments").getObjectValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClassname(String classname) {
|
|
||||||
this.setProperty("classname", classname);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClassname() {
|
|
||||||
return this.getPropertyAsString("classname");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQueueSize(String queueSize) {
|
|
||||||
this.setProperty("QUEUE_SIZE", queueSize, "5000");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQueueSize() {
|
|
||||||
return this.getPropertyAsString("QUEUE_SIZE", "5000");
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ErrorBackendListenerClient extends AbstractBackendListenerClient {
|
|
||||||
ErrorBackendListenerClient() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleSampleResults(List<SampleResult> sampleResults, BackendListenerContext context) {
|
|
||||||
BackendListener.log.warn("ErrorBackendListenerClient#handleSampleResult called, noop");
|
|
||||||
Thread.yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Worker extends Thread {
|
|
||||||
private final BackendListener.ListenerClientData listenerClientData;
|
|
||||||
private final BackendListenerContext context;
|
|
||||||
private final BackendListenerClient backendListenerClient;
|
|
||||||
|
|
||||||
private Worker(BackendListenerClient backendListenerClient, Arguments arguments, BackendListener.ListenerClientData listenerClientData) {
|
|
||||||
this.listenerClientData = listenerClientData;
|
|
||||||
arguments.addArgument("TestElement.name", this.getName());
|
|
||||||
this.context = new BackendListenerContext(arguments);
|
|
||||||
this.backendListenerClient = backendListenerClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
boolean isDebugEnabled = BackendListener.log.isDebugEnabled();
|
|
||||||
ArrayList sampleResults = new ArrayList(this.listenerClientData.queue.size());
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
boolean endOfLoop = false;
|
|
||||||
|
|
||||||
while(!endOfLoop) {
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
BackendListener.log.debug("Thread: {} taking SampleResult from queue: {}", Thread.currentThread().getName(), this.listenerClientData.queue.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
SampleResult sampleResult = (SampleResult)this.listenerClientData.queue.take();
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
BackendListener.log.debug("Thread: {} took SampleResult: {}, isFinal: {}", new Object[]{Thread.currentThread().getName(), sampleResult, sampleResult == BackendListener.FINAL_SAMPLE_RESULT});
|
|
||||||
}
|
|
||||||
|
|
||||||
while(!(endOfLoop = sampleResult == BackendListener.FINAL_SAMPLE_RESULT) && sampleResult != null) {
|
|
||||||
sampleResults.add(sampleResult);
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
BackendListener.log.debug("Thread: {} polling from queue: {}", Thread.currentThread().getName(), this.listenerClientData.queue.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
sampleResult = (SampleResult)this.listenerClientData.queue.poll();
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
BackendListener.log.debug("Thread: {} took from queue: {}, isFinal: {}", new Object[]{Thread.currentThread().getName(), sampleResult, sampleResult == BackendListener.FINAL_SAMPLE_RESULT});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDebugEnabled) {
|
|
||||||
BackendListener.log.debug("Thread: {} exiting with FINAL EVENT: {}, null: {}", new Object[]{Thread.currentThread().getName(), sampleResult == BackendListener.FINAL_SAMPLE_RESULT, sampleResult == null});
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendListener.sendToListener(this.backendListenerClient, this.context, sampleResults);
|
|
||||||
if (!endOfLoop) {
|
|
||||||
LockSupport.parkNanos(100L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException var8) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendListener.sendToListener(this.backendListenerClient, this.context, sampleResults);
|
|
||||||
BackendListener.log.info("Worker ended");
|
|
||||||
} finally {
|
|
||||||
this.listenerClientData.latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class ListenerClientData {
|
|
||||||
private BackendListenerClient client;
|
|
||||||
private BlockingQueue<SampleResult> queue;
|
|
||||||
private LongAdder queueWaits;
|
|
||||||
private LongAdder queueWaitTime;
|
|
||||||
private int instanceCount;
|
|
||||||
private CountDownLatch latch;
|
|
||||||
|
|
||||||
private ListenerClientData() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue