Permalink
Browse files

Merge remote-tracking branch 'voldemort/master'

  • Loading branch information...
2 parents 125e1f3 + 311788b commit c3a4cf35a9260c17697e9d8618ad31fc8093cc7d @ctasada committed Mar 3, 2012
View
120 src/java/voldemort/store/stats/Histogram.java
@@ -0,0 +1,120 @@
+package voldemort.store.stats;
+
+import voldemort.VoldemortException;
+import voldemort.annotations.concurrency.Threadsafe;
+
+import java.util.Arrays;
+
+/**
+ * A class for computing percentiles based on a histogram. Values are bucketed
+ * by a configurable bound (e.g., 0-1, 1-2, 2-3). When a value is inserted,
+ * perform a binary search to find the correct bucket.
+ *
+ *
+ */
+@Threadsafe
+public class Histogram {
+
+ private final int nBuckets;
+ private final int step;
+ private final int[] buckets;
+ private final int[] bounds;
+ private int size;
+
+ /**
+ * 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) {
+ this.nBuckets = nBuckets;
+ this.step = step;
+ this.buckets = new int[nBuckets];
+ this.bounds = new int[nBuckets];
+ init();
+ }
+
+ protected void init() {
+ int bound = 0;
+ for(int i = 0; i < nBuckets; i++, bound += step) {
+ bounds[i] = bound;
+ }
+ reset();
+ }
+
+ /**
+ * Reset the histogram back to empty (set all values to 0)
+ */
+ public synchronized void reset() {
+ Arrays.fill(buckets, 0);
+ size = 0;
+ }
+
+ /**
+ * Insert a value into the right bucket of the histogram. If the value is
+ * larger than any bound, insert into the last bucket
+ *
+ * @param data The value to insert into the histogram
+ */
+ public synchronized void insert(int data) {
+ int index = findBucket(data);
+ if(index == -1) {
+ throw new VoldemortException(data + " can't be bucketed, is invalid!");
+ }
+ buckets[index]++;
+ size++;
+ }
+
+ /**
+ * Find the a value <em>n</em> such that the percentile falls within
+ * [<em>n</em>, <em>n + step</em>)
+ *
+ * @param quantile The percentile to find
+ * @return Lower bound associated with the percentile
+ */
+ public synchronized int getQuantile(double quantile) {
+ int total = 0;
+ for(int i = 0; i < nBuckets; i++) {
+ total += buckets[i];
+ double currQuantile = ((double) total) / ((double) size);
+ if(currQuantile >= quantile) {
+ return bounds[i];
+ }
+ }
+ return 0;
+ }
+
+ private int findBucket(int needle) {
+ int max = step * nBuckets;
+ if(needle > max) {
+ return nBuckets - 1;
+ }
+ int low = 0;
+ int high = nBuckets - 1;
+ while(low <= high) {
+ int mid = (low + high) / 2;
+ int cmp = compareToBucket(mid, needle);
+ if(cmp == 0) {
+ return mid;
+ } else if(cmp > 0) {
+ high = mid - 1;
+ } else if(cmp < 0) {
+ low = mid + 1;
+ }
+ }
+ return -1;
+ }
+
+ private int compareToBucket(int bucket, int needle) {
+ int low = bounds[bucket];
+ int high = low + step;
+ if(low <= needle && high > needle) {
+ return 0;
+ } else if(low > needle) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+}
View
28 src/java/voldemort/store/stats/RequestCounter.java
@@ -16,6 +16,9 @@
private final AtomicReference<Accumulator> values;
private final int durationMS;
private final Time time;
+ private final Histogram histogram;
+ private volatile int q95LatencyMs;
+ private volatile int q99LatencyMs;
/**
* @param durationMS specifies for how long you want to maintain this
@@ -32,6 +35,9 @@ public RequestCounter(int durationMS) {
this.time = time;
this.values = new AtomicReference<Accumulator>(new Accumulator());
this.durationMS = durationMS;
+ this.histogram = new Histogram(65535, 1);
+ this.q95LatencyMs = 0;
+ this.q99LatencyMs = 0;
}
public long getCount() {
@@ -84,6 +90,17 @@ public long getMaxLatencyInMs() {
return getValidAccumulator().maxLatencyNS / Time.NS_PER_MS;
}
+ private void maybeResetHistogram() {
+ Accumulator accum = values.get();
+ long now = time.getMilliseconds();
+ if(now - accum.startTimeMS > durationMS) {
+ // Reset the histogram
+ q95LatencyMs = histogram.getQuantile(0.95);
+ q99LatencyMs = histogram.getQuantile(0.99);
+ histogram.reset();
+ }
+ }
+
private Accumulator getValidAccumulator() {
Accumulator accum = values.get();
@@ -131,6 +148,9 @@ public void addRequest(long timeNS) {
* @param getAllAggregatedCount Total number of keys returned for getAll calls
*/
public void addRequest(long timeNS, long numEmptyResponses, long bytes, long getAllAggregatedCount) {
+ int timeMs = (int) timeNS / (int) Time.NS_PER_MS;
+ histogram.insert(timeMs);
+ maybeResetHistogram();
for(int i = 0; i < 3; i++) {
Accumulator oldv = getValidAccumulator();
Accumulator newv = new Accumulator(oldv.startTimeMS,
@@ -175,6 +195,14 @@ public long getGetAllAggregatedCount() {
return getValidAccumulator().getAllAggregatedCount;
}
+ public int getQ95LatencyMs() {
+ return q95LatencyMs;
+ }
+
+ public int getQ99LatencyMs() {
+ return q99LatencyMs;
+ }
+
private class Accumulator {
final long startTimeMS;
View
8 src/java/voldemort/store/stats/StoreStats.java
@@ -100,6 +100,14 @@ public long getMaxLatencyInMs(Tracked op) {
return counters.get(op).getMaxLatencyInMs();
}
+ public long getQ95LatencyInMs(Tracked op) {
+ return counters.get(op).getQ95LatencyMs();
+ }
+
+ public long getQ99LatencyInMs(Tracked op) {
+ return counters.get(op).getQ99LatencyMs();
+ }
+
public Map<Tracked, RequestCounter> getCounters() {
return Collections.unmodifiableMap(counters);
}
View
40 src/java/voldemort/store/stats/StoreStatsJmx.java
@@ -172,6 +172,46 @@ public long getMaxDeleteLatency() {
return stats.getMaxLatencyInMs(Tracked.DELETE);
}
+ @JmxGetter(name = "q95PutLatencyInMs", description="")
+ public long getQ95PutLatency() {
+ return stats.getQ95LatencyInMs(Tracked.PUT);
+ }
+
+ @JmxGetter(name = "q95GetLatencyInMs", description="")
+ public long getQ95GetLatency() {
+ return stats.getQ95LatencyInMs(Tracked.GET);
+ }
+
+ @JmxGetter(name = "q95GetAllLatencyInMs", description="")
+ public long getQ95GetAllLatency() {
+ return stats.getQ95LatencyInMs(Tracked.GET_ALL);
+ }
+
+ @JmxGetter(name = "q95DeleteLatencyInMs", description="")
+ public long getQ95DeleteLatency() {
+ return stats.getQ95LatencyInMs(Tracked.DELETE);
+ }
+
+ @JmxGetter(name = "q99PutLatencyInMs", description="")
+ public long getQ99PutLatency() {
+ return stats.getQ99LatencyInMs(Tracked.PUT);
+ }
+
+ @JmxGetter(name = "q99GetLatencyInMs", description="")
+ public long getQ99GetLatency() {
+ return stats.getQ99LatencyInMs(Tracked.GET);
+ }
+
+ @JmxGetter(name = "q99GetAllLatencyInMs", description="")
+ public long getQ99GetAllLatency() {
+ return stats.getQ99LatencyInMs(Tracked.GET_ALL);
+ }
+
+ @JmxGetter(name = "q99DeleteLatencyInMs", description="")
+ public long getQ99DeleteLatency() {
+ return stats.getQ99LatencyInMs(Tracked.DELETE);
+ }
+
@JmxGetter(name = "maxPutSizeInBytes", description = "Maximum size of value returned in bytes by PUT.")
public long getMaxPutSizeInBytes() {
return stats.getMaxSizeInBytes(Tracked.PUT);
View
48 test/unit/voldemort/store/stats/HistogramTest.java
@@ -0,0 +1,48 @@
+package voldemort.store.stats;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class HistogramTest {
+
+ private Histogram histogram;
+
+ @Before
+ public void setUp() {
+ histogram = new Histogram(10, 5);
+ histogram.insert(1);
+ histogram.insert(6);
+ histogram.insert(11);
+ histogram.insert(16);
+ histogram.insert(21);
+ histogram.insert(26);
+ histogram.insert(31);
+ histogram.insert(36);
+ histogram.insert(41);
+ histogram.insert(46);
+ histogram.insert(56);
+
+ // Test that exceeding the size of the structure merely increments
+ // the last bucket
+ histogram.insert(66);
+ histogram.insert(76);
+ }
+
+ @Test
+ public void testAverage() {
+ assertEquals(histogram.getQuantile(0.50), 30);
+ }
+
+ @Test
+ public void test95thQuartile() {
+ assertEquals(histogram.getQuantile(0.95), 45);
+ }
+
+ @Test
+ public void test99thQuartile() {
+ assertEquals(histogram.getQuantile(0.99), 45);
+ }
+
+}

0 comments on commit c3a4cf3

Please sign in to comment.