org.apache.jmeter.control.ThroughputController Maven / Gradle / Ivy
/*
* 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.control;
import java.io.Serializable;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.testelement.property.FloatProperty;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.StringProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class represents a controller that can control the number of times that
* it is executed, either by the total number of times the user wants the
* controller executed (BYNUMBER) or by the percentage of time it is called
* (BYPERCENT)
*
* The current implementation executes the first N samples (BYNUMBER)
* or the last N% of samples (BYPERCENT).
*/
public class ThroughputController
extends GenericController
implements Serializable, LoopIterationListener, TestStateListener {
private static final long serialVersionUID = 234L;
private static final Logger log = LoggerFactory.getLogger(ThroughputController.class);
public static final int BYNUMBER = 0;
public static final int BYPERCENT = 1;
private static final String STYLE = "ThroughputController.style";// $NON-NLS-1$
private static final String PERTHREAD = "ThroughputController.perThread";// $NON-NLS-1$
private static final String MAXTHROUGHPUT = "ThroughputController.maxThroughput";// $NON-NLS-1$
private static final String PERCENTTHROUGHPUT = "ThroughputController.percentThroughput";// $NON-NLS-1$
private static class MutableInteger{
private int integer;
MutableInteger(int value){
integer = value;
}
int incr(){
return ++integer;
}
int intValue() {
return integer;
}
}
// These items are shared between threads in a group by the clone() method
// They are initialised by testStarted() so don't need to be serialised
private transient MutableInteger globalNumExecutions;
private transient MutableInteger globalIteration;
private transient Object counterLock = new Object(); // ensure counts are updated correctly
/** Number of iterations on which we've chosen to deliver samplers. */
private int numExecutions = 0;
/** Index of the current iteration. 0-based. */
private int iteration = -1;
/** Whether to deliver samplers on this iteration. */
private boolean runThisTime;
public ThroughputController() {
setStyle(BYNUMBER);
setPerThread(true);
setMaxThroughput(1);
setPercentThroughput(100);
runThisTime = false;
}
public void setStyle(int style) {
setProperty(new IntegerProperty(STYLE, style));
}
public int getStyle() {
return getPropertyAsInt(STYLE);
}
public void setPerThread(boolean perThread) {
setProperty(new BooleanProperty(PERTHREAD, perThread));
}
public boolean isPerThread() {
return getPropertyAsBoolean(PERTHREAD);
}
public void setMaxThroughput(int maxThroughput) {
setProperty(new IntegerProperty(MAXTHROUGHPUT, maxThroughput));
}
public void setMaxThroughput(String maxThroughput) {
setProperty(new StringProperty(MAXTHROUGHPUT, maxThroughput));
}
public String getMaxThroughput() {
return getPropertyAsString(MAXTHROUGHPUT);
}
protected int getMaxThroughputAsInt() {
JMeterProperty prop = getProperty(MAXTHROUGHPUT);
int retVal = 1;
if (prop instanceof IntegerProperty) {
retVal = prop.getIntValue();
} else {
String valueString = prop.getStringValue();
try {
retVal = Integer.parseInt(valueString);
} catch (NumberFormatException e) {
log.warn("Error parsing '{}'", valueString, e);
}
}
return retVal;
}
public void setPercentThroughput(float percentThroughput) {
setProperty(new FloatProperty(PERCENTTHROUGHPUT, percentThroughput));
}
public void setPercentThroughput(String percentThroughput) {
setProperty(new StringProperty(PERCENTTHROUGHPUT, percentThroughput));
}
public String getPercentThroughput() {
return getPropertyAsString(PERCENTTHROUGHPUT);
}
protected float getPercentThroughputAsFloat() {
JMeterProperty prop = getProperty(PERCENTTHROUGHPUT);
float retVal = 100;
if (prop instanceof FloatProperty) {
retVal = prop.getFloatValue();
} else {
String valueString = prop.getStringValue();
try {
retVal = Float.parseFloat(valueString);
} catch (NumberFormatException e) {
log.warn("Error parsing '{}'", valueString, e);
}
}
return retVal;
}
@SuppressWarnings("SynchronizeOnNonFinalField")
private int getExecutions() {
if (!isPerThread()) {
synchronized (counterLock) {
return globalNumExecutions.intValue();
}
}
return numExecutions;
}
@Override
public Sampler next() {
if (runThisTime) {
return super.next();
}
return null;
}
/**
* Decide whether to return any samplers on this iteration.
*/
private boolean decide(int executions, int iterations) {
if (getStyle() == BYNUMBER) {
return executions < getMaxThroughputAsInt();
}
return (100.0 * executions + 50.0) / (iterations + 1) < getPercentThroughputAsFloat();
}
@Override
public boolean isDone() {
return subControllersAndSamplers.isEmpty()
||
(
(getStyle() == BYNUMBER
&& (
(getExecutions() >= getMaxThroughputAsInt()
&& current >= getSubControllers().size())
|| (getMaxThroughputAsInt() == 0)))
|| (getStyle() == BYPERCENT
&& Float.compare(getPercentThroughputAsFloat(), 0.0f)==0)
);
}
@Override
public Object clone() {
ThroughputController clone = (ThroughputController) super.clone();
clone.numExecutions = numExecutions;
clone.iteration = iteration;
clone.runThisTime = false;
// Ensure global counters and lock are shared across threads in the group
clone.globalIteration = globalIteration;
clone.globalNumExecutions = globalNumExecutions;
clone.counterLock = counterLock;
return clone;
}
@Override
@SuppressWarnings("SynchronizeOnNonFinalField")
public void iterationStart(LoopIterationEvent iterEvent) {
if (!isPerThread()) {
synchronized (counterLock) {
globalIteration.incr();
runThisTime = decide(globalNumExecutions.intValue(), globalIteration.intValue());
if (runThisTime) {
globalNumExecutions.incr();
}
}
} else {
iteration++;
runThisTime = decide(numExecutions, iteration);
if (runThisTime) {
numExecutions++;
}
}
}
@Override
@SuppressWarnings("SynchronizeOnNonFinalField")
public void testStarted() {
synchronized (counterLock) {
globalNumExecutions = new MutableInteger(0);
globalIteration = new MutableInteger(-1);
}
}
@Override
public void testStarted(String host) {
testStarted();
}
@Override
public void testEnded() {
// NOOP
}
@Override
public void testEnded(String host) {
// NOOP
}
@Override
protected Object readResolve(){
super.readResolve();
counterLock = new Object();
return this;
}
}