// =================================================================================================
// 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.stats;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.twitter.common.quantity.Amount;
import com.twitter.common.quantity.Time;
import com.twitter.common.util.Clock;
/**
* Histogram windowed over time.
* <p>
* This histogram is composed of a series of ({@code slices} + 1) histograms representing a window
* of {@code range} duration. We only update the latest one, and we query the oldest ones (i.e. all
* histograms except the head).
* </p>
* <pre>
* range
* <------------->
* [AAA][BBB][CCC][DDD] here slices = 3
* --------------------->
* t1 t2
*
* For t in [t1,t2) we:
* insert elements in DDD
* query quantile over [AAA][BBB][CCC]
* </pre>
* <p>
* When {@code t} is in {@code [t1, t2)} we insert value into the latest histogram (DDD here),
* when we query the histogram, we 'merge' all other histograms (all except the latest) and query
* it. when {@code t > t2} the oldest histogram become the newest (like in a ring buffer) and
* so on ...
* </p>
* <p>
* Note: We use MergedHistogram class to represent a merged histogram without actually
* merging the underlying histograms.
* </p>
*/
public class WindowedHistogram<H extends Histogram> extends Windowed<H> implements Histogram {
private long mergedHistIndex = -1L;
private Function<H[], Histogram> merger;
private Histogram mergedHistogram = null;
/**
* Create a WindowedHistogram of {@code slices + 1} elements over a time {@code window}.
* This code is independent from the implementation of Histogram, you just need to provide
* a {@code Supplier<H>} to create the histograms and a {@code Function<H[], Histogram>} to
* merge them.
*
* @param clazz the type of the underlying Histogram H
* @param window the length of the window
* @param slices the number of slices (the window will be divided into {@code slices} slices)
* @param sliceProvider the supplier of histogram
* @param merger the function that merge an array of histogram H[] into a single Histogram
* @param clock the clock used for to select the appropriate histogram
*/
public WindowedHistogram(Class<H> clazz, Amount<Long, Time> window, int slices,
Supplier<H> sliceProvider, Function<H[], Histogram> merger, Clock clock) {
super(clazz, window, slices, sliceProvider, new Function<H, H>() {
@Override
public H apply(H h) { h.clear(); return h; }
}, clock);
Preconditions.checkNotNull(merger);
this.merger = merger;
}
@Override
public synchronized void add(long x) {
getCurrent().add(x);
}
@Override
public synchronized void clear() {
for (Histogram h: buffers) {
h.clear();
}
}
@Override
public synchronized long getQuantile(double quantile) {
long currentIndex = getCurrentIndex();
if (mergedHistIndex < currentIndex) {
H[] tmp = getTenured();
mergedHistogram = merger.apply(tmp);
mergedHistIndex = currentIndex;
}
return mergedHistogram.getQuantile(quantile);
}
@Override
public synchronized long[] getQuantiles(double[] quantiles) {
return Histograms.extractQuantiles(this, quantiles);
}
}