Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

V313patch #38

Merged
2 commits merged into from

2 participants

Jakob Homan jkreps
Jakob Homan

Implemented per http://code.google.com/p/project-voldemort/issues/detail?id=313

  • New JMX options:
    • percentGetReturningEmptyResponse
    • percentGetAllReturningEmptyResponse
    • max{Put,Get,GetAll,Delete}LatencyInMs
    • {average,max}{Put,Get,GetAll}SizeInBytes
  • New unit tests for all.

Had to specialize the type for StatsTrackingStore to in order to support the size metrics.

Since the contract for get and get_all is to return empty lists or no result, respectively, for empty results, this is how the "null response rate" has been descirbed. Before this patch the RequestCounter class (and accompanying Accumulator class) were very generic. This patch adds operation-specific logic into it. This bends the abstraction a reasonable amount, though much more would argue for subclassing these. It may also be reasonable to do so now; let me know what you think.

Still need to add support to StatusServlet for these new JMX stats, if needed. Wanted to get feedback at this point.

All unit tests pass, except for for voldemort.store.mysql.MysqlStorageEngineTest, which fails on master for me as well.

jkreps
Collaborator

Looks great. Only comment I would have is whether it is possible to use Time interface to fake out the clock instead of Thread.sleep? This doesn't always work, but we have had a lot of trouble with sleeps in tests being a little slow and fragile.

Jakob Homan

Updated to use mockito to mock the Time interface to fake out the passage of time, so there's no need to sleep. Good call. This required adding a new constructor for the dependency injection.

Re: StatusServlet. This seems like an odd beast. After going to the effort of publishing via jmx, we then run our own servlet to display them? Does this need to be updated for the new stats? It seems like a burden to be in the business of displaying the jmx results, particularly since the servlet has a dependency on the jmx but is not closely tied to it.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 6, 2011
  1. Jakob Homan
Commits on Jan 7, 2011
  1. Jakob Homan
This page is out of date. Refresh to see the latest.
8 src/java/voldemort/server/http/gui/StatusServlet.java
View
@@ -80,8 +80,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
Store<ByteArray, byte[], byte[]> store = server.getStoreRepository()
.getLocalStore(storeName);
- if(store != null && store instanceof StatTrackingStore<?, ?, ?>) {
- ((StatTrackingStore<?, ?, ?>) store).resetStatistics();
+ if(store != null && store instanceof StatTrackingStore) {
+ ((StatTrackingStore) store).resetStatistics();
}
}
}
@@ -152,9 +152,9 @@ protected void outputJSON(HttpServletResponse response) {
int i = 0;
for(Store<ByteArray, byte[], byte[]> store: server.getStoreRepository().getAllLocalStores()) {
- if(store instanceof StatTrackingStore<?, ?, ?>) {
+ if(store instanceof StatTrackingStore) {
- StatTrackingStore<?, ?, ?> statStore = (StatTrackingStore<?, ?, ?>) store;
+ StatTrackingStore statStore = (StatTrackingStore) store;
Map<Tracked, RequestCounter> stats = statStore.getStats().getCounters();
2  src/java/voldemort/server/storage/StorageService.java
View
@@ -379,7 +379,7 @@ public void registerEngine(StorageEngine<ByteArray, byte[], byte[]> engine) {
}
if(voldemortConfig.isStatTrackingEnabled()) {
- StatTrackingStore<ByteArray, byte[], byte[]> statStore = new StatTrackingStore<ByteArray, byte[], byte[]>(store,
+ StatTrackingStore statStore = new StatTrackingStore(store,
this.storeStats);
store = statStore;
if(voldemortConfig.isJmxEnabled()) {
97 src/java/voldemort/store/stats/RequestCounter.java
View
@@ -2,6 +2,7 @@
import java.util.concurrent.atomic.AtomicReference;
+import voldemort.utils.SystemTime;
import voldemort.utils.Time;
/**
@@ -14,12 +15,21 @@
private final AtomicReference<Accumulator> values;
private final int durationMS;
+ private final Time time;
/**
* @param durationMS specifies for how long you want to maintain this
* counter (in milliseconds).
*/
public RequestCounter(int durationMS) {
+ this(durationMS, SystemTime.INSTANCE);
+ }
+
+ /**
+ * For testing request expiration via an injected time provider
+ */
+ RequestCounter(int durationMS, Time time) {
+ this.time = time;
this.values = new AtomicReference<Accumulator>(new Accumulator());
this.durationMS = durationMS;
}
@@ -34,7 +44,7 @@ public long getTotalCount() {
public float getThroughput() {
Accumulator oldv = getValidAccumulator();
- double elapsed = (System.currentTimeMillis() - oldv.startTimeMS)
+ double elapsed = (time.getMilliseconds() - oldv.startTimeMS)
/ (double) Time.MS_PER_SECOND;
if(elapsed > 0f) {
return (float) (oldv.count / elapsed);
@@ -59,10 +69,14 @@ public int getDuration() {
return durationMS;
}
+ public long getMaxLatencyInMs() {
+ return getValidAccumulator().maxLatencyNS / Time.NS_PER_MS;
+ }
+
private Accumulator getValidAccumulator() {
Accumulator accum = values.get();
- long now = System.currentTimeMillis();
+ long now = time.getMilliseconds();
/*
* if still in the window, just return it
@@ -94,45 +108,100 @@ private Accumulator getValidAccumulator() {
* @param timeNS time of operation, in nanoseconds
*/
public void addRequest(long timeNS) {
+ addRequest(timeNS, 0, 0, 0);
+ }
+ /**
+ * @see #addRequest(long)
+ * Detailed request to track additionald data about PUT, GET and GET_ALL
+ *
+ * @param numEmptyResponses For GET and GET_ALL, how many keys were no values found
+ * @param bytes Total number of bytes across all versions of values' bytes
+ * @param getAllAggregatedCount Total number of keys returned for getAll calls
+ */
+ public void addRequest(long timeNS, long numEmptyResponses, long bytes, long getAllAggregatedCount) {
for(int i = 0; i < 3; i++) {
Accumulator oldv = getValidAccumulator();
-
- long startTimeMS = oldv.startTimeMS;
- long count = oldv.count + 1;
- long totalTimeNS = oldv.totalTimeNS + timeNS;
- long total = oldv.total + 1;
-
- if(values.compareAndSet(oldv, new Accumulator(startTimeMS, count, totalTimeNS, total))) {
+ Accumulator newv = new Accumulator(oldv.startTimeMS,
+ oldv.count + 1,
+ oldv.totalTimeNS + timeNS,
+ oldv.total + 1,
+ oldv.numEmptyResponses + numEmptyResponses,
+ Math.max(timeNS, oldv.maxLatencyNS),
+ oldv.totalBytes + bytes,
+ Math.max(oldv.maxBytes, bytes),
+ oldv.getAllAggregatedCount + getAllAggregatedCount);
+ if(values.compareAndSet(oldv, newv))
return;
- }
}
}
- private static class Accumulator {
+ /**
+ * Return the number of requests that have returned returned no value for the requested key. Tracked only for GET.
+ */
+ public long getNumEmptyResponses() {
+ return getValidAccumulator().numEmptyResponses;
+ }
+
+ /**
+ * Return the size of the largest response or request in bytes returned. Tracked only for GET, GET_ALL and PUT.
+ */
+ public long getMaxSizeInBytes() {
+ return getValidAccumulator().maxBytes;
+ }
+
+ /**
+ * Return the average size of all the versioned values returned. Tracked only for GET, GET_ALL and PUT.
+ */
+ public double getAverageSizeInBytes() {
+ return getValidAccumulator().getAverageBytes();
+ }
+
+ /**
+ * Return the aggergated number of keys returned across all getAll calls, taking into account multiple values returned per call.
+ */
+ public long getGetAllAggregatedCount() {
+ return getValidAccumulator().getAllAggregatedCount;
+ }
+
+ private class Accumulator {
final long startTimeMS;
final long count;
final long totalTimeNS;
final long total;
+ final long numEmptyResponses; // GET and GET_ALL: number of empty responses that have been returned
+ final long getAllAggregatedCount; // GET_ALL: a single call to GET_ALL can return multiple k-v pairs. Track total returned.
+ final long maxLatencyNS;
+ final long maxBytes; // Maximum single value
+ final long totalBytes; // Sum of all the values
public Accumulator() {
- this(System.currentTimeMillis(), 0, 0, 0);
+ this(RequestCounter.this.time.getMilliseconds(), 0, 0, 0, 0, 0, 0, 0, 0);
}
public Accumulator newWithTotal() {
- return new Accumulator(System.currentTimeMillis(), 0, 0, total);
+ return new Accumulator(RequestCounter.this.time.getMilliseconds(), 0, 0, total, 0, 0, 0, 0, 0);
}
- public Accumulator(long startTimeMS, long count, long totalTimeNS, long total) {
+ public Accumulator(long startTimeMS, long count, long totalTimeNS, long total, long numEmptyResponses, long maxLatencyNS, long totalBytes, long maxBytes, long getAllAggregatedCount) {
this.startTimeMS = startTimeMS;
this.count = count;
this.totalTimeNS = totalTimeNS;
this.total = total;
+ this.numEmptyResponses = numEmptyResponses;
+ this.maxLatencyNS = maxLatencyNS;
+ this.totalBytes = totalBytes;
+ this.maxBytes = maxBytes;
+ this.getAllAggregatedCount = getAllAggregatedCount;
}
public double getAverageTimeNS() {
return count > 0 ? 1f * totalTimeNS / count : -0f;
}
+
+ public double getAverageBytes() {
+ return count > 0 ? 1f * totalBytes / count : -0f;
+ }
}
}
57 src/java/voldemort/store/stats/StatTrackingStore.java
View
@@ -29,23 +29,24 @@
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;
+import voldemort.utils.ByteArray;
/**
* A store wrapper that tracks basic usage statistics
*
*
*/
-public class StatTrackingStore<K, V, T> extends DelegatingStore<K, V, T> {
+public class StatTrackingStore extends DelegatingStore<ByteArray, byte [], byte []> {
private StoreStats stats;
- public StatTrackingStore(Store<K, V, T> innerStore, StoreStats parentStats) {
+ public StatTrackingStore(Store<ByteArray, byte [], byte []> innerStore, StoreStats parentStats) {
super(innerStore);
this.stats = new StoreStats(parentStats);
}
@Override
- public boolean delete(K key, Version version) throws VoldemortException {
+ public boolean delete(ByteArray key, Version version) throws VoldemortException {
long start = System.nanoTime();
try {
return super.delete(key, version);
@@ -58,34 +59,68 @@ public boolean delete(K key, Version version) throws VoldemortException {
}
@Override
- public List<Versioned<V>> get(K key, T transforms) throws VoldemortException {
+ public List<Versioned<byte []>> get(ByteArray key, byte [] transforms) throws VoldemortException {
+ List<Versioned<byte []>> result = null;
long start = System.nanoTime();
try {
- return super.get(key, transforms);
+ result = super.get(key, transforms);
+ return result;
} catch(VoldemortException e) {
stats.recordTime(Tracked.EXCEPTION, System.nanoTime() - start);
throw e;
} finally {
- stats.recordTime(Tracked.GET, System.nanoTime() - start);
+ long duration = System.nanoTime() - start;
+ long totalBytes = 0;
+ boolean returningEmpty = true;
+ if(result != null) {
+ returningEmpty = result.size() == 0;
+ for(Versioned<byte []> bytes : result) {
+ totalBytes += bytes.getValue().length;
+ }
+ }
+ stats.recordGetTime(duration, returningEmpty, totalBytes);
}
}
@Override
- public Map<K, List<Versioned<V>>> getAll(Iterable<K> keys, Map<K, T> transforms)
+ public Map<ByteArray, List<Versioned<byte []>>> getAll(Iterable<ByteArray> keys, Map<ByteArray, byte []> transforms)
throws VoldemortException {
+ Map<ByteArray,List<Versioned<byte []>>> result = null;
long start = System.nanoTime();
try {
- return super.getAll(keys, transforms);
+ result = super.getAll(keys, transforms);
+ return result;
} catch(VoldemortException e) {
stats.recordTime(Tracked.EXCEPTION, System.nanoTime() - start);
throw e;
} finally {
- stats.recordTime(Tracked.GET_ALL, System.nanoTime() - start);
+ long duration = System.nanoTime() - start;
+ long totalBytes = 0;
+ int requestedValues = 0;
+ int returnedValues = 0;
+
+ // Determine how many values were requested
+ for(ByteArray k : keys) {
+ requestedValues++;
+ }
+
+ if(result != null) {
+ // Determine the number of values being returned
+ returnedValues = result.keySet().size();
+ // Determine the total size of the response
+ for(List<Versioned<byte[]>> value : result.values()) {
+ for(Versioned<byte[]> bytes : value) {
+ totalBytes += bytes.getValue().length;
+ }
+ }
+ }
+
+ stats.recordGetAllTime(duration, requestedValues, returnedValues, totalBytes);
}
}
@Override
- public void put(K key, Versioned<V> value, T transforms) throws VoldemortException {
+ public void put(ByteArray key, Versioned<byte []> value, byte [] transforms) throws VoldemortException {
long start = System.nanoTime();
try {
super.put(key, value, transforms);
@@ -96,7 +131,7 @@ public void put(K key, Versioned<V> value, T transforms) throws VoldemortExcepti
stats.recordTime(Tracked.EXCEPTION, System.nanoTime() - start);
throw e;
} finally {
- stats.recordTime(Tracked.PUT, System.nanoTime() - start);
+ stats.recordPutTimeAndSize(System.nanoTime() - start, value.getValue().length);
}
}
66 src/java/voldemort/store/stats/StoreStats.java
View
@@ -31,6 +31,51 @@ public StoreStats(StoreStats parent) {
this.parent = parent;
}
+ /**
+ * Record the duration of specified op. For PUT, GET and GET_ALL use specific methods for those ops.
+ */
+ public void recordTime(Tracked op, long timeNS) {
+ recordTime(op, timeNS, 0, 0, 0);
+ }
+
+ /**
+ * Record the duration of a put operation, along with the size of the values returned.
+ */
+ public void recordPutTimeAndSize(long timeNS, long size) {
+ recordTime(Tracked.PUT, timeNS, 0, size, 0);
+ }
+
+ /**
+ * Record the duration of a get operation, along with whether or not an empty response (ie no values matched)
+ * and the size of the values returned.
+ */
+ public void recordGetTime(long timeNS, boolean emptyResponse, long totalBytes) {
+ recordTime(Tracked.GET, timeNS, emptyResponse ? 1 : 0, totalBytes, 0);
+ }
+
+ /**
+ * Record the duration of a get_all operation, along with how many values were requested, how may were actually
+ * returned and the size of the values returned.
+ */
+ public void recordGetAllTime(long timeNS, int requested, int returned, long totalBytes) {
+ recordTime(Tracked.GET_ALL, timeNS, requested - returned, totalBytes, requested);
+ }
+
+ /**
+ * Method to service public recording APIs
+ *
+ * @param op Operation being tracked
+ * @param timeNS Duration of operation
+ * @param numEmptyResponses GET and GET_ALL: number of empty responses being sent back, ie requested keys for which there were no values
+ * @param size Total size of response payload, ie sum of lengths of bytes in all versions of values
+ * @param getAllAggregateRequests Total of key-values requested in aggregatee from get_all operations
+ */
+ private void recordTime(Tracked op, long timeNS, long numEmptyResponses, long size, long getAllAggregateRequests) {
+ counters.get(op).addRequest(timeNS, numEmptyResponses, size, getAllAggregateRequests);
+ if(parent != null)
+ parent.recordTime(op, timeNS, numEmptyResponses, size, getAllAggregateRequests);
+ }
+
public long getCount(Tracked op) {
return counters.get(op).getCount();
}
@@ -43,14 +88,27 @@ public double getAvgTimeInMs(Tracked op) {
return counters.get(op).getAverageTimeInMs();
}
- public void recordTime(Tracked op, long timeNS) {
- counters.get(op).addRequest(timeNS);
- if(parent != null)
- parent.recordTime(op, timeNS);
+ public long getNumEmptyResponses(Tracked op) {
+ return counters.get(op).getNumEmptyResponses();
+ }
+
+ public long getMaxLatencyInMs(Tracked op) {
+ return counters.get(op).getMaxLatencyInMs();
}
public Map<Tracked, RequestCounter> getCounters() {
return Collections.unmodifiableMap(counters);
}
+ public long getMaxSizeInBytes(Tracked op) {
+ return counters.get(op).getMaxSizeInBytes();
+ }
+
+ public double getAvgSizeinBytes(Tracked op) {
+ return counters.get(op).getAverageSizeInBytes();
+ }
+
+ public long getGetAllAggregatedCount() {
+ return counters.get(Tracked.GET_ALL).getGetAllAggregatedCount();
+ }
}
64 src/java/voldemort/store/stats/StoreStatsJmx.java
View
@@ -112,4 +112,68 @@ public double getOperationThroughput() {
+ stats.getThroughput(Tracked.GET_ALL) + stats.getThroughput(Tracked.PUT);
}
+ @JmxGetter(name = "percentGetReturningEmptyResponse", description = "The percentage of calls to GET for which no value was found.")
+ public double getPercentGetReturningEmptyResponse() {
+ return numEmptyResponses(stats.getNumEmptyResponses(Tracked.GET), stats.getCount(Tracked.GET));
+ }
+
+ @JmxGetter(name = "percentGetAllReturningEmptyResponse", description = "The percentage of calls to GET_ALL for which no value was found, taking into account multiple returned key-values.")
+ public double getPercentGetAllReturningEmptyResponse() {
+ return numEmptyResponses(stats.getNumEmptyResponses(Tracked.GET_ALL), stats.getGetAllAggregatedCount());
+ }
+
+ private double numEmptyResponses(long numEmpty, long total) {
+ return total == 0 ? 0.0d : numEmpty / (float)total;
+ }
+
+ @JmxGetter(name = "maxPutLatencyInMs", description = "Maximum latency in ms of PUT")
+ public long getMaxPutLatency() {
+ return stats.getMaxLatencyInMs(Tracked.PUT);
+ }
+
+ @JmxGetter(name = "maxGetLatencyInMs", description = "Maximum latency in ms of GET")
+ public long getMaxGetLatency() {
+ return stats.getMaxLatencyInMs(Tracked.GET);
+ }
+
+ @JmxGetter(name = "maxGetAllLatencyInMs", description = "Maximum latency in ms of GET_ALL")
+ public long getMaxGetAllLatency() {
+ return stats.getMaxLatencyInMs(Tracked.GET_ALL);
+ }
+
+ @JmxGetter(name = "maxDeleteLatencyInMs", description = "Maximum latency in ms of DELETE")
+ public long getMaxDeleteLatency() {
+ return stats.getMaxLatencyInMs(Tracked.DELETE);
+ }
+
+ @JmxGetter(name = "maxPutSizeInBytes", description = "Maximum size of value returned in bytes by PUT.")
+ public long getMaxPutSizeInBytes() {
+ return stats.getMaxSizeInBytes(Tracked.PUT);
+ }
+
+ @JmxGetter(name = "maxGetAllSizeInBytes", description = "Maximum size of value returned in bytes by GET_ALL.")
+ public long getMaxGetAllSizeInBytes() {
+ return stats.getMaxSizeInBytes(Tracked.GET_ALL);
+ }
+
+ @JmxGetter(name = "maxGetSizeInBytes", description = "Maximum size of value returned in bytes by GET.")
+ public long getMaxGetSizeInBytes() {
+ return stats.getMaxSizeInBytes(Tracked.GET);
+ }
+
+ @JmxGetter(name = "averageGetValueSizeInBytes", description = "Average size in bytes of GET request")
+ public double getAverageGetSizeInBytes() {
+ return stats.getAvgSizeinBytes(Tracked.GET);
+ }
+
+ @JmxGetter(name = "averageGetAllSizeInBytes", description = "Average size in bytes of GET_ALL request")
+ public double getAverageGetAllSizeInBytes() {
+ return stats.getAvgSizeinBytes(Tracked.GET_ALL);
+ }
+
+ @JmxGetter(name = "averagePutSizeInBytes", description = "Average size in bytes of PUT request")
+ public double getAveragePutSizeInBytes() {
+ return stats.getAvgSizeinBytes(Tracked.PUT);
+ }
+
}
4 test/unit/voldemort/store/routed/RoutedStoreTest.java
View
@@ -1036,10 +1036,10 @@ public void testNoReadRepair() throws Exception {
Map<Integer, Store<ByteArray, byte[], byte[]>> subStores = Maps.newHashMap();
/* We just need to keep a store from one node */
- StatTrackingStore<ByteArray, byte[], byte[]> statTrackingStore = null;
+ StatTrackingStore statTrackingStore = null;
for(int i = 0; i < 3; ++i) {
int id = Iterables.get(cluster.getNodes(), i).getId();
- statTrackingStore = new StatTrackingStore<ByteArray, byte[], byte[]>(new InMemoryStorageEngine<ByteArray, byte[], byte[]>("test"),
+ statTrackingStore = new StatTrackingStore(new InMemoryStorageEngine<ByteArray, byte[], byte[]>("test"),
null);
subStores.put(id, statTrackingStore);
95 test/unit/voldemort/store/stats/StatsTest.java
View
@@ -0,0 +1,95 @@
+package voldemort.store.stats;
+
+import org.junit.Test;
+import voldemort.utils.Time;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static voldemort.utils.Time.NS_PER_MS;
+
+
+public class StatsTest {
+ private RequestCounter getTestRequestCounter() {
+ return new RequestCounter(50000000);
+ }
+
+ @Test
+ public void emptyResponseCountsAccumulateCorrectly() {
+ RequestCounter rc = getTestRequestCounter();
+ assertEquals(0l, rc.getNumEmptyResponses());
+ rc.addRequest(40, 1, 0, 0);
+ assertEquals(1l, rc.getNumEmptyResponses());
+ rc.addRequest(40, 0, 0, 0);
+ assertEquals(1l, rc.getNumEmptyResponses());
+ rc.addRequest(40, 1, 0, 0);
+ assertEquals(2l, rc.getNumEmptyResponses());
+ }
+
+ @Test
+ public void maxLatencyIsAccurate() {
+ RequestCounter rc = getTestRequestCounter();
+ assertEquals(0, rc.getMaxLatencyInMs());
+
+ // Stats go in as ns, but come out as ms
+ for(long duration : new long [] { 22, 99, 33, 0 }) {
+ rc.addRequest(duration * NS_PER_MS);
+ }
+ assertEquals(99, rc.getMaxLatencyInMs());
+ rc.addRequest(523 * NS_PER_MS, 1, 0, 0);
+ assertEquals(523, rc.getMaxLatencyInMs());
+ }
+
+ @Test
+ public void averageValueIsAccurate() {
+ RequestCounter rc = getTestRequestCounter();
+ for(long i = 0, sum = 0; i < 100000; i++) {
+ sum += i;
+ rc.addRequest(42, 1, i, 0);
+ assertEquals(sum / ((float) i + 1), rc.getAverageSizeInBytes(), 0.05f);
+ }
+ }
+
+ @Test
+ public void maxValueIsAccurate() {
+ RequestCounter rc = getTestRequestCounter();
+
+ assertEquals(0, rc.getMaxSizeInBytes());
+ for(long requestSize : new long [] {42l, 923423l, 334l, 99}) {
+ rc.addRequest(1, 1, requestSize, 0);
+ }
+ assertEquals(923423l, rc.getMaxSizeInBytes());
+ rc.addRequest(5, 0, 1414232l, 0);
+ assertEquals(1414232l, rc.getMaxSizeInBytes());
+ }
+
+ @Test
+ public void statsExpireOnTime() throws InterruptedException {
+ final long startTime = 1445468640; // Oct 21, 2015
+ final int delay = 1000;
+ Time mockTime = mock(Time.class);
+
+ when(mockTime.getMilliseconds()).thenReturn(startTime);
+
+ RequestCounter rc = new RequestCounter(delay, mockTime);
+
+ // Add some new stats and verify they were calculated correctly
+ rc.addRequest(100 * NS_PER_MS, 1, 200, 1);
+ rc.addRequest(50 * NS_PER_MS, 0, 1000, 2);
+ assertEquals(1, rc.getNumEmptyResponses());
+ assertEquals(100, rc.getMaxLatencyInMs());
+ assertEquals(75d, rc.getAverageTimeInMs(), 0.0f);
+ assertEquals(1000, rc.getMaxSizeInBytes());
+ assertEquals(3, rc.getGetAllAggregatedCount());
+
+ // Jump into the future after the counter should have expired
+ when(mockTime.getMilliseconds()).thenReturn(startTime + delay + 1);
+
+ // Now verify that the counter has aged out the previous values
+ assertEquals(0, rc.getNumEmptyResponses());
+ assertEquals(0, rc.getMaxLatencyInMs());
+ assertEquals(0, rc.getAverageTimeInMs(), 0.0f);
+ assertEquals(0, rc.getMaxSizeInBytes());
+ assertEquals(0, rc.getGetAllAggregatedCount());
+ }
+}
78 test/unit/voldemort/store/stats/StoreStatsJmxTest.java
View
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
+import static voldemort.store.stats.Tracked.*;
+import static voldemort.utils.Time.NS_PER_MS;
public class StoreStatsJmxTest {
@Test
@@ -42,4 +44,80 @@ public void getThroughPutShouldBeSumOfThroughputOperations() {
}
}
+ // Verify that the various classes are wired correctly between the JMX class and the stats tracking classes
+ @Test
+ public void getMaxLatenciesWork() {
+ // Max latency should be tracked across calls and one Op's should not affect the others.
+ for(Tracked op : EnumSet.of(GET, GET_ALL, DELETE, PUT)) {
+ StoreStats stats = new StoreStats();
+ StoreStatsJmx jmx = new StoreStatsJmx(stats);
+
+ stats.recordTime(op, 5 * NS_PER_MS);
+ stats.recordTime(op, 15 * NS_PER_MS);
+ stats.recordTime(op, 7 * NS_PER_MS);
+
+ assertEquals(op == PUT ? 15 : 0, jmx.getMaxPutLatency());
+ assertEquals(op == GET ? 15 : 0, jmx.getMaxGetLatency());
+ assertEquals(op == GET_ALL ? 15 : 0, jmx.getMaxGetAllLatency());
+ assertEquals(op == DELETE ? 15 : 0, jmx.getMaxDeleteLatency());
+ }
+ }
+
+ // Verify that the logic for determing maximum value returned and average value return has been wired up correctly.
+ @Test
+ public void maxAndAvgSizeOfValuesAreCalculatedCorrectly() {
+ for(Tracked op : EnumSet.of(GET, GET_ALL, PUT)) {
+ StoreStats stats = new StoreStats();
+ StoreStatsJmx jmx = new StoreStatsJmx(stats);
+
+ long [] valueSizes = new long [] {100, 450, 200, 300};
+ long sum = 0l;
+ long max = valueSizes[0];
+ final long timeNS = 1 * NS_PER_MS;
+
+ for(long v : valueSizes) {
+ if(op == GET)
+ stats.recordGetTime(timeNS, false, v);
+ else if (op == GET_ALL)
+ stats.recordGetAllTime(timeNS, 1, 1, v);
+ else // PUT
+ stats.recordPutTimeAndSize(timeNS, v);
+
+ sum += v;
+ max = Math.max(max, v);
+ }
+ double average = sum / (double)valueSizes.length;
+
+ assertEquals(op == PUT ? max : 0, jmx.getMaxPutSizeInBytes());
+ assertEquals(op == PUT ? average : 0, jmx.getAveragePutSizeInBytes(), 0.0);
+ assertEquals(op == GET ? max : 0, jmx.getMaxGetSizeInBytes());
+ assertEquals(op == GET ? average : 0, jmx.getAverageGetSizeInBytes(), 0.0);
+ assertEquals(op == GET_ALL ? max : 0, jmx.getMaxGetAllSizeInBytes());
+ assertEquals(op == GET_ALL ? average : 0, jmx.getAverageGetAllSizeInBytes(), 0.0);
+ }
+ }
+
+ @Test
+ public void testGetPercentageGetEmptyResponses() {
+ StoreStats stats = new StoreStats();
+ StoreStatsJmx jmx = new StoreStatsJmx(stats);
+
+ stats.recordGetTime(100, false, 1000);
+ assertEquals(0, jmx.getPercentGetReturningEmptyResponse(), 0.0);
+ stats.recordGetTime(200, true, 1001);
+ assertEquals(0.5, jmx.getPercentGetReturningEmptyResponse(), 0.0);
+ stats.recordGetTime(300, false, 1002);
+ assertEquals(0.33, jmx.getPercentGetReturningEmptyResponse(), 0.05);
+ }
+
+ @Test
+ public void testGetPercentageGetAllEmptyResponses() {
+ StoreStats stats = new StoreStats();
+ StoreStatsJmx jmx = new StoreStatsJmx(stats);
+
+ stats.recordGetAllTime(100, 2, 2, 1000); // requested values for two keys, got both of them
+ assertEquals(0.0, jmx.getPercentGetAllReturningEmptyResponse(), 0.0);
+ stats.recordGetAllTime(200, 2, 0, 1001); // requested 2, got 0
+ assertEquals(0.5, jmx.getPercentGetAllReturningEmptyResponse(), 0.0);
+ }
}
Something went wrong with that request. Please try again.