/** * Copyright 2015 Netflix, 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 com.netflix.hystrix.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import com.netflix.hystrix.util.HystrixRollingNumber.Time; public class HystrixRollingNumberTest { @Test public void testCreatesBuckets() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // confirm the initial settings assertEquals(200, counter.timeInMilliseconds); assertEquals(10, counter.numberOfBuckets); assertEquals(20, counter.bucketSizeInMillseconds); // we start out with 0 buckets in the queue assertEquals(0, counter.buckets.size()); // add a success in each interval which should result in all 10 buckets being created with 1 success in each for (int i = 0; i < counter.numberOfBuckets; i++) { counter.increment(HystrixRollingNumberEvent.SUCCESS); time.increment(counter.bucketSizeInMillseconds); } // confirm we have all 10 buckets assertEquals(10, counter.buckets.size()); // add 1 more and we should still only have 10 buckets since that's the max counter.increment(HystrixRollingNumberEvent.SUCCESS); assertEquals(10, counter.buckets.size()); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testResetBuckets() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // we start out with 0 buckets in the queue assertEquals(0, counter.buckets.size()); // add 1 counter.increment(HystrixRollingNumberEvent.SUCCESS); // confirm we have 1 bucket assertEquals(1, counter.buckets.size()); // confirm we still have 1 bucket assertEquals(1, counter.buckets.size()); // add 1 counter.increment(HystrixRollingNumberEvent.SUCCESS); // we should now have a single bucket with no values in it instead of 2 or more buckets assertEquals(1, counter.buckets.size()); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testEmptyBucketsFillIn() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // add 1 counter.increment(HystrixRollingNumberEvent.SUCCESS); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // wait past 3 bucket time periods (the 1st bucket then 2 empty ones) time.increment(counter.bucketSizeInMillseconds * 3); // add another counter.increment(HystrixRollingNumberEvent.SUCCESS); // we should have 4 (1 + 2 empty + 1 new one) buckets assertEquals(4, counter.buckets.size()); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testIncrementInSingleBucket() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.TIMEOUT); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 4 assertEquals(4, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SUCCESS).sum()); assertEquals(2, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.FAILURE).sum()); assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testTimeout() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(HystrixRollingNumberEvent.TIMEOUT); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 1 assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // incremenet again in latest bucket counter.increment(HystrixRollingNumberEvent.TIMEOUT); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the counts of the last bucket assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); // the total counts assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testShortCircuited() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 1 assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // incremenet again in latest bucket counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the counts of the last bucket assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); // the total counts assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testThreadPoolRejection() { testCounterType(HystrixRollingNumberEvent.THREAD_POOL_REJECTED); } @Test public void testFallbackSuccess() { testCounterType(HystrixRollingNumberEvent.FALLBACK_SUCCESS); } @Test public void testFallbackFailure() { testCounterType(HystrixRollingNumberEvent.FALLBACK_FAILURE); } @Test public void testExceptionThrow() { testCounterType(HystrixRollingNumberEvent.EXCEPTION_THROWN); } private void testCounterType(HystrixRollingNumberEvent type) { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(type); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 1 assertEquals(1, counter.buckets.getLast().getAdder(type).sum()); assertEquals(1, counter.getRollingSum(type)); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // increment again in latest bucket counter.increment(type); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the counts of the last bucket assertEquals(1, counter.buckets.getLast().getAdder(type).sum()); // the total counts assertEquals(2, counter.getRollingSum(type)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testIncrementInMultipleBuckets() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.TIMEOUT); counter.increment(HystrixRollingNumberEvent.TIMEOUT); counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.TIMEOUT); counter.increment(HystrixRollingNumberEvent.SHORT_CIRCUITED); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the counts of the last bucket assertEquals(2, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SUCCESS).sum()); assertEquals(3, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.FAILURE).sum()); assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.TIMEOUT).sum()); assertEquals(1, counter.buckets.getLast().getAdder(HystrixRollingNumberEvent.SHORT_CIRCUITED).sum()); // the total counts assertEquals(6, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); assertEquals(5, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); assertEquals(3, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.SHORT_CIRCUITED)); // wait until window passes time.increment(counter.timeInMilliseconds); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); // the total counts should now include only the last bucket after a reset since the window passed assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.TIMEOUT)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testCounterRetrievalRefreshesBuckets() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.SUCCESS); counter.increment(HystrixRollingNumberEvent.FAILURE); counter.increment(HystrixRollingNumberEvent.FAILURE); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // we should have 1 bucket since nothing has triggered the update of buckets in the elapsed time assertEquals(1, counter.buckets.size()); // the total counts assertEquals(4, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); assertEquals(2, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); // we should have 4 buckets as the counter 'gets' should have triggered the buckets being created to fill in time assertEquals(4, counter.buckets.size()); // wait until window passes time.increment(counter.timeInMilliseconds); // the total counts should all be 0 (and the buckets cleared by the get, not only increment) assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); // increment counter.increment(HystrixRollingNumberEvent.SUCCESS); // the total counts should now include only the last bucket after a reset since the window passed assertEquals(1, counter.getRollingSum(HystrixRollingNumberEvent.SUCCESS)); assertEquals(0, counter.getRollingSum(HystrixRollingNumberEvent.FAILURE)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testUpdateMax1() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 10); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 10 assertEquals(10, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); assertEquals(10, counter.getRollingMaxValue(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); // increment again in latest bucket counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 20); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the max assertEquals(20, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); // counts per bucket long values[] = counter.getValues(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE); assertEquals(10, values[0]); // oldest bucket assertEquals(0, values[1]); assertEquals(0, values[2]); assertEquals(20, values[3]); // latest bucket } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testUpdateMax2() { MockedTime time = new MockedTime(); try { HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); // increment counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 10); counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 20); // we should have 1 bucket assertEquals(1, counter.buckets.size()); // the count should be 30 assertEquals(30, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); assertEquals(30, counter.getRollingMaxValue(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds * 3); counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 30); counter.updateRollingMax(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE, 50); // we should have 4 buckets assertEquals(4, counter.buckets.size()); // the count assertEquals(50, counter.buckets.getLast().getMaxUpdater(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE).max()); assertEquals(50, counter.getValueOfLatestBucket(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE)); // values per bucket long values[] = counter.getValues(HystrixRollingNumberEvent.THREAD_MAX_ACTIVE); assertEquals(30, values[0]); // oldest bucket assertEquals(0, values[1]); assertEquals(0, values[2]); assertEquals(50, values[3]); // latest bucket } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testMaxValue() { MockedTime time = new MockedTime(); try { HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); counter.updateRollingMax(type, 10); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds); counter.updateRollingMax(type, 30); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds); counter.updateRollingMax(type, 40); // sleep to get to a new bucket time.increment(counter.bucketSizeInMillseconds); counter.updateRollingMax(type, 15); assertEquals(40, counter.getRollingMaxValue(type)); } catch (Exception e) { e.printStackTrace(); fail("Exception: " + e.getMessage()); } } @Test public void testEmptySum() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.COLLAPSED; HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); assertEquals(0, counter.getRollingSum(type)); } @Test public void testEmptyMax() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); assertEquals(0, counter.getRollingMaxValue(type)); } @Test public void testEmptyLatestValue() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; HystrixRollingNumber counter = new HystrixRollingNumber(time, 200, 10); assertEquals(0, counter.getValueOfLatestBucket(type)); } @Test public void testRolling() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.THREAD_MAX_ACTIVE; HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); // iterate over 20 buckets on a queue sized for 2 for (int i = 0; i < 20; i++) { // first bucket counter.getCurrentBucket(); try { time.increment(counter.bucketSizeInMillseconds); } catch (Exception e) { // ignore } assertEquals(2, counter.getValues(type).length); counter.getValueOfLatestBucket(type); // System.out.println("Head: " + counter.buckets.state.get().head); // System.out.println("Tail: " + counter.buckets.state.get().tail); } } @Test public void testCumulativeCounterAfterRolling() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); assertEquals(0, counter.getCumulativeSum(type)); // iterate over 20 buckets on a queue sized for 2 for (int i = 0; i < 20; i++) { // first bucket counter.increment(type); try { time.increment(counter.bucketSizeInMillseconds); } catch (Exception e) { // ignore } assertEquals(2, counter.getValues(type).length); counter.getValueOfLatestBucket(type); } // cumulative count should be 20 (for the number of loops above) regardless of buckets rolling assertEquals(20, counter.getCumulativeSum(type)); } @Test public void testCumulativeCounterAfterRollingAndReset() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); assertEquals(0, counter.getCumulativeSum(type)); // iterate over 20 buckets on a queue sized for 2 for (int i = 0; i < 20; i++) { // first bucket counter.increment(type); try { time.increment(counter.bucketSizeInMillseconds); } catch (Exception e) { // ignore } assertEquals(2, counter.getValues(type).length); counter.getValueOfLatestBucket(type); if (i == 5 || i == 15) { // simulate a reset occurring every once in a while // so we ensure the absolute sum is handling it okay counter.reset(); } } // cumulative count should be 20 (for the number of loops above) regardless of buckets rolling assertEquals(20, counter.getCumulativeSum(type)); } @Test public void testCumulativeCounterAfterRollingAndReset2() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); assertEquals(0, counter.getCumulativeSum(type)); counter.increment(type); counter.increment(type); counter.increment(type); // iterate over 20 buckets on a queue sized for 2 for (int i = 0; i < 20; i++) { try { time.increment(counter.bucketSizeInMillseconds); } catch (Exception e) { // ignore } if (i == 5 || i == 15) { // simulate a reset occurring every once in a while // so we ensure the absolute sum is handling it okay counter.reset(); } } // no increments during the loop, just some before and after counter.increment(type); counter.increment(type); // cumulative count should be 5 regardless of buckets rolling assertEquals(5, counter.getCumulativeSum(type)); } @Test public void testCumulativeCounterAfterRollingAndReset3() { MockedTime time = new MockedTime(); HystrixRollingNumberEvent type = HystrixRollingNumberEvent.SUCCESS; HystrixRollingNumber counter = new HystrixRollingNumber(time, 20, 2); assertEquals(0, counter.getCumulativeSum(type)); counter.increment(type); counter.increment(type); counter.increment(type); // iterate over 20 buckets on a queue sized for 2 for (int i = 0; i < 20; i++) { try { time.increment(counter.bucketSizeInMillseconds); } catch (Exception e) { // ignore } } // since we are rolling over the buckets it should reset naturally // no increments during the loop, just some before and after counter.increment(type); counter.increment(type); // cumulative count should be 5 regardless of buckets rolling assertEquals(5, counter.getCumulativeSum(type)); } private static class MockedTime implements Time { private AtomicInteger time = new AtomicInteger(0); @Override public long getCurrentTimeInMillis() { return time.get(); } public void increment(int millis) { time.addAndGet(millis); } } }