kg.apc.jmeter.timers.VariableThroughputTimer Maven / Gradle / Ivy
The newest version!
// TODO: fight with lagging on start
// TODO: create a thread which will wake up at least one sampler to provide rps
package kg.apc.jmeter.timers;
import kg.apc.jmeter.JMeterPluginsUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.engine.util.NoThreadClone;
import org.apache.jmeter.gui.util.PowerTableModel;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.property.CollectionProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.NullProperty;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.timers.Timer;
import org.apache.jmeter.util.JMeterUtils;
import org.slf4j.LoggerFactory;
import java.util.List;
public class VariableThroughputTimer
extends AbstractTestElement
implements Timer, NoThreadClone, TestStateListener {
private static final long serialVersionUID = -8557540133988335686L;
protected static final String[] columnIdentifiers = new String[]{
"Start RPS", "End RPS", "Duration, sec"
};
protected static final Class[] columnClasses = new Class[]{
String.class, String.class, String.class
};
public static final String DATA_PROPERTY_POSTFIX = "load_profile";
public static final int DURATION_FIELD_NO = 2;
public static final int FROM_FIELD_NO = 0;
public static final int TO_FIELD_NO = 1;
private static final org.slf4j.Logger log = LoggerFactory.getLogger(VariableThroughputTimer.class);
/* put this in fields because we don't want create variables in tight loops */
/**
* Current threads waiting, if < 1 it means we don't have enough threads to reach RPS
*/
private int cntDelayed;
/**
* Current second in Millis
*/
private double time = 0;
/**
* How many requests per millis
*/
private double msecPerReq;
/**
* Number of samples sent since last second
*/
private long cntSent;
/**
* Current RPS
*/
private double rps;
private double startSec = 0;
private CollectionProperty overrideProp;
private int stopTries;
private double lastStopTry;
private boolean stopping;
public String getDataProperty() {
return dataProperty;
}
private String dataProperty;
public VariableThroughputTimer() {
super();
setDataProperty();
}
/**
* Internally handles that delay for caller thread
*
* @return 0
*/
public synchronized long delay() {
while (true) {
long curTimeMs = System.currentTimeMillis();
long millisSinceLastSecond = curTimeMs % 1000;
long nowInMsRoundedAtSec = curTimeMs - millisSinceLastSecond;
checkNextSecond(nowInMsRoundedAtSec);
int delayMs = getDelay(millisSinceLastSecond);
if (stopping) {
delayMs = delayMs > 0 ? 10 : 0;
notify(); // NOSONAR Don't notifyAll as cost is too big in terms of performances
}
if (delayMs < 1) {
notify(); // NOSONAR Don't notifyAll as cost is too big in terms of performances
break;
}
cntDelayed++;
try {
wait(delayMs);
} catch (InterruptedException ex) {
log.debug("Waiting thread was interrupted", ex);
Thread.currentThread().interrupt();
}
cntDelayed--;
}
cntSent++;
return 0;
}
/**
* If we have switched to next second:
*
* Updates time
* Updates startSec
* Resets cntSent
*
*
* @param nowInMsRoundedAtSec Now in millis rounded as second
*/
private synchronized void checkNextSecond(double nowInMsRoundedAtSec) {
// next second
if (time == nowInMsRoundedAtSec) {
return;
}
if (startSec == 0) {
startSec = nowInMsRoundedAtSec;
}
time = nowInMsRoundedAtSec;
Pair pair = getRPSForSecond((nowInMsRoundedAtSec - startSec) / 1000);
double nextRps = pair.getLeft();
if (nextRps < 0) {
stopping = true;
int factor = stopTries > 10 ? 2 : 1;
rps = rps > 0 ? rps * factor : 1;
stopTest();
notifyAll();
} else {
rps = nextRps;
}
if (log.isDebugEnabled()) {
log.debug("Second changed {} , waiting: {}, samples sent {}, current rps: {} rps",
((nowInMsRoundedAtSec - startSec) / 1000), cntDelayed, cntSent, rps);
}
if (cntDelayed < 1 && cntSent < rps) {
log.warn("No free threads available in current Thread Group {}, made {} samples/s for expected rps {} samples/s, increase your number of threads",
JMeterContextService.getContext().getThreadGroup().getName(), cntSent, rps);
}
String elementName = getName();
JMeterUtils.setProperty(elementName + "_totalDuration", String.valueOf(pair.getRight()));
JMeterUtils.setProperty(elementName + "_cntDelayed", String.valueOf(cntDelayed));
JMeterUtils.setProperty(elementName + "_cntSent", String.valueOf(cntSent));
JMeterUtils.setProperty(elementName + "_rps", String.valueOf(rps));
cntSent = 0;
msecPerReq = 1000d / rps;
}
/**
* @param millisSinceLastSecond Millis since last second tick
* @return delay in Millis to apply at current millis, < 0 if no delay
*/
private int getDelay(long millisSinceLastSecond) {
if (log.isDebugEnabled()) {
log.debug("Calculating {} {} {}", millisSinceLastSecond, cntSent * msecPerReq, cntSent);
}
if (millisSinceLastSecond < (cntSent * msecPerReq)) {
// TODO : Explain this for other maintainers
// cntDelayed + 1 : Current threads waiting + this thread
return (int) (1 + 1000.0 * (cntDelayed + 1) / rps);
}
// we're under rate
return 0;
}
public void setDataProperty(){
if (!StringUtils.isEmpty(getName()) &&
!StringUtils.isEmpty(JMeterUtils.getProperty(getName() + "." + DATA_PROPERTY_POSTFIX))) {
dataProperty = getName() + "." + DATA_PROPERTY_POSTFIX;
} else {
dataProperty = DATA_PROPERTY_POSTFIX;
}
log.debug("Load property set to {}", dataProperty);
}
public void setData(CollectionProperty rows) {
setProperty(rows);
}
public JMeterProperty getData() {
if (overrideProp != null) {
return overrideProp;
}
log.debug("Loading profile from property {}", dataProperty);
setDataProperty();
return getProperty(dataProperty);
}
/**
* @param elapsedSinceStartOfTestSec Elapsed time since start of test in seconds
* @return double RPS at that second or -1 if we're out of schedule
*/
public Pair getRPSForSecond(final double elapsedSinceStartOfTestSec) {
JMeterProperty data = getData();
if (data instanceof NullProperty) {
return Pair.of(-1.0, 0L);
}
CollectionProperty rows = (CollectionProperty) data;
PropertyIterator scheduleIT = rows.iterator();
double newSec = elapsedSinceStartOfTestSec;
double result = -1;
boolean resultComputed = false;
long totalDuration = 0;
while (scheduleIT.hasNext()) {
@SuppressWarnings("unchecked")
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy