// =================================================================================================
// 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 java.lang.reflect.Array;
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;
/**
* Windowed is an abstraction that let you span a class across a sliding window.
* It creates a ring buffer of T and reuse the buffer after clearing it or use a new one (via
* the {@code clearer} function).
*
* <pre>
* tenured instances
* ++++++++++++++++++++++++++++++++++
* [----A-----][-----B----][-----C----][-----D----]
* ++++++++++
* current instance
* </pre>
*
* The schema above represents the valid instances over time
* (A,B,C) are the tenured ones
* D is the current instance
*/
public abstract class Windowed<T> {
private Class<T> clazz;
protected final T[] buffers;
private final long sliceDuration;
private final Clock clock;
private long index = -1L;
private Function<T, T> clearer;
/**
* @param clazz the type of the underlying element T
* @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 element
* @param clearer the function that clear (or re-create) an element
* @param clock the clock used for to select the appropriate histogram
*/
public Windowed(Class<T> clazz, Amount<Long, Time> window, int slices,
Supplier<T> sliceProvider, Function<T, T> clearer, Clock clock) {
Preconditions.checkNotNull(window);
// Ensure that we have at least 1ms per slice
Preconditions.checkArgument(window.as(Time.MILLISECONDS) > (slices + 1));
Preconditions.checkArgument(window.as(Time.MILLISECONDS) > (slices + 1));
Preconditions.checkArgument(0 < slices);
Preconditions.checkNotNull(sliceProvider);
Preconditions.checkNotNull(clock);
this.clazz = clazz;
this.sliceDuration = window.as(Time.MILLISECONDS) / slices;
@SuppressWarnings("unchecked") // safe because we have the clazz proof of type H
T[] bufs = (T[]) Array.newInstance(clazz, slices + 1);
for (int i = 0; i < bufs.length; i++) {
bufs[i] = sliceProvider.get();
}
this.buffers = bufs;
this.clearer = clearer;
this.clock = clock;
}
/**
* Return the index of the latest Histogram.
* You have to modulo it with buffer.length before accessing the array with this number.
*/
protected int getCurrentIndex() {
long now = clock.nowMillis();
return (int) (now / sliceDuration);
}
/**
* Check for expired elements and return the current one.
*/
protected T getCurrent() {
sync(getCurrentIndex());
return buffers[(int) (index % buffers.length)];
}
/**
* Check for expired elements and return all the tenured (old) ones.
*/
protected T[] getTenured() {
long currentIndex = getCurrentIndex();
sync(currentIndex);
@SuppressWarnings("unchecked") // safe because we have the clazz proof of type T
T[] tmp = (T[]) Array.newInstance(clazz, buffers.length - 1);
for (int i = 0; i < tmp.length; i++) {
int idx = (int) ((currentIndex + 1 + i) % buffers.length);
tmp[i] = buffers[idx];
}
return tmp;
}
/**
* Clear all the elements.
*/
public void clear() {
for (int i = 0; i <= buffers.length; i++) {
buffers[i] = clearer.apply(buffers[i]);
}
}
/**
* Synchronize elements with a point in time.
* i.e. Check for expired ones and clear them, and update the index variable.
*/
protected void sync(long currentIndex) {
if (index < currentIndex) {
long from = Math.max(index + 1, currentIndex - buffers.length + 1);
for (long i = from; i <= currentIndex; i++) {
int idx = (int) (i % buffers.length);
buffers[idx] = clearer.apply(buffers[idx]);
}
index = currentIndex;
}
}
}