package com.twitter.common.metrics;
import java.util.logging.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.twitter.common.base.MorePreconditions;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Data;
import com.twitter.common.stats.ApproximateHistogram;
import com.twitter.common.stats.Precision;
import com.twitter.common.stats.Statistics;
/**
* A Histogram is a represention of a distribution of values.
* It can be queried for quantiles or basic statistics (min, max, avg, count).
*/
public class Histogram {
@VisibleForTesting
static final double[] DEFAULT_QUANTILES = {.25, .50, .75, .90, .95, .99, .999, .9999};
private static final Logger LOG = Logger.getLogger(Histogram.class.getName());
private final com.twitter.common.stats.Histogram histogram;
private final String name;
private final double[] quantiles;
private final MetricRegistry registry;
private Statistics stats;
/**
* Construct an histogram, create gauges and register it into the registry.
*
* @param name is the name of the histogram used for Gauge name.
* @param histogram is the inner common.stats.Histogram used for storing data.
* @param quantiles is the quantiles values that will be used for the gauges.
* @param registry is the registry in which gauges will be registered.
*/
private Histogram(
String name,
com.twitter.common.stats.Histogram histogram,
double[] quantiles,
MetricRegistry registry) {
MorePreconditions.checkNotBlank(name);
Preconditions.checkNotNull(quantiles);
Preconditions.checkArgument(0 < quantiles.length);
Preconditions.checkNotNull(registry);
this.name = name;
this.histogram = histogram;
this.quantiles = quantiles;
this.registry = registry;
this.stats = new Statistics();
registerInto(registry);
}
/**
* Construct a Histogram with default arguments except name.
* @see #Histogram(String, Histogram, double[], MetricRegistry).
*/
public Histogram(String name, MetricRegistry registry) {
this(name, new ApproximateHistogram(), DEFAULT_QUANTILES, registry);
}
/**
* Construct a Histogram with default arguments except name and precision.
* @see #Histogram(String, Histogram, double[], MetricRegistry).
*/
public Histogram(String name, Precision precision, MetricRegistry registry) {
this(name, new ApproximateHistogram(precision), DEFAULT_QUANTILES, registry);
}
/**
* Construct a Histogram with default arguments except name and maxMemory.
* @see #Histogram(String, Histogram, double[], MetricRegistry).
*/
public Histogram(String name, Amount<Long, Data> maxMemory, MetricRegistry registry) {
this(name, new ApproximateHistogram(maxMemory), DEFAULT_QUANTILES, registry);
}
/**
* Resets the state of this Histogram. Clears all data points collected so far.
*/
public synchronized void clear() {
stats = new Statistics();
histogram.clear();
}
/**
* Adds a data point.
*/
public synchronized void add(long n) {
stats.accumulate(n);
histogram.add(n);
}
/**
* Create multiple Gauges and register them into the MetricRegistry.
*/
private void registerInto(final MetricRegistry metricRegistry) {
metricRegistry.register(new AbstractGauge<Long>(name + "_count") {
@Override public Long read() {
return stats.populationSize();
}
});
metricRegistry.register(new AbstractGauge<Long>(name + "_min") {
@Override public Long read() {
if (stats.populationSize() == 0) {
return 0L;
} else {
return stats.min();
}
}
});
metricRegistry.register(new AbstractGauge<Long>(name + "_max") {
@Override public Long read() {
if (stats.populationSize() == 0) {
return 0L;
} else {
return stats.max();
}
}
});
for (final double p : quantiles) {
metricRegistry.register(new AbstractGauge<Long>(name + "_" + gaugeName(p)) {
@Override public Long read() {
double[] qs = {p};
return histogram.getQuantiles(qs)[0];
}
});
}
}
@VisibleForTesting
static String gaugeName(double quantile) {
String gname = "p" + (int) (quantile * 10000);
if (3 < gname.length() && "00".equals(gname.substring(3))) {
gname = gname.substring(0, 3);
}
return gname;
}
}