/** * This returns the mean (average) of all values in the current snapshot. This is not a percentile but often desired so captured and exposed here. * * @return mean of all values */ public int getMean() { /* no-op if disabled */ if (!enabled.get()) return -1; // force logic to move buckets forward in case other requests aren't making it happen getCurrentBucket(); // fetch the current snapshot return getCurrentPercentileSnapshot().getMean(); }
@Test public void testSampleDataOverTime2() { System.out.println("\n\n***************************** testSampleDataOverTime2 \n"); MockedTime time = new MockedTime(); int previousTime = 0; HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); for (int i = 0; i < SampleDataHolder2.data.length; i++) { int timeInMillisecondsSinceStart = SampleDataHolder2.data[i][0]; int latency = SampleDataHolder2.data[i][1]; time.increment(timeInMillisecondsSinceStart - previousTime); previousTime = timeInMillisecondsSinceStart; p.addValue(latency); } System.out.println("0.01: " + p.getPercentile(0.01)); System.out.println("Median: " + p.getPercentile(50)); System.out.println("90th: " + p.getPercentile(90)); System.out.println("99th: " + p.getPercentile(99)); System.out.println("99.5th: " + p.getPercentile(99.5)); System.out.println("99.99: " + p.getPercentile(99.99)); if (p.getPercentile(50) > 90 || p.getPercentile(50) < 50) { fail("We expect around 60-70 but got: " + p.getPercentile(50)); } if (p.getPercentile(99) < 400) { fail("We expect to see some high values over 400 but got: " + p.getPercentile(99)); } }
@Test public void testThreadSafety() { final MockedTime time = new MockedTime(); final HystrixRollingPercentile p = new HystrixRollingPercentile(time, 100, 25, 1000, HystrixProperty.Factory.asProperty(true)); aggregateMetrics.addAndGet(p.getMean() + p.getPercentile(10) + p.getPercentile(50) + p.getPercentile(90)); System.out.println(p.getMean() + " : " + p.getPercentile(50) + " : " + p.getPercentile(75) + " : " + p.getPercentile(90) + " : " + p.getPercentile(95) + " : " + p.getPercentile(99));
@Override public void run() { while (!Thread.currentThread().isInterrupted()) { aggregateMetrics.addAndGet(p.getMean() + p.getPercentile(10) + p.getPercentile(50) + p.getPercentile(90)); //System.out.println("AGGREGATE : " + p.getPercentile(10) + " : " + p.getPercentile(50) + " : " + p.getPercentile(90)); } } });
} else if (currentTime - (lastBucket.windowStart + this.bucketSizeInMilliseconds) > timeInMilliseconds) { reset(); return getCurrentBucket(); } else { // we're past the window so we need to create a new bucket Bucket[] allBuckets = buckets.getArray(); return getCurrentBucket();
/** * This code should work without throwing exceptions but the data returned will all be -1 since the rolling percentile is disabled. */ @Test public void testDoesNothingWhenDisabled() { MockedTime time = new MockedTime(); int previousTime = 0; HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, HystrixProperty.Factory.asProperty(false)); for (int i = 0; i < SampleDataHolder2.data.length; i++) { int timeInMillisecondsSinceStart = SampleDataHolder2.data[i][0]; int latency = SampleDataHolder2.data[i][1]; time.increment(timeInMillisecondsSinceStart - previousTime); previousTime = timeInMillisecondsSinceStart; p.addValue(latency); } assertEquals(-1, p.getPercentile(50)); assertEquals(-1, p.getPercentile(75)); assertEquals(-1, p.getMean()); }
/** * Add value (or values) to current bucket. * * @param value * Value to be stored in current bucket such as execution latency in milliseconds */ public void addValue(int... value) { /* no-op if disabled */ if (!enabled.get()) return; for (int v : value) { try { getCurrentBucket().data.addValue(v); } catch (Exception e) { logger.error("Failed to add value: " + v, e); } } }
@Override public void run() { for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { int nextInt = r.nextInt(100); p.addValue(nextInt); added.getAndIncrement(); } latch.countDown(); } });
@Test public void testWriteThreadSafety() { final MockedTime time = new MockedTime(); final HystrixRollingPercentile p = new HystrixRollingPercentile(time, 100, 25, 1000, HystrixProperty.Factory.asProperty(true)); final int NUM_THREADS = 10; final int NUM_ITERATIONS = 1000; final CountDownLatch latch = new CountDownLatch(NUM_THREADS); final Random r = new Random(); final AtomicInteger added = new AtomicInteger(0); for (int i = 0; i < NUM_THREADS; i++) { threadPool.submit(new Runnable() { @Override public void run() { for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { int nextInt = r.nextInt(100); p.addValue(nextInt); added.getAndIncrement(); } latch.countDown(); } }); } try { latch.await(100, TimeUnit.SECONDS); assertEquals(added.get(), p.buckets.peekLast().data.length()); } catch (InterruptedException ex) { fail("Timeout on all threads writing percentiles"); } }
} else if (currentTime - (lastBucket.windowStart + this.bucketSizeInMilliseconds) > timeInMilliseconds) { reset(); return getCurrentBucket(); } else { // we're past the window so we need to create a new bucket Bucket[] allBuckets = buckets.getArray(); return getCurrentBucket();
/** * Add value (or values) to current bucket. * * @param value * Value to be stored in current bucket such as execution latency in milliseconds */ public void addValue(int... value) { /* no-op if disabled */ if (!enabled.get()) return; for (int v : value) { try { getCurrentBucket().data.addValue(v); } catch (Exception e) { logger.error("Failed to add value: " + v, e); } } }
@Override public void run() { for (int j = 1; j < NUM_ITERATIONS / NUM_THREADS + 1; j++) { int nextInt = r.nextInt(100); p.addValue(nextInt); if (threadId == 0) { time.increment(1); } } latch.countDown(); } });
/** * Compute a percentile from the underlying rolling buckets of values. * <p> * For performance reasons it maintains a single snapshot of the sorted values from all buckets that is re-generated each time the bucket rotates. * <p> * This means that if a bucket is 5000ms, then this method will re-compute a percentile at most once every 5000ms. * * @param percentile * value such as 99 (99th percentile), 99.5 (99.5th percentile), 50 (median, 50th percentile) to compute and retrieve percentile from rolling buckets. * @return int percentile value */ public int getPercentile(double percentile) { /* no-op if disabled */ if (!enabled.get()) return -1; // force logic to move buckets forward in case other requests aren't making it happen getCurrentBucket(); // fetch the current snapshot return getCurrentPercentileSnapshot().getPercentile(percentile); }
HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); int previousTime = 0; for (int i = 0; i < SampleDataHolder1.data.length; i++) { time.increment(timeInMillisecondsSinceStart - previousTime); previousTime = timeInMillisecondsSinceStart; p.addValue(latency); System.out.println("0.01: " + p.getPercentile(0.01)); System.out.println("Median: " + p.getPercentile(50)); System.out.println("90th: " + p.getPercentile(90)); System.out.println("99th: " + p.getPercentile(99)); System.out.println("99.5th: " + p.getPercentile(99.5)); System.out.println("99.99: " + p.getPercentile(99.99)); System.out.println("Median: " + p.getPercentile(50)); System.out.println("Median: " + p.getPercentile(50)); System.out.println("Median: " + p.getPercentile(50)); if (p.getPercentile(50) > 5) { fail("We expect around 2 but got: " + p.getPercentile(50)); if (p.getPercentile(99.5) < 20) { fail("We expect to see some high values over 20 but got: " + p.getPercentile(99.5));
/** * This returns the mean (average) of all values in the current snapshot. This is not a percentile but often desired so captured and exposed here. * * @return mean of all values */ public int getMean() { /* no-op if disabled */ if (!enabled.get()) return -1; // force logic to move buckets forward in case other requests aren't making it happen getCurrentBucket(); // fetch the current snapshot return getCurrentPercentileSnapshot().getMean(); }
@Test public void testValueIsZeroAfterRollingWindowPassesAndNoTraffic() { MockedTime time = new MockedTime(); HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); p.addValue(1000); p.addValue(1000); p.addValue(1000); p.addValue(2000); p.addValue(4000); assertEquals(1, p.buckets.size()); // no bucket turnover yet so percentile not yet generated assertEquals(0, p.getPercentile(50)); time.increment(6000); // still only 1 bucket until we touch it again assertEquals(1, p.buckets.size()); // a bucket has been created so we have a new percentile assertEquals(1500, p.getPercentile(50)); // let 1 minute pass time.increment(60000); // no data in a minute should mean all buckets are empty (or reset) so we should not have any percentiles assertEquals(0, p.getPercentile(50)); }
/** * Compute a percentile from the underlying rolling buckets of values. * <p> * For performance reasons it maintains a single snapshot of the sorted values from all buckets that is re-generated each time the bucket rotates. * <p> * This means that if a bucket is 5000ms, then this method will re-compute a percentile at most once every 5000ms. * * @param percentile * value such as 99 (99th percentile), 99.5 (99.5th percentile), 50 (median, 50th percentile) to compute and retrieve percentile from rolling buckets. * @return int percentile value */ public int getPercentile(double percentile) { /* no-op if disabled */ if (!enabled.get()) return -1; // force logic to move buckets forward in case other requests aren't making it happen getCurrentBucket(); // fetch the current snapshot return getCurrentPercentileSnapshot().getPercentile(percentile); }
@Test public void testRolling() { MockedTime time = new MockedTime(); HystrixRollingPercentile p = new HystrixRollingPercentile(time, timeInMilliseconds, numberOfBuckets, bucketDataLength, enabled); p.addValue(1000); p.addValue(1000); p.addValue(1000); p.addValue(2000); assertEquals(0, p.getPercentile(50)); assertEquals(1000, p.getPercentile(50)); p.addValue(1000); p.addValue(500); p.addValue(200); p.addValue(200); p.addValue(1600); p.addValue(200); p.addValue(1600); p.addValue(1600); assertEquals(1000, p.getPercentile(50)); assertEquals(ps.getPercentile(0.15), p.getPercentile(0.15)); assertEquals(ps.getPercentile(0.50), p.getPercentile(0.50)); assertEquals(ps.getPercentile(0.90), p.getPercentile(0.90)); assertEquals(ps.getPercentile(0.995), p.getPercentile(0.995));