JMeter Gui – TestElement约定
在编写任何JMeter组件时,必须注意某些特定的约定——如果JMeter环境中正确地运行JMeter组件,那么它将会运行。本部分描述了组件的GUI部分必须满足的约定。
JMeter中的GUI代码严格地与测试元件代码(这里指逻辑控制代码,下同)分离。因此,当编写一个组件时,将会有一个用于测试元件的类,另一个用于GUI表示。GUI类是无状态的,因此它不应该挂在对测试元件的引用上(尽管有例外)。
GUI元素应该继承适当的抽象类:
- AbstractSamplerGui
- AbstractAssertionGui
- AbstractConfigGui
- AbstractControllerGui
- AbstractPostProcessorGui
- AbstractPreProcessorGui
- AbstractVisualizer
- AbstractTimerGui
- ……
这些抽象类提供了大量的管道工作,不用扩展,用来代替直接实现接口。
已经继承了适当的GUI类,剩下要遵循以下步骤:
1、实现getResourceLabel()
该方法返回资源的标题/名称。
2、创建GUI。无论使用什么样式,都要布局GUI。类最终要继承JPanel,因此布局必须在的类自己的ContentPane中。不要通过动作和事件将·GUI元素连接到测试元件类。让swing的内部模型尽可能多地挂在所有数据上。
(1)一些标准的GUI内容应该添加到所有JMeter GUI组件中:
a、调用setBorder(makeBorder())。这将给它提供标准的JMeter边框。
b、通过makeTitlePanel()添加标题窗格。通常这是添加到GUI中的第一件事,应该在一个垂直布局方案中完成,或者使用JMeter的VerticalLayout类。下面是一个示例init()方法:
private void init() {
setLayout(new BorderLayout());
setBorder(makeBorder());
Box box = Box.createVerticalBox();
box.add(makeTitlePanel());
box.add(makeSourcePanel());
add(box,BorderLayout.NORTH);
add(makeParameterPanel(),BorderLayout.CENTER);
}
3、实现public void configure(TestElement el)
(1)一定调用super.configure(e),这将填充一些数据,比如元素的名称
(2)使用此方法将数据设置为GUI元素。例子
public void configure(TestElement el) {
super.configure(el);
useHeaders.setSelected(el.getPropertyAsBoolean(RegexExtractor.USEHEADERS));
useBody.setSelected(!el.getPropertyAsBoolean(RegexExtractor.USEHEADERS));
regexField.setText(el.getPropertyAsString(RegexExtractor.REGEX));
templateField.setText(el.getPropertyAsString(RegexExtractor.TEMPLATE));
defaultField.setText(el.getPropertyAsString(RegexExtractor.DEFAULT));
matchNumberField.setText(el.getPropertyAsString(RegexExtractor.MATCH_NUM));
refNameField.setText(el.getPropertyAsString(RegexExtractor.REFNAME));
}
(3)实现public void modifyTestElement(TestElement e),这是将数据从GUI元素移动到TestElement的地方。这是前一种方法的逻辑逆操作
a、调用super.configureTestElement(e),处理一些默认数据
b、例子
public void modifyTestElement(TestElement e) {
super.configureTestElement(e);
e.setProperty(new BooleanProperty(
RegexExtractor.USEHEADERS,
useHeaders.isSelected()));
e.setProperty(RegexExtractor.MATCH_NUMBER,
matchNumberField.getText());
if (e instanceof RegexExtractor) {
RegexExtractor regex = (RegexExtractor)e;
regex.setRefName(refNameField.getText());
regex.setRegex(regexField.getText());
regex.setTemplate(templateField.getText());
regex.setDefaultValue(defaultField.getText());
}
}
(4)实现public TestElement createTestElement(),该方法应该创建TestElement类的一个新实例,然后将其传递modifyTestElement(TestElement)方法
public TestElement createTestElement() {
RegexExtractor extractor = new RegexExtractor();
modifyTestElement(extractor);
return extractor;
}
不能保留对测试元件的引用的原因是因为JMeter的测试元件重用了多个GUI类对象的实例。这样可以节省很多内存。它还使得编写新组件的GUI部分变得非常容易。您仍然需要与Swing中的布局进行斗争,但是不必担心如何创建正确的事件和从GUI元素中获取数据放入测试元件中。JMeter知道什么时候调用自定义配置,以及可以用一种非常简单的方式来完成它的修改。
总结:
GUI与测试元件分离:GUI部分通过继承各种组件GUI抽象类,测试元件部分通过继承组件抽象类和实现各种接口方式从而实现不同组件的内部逻辑控制;
GUI与测试元件不分离:与分离方法的区别在于不单独实现GUI部分,在测试元件部分通过实现TestBean接口方法从而实现对GUI界面的配置。(TestBean是一个空接口:Marker interface to tell JMeter to make a Test Bean Gui for the class)
#jmeter插件分类
GUI的组件主要包括10大组件
- ThreadGroup(线程组)
- Test Fragment(测试片段)
- logic Controller(逻辑控制器)
- Config element(配置元件)
- Timer(定时器)
- pre processor(前置处理器)
- post processor(后置处理器)
- Sampler(测试抽样器)
- Assertion(断言)
- Listener(监听器);
非GUI组件
- Function(函数)
#JMeter插件式组件实现
TestElement是所有组件的最基本单元,组件类都是TestElement类的子类
依据上面介绍,组件的实现分两部分:GUI和TestElement
##GUI部分的实现
继承实现对应的抽象类
抽象类 | 继承的类 | 对应组件备注 |
AbstractAssertionGui | AbstractScopedJMeterGuiComponent | 断言 |
AbstractConfigGui | AbstractJMeterGuiComponent | 配置 |
AbstractControllerGui | AbstractJMeterGuiComponent | 控制 |
AbstractPostProcessorGui | AbstractScopedJMeterGuiComponent | 后置处理器 |
AbstractPreProcessorGui | AbstractJMeterGuiComponent | 前置处理器 |
AbstractSamplerGui | AbstractJMeterGuiComponent | 取样器 |
AbstractThreadGroupGui | AbstractJMeterGuiComponent | 线程组 |
AbstractTimerGui | AbstractJMeterGuiComponent | 定时器 |
AbstractListenerGui | AbstractJMeterGuiComponent | 监听器 |
AbstractVisualizer | AbstractListenerGui | 组件依赖 |
AbstractScopedJMeterGuiComponent | AbstractJMeterGuiComponent | 组件依赖 |
AbstractJMeterGuiComponent | JPanel | 组件依赖 |
AbstractFunction | Function | 组件依赖 |
AbstractJMeterGuiComponent | JPanel | 组件依赖 |
##逻辑控制实现
###Assertion(断言)组件
Assertion(断言)组件通过继承AbstractTestElement抽象类(或者AbstractTestElement子类),实现Assertion接口的getResult(SampleResult result)方法对结果内容进行判断,从而实现断言方法,用于对Sampler组件所产生的抽样采集结果内容进行断言。
比如:
public class XMLSchemaAssertion extends AbstractTestElement implements Serializable, Assertion {
……
@Override
public AssertionResult getResult(SampleResult response) {
AssertionResult result = new AssertionResult(getName());
……
return result;
}
……
}
再比如:
public abstract class AbstractScopedTestElement extends AbstractTestElement {...}
public abstract class AbstractScopedAssertion extends AbstractScopedTestElement{...}
public class DurationAssertion extends AbstractScopedAssertion implements Serializable, Assertion {
public static final String DURATION_KEY = "DurationAssertion.duration";
@Override
public AssertionResult getResult(SampleResult response) {
……
return result;
}
private long getAllowedDuration() {
return getPropertyAsLong(DURATION_KEY);
}
public void setAllowedDuration(long duration) {
setProperty(DURATION_KEY, duration);
}
}
###Config(配置元件)组件
Config(配置元件)组件相对其他组件比较特殊,通过继承ConfigTestElement类或只需要GUI部分的实现即可完成本体任务
public class CSVDataSet extends ConfigTestElement
implements TestBean, LoopIterationListener, NoConfigMerge {
private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class);
……
}
###ThreadGroup(线程组)组件
ThreadGroup(线程组)组件继承AbstractThreadGroup抽象类,通过重写各类控制方法来达到控制和协调各线程(虚拟用户)的行为,线程组是构建一个性能测试模型的最基本组件。
public class ThreadGroupTest extends AbstractThreadGroup {
private static final long serialVersionUID = 1L;
@Override
public void threadFinished(JMeterThread arg0) {}
@Override
public int numberOfActiveThreads() {return 0;}
@Override
public void start(int arg0, ListenerNotifier arg1, ListedHashTree arg2, StandardJMeterEngine arg3) {}
@Override
public void stop() {}
@Override
public boolean stopThread(String arg0, boolean arg1) {return false;}
@Override
public void tellThreadsToStop() {}
@Override
public boolean verifyThreadsStopped() {return false;}
@Override
public void waitThreadsStopped() {}
}
###Timer(定时器)组件
Timer(定时器)组件通过继承AbstractTestElement抽象类,实现Timer接口的delay()方法来实现对时间的控制
public class TimerTest extends AbstractTestElement implements Timer{
private static final long serialVersionUID = 1L;
@Override
public long delay() {
return 0;
}
}
控制线程延时,即用来模仿思考时间(ThinkTime)或键盘时间(KeyTime);
控制线程行为,如SyncTimer(同步计时器),就是内部利用CyclicBarrier来控制阻塞和释放全部运行线程的逻辑行为,从而达到“集合点”的目的。
public class SyncTimer extends AbstractTestElement implements Timer, Serializable, TestBean, TestStateListener, ThreadListener {
private static final Logger log = LoggerFactory.getLogger(SyncTimer.class);
private static class BarrierWrapper implements Cloneable {
private CyclicBarrier barrier;
public BarrierWrapper() {
this.barrier = null;
}
public BarrierWrapper(int parties) {
this.barrier = new CyclicBarrier(parties);
}
public synchronized void setup(int parties) {
if(this.barrier== null) {
this.barrier = new CyclicBarrier(parties);
}
}
public int await() throws InterruptedException, BrokenBarrierException{
return barrier.await();
}
public int await(long timeout, TimeUnit timeUnit) throws InterruptedException, BrokenBarrierException, TimeoutException {
return barrier.await(timeout, timeUnit);
}
public void reset() {
barrier.reset();
}
@Override
protected Object clone() {
BarrierWrapper barrierWrapper= null;
try {
barrierWrapper = (BarrierWrapper) super.clone();
barrierWrapper.barrier = this.barrier;
} catch (CloneNotSupportedException e) {
}
return barrierWrapper;
}
}
……
public void threadStarted() {
if(getGroupSize() == 0) {
int numThreadsInGroup = JMeterContextService.getContext().getThreadGroup().getNumThreads();
// Unique Barrier creation ensured by synchronized setup
this.barrier.setup(numThreadsInGroup);
}
}
……
}
###pre processor(前置处理器)组件
pre processor(前置处理器)组件通过继承AbstractTestElement抽象类,实现PreProcessor接口的process ()方法控制逻辑
比如:BeanShellPreProcessor
public class BeanShellPreProcessor extends BeanShellTestElement
implements Cloneable, PreProcessor, TestBean
{
private static final Logger log = LoggerFactory.getLogger(BeanShellPreProcessor.class);
private static final long serialVersionUID = 5;
// can be specified in jmeter.properties
private static final String INIT_FILE = "beanshell.preprocessor.init"; //$NON-NLS-1$
@Override
protected String getInitFileProperty() {
return INIT_FILE;
}
@Override
public void process(){
final BeanShellInterpreter bshInterpreter = getBeanShellInterpreter();
if (bshInterpreter == null) {
log.error("BeanShell not found");
return;
}
JMeterContext jmctx = JMeterContextService.getContext();
Sampler sam = jmctx.getCurrentSampler();
try {
// Add variables for access to context and variables
bshInterpreter.set("sampler", sam);//$NON-NLS-1$
processFileOrScript(bshInterpreter);
} catch (JMeterException e) {
if (log.isWarnEnabled()) {
log.warn("Problem in BeanShell script. {}", e.toString());
}
}
}
@Override
public Object clone() {
return super.clone();
}
}
作用:对线程上下文中的当前Sampler和前一个SampleResult进行识别和判断。
###post processor(后置处理器)组件
post processor(后置处理器)组件通过继承AbstractTestElement抽象类,实现PostProcessor接口的process ()方法控制逻辑
public abstract class AbstractScopedTestElement extends AbstractTestElement {……}
public class RegexExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable {
……
@Override
public void process() {}
……
}
作用:对线程上下文中的前一个SampleResult进行识别和判断。
###Controller(控制器)组件
Controller(控制器)组件通过继承GenericController类
比如foreach,重写isDone、next、nextIsNull、getIterCount、reInitialize、initialize、triggerEndOfLoop
public class ForeachController extends GenericController implements Serializable {
public ForeachController() {}
……
@Override
public boolean isDone() {}
@Override
public Sampler next() {}
@Override
protected Sampler nextIsNull() throws NextIsNullException {}
@Override
protected int getIterCount() {return loopCount+1;}
@Override
protected void reInitialize() {}
@Override
public void triggerEndOfLoop() {}
@Override
public void initialize() {}
}
###Sampler(测试抽样器)组件
Sampler(测试抽样器)组件继承AbstractSampler抽象类,通过重写SampleResult sample(Entry e)方法,实现测试过程以及测试结果的采集功能。
public class DebugSampler extends AbstractSampler implements TestBean {
……
@Override
public SampleResult sample(Entry e) {}
……
}
###Listener(监听器)
直接继承AbstractTestElement,实现sampleListener或Visualizer等接口方法
public class Summariser extends AbstractTestElement
implements Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable {
……
@Override
@SuppressWarnings("SynchronizeOnNonFinalField")
public void sampleOccurred(SampleEvent e) {
SampleResult s = e.getResult();
if(IGNORE_TC_GENERATED_SAMPLERESULT && TransactionController.isFromTransactionController(s)) {
return;
}
long now = System.currentTimeMillis() / 1000;// in seconds
SummariserRunningSample myDelta = null;
SummariserRunningSample myTotal = null;
boolean reportNow = false;
synchronized (myTotals) {
if (s != null) {
myTotals.delta.addSample(s);
}
if ((now > myTotals.last + INTERVAL_WINDOW) && (now % INTERVAL <= INTERVAL_WINDOW)) {
reportNow = true;
// copy the data to minimise the synch time
myDelta = new SummariserRunningSample(myTotals.delta);
myTotals.moveDelta();
myTotal = new SummariserRunningSample(myTotals.total);
myTotals.last = now; // stop double-reporting
}
}
if (reportNow) {
formatAndWriteToLog(myName, myDelta, "+");
// Only if we have updated them
if (myTotal != null && myDelta != null &&myTotal.getNumSamples() != myDelta.getNumSamples()) { // NOSONAR
formatAndWriteToLog(myName, myTotal, "=");
}
}
}
……
}
可以从实际用途上将其分为两大类Report (报告)和Visualizers(监视器)。
Report (报告)继承AbstractListenerElement抽象类,通过实现sampleOccurred(SampleEvent e)方法,对所有采集事件中所产生的SampleResult进行处理,从而生成报告;
public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
TestStateListener, Remoteable, NoThreadClone {
……
@Override
public void sampleOccurred(SampleEvent event) {
SampleResult result = event.getResult();
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
String savee = CSVSaveService.resultToDelimitedString(event);
out.println(savee);
}
} catch (Exception err) {
log.error("Error trying to record a sample", err); // should throw exception back to caller
}
}
}
if(summariser != null) {
summariser.sampleOccurred(event);
}
}
……
}
Visualizers(监视器)主要用于特定的监控任务,比如监控系统资源利用率的组件,与Report的区别在于Visualizers必须继承一个 ResultCollector类,并在收集器中通过开启额外线程方式完成自定义的数据采集。
public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, TestStateListener, Remoteable, NoThreadClone {……}
比如标准插件中自定义一个JMXMonCollector
public class CorrectedResultCollector extends ResultCollector {}
public class JMXMonCollector extends CorrectedResultCollector implements Runnable, JMXMonSampleGenerator {……}
注意:对于一个需要配置的组件类则需要实现ConfigMergabilityIndicator接口的public boolean applies(ConfigTestElement configElement)方法,用来指明哪些Config组件可以用来对其进行配置
这里参考DebugSampler的源代码如下:
public class DebugSampler extends AbstractSampler implements TestBean {
……
private static final Set<String> APPLIABLE_CONFIG_CLASSES = new HashSet<>(
Arrays.asList("org.apache.jmeter.config.gui.SimpleConfigGui"));
@Override
public boolean applies(ConfigTestElement configElement) {
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
return APPLIABLE_CONFIG_CLASSES.contains(guiClass);
}
……
}
以上代码指明SimpleConfigGui配置元件可以对DebugSampler组件进行配置。
##函数
Function(函数)是非GUI组件,这类组件的实现比较简单,而且功能比较单一,只需要继承相应的抽象类即可~
写一个求字符长度的函数:StrLen
public class StrLen extends AbstractFunction {
private static final List<String> desc = new LinkedList<String>();
private static final String KEY = "__strLen";
static {
desc.add("String to measure length");
desc.add("Name of variable in which to store the result (optional)");
}
private Object[] values;
public StrLen() {
}
@Override
public synchronized String execute(SampleResult previousResult, Sampler currentSampler)
throws InvalidVariableException {
JMeterVariables vars = getVariables();
Integer len = ((CompoundVariable) values[0]).execute().length();
if (vars != null && values.length > 1) {
String varName = ((CompoundVariable) values[1]).execute().trim();
vars.put(varName, len.toString());
}
return len.toString();
}
@Override
public synchronized void setParameters(Collection<CompoundVariable> parameters) throws InvalidVariableException {
checkMinParameterCount(parameters, 1);
values = parameters.toArray();
}
@Override
public String getReferenceKey() {
return KEY;
}
public List<String> getArgumentDesc() {
return desc;
}
}
#附录
jmeter一些GUI类继承关系
GUI类 | 继承的类 |
ResultActionGui | AbstractPostProcessorGui |
ResultSaverGui | AbstractListenerGui |
SummariserGui | AbstractListenerGui |
TestBeanGUI | AbstractJMeterGuiComponent |
ThreadGroupGui | AbstractThreadGroupGui |
SetupThreadGroupGui | ThreadGroupGui |
PostThreadGroupGui | ThreadGroupGui |
WorkBenchGui | AbstractJMeterGuiComponent |
AssertionGui | AbstractAssertionGui |
BeanShellAssertionGui | AbstractAssertionGui |
urationAssertionGui | AbstractAssertionGui |
HTMLAssertionGui | AbstractAssertionGui |
MD5HexAssertionGUI | AbstractAssertionGui |
SizeAssertionGui | AbstractAssertionGui |
SMIMEAssertionGui | AbstractAssertionGui |
XMLAssertionGui | AbstractAssertionGui |
XMLSchemaAssertionGUI | AbstractAssertionGui |
XPathAssertionGui | AbstractAssertionGui |
CriticalSectionControllerGui | AbstractControllerGui |
IncludeControllerGui | AbstractControllerGui |
InterleaveControlGui | AbstractControllerGui |
ModuleControllerGui | AbstractControllerGui |
OnceOnlyControllerGui | AbstractControllerGui |
RandomControlGui | AbstractControllerGui |
RandomOrderControllerGui | LogicControllerGui |
SwitchControllerGui | AbstractControllerGui |
ThroughputControllerGui | AbstractControllerGui |
HtmlExtractorGui | AbstractPostProcessorGui |
RegexExtractorGui | AbstractPostProcessorGui |
XPathExtractorGui | AbstractPostProcessorGui |
JSONPostProcessorGui | AbstractPostProcessorGui |
CounterConfigGui | AbstractConfigGui |
SampleTimeoutGui | AbstractPreProcessorGui |
UserParametersGui | AbstractPreProcessorGui |
TestActionGui | AbstractSamplerGui |
AbstractRandomTimerGui | AbstractTimerGui |
ConstantTimerGui | AbstractTimerGui |
GaussianRandomTimerGui | AbstractRandomTimerGui |
PoissonRandomTimerGui | AbstractRandomTimerGui |
UniformRandomTimerGui | AbstractRandomTimerGui |
AbstractAssertionGui | AbstractScopedJMeterGuiComponent |
AbstractConfigGui | AbstractJMeterGuiComponent |
LoginConfigGui | AbstractConfigGui |
ObsoleteGui | AbstractJMeterGuiComponent |
SimpleConfigGui | AbstractConfigGui |
AbstractControllerGui | AbstractJMeterGuiComponent |
LogicControllerGui | AbstractControllerGui |
RunTimeGui | AbstractControllerGui |
TestFragmentControllerGui | AbstractControllerGui |
TestPlanGui | AbstractJMeterGuiComponent |
TransactionControllerGui | AbstractControllerGui |
WhileControllerGui | AbstractControllerGui |
WorkBenchGui | AbstractJMeterGuiComponent |
FunctionHelper | JDialog |
AbstractJMeterGuiComponent | JPanel |
AbstractScopedJMeterGuiComponent | AbstractJMeterGuiComponent |
AbstractPostProcessorGui | AbstractScopedJMeterGuiComponent |
AbstractPreProcessorGui | AbstractJMeterGuiComponent |
ResultActionGui | AbstractPostProcessorGui |
ResultSaverGui | AbstractListenerGui |
SummariserGui | AbstractListenerGui |
AbstractSamplerGui | AbstractJMeterGuiComponent |
TestBeanGUI | AbstractJMeterGuiComponent |
AbstractThreadGroupGui | AbstractJMeterGuiComponent |
PostThreadGroupGui | ThreadGroupGui |
SetupThreadGroupGui | ThreadGroupGui |
ThreadGroupGui | AbstractThreadGroupGui |
AbstractTimerGui | AbstractJMeterGuiComponent |
AbstractListenerGui | AbstractJMeterGuiComponent |
AbstractVisualizer | AbstractListenerGui |