package org.terracotta.statistics.derived.latency;
import org.terracotta.statistics.Time;
import org.terracotta.statistics.derived.histogram.BarSplittingBiasedHistogram;
import org.terracotta.statistics.derived.histogram.Histogram;
import org.terracotta.statistics.observer.ChainedEventObserver;
import java.time.Duration;
import java.util.List;
import java.util.function.Function;
import java.util.function.LongSupplier;
import static java.lang.Math.nextDown;
public class DefaultLatencyHistogramStatistic implements LatencyHistogramStatistic, ChainedEventObserver {
private final BarSplittingBiasedHistogram histogram;
private final LongSupplier timeSupplier;
private final long pruningDelay;
private final LatencyHistogramQuery query = new LatencyHistogramQuery() {
@Override
public Long minimum() {
return nullOrVal(histogram.getMinimum());
}
@Override
public Long maximum() {
return nullOrVal(histogram.getMaximum());
}
@Override
public long count() {
return histogram.size();
}
@Override
public Long percentile(double percent) {
return nullOrVal(percent == 0.0 ? histogram.getMinimum() : nextDown(histogram.getQuantileBounds(percent)[1]));
}
@Override
public long[] percentileBounds(double percent) {
if (percent == 0.0) {
double v = histogram.getMinimum();
return Double.isNaN(v) ? null : new long[]{(long) v, (long) v};
}
double[] bounds = histogram.getQuantileBounds(percent);
if (Double.isNaN(bounds[0]) || Double.isNaN(bounds[1])) {
return null;
}
return new long[]{(long) bounds[0], (long) nextDown(bounds[1])};
}
@Override
public List<Histogram.Bucket> buckets() {
return histogram.getBuckets();
}
};
private long nextPruning;
public DefaultLatencyHistogramStatistic(
double phi,
int bucketCount,
Duration window,
LongSupplier timeSupplier) {
this.timeSupplier = timeSupplier;
this.histogram = new BarSplittingBiasedHistogram(phi, bucketCount, window.toNanos());
this.pruningDelay = window.toNanos() / 2;
}
public DefaultLatencyHistogramStatistic(double phi,
int bucketCount,
Duration window) {
this(phi, bucketCount, window, Time::time);
}
@Override
public List<org.terracotta.statistics.derived.histogram.Histogram.Bucket> buckets() {
return query(LatencyHistogramQuery::buckets);
}
@Override
public long count() {
return query(LatencyHistogramQuery::count);
}
@Override
public Long minimum() {
return query(LatencyHistogramQuery::minimum);
}
@Override
public Long maximum() {
return query(LatencyHistogramQuery::maximum);
}
@Override
public Long percentile(double percent) {
return query(h -> h.percentile(percent));
}
@Override
public long[] percentileBounds(double percent) {
return query(h -> h.percentileBounds(percent));
}
@Override
public synchronized void event(long time, long latency) {
histogram.event(latency, time);
tryExpire(false, () -> time);
}
@Override
public synchronized <T> T query(Function<LatencyHistogramQuery, T> fn) {
tryExpire(true, timeSupplier);
return fn.apply(query);
}
@Override
public String toString() {
return query(query -> "{" +
"count=" + query.count() +
", minimum=" + query.minimum() +
", maximum=" + query.maximum() +
", median=" + query.median() +
'}');
}
private void tryExpire(boolean force, LongSupplier time) {
long now = time.getAsLong();
if (force || now >= nextPruning) {
nextPruning = now + pruningDelay;
histogram.expire(now);
}
}
private static Long nullOrVal(double val) {
return Double.isNaN(val) ? null : (long) val;
}
}