package org.radargun.stats;
import java.util.ArrayList;
import java.util.Arrays;
import org.radargun.config.DefinitionElement;
import org.radargun.stats.representation.DefaultOutcome;
import org.radargun.stats.representation.Histogram;
import org.radargun.stats.representation.MeanAndDev;
import org.radargun.stats.representation.OperationThroughput;
import org.radargun.stats.representation.Percentile;
/**
* This class remembers all requests as these came, storing them in memory.
*
* @author Radim Vansa <rvansa@redhat.com>
*/
@DefinitionElement(name = "all", doc = "Operation statistics recording all requests' response times.")
public class AllRecordingOperationStats implements OperationStats {
private static final int INITIAL_CAPACITY = (1 << 10);
protected static final int MAX_CAPACITY = (1 << 20); // max 8MB
/* We don't use ArrayList because it would box all the longs */
protected long[] responseTimes = new long[INITIAL_CAPACITY];
protected int pos = 0;
protected boolean full = false;
protected long errors;
/**
*
* Factory method to use in the copy method
*
* @return a new AllRecordingOperationStats instance
*/
@Override
public AllRecordingOperationStats newInstance() {
return new AllRecordingOperationStats();
}
@Override
public void record(Request request) {
ensureCapacity();
responseTimes[pos++] = request.duration();
if (!request.isSuccessful()) {
errors++;
}
}
@Override
public void record(Message message) {
if (message.isValid()) {
ensureCapacity();
responseTimes[pos++] = message.totalTime();
} else {
errors++;
}
}
@Override
public void record(RequestSet requestSet) {
ensureCapacity();
responseTimes[pos++] = requestSet.sumDurations();
if (!requestSet.isSuccessful()) {
errors++;
}
}
public void ensureCapacity() {
if (pos >= responseTimes.length) {
int newCapacity = Math.min(responseTimes.length << 1, MAX_CAPACITY);
if (newCapacity <= responseTimes.length) {
pos = 0;
full = true;
}
long[] temp = new long[newCapacity];
System.arraycopy(responseTimes, 0, temp, 0, responseTimes.length);
responseTimes = temp;
}
}
@Override
public void merge(OperationStats o) {
if (!(o instanceof AllRecordingOperationStats)) throw new IllegalArgumentException();
AllRecordingOperationStats other = (AllRecordingOperationStats) o;
int mySize = full ? responseTimes.length : pos;
int otherSize = other.full ? other.responseTimes.length : other.pos;
if (mySize + otherSize > responseTimes.length) {
// when merging, ignore the capacity limit
long[] temp = new long[mySize + otherSize];
System.arraycopy(responseTimes, 0, temp, 0, mySize);
responseTimes = temp;
}
System.arraycopy(other.responseTimes, 0, responseTimes, mySize, otherSize);
pos = mySize + otherSize;
full = responseTimes.length > MAX_CAPACITY;
}
@Override
public OperationStats copy() {
AllRecordingOperationStats copy = this.newInstance();
copy.responseTimes = Arrays.copyOf(responseTimes, responseTimes.length);
copy.full = full;
copy.pos = pos;
return copy;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getRepresentation(Class<T> clazz, Statistics ownerStatistics, Object... args) {
long requests = full ? responseTimes.length : pos;
if (clazz == DefaultOutcome.class) {
return (T) new DefaultOutcome(requests, errors, getMeanDuration(), getMaxDuration());
} else if (clazz == OperationThroughput.class) {
return (T) OperationThroughput.compute(requests, errors, ownerStatistics);
} else if (clazz == Percentile.class) {
double percentile = Percentile.getPercentile(args);
int size = full ? responseTimes.length : pos;
Arrays.sort(responseTimes, 0, size);
return (T) new Percentile(responseTimes[Math.min((int) Math.ceil(percentile / 100d * size), size - 1)]);
} else if (clazz == Histogram.class) {
return (T) getHistogram(args);
} else if (clazz == MeanAndDev.class) {
double temp = 0;
double mean = getMeanDuration();
for (int i = 0; i < requests; ++i) {
temp += ((mean - responseTimes[i]) * (mean - responseTimes[i]));
}
if (requests < 2) {
return (T) new MeanAndDev(mean, 0);
} else {
return (T) new MeanAndDev(mean, Math.sqrt(temp / (requests - 1)));
}
} else {
return null;
}
}
private <T> Histogram getHistogram(Object[] args) {
int size = full ? responseTimes.length : pos;
Arrays.sort(responseTimes, 0, size);
if (args.length == 0) {
long[] ranges = new long[size + 1];
System.arraycopy(responseTimes, 0, ranges, 0, size);
ranges[size] = responseTimes[size - 1];
long[] counts = new long[size];
Arrays.fill(counts, 1L);
return new Histogram(ranges, counts);
} else {
ArrayList<Long> ranges = new ArrayList<>();
ArrayList<Long> counts = new ArrayList<>();
int buckets = Histogram.getBuckets(args);
double percentile = Histogram.getPercentile(args);
int end = (int) Math.ceil(size * percentile / 100);
long min = size == 0 ? 1 : responseTimes[0];
long max = size == 0 ? 1 : responseTimes[end];
double exponent = Math.pow((double) max / (double) min, 1d / buckets);
double current = min * exponent;
long accCount, lastCount = 0;
for (accCount = 0; accCount < end; ) {
long responseTime = responseTimes[(int) accCount];
accCount++;
if (responseTime >= current) {
ranges.add(responseTime);
counts.add(accCount - lastCount);
lastCount = accCount;
current = current * exponent;
}
}
if (accCount > 0) {
ranges.add(max);
counts.add(accCount - lastCount);
}
return new Histogram(ranges.stream().mapToLong(l -> l).toArray(), counts.stream().mapToLong(l -> l).toArray());
}
}
@Override
public boolean isEmpty() {
return pos == 0 && !full;
}
protected long getMaxDuration() {
long max = Long.MIN_VALUE;
long requests = full ? responseTimes.length : pos;
if (requests == 0) {
return 0;
} else {
for (int i = 0; i < requests; ++i) {
max = Math.max(max, responseTimes[i]);
}
return max;
}
}
protected long getMinDuration() {
long min = Long.MAX_VALUE;
long requests = full ? responseTimes.length : pos;
if (requests == 0) {
return 0;
} else {
for (int i = 0; i < requests; ++i) {
min = Math.min(min, responseTimes[i]);
}
return min;
}
}
protected double getMeanDuration() {
long durationSum = 0;
long requests = full ? responseTimes.length : pos;
if (requests == 0) {
return 0;
} else {
for (int i = 0; i < requests; ++i) {
durationSum += responseTimes[i];
}
return durationSum / requests;
}
}
}