// =================================================================================================
// Copyright 2013 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =================================================================================================
package com.twitter.common.metrics;
import javax.annotation.Nullable;
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.quantity.Time;
import com.twitter.common.stats.Precision;
import com.twitter.common.stats.WindowedApproxHistogram;
import com.twitter.common.stats.WindowedStatistics;
import com.twitter.common.util.Clock;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_MAX_MEMORY;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_SLICES;
import static com.twitter.common.stats.WindowedApproxHistogram.DEFAULT_WINDOW;
/**
* A Histogram is a representation of a distribution of values.
* It can be queried for quantiles or basic statistics (min, max, avg, count).
*/
public class Histogram implements HistogramInterface {
@VisibleForTesting
public static final double[] DEFAULT_QUANTILES = {.50, .90, .95, .99, .999, .9999};
private final String name;
private final com.twitter.common.stats.Histogram histogram;
private final WindowedStatistics stats;
private final double[] quantiles;
/**
* Construct a Histogram.
* This constructor only exists for backward compatibility reasons.
* See #Histogram(String, Amount<Long, Time>, int, Amount<Long, Data>,
* Precision, double[], Clock)
*/
public Histogram(String name, Amount<Long, Time> window, int slices,
@Nullable Amount<Long, Data> maxMemory, @Nullable Precision precision,
double[] quantiles,
Clock clock,
@Nullable MetricRegistry registry) {
Preconditions.checkArgument(precision != null ^ maxMemory != null,
"You must specify either memory or precision constraint but not both!");
Preconditions.checkNotNull(window);
Preconditions.checkArgument(0 < slices);
for (double q: quantiles) {
Preconditions.checkArgument(0.0 <= q && q <= 1.0);
}
Preconditions.checkNotNull(clock);
this.name = MorePreconditions.checkNotBlank(name);
this.quantiles = Preconditions.checkNotNull(quantiles);
if (maxMemory != null) {
this.histogram = new WindowedApproxHistogram(window, slices, maxMemory, clock);
} else {
this.histogram = new WindowedApproxHistogram(window, slices, precision, clock);
}
this.stats = new WindowedStatistics(window, slices, clock);
if (registry != null) {
registry.registerHistogram(this);
}
}
/**
* Default constructor
* This histogram is composed of a WindowedHistogram with a window duration of {@code window}
* and decomposed in {@code slices} Histograms (See #WindowedHistogram for more details about
* that).
*
* @param window duration of the window
* @param slices number of slices in the window
* @param maxMemory maximum memory used by the whole histogram (can be null if precision isn't)
* @param precision precision of the whole histogram (can be null if maxMemory isn't)
* @param quantiles array of quantiles that will be computed
* @param clock clock used to store elements in the the WindowedHistogram (for testing purposes
* only)
*/
public Histogram(String name, Amount<Long, Time> window, int slices,
@Nullable Amount<Long, Data> maxMemory, @Nullable Precision precision,
double[] quantiles,
Clock clock) {
this(name, window, slices,
maxMemory, precision,
quantiles,
clock,
null);
}
/**
* Construct a Histogram with default arguments except name.
* @see #Histogram(String, Amount, int, Amount, Precision, double[], Clock, MetricRegistry)
*/
public Histogram(String name) {
this(name, DEFAULT_WINDOW, DEFAULT_SLICES,
DEFAULT_MAX_MEMORY, null,
DEFAULT_QUANTILES,
Clock.SYSTEM_CLOCK,
null);
}
/**
* Construct a Histogram with default arguments except name.
* @see #Histogram(String, Amount, int, Amount, Precision, double[], Clock, MetricRegistry)
*
* 12/11/2013: Remove this method after the next deprecation cycle.
* @deprecated Prefer registry.createHistogram(String)
*/
@Deprecated
public Histogram(String name, MetricRegistry registry) {
this(name, DEFAULT_WINDOW, DEFAULT_SLICES,
DEFAULT_MAX_MEMORY, null,
DEFAULT_QUANTILES,
Clock.SYSTEM_CLOCK,
registry);
}
/**
* Construct a Histogram with default arguments except name and precision.
* @see #Histogram(String, Amount, int, Amount, Precision, double[], Clock, MetricRegistry)
*/
public Histogram(String name, Precision precision) {
this(name, DEFAULT_WINDOW, DEFAULT_SLICES,
null, precision,
DEFAULT_QUANTILES,
Clock.SYSTEM_CLOCK,
null);
}
/**
* Construct a Histogram with default arguments except name and maxMemory.
* @see #Histogram(String, Amount, int, Amount, Precision, double[], Clock, MetricRegistry)
*/
public Histogram(String name, Amount<Long, Data> maxMemory) {
this(name, DEFAULT_WINDOW, DEFAULT_SLICES,
maxMemory, null,
DEFAULT_QUANTILES,
Clock.SYSTEM_CLOCK,
null);
}
@Override
public String getName() {
return name;
}
@Override
public synchronized void clear() {
stats.clear();
histogram.clear();
}
@Override
public synchronized void add(long n) {
stats.accumulate(n);
histogram.add(n);
}
@Override
public synchronized Snapshot snapshot() {
stats.refresh();
final long count = stats.populationSize();
final long sum = stats.sum();
final double avg = stats.mean();
final long min = count == 0 ? 0L : stats.min();
final long max = count == 0 ? 0L : stats.max();
final double stddev = stats.standardDeviation();
final Percentile[] ps = new Percentile[quantiles.length];
long[] values = histogram.getQuantiles(quantiles);
for (int i = 0; i < ps.length; i++) {
ps[i] = new Percentile(quantiles[i], values[i]);
}
return new Snapshot() {
@Override public long count() { return count; }
@Override public long sum() { return sum; }
@Override public double avg() { return avg; }
@Override public long min() { return min; }
@Override public long max() { return max; }
@Override public double stddev() { return stddev; }
@Override public Percentile[] percentiles() { return ps; }
};
}
}