package org.radargun.stages.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.radargun.config.DefinitionElement;
import org.radargun.config.Init;
import org.radargun.config.Property;
import org.radargun.config.PropertyHelper;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.stats.Statistics;
import org.radargun.stats.representation.DefaultOutcome;
import org.radargun.stats.representation.OperationThroughput;
import org.radargun.utils.NanoTimeConverter;
import org.radargun.utils.ReflexiveConverters;
import org.radargun.utils.Utils;
/**
* @author Radim Vansa <rvansa@redhat.com>
*/
public abstract class PerformanceCondition {
private static final Log log = LogFactory.getLog(PerformanceCondition.class);
public abstract boolean evaluate(Statistics statistics);
@DefinitionElement(name = "any", doc = "Any of inner conditions is true", resolveType = DefinitionElement.ResolveType.PASS_BY_DEFINITION)
protected static class Any extends PerformanceCondition {
@Property(name = "", doc = "Inner conditions", complexConverter = ListConverter.class)
public final List<PerformanceCondition> subs = new ArrayList<>();
@Override
public boolean evaluate(final Statistics statistics) {
return subs.stream().anyMatch(condition -> condition.evaluate(statistics));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("any [");
if (!subs.isEmpty()) sb.append(subs.get(0));
for (int i = 1; i < subs.size(); ++i) sb.append(", ").append(subs.get(i));
return sb.append("]").toString();
}
}
@DefinitionElement(name = "all", doc = "All inner conditions are true", resolveType = DefinitionElement.ResolveType.PASS_BY_DEFINITION)
protected static class All extends PerformanceCondition {
@Property(name = "", doc = "Inner conditions", complexConverter = ListConverter.class)
public final List<PerformanceCondition> subs = new ArrayList<>();
@Override
public boolean evaluate(final Statistics statistics) {
return subs.stream().allMatch(condition -> condition.evaluate(statistics));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("all [");
if (!subs.isEmpty()) sb.append(subs.get(0));
for (int i = 1; i < subs.size(); ++i) sb.append(", ").append(subs.get(i));
return sb.append("]").toString();
}
}
protected abstract static class AbstractCondition extends PerformanceCondition {
@Property(doc = "Identifier of the operation (or its derivate) that should be tested.", optional = false)
protected String on;
@Override
public String toString() {
return this.getClass().getSimpleName() + PropertyHelper.toString(this);
}
}
@DefinitionElement(name = "mean", doc = "Checks value of the mean response time of given operation.")
protected static class Mean extends AbstractCondition {
@Property(doc = "Test if the mean response time is below specified value (use time unit!)", converter = NanoTimeConverter.class)
protected Long below;
@Property(doc = "Test if the mean response time is above specified value (use time unit!)", converter = NanoTimeConverter.class)
protected Long over;
@Init
public void init() {
if (below != null && over != null) throw new IllegalStateException("Cannot define both 'below' and 'over'!");
if (below == null && over == null) throw new IllegalStateException("Must define either 'below' or 'over'!");
}
@Override
public boolean evaluate(Statistics statistics) {
DefaultOutcome outcome = statistics.getRepresentation(on, DefaultOutcome.class);
if (outcome == null) throw new IllegalStateException("Cannot determine mean from " + statistics);
log.info("Mean is " + Utils.prettyPrintTime((long) outcome.responseTimeMean, TimeUnit.NANOSECONDS) + PropertyHelper.toString(this));
if (below != null) return outcome.responseTimeMean < below;
if (over != null) return outcome.responseTimeMean > over;
throw new IllegalStateException();
}
}
protected abstract static class ThroughputBase extends AbstractCondition {
@Property(doc = "Test if the actual throughput is below specified value (operations per second)")
protected Long below;
@Property(doc = "Test if the actual throughput is above specified value (operations per second)")
protected Long over;
@Init
public void init() {
if (below != null && over != null) throw new IllegalStateException("Cannot define both 'below' and 'over'!");
if (below == null && over == null) throw new IllegalStateException("Must define either 'below' or 'over'!");
}
@Override
public boolean evaluate(Statistics statistics) {
org.radargun.stats.representation.OperationThroughput throughput = statistics.getRepresentation(on,
org.radargun.stats.representation.OperationThroughput.class,
TimeUnit.MILLISECONDS.toNanos(statistics.getEnd() - statistics.getBegin()));
if (throughput == null) throw new IllegalStateException("Cannot determine throughput from " + statistics);
log.info(getClass().getSimpleName() + " is " + getThroughput(throughput) + " ops/s " + PropertyHelper.toString(this));
if (below != null) return getThroughput(throughput) < below;
if (over != null) return getThroughput(throughput) > over;
throw new IllegalStateException();
}
protected abstract double getThroughput(OperationThroughput throughput);
}
@DefinitionElement(name = "throughput-gross", doc = "Checks value of gross throughput of given operation.")
protected static class GrossThroughput extends ThroughputBase {
protected double getThroughput(OperationThroughput throughput) {
return throughput.gross;
}
}
@DefinitionElement(name = "throughput-net", doc = "Checks value of net throughput of given operation.")
protected static class NetThroughput extends ThroughputBase {
protected double getThroughput(OperationThroughput throughput) {
return throughput.net;
}
}
@DefinitionElement(name = "requests", doc = "Checks number of executed operations.")
protected static class Requests extends AbstractCondition {
@Property(doc = "Test if the number of executed operations is below this value.")
protected Long below;
@Property(doc = "Test if the number of executed operations is above this value.")
protected Long over;
@Init
public void init() {
if (below != null && over != null) throw new IllegalStateException("Cannot define both 'below' and 'over'!");
if (below == null && over == null) throw new IllegalStateException("Must define either 'below' or 'over'!");
}
@Override
public boolean evaluate(Statistics statistics) {
DefaultOutcome outcome = statistics.getRepresentation(on, DefaultOutcome.class);
if (outcome == null) throw new IllegalStateException("Cannot determine request count from " + statistics);
log.info("Executed " + outcome.requests + " reqs " + PropertyHelper.toString(this));
if (below != null) return outcome.requests < below;
if (over != null) return outcome.requests > over;
throw new IllegalStateException();
}
}
@DefinitionElement(name = "errors", doc = "Checks number of executed operations.")
protected static class Errors extends AbstractCondition {
@Property(doc = "Test if the percentage of errors (out of total number of requests) is below this value.")
protected Integer percentBelow;
@Property(doc = "Test if the percentage of errors (out of total number of requests) is above this value.")
protected Integer percentOver;
@Property(doc = "Test if the total number of errors is below this value.")
protected Long totalBelow;
@Property(doc = "Test if the total number of errors is above this value.")
protected Long totalOver;
@Init
public void init() {
int defs = 0;
if (totalBelow != null) defs++;
if (totalOver != null) defs++;
if (percentBelow != null) defs++;
if (percentOver != null) defs++;
if (defs != 1)
throw new IllegalStateException("Must define exactly one of 'total-below', 'total-over', 'percent-below', 'percent-over'");
}
@Override
public boolean evaluate(Statistics statistics) {
DefaultOutcome outcome = statistics.getRepresentation(on, DefaultOutcome.class);
if (outcome == null) throw new IllegalStateException("Cannot determine error count from " + statistics);
log.info("Encountered " + outcome.errors + " errors " + PropertyHelper.toString(this));
if (totalBelow != null) return outcome.errors < totalBelow;
if (totalOver != null) return outcome.errors > totalOver;
if (percentBelow != null) return outcome.errors * 100 < outcome.requests * percentBelow;
if (percentBelow != null) return outcome.errors * 100 > outcome.requests * percentOver;
throw new IllegalStateException();
}
}
@DefinitionElement(name = "percentile", doc = "Checks value of the response time of given operation " +
"at some percentile (0 = fastest operation, 100 = slowest operation).")
protected static class Percentile extends AbstractCondition {
@Property(doc = "Test if the response time is below specified value (use time unit!)", converter = NanoTimeConverter.class)
protected Long below;
@Property(doc = "Test if the response time is above specified value (use time unit!)", converter = NanoTimeConverter.class)
protected Long over;
@Property(doc = "Percentile used for the comparison: (0 = fastest operation, 100 = slowest operation)", optional = false)
protected double value;
@Init
public void init() {
if (below != null && over != null) throw new IllegalStateException("Cannot define both 'below' and 'over'!");
if (below == null && over == null) throw new IllegalStateException("Must define either 'below' or 'over'!");
}
@Override
public boolean evaluate(Statistics statistics) {
org.radargun.stats.representation.Percentile percentile
= statistics.getRepresentation(on, org.radargun.stats.representation.Percentile.class, value);
if (percentile == null) throw new IllegalStateException("Cannot determine percentile from " + statistics);
log.info("Response time is " + Utils.prettyPrintTime((long) percentile.responseTimeMax, TimeUnit.NANOSECONDS) + PropertyHelper.toString(this));
if (below != null) return percentile.responseTimeMax < below;
if (over != null) return percentile.responseTimeMax > over;
throw new IllegalStateException();
}
}
public static class Converter extends ReflexiveConverters.ObjectConverter {
public Converter() {
super(new Class<?>[] {Any.class, All.class, Mean.class, GrossThroughput.class, NetThroughput.class, Requests.class, Errors.class, Percentile.class});
}
}
protected static class ListConverter extends ReflexiveConverters.ListConverter {
public ListConverter() {
super(new Class<?>[] {Any.class, All.class, Mean.class, GrossThroughput.class, NetThroughput.class, Requests.class, Errors.class, Percentile.class});
}
}
}