/*
* Copyright 2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License 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 voldemort.store.stats;
import java.util.Arrays;
import org.apache.log4j.Logger;
import voldemort.annotations.concurrency.Threadsafe;
/**
* A class for computing percentiles based on a simple histogram.
*
* The histogram starts at 0 and then has uniformly sized buckets. The number of
* buckets and width of each bucket is specified upon construction. Each bucket
* in the histogram "counts" the number of values inserted into the histogram
* that fall into the bucket's range.
*
* All interfaces for adding data to the histogram or querying the histogram for
* quantiles are synchronized to make this object threadsafe.
*
* @deprecated Use Tehuti Histograms instead...
*/
@Threadsafe
@Deprecated
public class Histogram {
private final int nBuckets;
private final int step;
private final int[] buckets;
private final long upperBound;
private int size;
private long sum;
private static final Logger logger = Logger.getLogger(Histogram.class);
private long resetIntervalMs = -1;
private long lastResetTimeMs;
/**
* Initialize an empty histogram
*
* @param nBuckets The number of buckets to use
* @param step The size of each bucket
*/
public Histogram(int nBuckets, int step, long resetIntervalMs) {
this(nBuckets, step);
this.resetIntervalMs = resetIntervalMs;
this.lastResetTimeMs = System.currentTimeMillis();
}
/**
* Initialize an empty histogram
*
* @param nBuckets The number of buckets to use
* @param step The size (width) of each bucket
*/
public Histogram(int nBuckets, int step) {
this.nBuckets = nBuckets;
this.step = step;
this.upperBound = step * nBuckets;
this.buckets = new int[nBuckets];
reset();
if(logger.isDebugEnabled()) {
logger.debug("Constructed a histogram with " + nBuckets + " buckets.");
}
}
/**
* Reset the histogram back to empty (set all values to 0)
*/
public synchronized void reset() {
Arrays.fill(buckets, 0);
size = 0;
sum = 0;
this.lastResetTimeMs = System.currentTimeMillis();
}
/**
* Insert a value into the right bucket of the histogram. If the value is
* larger than any bound, insert into the last bucket. If the value is less
* than zero, then ignore it.
*
* @param data The value to insert into the histogram
*/
public synchronized void insert(long data) {
resetIfNeeded();
long index = 0;
if(data >= this.upperBound) {
index = nBuckets - 1;
} else if(data < 0) {
logger.error(data + " can't be bucketed because it is negative!");
return;
} else {
index = data / step;
}
if(index < 0 || index >= nBuckets) {
// This should be dead code. Defending against code changes in
// future.
logger.error(data + " can't be bucketed because index is not in range [0,nBuckets).");
return;
}
buckets[(int) index]++;
sum += data;
size++;
}
/**
* Find the a value <em>n</em> such that the percentile falls within [
* <em>n</em>, <em>n + step</em>). This method does a <em>LINEAR</em> probe
* of the histogram. I.e., this method is O(nBuckets).
*
* @param quantile The percentile to find
* @return Lower bound associated with the percentile
*/
public synchronized long getQuantile(double quantile) {
resetIfNeeded();
int total = 0;
for(int i = 0; i < nBuckets; i++) {
total += buckets[i];
double currQuantile = ((double) total) / ((double) size);
if(currQuantile >= quantile) {
return i * step;
}
}
return 0;
}
/**
* Obtain the average of the data in the histogram
*
* Note: Caller is responsible for making sure 'sum' does not overflow
* within the reset interval
*
* @return the average over the current samples
*/
public synchronized double getAverage() {
if(size == 0)
return 0.0;
return (sum * 1.0) / size;
}
private void resetIfNeeded() {
if(resetIntervalMs > -1) {
if((System.currentTimeMillis() - lastResetTimeMs) >= this.resetIntervalMs) {
this.reset();
}
}
}
}