http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/objectsize/ObjectSizeCalculatorTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/objectsize/ObjectSizeCalculatorTest.java b/commons/src/test/java/com/twitter/common/objectsize/ObjectSizeCalculatorTest.java new file mode 100644 index 0000000..cf64a9b --- /dev/null +++ b/commons/src/test/java/com/twitter/common/objectsize/ObjectSizeCalculatorTest.java @@ -0,0 +1,216 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.objectsize; + +import org.junit.Before; +import org.junit.Test; + +import com.twitter.common.objectsize.ObjectSizeCalculator.MemoryLayoutSpecification; + +import static org.junit.Assert.assertEquals; + +public class ObjectSizeCalculatorTest { + + private int A; + private int O; + private int R; + private int S; + private ObjectSizeCalculator objectSizeCalculator; + + @Before + public void setUp() { + MemoryLayoutSpecification memoryLayoutSpecification = + new MemoryLayoutSpecification() { + @Override public int getArrayHeaderSize() { + return 16; + } + @Override public int getObjectHeaderSize() { + return 12; + } + @Override public int getObjectPadding() { + return 8; + } + @Override public int getReferenceSize() { + return 4; + } + @Override public int getSuperclassFieldPadding() { + return 4; + } + }; + + A = memoryLayoutSpecification.getArrayHeaderSize(); + O = memoryLayoutSpecification.getObjectHeaderSize(); + R = memoryLayoutSpecification.getReferenceSize(); + S = memoryLayoutSpecification.getSuperclassFieldPadding(); + + objectSizeCalculator = new ObjectSizeCalculator(memoryLayoutSpecification); + } + + @Test + public void testRounding() { + assertEquals(0, roundTo(0, 8)); + assertEquals(8, roundTo(1, 8)); + assertEquals(8, roundTo(7, 8)); + assertEquals(8, roundTo(8, 8)); + assertEquals(16, roundTo(9, 8)); + assertEquals(16, roundTo(15, 8)); + assertEquals(16, roundTo(16, 8)); + assertEquals(24, roundTo(17, 8)); + } + + @Test + public void testObjectSize() { + assertSizeIs(O, new Object()); + } + + static class ObjectWithFields { + int length; + int offset; + int hashcode; + char[] data = {}; + } + + @Test + public void testObjectWithFields() { + assertSizeIs(O + 3 * 4 + R + A, new ObjectWithFields()); + } + + public static class Class1 { + private boolean b1; + } + + @Test + public void testOneBooleanSize() { + assertSizeIs(O + 1, new Class1()); + } + + public static class Class2 extends Class1 { + private int i1; + } + + @Test + public void testSimpleSubclassSize() { + assertSizeIs(O + roundTo(1, S) + 4, new Class2()); + } + + @Test + public void testZeroLengthArray() { + assertSizeIs(A, new byte[0]); + assertSizeIs(A, new int[0]); + assertSizeIs(A, new long[0]); + assertSizeIs(A, new Object[0]); + } + + @Test + public void testByteArrays() { + assertSizeIs(A + 1, new byte[1]); + assertSizeIs(A + 8, new byte[8]); + assertSizeIs(A + 9, new byte[9]); + } + + @Test + public void testCharArrays() { + assertSizeIs(A + 2 * 1, new char[1]); + assertSizeIs(A + 2 * 4, new char[4]); + assertSizeIs(A + 2 * 5, new char[5]); + } + + @Test + public void testIntArrays() { + assertSizeIs(A + 4 * 1, new int[1]); + assertSizeIs(A + 4 * 2, new int[2]); + assertSizeIs(A + 4 * 3, new int[3]); + } + + @Test + public void testLongArrays() { + assertSizeIs(A + 8 * 1, new long[1]); + assertSizeIs(A + 8 * 2, new long[2]); + assertSizeIs(A + 8 * 3, new long[3]); + } + + @Test + public void testObjectArrays() { + assertSizeIs(A + R * 1, new Object[1]); + assertSizeIs(A + R * 2, new Object[2]); + assertSizeIs(A + R * 3, new Object[3]); + assertSizeIs(A + R * 1, new String[1]); + assertSizeIs(A + R * 2, new String[2]); + assertSizeIs(A + R * 3, new String[3]); + } + + public static class Circular { + Circular c; + } + + @Test + public void testCircular() { + Circular c1 = new Circular(); + long size = objectSizeCalculator.calculateObjectSize(c1); + c1.c = c1; + assertEquals(size, objectSizeCalculator.calculateObjectSize(c1)); + } + + static class ComplexObject<T> { + static class Node<T> { + final T value; + Node<T> previous; + Node<T> next; + + Node(T value) { + this.value = value; + } + } + + private Node<T> first; + private Node<T> last; + + void add(T item) { + Node<T> node = new Node<T>(item); + if (first == null) { + first = node; + } else { + last.next = node; + node.previous = last; + } + last = node; + } + } + + @Test + public void testComplexObject() { + ComplexObject<Object> l = new ComplexObject<Object>(); + l.add(new Object()); + l.add(new Object()); + l.add(new Object()); + + long expectedSize = 0; + expectedSize += roundTo(O + 2 * R, 8); // The complex object itself plus first and last refs. + expectedSize += roundTo(O + 3 * R, 8) * 3; // 3 Nodes - each with 3 object references. + expectedSize += roundTo(O, 8) * 3; // 3 vanilla objects contained in the node values. + + assertSizeIs(expectedSize, l); + } + + private void assertSizeIs(long size, Object o) { + assertEquals(roundTo(size, 8), objectSizeCalculator.calculateObjectSize(o)); + } + + private static long roundTo(long x, int multiple) { + return ObjectSizeCalculator.roundTo(x, multiple); + } +}
http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/quantity/AmountTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/quantity/AmountTest.java b/commons/src/test/java/com/twitter/common/quantity/AmountTest.java new file mode 100644 index 0000000..3e1430b --- /dev/null +++ b/commons/src/test/java/com/twitter/common/quantity/AmountTest.java @@ -0,0 +1,120 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.quantity; + +import com.google.common.collect.Lists; +import com.google.common.collect.Ordering; + +import org.junit.Test; + +import static com.google.common.testing.junit4.JUnitAsserts.assertNotEqual; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author John Sirois + */ +public class AmountTest { + + @Test + public void testEquals() { + assertEquals("expected value equality semantics", + Amount.of(1L, Time.DAYS), Amount.of(1L, Time.DAYS)); + + assertEquals("expected equality to be calculated from amounts converted to a common unit", + Amount.of(1L, Time.DAYS), Amount.of(24L, Time.HOURS)); + + assertNotEqual("expected unit conversions for equality tests to not lose precision", + Amount.of(25L, Time.HOURS), Amount.of(1L, Time.DAYS)); + assertNotEqual("expected unit conversions for equality tests to not lose precision", + Amount.of(1L, Time.DAYS), Amount.of(25L, Time.HOURS)); + + assertFalse("expected value equality to work only for the same Number types", + Amount.of(1L, Time.DAYS).equals(Amount.of(1.0, Time.DAYS))); + assertFalse("expected value equality to work only for the same Number types", + Amount.of(1L, Time.DAYS).equals(Amount.of(1, Time.DAYS))); + + assertFalse("amounts with incompatible units should never be equal even if their values are", + Amount.of(1L, Time.NANOSECONDS).equals(Amount.of(1L, Data.BITS))); + } + + @Test + public void testComparisonMixedUnits() { + assertTrue(Amount.of(1, Time.MINUTES).compareTo(Amount.of(59, Time.SECONDS)) > 0); + assertTrue(Amount.of(1, Time.MINUTES).compareTo(Amount.of(60, Time.SECONDS)) == 0); + assertTrue(Amount.of(1, Time.MINUTES).compareTo(Amount.of(61, Time.SECONDS)) < 0); + + assertTrue(Amount.of(59, Time.SECONDS).compareTo(Amount.of(1, Time.MINUTES)) < 0); + assertTrue(Amount.of(60, Time.SECONDS).compareTo(Amount.of(1, Time.MINUTES)) == 0); + assertTrue(Amount.of(61, Time.SECONDS).compareTo(Amount.of(1, Time.MINUTES)) > 0); + } + + @Test + @SuppressWarnings("unchecked") // Needed because type information lost in vargs. + public void testOrderingMixedUnits() { + assertEquals( + Lists.newArrayList( + Amount.of(1, Data.BITS), + Amount.of(1, Data.KB), + Amount.of(1, Data.MB), + Amount.of(1, Data.MB)), + Ordering.natural().sortedCopy(Lists.newArrayList( + Amount.of(1, Data.KB), + Amount.of(1024, Data.KB), + Amount.of(1, Data.BITS), + Amount.of(1, Data.MB)))); + } + + @Test + @SuppressWarnings("unchecked") // Needed because type information lost in vargs. + public void testOrderingSameUnits() { + assertEquals( + Lists.newArrayList( + Amount.of(1, Time.MILLISECONDS), + Amount.of(2, Time.MILLISECONDS), + Amount.of(3, Time.MILLISECONDS), + Amount.of(4, Time.MILLISECONDS)), + Ordering.natural().sortedCopy(Lists.newArrayList( + Amount.of(3, Time.MILLISECONDS), + Amount.of(2, Time.MILLISECONDS), + Amount.of(1, Time.MILLISECONDS), + Amount.of(4, Time.MILLISECONDS)))); + } + + @Test + public void testConvert() { + Amount<Long, Time> integralDuration = Amount.of(15L, Time.MINUTES); + assertEquals(Long.valueOf(15 * 60 * 1000), integralDuration.as(Time.MILLISECONDS)); + + assertEquals("expected conversion losing precision to use truncation", + Long.valueOf(0), integralDuration.as(Time.HOURS)); + + assertEquals("expected conversion losing precision to use truncation", + Long.valueOf(0), Amount.of(45L, Time.MINUTES).as(Time.HOURS)); + + Amount<Double, Time> decimalDuration = Amount.of(15.0, Time.MINUTES); + assertEquals(Double.valueOf(15 * 60 * 1000), decimalDuration.as(Time.MILLISECONDS)); + assertEquals(Double.valueOf(0.25), decimalDuration.as(Time.HOURS)); + } + + @Test(expected = Amount.TypeOverflowException.class) + public void testAmountThrowsTypeOverflowException() { + Amount.of(1000, Time.DAYS).asChecked(Time.MILLISECONDS); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/ApproximateHistogramTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/ApproximateHistogramTest.java b/commons/src/test/java/com/twitter/common/stats/ApproximateHistogramTest.java new file mode 100644 index 0000000..9eecceb --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/ApproximateHistogramTest.java @@ -0,0 +1,309 @@ +package com.twitter.common.stats; + +import java.util.Arrays; +import java.util.List; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; + +import org.junit.Test; + +import com.twitter.common.objectsize.ObjectSizeCalculator; +import com.twitter.common.quantity.Amount; +import com.twitter.common.quantity.Data; +import com.twitter.common.stats.ApproximateHistogram; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ApproximateHistogramTest { + final int b = 10; + final int h = 3; + + @Test + public void testCollapse() { + ApproximateHistogram hist = new ApproximateHistogram(); + long[] buf1 = {2,5,7}; + long[] buf2 = {3,8,9}; + long[] expected = {3,7,9}; + long[] result = new long[3]; + // [2,5,7] weight 2 and [3,8,9] weight 3 + // weight x array + concat = [2,2,5,5,7,7,3,3,3,8,8,8,9,9,9] + // sort = [2,2,3,3,3,5,5,7,7,8,8,8,9,9,9] + // select every nth elems = [3,7,9] (n = sum weight / 2, ie. 5/3 = 2) + // [2,2,3,3,3,5,5,7,7,8,8,8,9,9,9] + // . . ^ . . . . ^ . . . . ^ . . + // [-------] [-------] [-------] we make 3 packets of 5 elements and take the middle + + ApproximateHistogram.collapse(buf1, 2, buf2, 3, result); + assertArrayEquals(result, expected); + + long[] buf3 = {2, 5, 7, 9}; + long[] buf4 = {3, 8, 9, 12}; + long[] expected2 = {3, 7, 9, 12}; + long[] result2 = new long[4]; + ApproximateHistogram.collapse(buf3, 2, buf4, 2, result2); + assertArrayEquals(expected2, result2); + } + + @Test + public void testRecCollapse() { + long[] empty = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + long[] full = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + ApproximateHistogram hist = new ApproximateHistogram(b, h); + assertArrayEquals(empty, hist.buffer[0]); + assertArrayEquals(empty, hist.buffer[1]); + + initializeValues(hist, b, Suppliers.ofInstance(1L)); + assertArrayEquals(full, hist.buffer[0]); + assertArrayEquals(empty, hist.buffer[1]); + + initializeValues(hist, b, Suppliers.ofInstance(1L)); + assertArrayEquals(full, hist.buffer[0]); + assertArrayEquals(full, hist.buffer[1]); + + hist.add(1); + assertEquals(2, hist.currentTop); + // Buffers are not cleared so we can't check that! + assertArrayEquals(full, hist.buffer[2]); + + initializeValues(hist, 2 * b, Suppliers.ofInstance(1L)); + assertEquals(3, hist.currentTop); + assertArrayEquals(full, hist.buffer[3]); + } + + @Test + public void testReachingMaxDepth() { + ApproximateHistogram hist = new ApproximateHistogram(b, h); + + initializeValues(hist, 8 * b, Suppliers.ofInstance(1L)); + assertEquals(3, hist.currentTop); + + hist.add(1); + assertEquals(3, hist.currentTop); + } + + @Test + public void testMem() { + for (int b = 10; b < 100; b += 10) { + for (int h = 4; h < 16; h += 4) { + ApproximateHistogram hist = new ApproximateHistogram(b, h); + long actualSize = ObjectSizeCalculator.getObjectSize(hist); + long estimatedSize = ApproximateHistogram.memoryUsage(b, h); + assertTrue("Consume less memory than the constraint", actualSize < estimatedSize); + } + } + } + + @Test + public void testMemConstraint() { + ImmutableList.Builder<Amount<Long, Data>> builder = ImmutableList.builder(); + builder.add(Amount.of(1L, Data.KB)); + builder.add(Amount.of(4L, Data.KB)); + builder.add(Amount.of(8L, Data.KB)); + builder.add(Amount.of(16L, Data.KB)); + builder.add(Amount.of(32L, Data.KB)); + builder.add(Amount.of(64L, Data.KB)); + builder.add(Amount.of(256L, Data.KB)); + builder.add(Amount.of(1L, Data.MB)); + builder.add(Amount.of(16L, Data.MB)); + builder.add(Amount.of(32L, Data.MB)); + List<Amount<Long, Data>> sizes = builder.build(); + + for (Amount<Long, Data> maxSize: sizes) { + ApproximateHistogram hist = new ApproximateHistogram(maxSize); + for (long i = 0; i < 1000 * 1000; i++) { hist.add(i); } + long size = ObjectSizeCalculator.getObjectSize(hist); + assertTrue(size < maxSize.as(Data.BYTES)); + } + } + + @Test + public void testLowMemoryPrecision() { + double e = ApproximateHistogram.DEFAULT_PRECISION.getEpsilon(); + int n = ApproximateHistogram.DEFAULT_PRECISION.getN(); + int defaultDepth = ApproximateHistogram.computeDepth(e, n); + int defaultBufferSize = ApproximateHistogram.computeBufferSize(defaultDepth, n); + + ApproximateHistogram hist = new ApproximateHistogram(Amount.of(1L, Data.KB)); + int depth = hist.buffer.length - 1; + int bufferSize = hist.buffer[0].length; + + assertTrue(depth > defaultDepth); + assertTrue(bufferSize < defaultBufferSize); + } + + @Test + public void testHighMemoryPrecision() { + double e = ApproximateHistogram.DEFAULT_PRECISION.getEpsilon(); + int n = ApproximateHistogram.DEFAULT_PRECISION.getN(); + int defaultDepth = ApproximateHistogram.computeDepth(e, n); + int defaultBufferSize = ApproximateHistogram.computeBufferSize(defaultDepth, n); + + ApproximateHistogram hist = new ApproximateHistogram(Amount.of(1L, Data.MB)); + int depth = hist.buffer.length - 1; + int bufferSize = hist.buffer[0].length; + + assertTrue(depth < defaultDepth); + assertTrue(bufferSize > defaultBufferSize); + } + + private void initIndexArray(ApproximateHistogram hist, int b) { + Arrays.fill(hist.indices, b - 1); + int buf0Size = Math.min(b, hist.leafCount); + int buf1Size = Math.max(0, hist.leafCount - buf0Size); + hist.indices[0] = buf0Size - 1; + hist.indices[1] = buf1Size - 1; + } + + private long getBiggest(ApproximateHistogram hist) { + int j = hist.biggest(hist.indices); + int idx = hist.indices[j]; + hist.indices[j] -= 1; + return hist.buffer[j][idx]; + } + + @Test + public void testBiggestIndexFinder() { + ApproximateHistogram hist = new ApproximateHistogram(b, h); + int n = 3; + for (int i=1; i <= n; i++) { + hist.add(i); + } + + initIndexArray(hist, b); + for (int i=1; i <= n; i++) { + assertEquals(n - i + 1, getBiggest(hist)); + } + + n = 2 * b; + for (int i=4; i <= n; i++) { + hist.add(i); + } + + initIndexArray(hist, b); + for (int i=1; i <= n; i++) { + assertEquals(n - i + 1, getBiggest(hist)); + } + + hist.add(2*b + 1); + n += 1; + + initIndexArray(hist, b); + assertEquals(n, getBiggest(hist)); + + for (int i=2; i <= n; i += 2) { + assertEquals(n - i + 1, getBiggest(hist)); + } + } + + @Test + public void testIsBufferEmpty() { + ApproximateHistogram hist = new ApproximateHistogram(b, h); + + for (int i=0; i < 3*b; i++) { + hist.add(i); + } + assertEquals(false, hist.isBufferEmpty(2)); + assertEquals(true, hist.isBufferEmpty(3)); + + for (int i=0; i < 2*b; i++) { + hist.add(i); + } + assertEquals(true, hist.isBufferEmpty(2)); + assertEquals(false, hist.isBufferEmpty(3)); + } + + @Test + public void testHistogramWithNegative() { + ApproximateHistogram hist = new ApproximateHistogram(); + hist.add(-1L); + assertEquals(-1L, hist.getQuantile(0.0)); + assertEquals(-1L, hist.getQuantile(0.5)); + assertEquals(-1L, hist.getQuantile(1.0)); + } + + @Test + public void testHistogramWithEdgeCases() { + ApproximateHistogram hist = new ApproximateHistogram(); + hist.add(Long.MIN_VALUE); + assertEquals(Long.MIN_VALUE, hist.getQuantile(0.0)); + assertEquals(Long.MIN_VALUE, hist.getQuantile(1.0)); + + hist.add(Long.MAX_VALUE); + assertEquals(Long.MIN_VALUE, hist.getQuantile(0.0)); + assertEquals(Long.MAX_VALUE, hist.getQuantile(1.0)); + } + + @Test + public void testQueryZerothQuantile() { + // Tests that querying the zeroth quantile does not throw an exception + ApproximateHistogram hist = new ApproximateHistogram(b, h); + initializeValues(hist, 10, Suppliers.ofInstance(1L)); + assertEquals(1L, hist.getQuantile(0.0)); + } + + @Test + public void testSmallDataCase() { + // Tests that querying the zeroth quantile does not throw an exception + ApproximateHistogram hist = new ApproximateHistogram(b, h); + initializeValues(hist, 1, Suppliers.ofInstance(1L)); + assertEquals(1L, hist.getQuantile(0.5)); + } + + @Test + public void testSimpleCase() { + ApproximateHistogram hist = new ApproximateHistogram(); + int n = 10; + initializeValues(hist, n, monotonic()); + for (int i = 1; i <= n; i++) { + double q = i / 10.0; + assertEquals(i, hist.getQuantile(q), 1.0); + } + } + + @Test + public void testGetQuantiles() { + ApproximateHistogram hist = new ApproximateHistogram(); + int n = 10; + initializeValues(hist, n, monotonic()); + double[] quantiles = new double[n]; + for (int i = 0; i < n; i++) { + quantiles[i] = (i + 1) / 10.0; + } + long[] results = hist.getQuantiles(quantiles); + for (int i = 0; i < n; i++) { + long res = results[i]; + double q = quantiles[i]; + assertEquals(hist.getQuantile(q), res); + } + } + + @Test + public void testYetAnotherGetQuantiles() { + // this test originates from issue CSL-586 + ApproximateHistogram hist = new ApproximateHistogram(); + hist.add(0); + hist.add(4); + hist.add(9); + hist.add(8); + double[] quantiles = new double[]{0.5, 0.9, 0.99}; + long[] expected = new long[]{8,9,9}; + assertArrayEquals(hist.getQuantiles(quantiles), expected); + } + + private static void initializeValues(ApproximateHistogram hist, int n, Supplier<Long> what) { + for (int i=0; i<n ; i++) { + hist.add(what.get()); + } + } + + private static Supplier<Long> monotonic() { + return new Supplier<Long>() { + long i = 0; + @Override public Long get() { return ++i; } + }; + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/ElapsedTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/ElapsedTest.java b/commons/src/test/java/com/twitter/common/stats/ElapsedTest.java new file mode 100644 index 0000000..3a7ec34 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/ElapsedTest.java @@ -0,0 +1,70 @@ +package com.twitter.common.stats; + +import org.junit.Before; +import org.junit.Test; + +import com.twitter.common.quantity.Amount; +import com.twitter.common.quantity.Time; +import com.twitter.common.util.testing.FakeTicker; + +import static org.junit.Assert.assertEquals; + +/** + * @author William Farner + */ +public class ElapsedTest { + + private static final Amount<Long, Time> ONE_SECOND = Amount.of(1L, Time.SECONDS); + + private static final String NAME = "elapsed"; + + private FakeTicker ticker; + + @Before + public void setUp() { + ticker = new FakeTicker(); + Stats.flush(); + } + + private Elapsed elapsed(Time granularity) { + return new Elapsed(NAME, granularity, ticker); + } + + @Test + public void testTimeSince() { + Elapsed elapsed = elapsed(Time.MILLISECONDS); + checkValue(0); + ticker.advance(ONE_SECOND); + checkValue(1000); + + elapsed.reset(); + checkValue(0); + + elapsed.reset(); + ticker.advance(ONE_SECOND); + checkValue(1000); + ticker.advance(ONE_SECOND); + checkValue(2000); + ticker.advance(ONE_SECOND); + checkValue(3000); + ticker.advance(ONE_SECOND); + checkValue(4000); + } + + @Test + public void testGranularity() { + Elapsed elapsed = elapsed(Time.HOURS); + checkValue(0); + ticker.advance(Amount.of(1L, Time.DAYS)); + checkValue(24); + + elapsed.reset(); + ticker.advance(Amount.of(1L, Time.MINUTES)); + checkValue(0); + } + + private void checkValue(long expected) { + long actual = (Long) Stats.getVariable(NAME).read(); + assertEquals(expected, actual); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/EntropyTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/EntropyTest.java b/commons/src/test/java/com/twitter/common/stats/EntropyTest.java new file mode 100644 index 0000000..43c04c5 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/EntropyTest.java @@ -0,0 +1,61 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.google.common.collect.Lists; +import junit.framework.TestCase; +import org.junit.Test; + +import java.util.List; + +/** + * Tests Entropy. + * + * @author Gilad Mishne + */ +public class EntropyTest extends TestCase { + + private void assertEqualsWithDeviation(double expected, double predicted, double deviation) { + assertTrue(String.format("%2.4f not within %2.4f distance of %2.4f", + predicted, deviation, expected), + Math.abs(expected - predicted) <= deviation); + } + + @Test + public void test() throws Exception { + List<Integer> numbers = Lists.newArrayList(); + double deviation = 0.01; + + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 0, deviation); + + numbers.add(1); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 0, deviation); + + numbers.add(2); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 1, deviation); + + numbers.addAll(Lists.newArrayList(1, 2)); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 1, deviation); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).perplexity(), 2, deviation); + + numbers.addAll(Lists.newArrayList(2, 2, 3, 4)); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 1.75, deviation); + + numbers.addAll(Lists.newArrayList(1, 1, 1, 1)); + assertEqualsWithDeviation(new Entropy<Integer>(numbers).entropy(), 1.625, deviation); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/MergedHistogramTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/MergedHistogramTest.java b/commons/src/test/java/com/twitter/common/stats/MergedHistogramTest.java new file mode 100644 index 0000000..96b5988 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/MergedHistogramTest.java @@ -0,0 +1,97 @@ +// ================================================================================================= +// Copyright 2013 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import org.junit.Test; + +import com.twitter.common.quantity.Amount; +import com.twitter.common.quantity.Data; + +import static org.junit.Assert.assertEquals; + +public class MergedHistogramTest { + + @Test + public void testEmptyMergedHistogram() { + ApproximateHistogram empty[] = new ApproximateHistogram[0]; + Histogram mergedHistogram = ApproximateHistogram.merge(empty); + + assertEquals(0L, mergedHistogram.getQuantile(0.5)); + } + + @Test + public void testMergedSimilarHistogram() { + int n = 10; + ApproximateHistogram histograms[] = new ApproximateHistogram[n]; + for (int i = 0; i < n; i++) { + ApproximateHistogram h = new ApproximateHistogram(); + h.add(i); + histograms[i] = h; + } + + Histogram mergedHistogram = ApproximateHistogram.merge(histograms); + assertEquals(0L, mergedHistogram.getQuantile(0.0)); + assertEquals(1L, mergedHistogram.getQuantile(0.1)); + assertEquals(5L, mergedHistogram.getQuantile(0.5)); + assertEquals(9L, mergedHistogram.getQuantile(0.9)); + assertEquals(9L, mergedHistogram.getQuantile(0.99)); + } + + @Test + public void testMergedDifferentHistogram() { + int n = 10; + ApproximateHistogram histograms[] = new ApproximateHistogram[n]; + for (int i = 0; i < n; i++) { + ApproximateHistogram h = new ApproximateHistogram(Amount.of(2L + 4*i, Data.KB)); + h.add(i); + histograms[i] = h; + } + + Histogram mergedHistogram = ApproximateHistogram.merge(histograms); + assertEquals(0L, mergedHistogram.getQuantile(0.0)); + assertEquals(1L, mergedHistogram.getQuantile(0.1)); + assertEquals(5L, mergedHistogram.getQuantile(0.5)); + assertEquals(9L, mergedHistogram.getQuantile(0.9)); + assertEquals(9L, mergedHistogram.getQuantile(0.99)); + } + + @Test + public void testMergedBigHistogram() { + int n = 10; + int m = 5000; + ApproximateHistogram histograms[] = new ApproximateHistogram[n]; + int x = 0; + for (int i = 0; i < n; i++) { + ApproximateHistogram h = new ApproximateHistogram(); + while(x < m * (i + 1)) { + h.add(x); + x += 1; + } + histograms[i] = h; + } + long sum = m * n; + + double maxError = ApproximateHistogram.DEFAULT_PRECISION.getEpsilon() * + ApproximateHistogram.DEFAULT_PRECISION.getN(); + Histogram mergedHistogram = ApproximateHistogram.merge(histograms); + for (int i = 1; i < 10; i++) { + double q = i / 10.0; + double expected = q * sum; + assertEquals(expected, mergedHistogram.getQuantile(q), maxError); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/MovingAverageTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/MovingAverageTest.java b/commons/src/test/java/com/twitter/common/stats/MovingAverageTest.java new file mode 100644 index 0000000..8791820 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/MovingAverageTest.java @@ -0,0 +1,86 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import java.util.List; + +import com.google.common.collect.Lists; + +import org.junit.Before; +import org.junit.Test; + +import com.twitter.common.testing.easymock.EasyMockTest; + +import static org.easymock.EasyMock.expect; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Test for MovingAverage. + * + * @author William Farner + */ +public class MovingAverageTest extends EasyMockTest { + + private Stat<Integer> input; + + @Before + public void setUp() { + input = createMock(new Clazz<Stat<Integer>>() {}); + } + + @Test + public void testEmptySeries() { + runTest(Lists.<Integer>newArrayList(), Lists.<Double>newArrayList()); + } + + @Test + public void testConstantValues() { + runTest( + Lists.newArrayList( 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5), + Lists.newArrayList(5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d, 5d)); + } + + @Test + public void testLinear() { + runTest( + Lists.newArrayList( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), + Lists.newArrayList(1d, 1.5d, 2d, 2.5d, 3d, 3.5d, 4d, 4.5d, 5d, 5.5d, 6.5d, 7.5d, 8.5d)); + } + + @Test + public void testStep() { + runTest( + Lists.newArrayList( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10), + Lists.newArrayList(0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 1d, 2d, 3d, 4d, 5d)); + } + + private void runTest(List<Integer> inputs, List<Double> expectedOutputs) { + expect(input.getName()).andReturn("test").atLeastOnce(); + for (int value : inputs) { + expect(input.read()).andReturn(value); + } + + control.replay(); + + MovingAverage<Integer> movingAvg = MovingAverage.of(input, 10 /* window size */); + + for (double output : expectedOutputs) { + assertThat(movingAvg.sample(), is(output)); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/MovingWindowDeltaTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/MovingWindowDeltaTest.java b/commons/src/test/java/com/twitter/common/stats/MovingWindowDeltaTest.java new file mode 100644 index 0000000..1cb6cbb --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/MovingWindowDeltaTest.java @@ -0,0 +1,103 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.google.common.collect.Lists; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicLong; + +import javax.xml.bind.annotation.XmlElement; + +import com.google.common.base.Supplier; + +/** + * Test for MovingWindowDelta. + * + * @author Feng Zhuge + */ +public class MovingWindowDeltaTest { + private static final int DEFAULT_WINDOW_SIZE = 5; + + private AtomicLong externalValue = new AtomicLong(); + + public Supplier<AtomicLong> getSupplier() { + return new Supplier<AtomicLong>() { + @Override + public AtomicLong get() { + return externalValue; + } + }; + } + + @Test + public void testOneSample() { + MovingWindowDelta<AtomicLong> movingWindowDelta = MovingWindowDelta.of( + "test", getSupplier(), DEFAULT_WINDOW_SIZE); + + externalValue.getAndSet(7l); + externalValue.getAndSet(11l); + + assertEquals(11l, movingWindowDelta.doSample().longValue()); + } + + @Test + public void testMultipleSamples() { + MovingWindowDelta<AtomicLong> movingWindowDelta = MovingWindowDelta.of( + "test", getSupplier(), DEFAULT_WINDOW_SIZE); + + externalValue.getAndSet(3l); + assertEquals(3l, movingWindowDelta.doSample().longValue()); + externalValue.getAndSet(8l); + assertEquals(8l, movingWindowDelta.doSample().longValue()); + } + + @Test + public void TestExpiringCounts() { + MovingWindowDelta<AtomicLong> movingWindowDelta = MovingWindowDelta.of( + "test", getSupplier(), DEFAULT_WINDOW_SIZE); + + long expectedDelta; + for (long i = 0; i < 100; ++i) { + expectedDelta = i < DEFAULT_WINDOW_SIZE ? i + 1 : DEFAULT_WINDOW_SIZE; + + externalValue.getAndSet(i + 1); + assertEquals(expectedDelta, movingWindowDelta.doSample().longValue()); + } + } + + @Test + public void TestDifferentValueExpiring() { + MovingWindowDelta<AtomicLong> movingWindowDelta = + MovingWindowDelta.of("test", getSupplier(), 5); + + long ret = 0l; + for (long i = 0; i < 10; ++i) { + externalValue.getAndSet(i * i); + ret = movingWindowDelta.doSample(); + } + assertEquals(65l, ret); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/NumericStatExporterTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/NumericStatExporterTest.java b/commons/src/test/java/com/twitter/common/stats/NumericStatExporterTest.java new file mode 100644 index 0000000..3fc2557 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/NumericStatExporterTest.java @@ -0,0 +1,147 @@ +// ================================================================================================= +// Copyright 2012 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Matchers; +import org.mockito.Mock; + +import com.twitter.common.application.ShutdownRegistry; +import com.twitter.common.base.Closure; +import com.twitter.common.base.Command; +import com.twitter.common.quantity.Amount; +import com.twitter.common.quantity.Time; +import com.twitter.common.testing.mockito.MockitoTest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Unit tests for {@code NumericStatExporter} + */ +public class NumericStatExporterTest extends MockitoTest { + private static final Amount<Long, Time> TEST_EXPORT_INTERVAL = Amount.of(1L, Time.MINUTES); + private static final String MOCK_STAT_NAME = "NumericStatExporterTest_mock_stat"; + private static final int MOCK_STAT_READ_VALUE = 0; + private static final int MOCK_STAT_SAMPLED_VALUE = 1; + + @Mock + private Closure<Map<String, ? extends Number>> mockExportSink; + @Mock + private ScheduledExecutorService mockExecutor; + @Mock + private ShutdownRegistry mockShutdownRegistry; + @Mock + private RecordingStat<Integer> mockRecordingStat; + + @Captor + private ArgumentCaptor<Runnable> runnableCaptor; + @Captor + private ArgumentCaptor<Command> commandCaptor; + @Captor + private ArgumentCaptor<Map<String, ? extends Number>> statReadValueMapCaptor; + + + private NumericStatExporter numericStatExporter; + + @Before + public void setUp() { + when(mockRecordingStat.getName()).thenReturn(MOCK_STAT_NAME); + when(mockRecordingStat.read()).thenReturn(MOCK_STAT_READ_VALUE); + when(mockRecordingStat.sample()).thenReturn(MOCK_STAT_SAMPLED_VALUE); + Stats.export(mockRecordingStat); + + numericStatExporter + = new NumericStatExporter(mockExportSink, mockExecutor, TEST_EXPORT_INTERVAL); + } + + @Test + public void testStartMethodScheduleExport() { + numericStatExporter.start(mockShutdownRegistry); + + // Verify the executor is scheduled properly. + verify(mockExecutor).scheduleAtFixedRate(runnableCaptor.capture(), + anyLong(), anyLong(), Matchers.<TimeUnit>anyObject()); + // Verify the behavior of the schedule runnable. + runnableCaptor.getValue().run(); + verify(mockExportSink).execute(statReadValueMapCaptor.capture()); + // Verify stat reading behavior. + assertEquals(MOCK_STAT_READ_VALUE, statReadValueMapCaptor.getValue().get(MOCK_STAT_NAME)); + } + + @Test + public void testStartMethodShutdownRegistryFinalSampleAndExport() { + numericStatExporter.start(mockShutdownRegistry); + + // Verify the shutdown registry is called. + verify(mockShutdownRegistry).addAction(commandCaptor.capture()); + // Verify the behavior of the shutdown registry command. + commandCaptor.getValue().execute(); + + // The shutdown command calls stop(), which we'll test separately. + + // Now verifies the final sample and export behavior. + verify(mockExportSink).execute(statReadValueMapCaptor.capture()); + // Verify stat sampling and reading behavior. + assertEquals(MOCK_STAT_SAMPLED_VALUE, statReadValueMapCaptor.getValue().get(MOCK_STAT_NAME)); + } + + @Test + public void testStopMethodAwaitTerminationReturnsFast() throws Exception { + when(mockExecutor.awaitTermination(anyLong(), Matchers.<TimeUnit>anyObject())) + .thenReturn(true); + numericStatExporter.stop(); + verify(mockExecutor).awaitTermination(eq(1L), eq(TimeUnit.SECONDS)); + verifyNoMoreInteractions(mockExecutor); + } + + @Test + public void testStopMethodAwaitTerminationReturnsSlowly() throws Exception { + when(mockExecutor.awaitTermination(anyLong(), Matchers.<TimeUnit>anyObject())) + .thenReturn(false); + numericStatExporter.stop(); + verify(mockExecutor, times(2)).awaitTermination(eq(1L), eq(TimeUnit.SECONDS)); + verify(mockExecutor).shutdownNow(); + verifyNoMoreInteractions(mockExecutor); + } + + @Test + public void testStopMethodAwaitTerminationInterrupted() throws Exception { + when(mockExecutor.awaitTermination(anyLong(), Matchers.<TimeUnit>anyObject())) + .thenThrow(new InterruptedException("mock failure")); + numericStatExporter.stop(); + verify(mockExecutor).awaitTermination(eq(1L), eq(TimeUnit.SECONDS)); + verify(mockExecutor).shutdownNow(); + verifyNoMoreInteractions(mockExecutor); + // We need to reset the thread's interrupt flag so other tests who uses certain + // concurrent calls like latches and various waits wouldn't fail. + Thread.currentThread().interrupted(); + } +} + http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/PercentileTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/PercentileTest.java b/commons/src/test/java/com/twitter/common/stats/PercentileTest.java new file mode 100644 index 0000000..e3b7efe --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/PercentileTest.java @@ -0,0 +1,207 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.google.common.collect.Lists; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +/** + * @author William Farner + */ +public class PercentileTest { + + private static final double EPSILON = 1e-6; + private static final float SAMPLE_RATE = 100; + private static final double[] PERCENTILES = new double[] {0, 10, 50, 90, 99, 99.9, 99.99, 100}; + + private Percentile<Integer> percentiles; + + @Before + public void setUp() { + percentiles = new Percentile<Integer>("test", SAMPLE_RATE, PERCENTILES); + } + + @Test + public void testNoData() { + checkPercentiles(percentiles, 0, 0, 0, 0, 0, 0, 0, 0); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testSingleValue() { + percentiles.record(10); + checkPercentiles(percentiles, 10, 10, 10, 10, 10, 10, 10, 10); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testConstant() { + for (int i = 0; i < 100; i++) { + percentiles.record(10); + } + + checkPercentiles(percentiles, 10, 10, 10, 10, 10, 10, 10, 10); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testLinear() { + for (int i = 0; i < 10001; i++) { + percentiles.record(i); + } + + checkPercentiles(percentiles, 0, 1000, 5000, 9000, 9900, 9990, 9999, 10000); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testMultipleSampleWindows() { + Percentile<Integer> mypercentile = new Percentile<Integer>("test", 2, null, PERCENTILES); + + for (int i = 0; i < 10000; i++) { + mypercentile.record(i); + } + // Large number filler so that our percentile hit an integer index. + mypercentile.record(90000); + checkPercentiles(mypercentile, 0, 1000, 5000, 9000, 9900, 9990, 9999, 90000); + + for (int i = 10000; i < 20000; i++) { + mypercentile.record(i); + } + checkPercentiles(mypercentile, 0, 2000, 10000, 18000, 19800, 19980, 19998, 90000); + + for (int i = 20000; i < 30000; i++) { + mypercentile.record(i); + } + // Previous filler is flushed from the sample queue. Refill. + mypercentile.record(90000); + checkPercentiles(mypercentile, 10000, 12000, 20000, 28000, 29800, 29980, 29998, 90000); + } + + + @Test + public void testNullSampler() { + int N = 10001; + Percentile<Integer> mypercentile = new Percentile<Integer>("test", 1, null, PERCENTILES); + for (int i = 0; i < N; i++) { + mypercentile.record(i); + } + assertThat(mypercentile.samples.size(), is(N)); + checkPercentiles(mypercentile, 0, 1000, 5000, 9000, 9900, 9990, 9999, 10000); + checkValuesAreFlushed(mypercentile); + } + + @Test + public void testReverseLinear() { + for (int i = 0; i < 10001; i++) { + percentiles.record(i); + } + + checkPercentiles(percentiles, 0, 1000, 5000, 9000, 9900, 9990, 9999, 10000); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testShuffledSteps() { + List<Integer> values = Lists.newArrayList(); + for (int i = 0; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + values.add(i); + } + } + values.add(2000); + Collections.shuffle(values); + for (int sample : values) { + percentiles.record(sample); + } + + checkPercentiles(percentiles, 0, 100, 500, 900, 990, 999, 999, 2000); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testNegativeValues() { + List<Integer> values = Lists.newArrayList(); + for (int i = 0; i < 1000; i++) { + for (int j = 0; j < 10; j++) { + values.add(-1 * i); + } + } + values.add(-2000); + Collections.shuffle(values); + for (int sample : values) { + percentiles.record(sample); + } + + checkPercentiles(percentiles, -2000, -900, -500, -100, -10, -1, 0, 0); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testPercentileInterpolates() { + for (int i = 0; i < 9999; i++) { + percentiles.record(i); + } + + checkPercentiles(percentiles, 0, 999.8, 4999, 8998.2, 9898.02, 9988.002, 9997.0002, 9998); + checkValuesAreFlushed(percentiles); + } + + @Test + public void testHonorsBufferLimit() { + for (int i = 0; i < 1000; i++) { + percentiles.record(0); + } + + // Now fill the buffer with a constant. + for (int i = 0; i < Percentile.MAX_BUFFER_SIZE; i++) { + percentiles.record(1); + } + + assertThat(percentiles.samples.size(), is(Percentile.MAX_BUFFER_SIZE)); + checkPercentiles(percentiles, 1, 1, 1, 1, 1, 1, 1, 1); + checkValuesAreFlushed(percentiles); + } + + private void checkPercentiles(Percentile<Integer> input_percentiles, double... values) { + assertThat(values.length, is(PERCENTILES.length)); + + for (int i = 0; i < values.length; i++) { + checkPercentile(input_percentiles, PERCENTILES[i], values[i]); + } + } + + private void checkValuesAreFlushed(Percentile<Integer> input_percentiles, double... values) { + // Check that the values were flushed. + for (int i = 0; i < values.length; i++) { + checkPercentile(input_percentiles, PERCENTILES[i], 0); + } + assertThat(percentiles.samples.isEmpty(), is(true)); + } + + private void checkPercentile(Percentile<Integer> input_percentiles, + double percentile, double value) { + assertEquals(value, input_percentiles.getPercentile(percentile).sample(), EPSILON); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/PipelineStatsTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/PipelineStatsTest.java b/commons/src/test/java/com/twitter/common/stats/PipelineStatsTest.java new file mode 100644 index 0000000..9656119 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/PipelineStatsTest.java @@ -0,0 +1,150 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.google.common.collect.Sets; +import com.twitter.common.quantity.Time; +import com.twitter.common.util.Clock; +import com.twitter.common.util.testing.FakeClock; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests the PipelineStats class. + * + * @author William Farner + */ +public class PipelineStatsTest { + + private Clock clock = new FakeClock(); + private PipelineStats stats; + + @Before + public void setUp() { + stats = new PipelineStats("test", Sets.newHashSet("a", "b", "c"), clock, Time.MILLISECONDS); + } + + @Test + public void testEmptyFlow() { + PipelineStats.Snapshot pipeline = stats.newSnapshot(); + pipeline.end(); + + checkSample("a", 0, 0); + checkSample("b", 0, 0); + checkSample("c", 0, 0); + checkSample("full", 1, 0); + } + + @Test + public void testSimpleFlow() throws Exception { + PipelineStats.Snapshot pipeline = stats.newSnapshot(); + pipeline.start("a"); + clock.waitFor(10); + pipeline.start("b"); + clock.waitFor(20); + pipeline.start("c"); + clock.waitFor(30); + pipeline.end(); + + checkSample("a", 1, 10); + checkSample("b", 1, 20); + checkSample("c", 1, 30); + checkSample("full", 1, 60); + } + + @Test + public void testEarlyExit() throws Exception { + PipelineStats.Snapshot pipeline = stats.newSnapshot(); + pipeline.start("a"); + clock.waitFor(10); + pipeline.start("b"); + clock.waitFor(20); + pipeline.end(); + + checkSample("a", 1, 10); + checkSample("b", 1, 20); + checkSample("full", 1, 30); + } + + @Test + public void testDuplicatedStages() throws Exception { + PipelineStats.Snapshot pipeline = stats.newSnapshot(); + pipeline.start("a"); + clock.waitFor(10); + pipeline.start("b"); + clock.waitFor(20); + pipeline.start("b"); + clock.waitFor(10); + pipeline.start("b"); + clock.waitFor(50); + pipeline.start("c"); + clock.waitFor(30); + pipeline.start("c"); + clock.waitFor(70); + pipeline.end(); + + checkSample("a", 1, 10); + checkSample("b", 3, 80); + checkSample("c", 2, 100); + checkSample("full", 1, 190); + } + + @Test + public void testSimultaneousSnapshots() throws Exception { + PipelineStats.Snapshot pipeline1 = stats.newSnapshot(); + PipelineStats.Snapshot pipeline2 = stats.newSnapshot(); + pipeline1.start("a"); + clock.waitFor(10); + pipeline2.start("a"); + pipeline1.start("b"); + clock.waitFor(20); + pipeline2.start("b"); + clock.waitFor(10); + pipeline2.start("c"); + clock.waitFor(10); + pipeline2.end(); + + // Only pipeline2 was recorded, so we should not see pipeline1 in the time series yet. + checkSample("a", 1, 20); + checkSample("b", 1, 10); + checkSample("c", 1, 10); + checkSample("full", 1, 40); + + pipeline1.start("c"); + clock.waitFor(30); + pipeline1.end(); + + // The current sample will now be the sum of pipeline1 and pipeline2. + checkSample("a", 2, 30); + checkSample("b", 2, 50); + checkSample("c", 2, 40); + checkSample("full", 2, 120); + } + + private void checkSample(String stage, long events, long latency) { + AtomicLong eventsCounter = stats.getStatsForStage(stage).getEventCounter(); + AtomicLong latencyCounter = stats.getStatsForStage(stage).getTotalCounter(); + + assertThat(eventsCounter.get(), is(events)); + assertThat(latencyCounter.get(), is(latency)); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/PrintableHistogramTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/PrintableHistogramTest.java b/commons/src/test/java/com/twitter/common/stats/PrintableHistogramTest.java new file mode 100644 index 0000000..b1c024d --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/PrintableHistogramTest.java @@ -0,0 +1,30 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import junit.framework.TestCase; + +public class PrintableHistogramTest extends TestCase { + + public void testPrintHistogram() { + PrintableHistogram hist = new PrintableHistogram(10, 20, 30, 40, 50, 60, 70, 80, 90, 100); + for (int i = 10; i > 0; i--) { + hist.addValue(i * 10, 10 - i); + } + System.out.println(hist); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/RateTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/RateTest.java b/commons/src/test/java/com/twitter/common/stats/RateTest.java new file mode 100644 index 0000000..9c5058a --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/RateTest.java @@ -0,0 +1,242 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.twitter.common.base.Supplier; +import com.twitter.common.util.testing.FakeTicker; +import org.easymock.IMocksControl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.twitter.common.util.testing.FakeTicker; + +import java.util.concurrent.atomic.AtomicLong; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +/** + * @author William Farner + */ +public class RateTest { + + private static final int ONE_SEC = 1000000000; + private static final double EPSILON = 1E-6; + + private IMocksControl control; + private FakeTicker ticker; + + private Stat<Integer> input; + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + control = createControl(); + + ticker = new FakeTicker(); + input = control.createMock(Stat.class); + } + + @After + public void verify() { + Stats.flush(); + control.verify(); + } + + @Test + public void testInputsRegistered() { + expect(input.getName()).andReturn("test"); + expectLastCall().atLeastOnce(); + + control.replay(); + + Rate.of(input); + assertNotNull(Stats.getVariable("test")); + } + + @Test + public void testNoHistory() throws Exception { + expectCalls(0); + + control.replay(); + + assertResults(); + } + + @Test + public void testFlat() throws Exception { + expectCalls(10, 10); + + control.replay(); + + assertResults(0); + } + + @Test + public void testFixedRate() throws Exception { + expectCalls(10, 20, 30, 40); + + control.replay(); + + assertResults(10, 10, 10); + } + + @Test + public void testVariableRate() throws Exception { + expectCalls(10, 20, 50, 150); + + control.replay(); + + assertResults(10, 30, 100); + } + + @Test + public void testVariableRateAtomicLong() throws Exception { + AtomicLong value = new AtomicLong(); + Rate<AtomicLong> rate = Rate.of("test", value).withTicker(ticker).build(); + + ticker.waitNanos(ONE_SEC); + value.set(10); + assertEquals(0d, rate.sample(), EPSILON); + + ticker.waitNanos(ONE_SEC); + value.set(20); + assertEquals(10d, rate.sample(), EPSILON); + + ticker.waitNanos(ONE_SEC); + value.set(50); + assertEquals(30d, rate.sample(), EPSILON); + + ticker.waitNanos(ONE_SEC); + value.set(100); + assertEquals(50d, rate.sample(), EPSILON); + + control.replay(); + } + + @Test + public void testNegativeRate() throws Exception { + expectCalls(40, 30, 20, 10); + + control.replay(); + + assertResults(-10, -10, -10); + } + + @Test + public void testZeroDelta() throws Exception { + expectCalls(10, 10, 10); + + control.replay(); + + assertResults(0, 0); + } + + @Test + public void testLongWindow() throws Exception { + expectCalls(10, 10, 0, 10, 10); + + control.replay(); + + assertResults(Rate.of(input).withWindowSize(3).withTicker(ticker).build(), 0, -5, 0, 0); + } + + @Test + public void testRateOfRate() throws Exception { + expectCalls(10, 20, 30, 40, 50, 60); + + control.replay(); + + Rate<Integer> rate = Rate.of(input).withTicker(ticker).build(); + Rate<Double> rateOfRate = Rate.of(rate).withTicker(ticker).build(); + + assertThat(rate.sample(), is(0d)); + assertThat(rateOfRate.sample(), is(0d)); + ticker.waitNanos(ONE_SEC); + assertThat(rate.sample(), is(10d)); + assertThat(rateOfRate.sample(), is(10d)); + ticker.waitNanos(ONE_SEC); + assertThat(rate.sample(), is(10d)); + assertThat(rateOfRate.sample(), is(0d)); + ticker.waitNanos(ONE_SEC); + assertThat(rate.sample(), is(10d)); + assertThat(rateOfRate.sample(), is(0d)); + ticker.waitNanos(ONE_SEC); + assertThat(rate.sample(), is(10d)); + assertThat(rateOfRate.sample(), is(0d)); + ticker.waitNanos(ONE_SEC); + assertThat(rate.sample(), is(10d)); + assertThat(rateOfRate.sample(), is(0d)); + } + + @Test + public void testScaleFactor() throws Exception { + expectCalls(10, 20, 30, 40); + + control.replay(); + + assertResults(Rate.of(input).withTicker(ticker).withScaleFactor(10).build(), 100, 100, 100); + } + + @Test + public void testFractionalScaleFactor() throws Exception { + expectCalls(10, 20, 30, 40); + + control.replay(); + + assertResults(Rate.of(input).withTicker(ticker).withScaleFactor(0.1).build(), 1, 1, 1); + } + + @Test + public void testSupplier() throws Exception { + Supplier<Long> supplier = new Supplier<Long>() { + long value = 0; + @Override public Long get() { + value += 10; + return value; + } + }; + + control.replay(); + assertResults(Rate.of("test", supplier).withTicker(ticker).build(), 10, 10, 10); + } + + private void expectCalls(int... samples) { + expect(input.getName()).andReturn("test"); + expectLastCall().atLeastOnce(); + for (int sample : samples) { + expect(input.read()).andReturn(sample); + } + } + + private void assertResults(double... results) throws Exception { + assertResults(Rate.of(input).withTicker(ticker).build(), results); + } + + private void assertResults(Rate rate, double... results) throws Exception { + // First result is always zero. + assertEquals(0d, rate.sample().doubleValue(), EPSILON); + ticker.waitNanos(ONE_SEC); + + for (double result : results) { + assertEquals(result, rate.sample().doubleValue(), EPSILON); + ticker.waitNanos(ONE_SEC); + } + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/ReservoirSamplerTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/ReservoirSamplerTest.java b/commons/src/test/java/com/twitter/common/stats/ReservoirSamplerTest.java new file mode 100644 index 0000000..d2e1f8c --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/ReservoirSamplerTest.java @@ -0,0 +1,63 @@ +package com.twitter.common.stats; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import com.twitter.common.testing.easymock.EasyMockTest; +import com.twitter.common.util.Random; + +import static org.easymock.EasyMock.expect; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +/** + * Tests the Reservoir Sampler code + * + * @author Delip Rao + */ +public class ReservoirSamplerTest extends EasyMockTest { + + private Random random; + + @Before + public void setUp() throws Exception { + random = createMock(Random.class); + } + + @Test + public void testSampling() throws Exception { + int mockValues[] = {3, 4, 5, 6, 7}; + for (int value : mockValues) { + expect(random.nextInt(value + 1)).andReturn(value); + } + control.replay(); + + ReservoirSampler<Integer> sampler = new ReservoirSampler<Integer>(3, random); + List<Integer> stream = ImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8); + for (int i : stream) { + sampler.sample(i); + } + List<Integer> expectedSamples = ImmutableList.of(1, 2, 3); + assertEquals("The samples should be 1, 2, 3", expectedSamples, + ImmutableList.copyOf(sampler.getSamples())); + } + + @Test + public void testNoSampling() throws Exception { + // no calls to random.nextInt should happen in this test + control.replay(); + List<Integer> stream = ImmutableList.of(1, 2, 3); + // reservoir is larger than the stream. No sampling should happen here. + ReservoirSampler<Integer> sampler = new ReservoirSampler<Integer>(20); + for (int i : stream) { + sampler.sample(i); + } + assertEquals("The samples should be same as the stream", stream, + ImmutableList.copyOf(sampler.getSamples())); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/StatisticsTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/StatisticsTest.java b/commons/src/test/java/com/twitter/common/stats/StatisticsTest.java new file mode 100644 index 0000000..0f6adf5 --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/StatisticsTest.java @@ -0,0 +1,119 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import java.util.Arrays; +import java.util.List; + +import junit.framework.TestCase; + +import com.google.common.collect.ImmutableList; + +/** + * Tests the functionality of the Statistics class. + * + * @author William Farner + */ +public class StatisticsTest extends TestCase { + private static final double ERROR_THRESHOLD = 1e-10; + + private static final List<Integer> EMPTY_SET = ImmutableList.of(); + + private static final List<Integer> TEST_SET_A = Arrays.asList(76117373, 76167137, 75870125, 75880508, 78099974, + 77810738, 75763975, 78042301, 76109165, 77816921, + 76115544, 76075750, 75391297, 75597249, 77793835, + 76001118, 77752542, 78413670, 60351776, 75607235, + 76057629, 80011920, 24067379, 75767484, 80052983, + 79278613, 75600277); + + private Statistics createAndLoad(List<Integer> values) { + Statistics stats = new Statistics(); + for (long value : values) { + stats.accumulate(value); + } + + return stats; + } + + private void checkWithinThreshold(double expected, double actual) { + assertTrue(Math.abs(expected - actual) < ERROR_THRESHOLD); + } + + public void testMin() { + // min is undefined for an empty set, but it should not fail. + Statistics stats = createAndLoad(EMPTY_SET); + stats.min(); + + stats = createAndLoad(TEST_SET_A); + assertEquals(24067379, stats.min()); + } + + public void testMax() { + // max is undefined for an empty set, but it should not fail. + Statistics stats = createAndLoad(EMPTY_SET); + stats.max(); + + stats = createAndLoad(TEST_SET_A); + assertEquals(80052983, stats.max()); + } + + public void testMean() { + // mean is undefined for an empty set, but it should not fail. + Statistics stats = createAndLoad(EMPTY_SET); + stats.mean(); + + stats = createAndLoad(TEST_SET_A); + checkWithinThreshold(7.435609325925925E7, stats.mean()); + } + + public void testVariance() { + Statistics stats = createAndLoad(EMPTY_SET); + assertEquals(Double.NaN, stats.variance()); + + stats = createAndLoad(TEST_SET_A); + checkWithinThreshold(1.089077613763465E14, stats.variance()); + } + + public void testStandardDeviation() { + Statistics stats = createAndLoad(EMPTY_SET); + assertEquals(Double.NaN, stats.standardDeviation()); + + stats = createAndLoad(TEST_SET_A); + checkWithinThreshold(1.0435888145066835E7, stats.standardDeviation()); + } + + public void testPopulationSize() { + Statistics stats = createAndLoad(EMPTY_SET); + assertEquals(0L, stats.populationSize()); + + stats = createAndLoad(TEST_SET_A); + assertEquals(TEST_SET_A.size(), stats.populationSize()); + } + + public void testSum() { + Statistics stats = createAndLoad(EMPTY_SET); + assertEquals(0L, stats.sum()); + + stats = createAndLoad(TEST_SET_A); + long expectedSum = 0; + for (long x: TEST_SET_A) { + expectedSum += x; + } + assertEquals(expectedSum, stats.sum()); + } + +} http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/StatsTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/StatsTest.java b/commons/src/test/java/com/twitter/common/stats/StatsTest.java new file mode 100644 index 0000000..4cf7bab --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/StatsTest.java @@ -0,0 +1,113 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import org.junit.After; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +import com.google.common.util.concurrent.AtomicDouble; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertThat; + +/** + * @author William Farner + */ +public class StatsTest { + + @After + public void tearDown() { + Stats.flush(); + } + + @Test + public void testSimpleExport() { + AtomicLong var = Stats.exportLong("test_long"); + assertCounter("test_long", 0); + var.incrementAndGet(); + assertCounter("test_long", 1); + var.addAndGet(100); + assertCounter("test_long", 101); + } + + @Test + public void testDoubleExport() { + AtomicDouble var = Stats.exportDouble("test_double"); + assertCounter("test_double", 0.0); + var.addAndGet(1.1); + assertCounter("test_double", 1.1); + var.addAndGet(5.55); + assertCounter("test_double", 6.65); + } + + @Test + public void testNotSame() { + AtomicLong firstExport = Stats.exportLong("somevar"); + firstExport.incrementAndGet(); + firstExport.incrementAndGet(); + assertCounter("somevar", 2L); + AtomicLong secondExport = Stats.exportLong("somevar"); + assertNotSame(firstExport, secondExport); + secondExport.incrementAndGet(); + assertCounter("somevar", 2L); // We keep the first one! + } + + @Test + public void testNormalizesSpace() { + AtomicLong leading = Stats.exportLong(" leading space"); + AtomicLong trailing = Stats.exportLong("trailing space "); + AtomicLong surround = Stats.exportLong(" surround space "); + + leading.incrementAndGet(); + trailing.incrementAndGet(); + surround.incrementAndGet(); + assertCounter("__leading_space", 1); + assertCounter("trailing_space___", 1); + assertCounter("___surround_space___", 1); + } + + @Test + public void testNormalizesIllegalChars() { + AtomicLong colon = Stats.exportLong("a:b"); + AtomicLong plus = Stats.exportLong("b+c"); + AtomicLong hyphen = Stats.exportLong("c-d"); + AtomicLong slash = Stats.exportLong("d/f"); + + colon.incrementAndGet(); + plus.incrementAndGet(); + hyphen.incrementAndGet(); + slash.incrementAndGet(); + assertCounter("a_b", 1); + assertCounter("b_c", 1); + assertCounter("c_d", 1); + assertCounter("d_f", 1); + } + + private void assertCounter(String name, long value) { + assertThat(Stats.<Long>getVariable(name).read(), is(value)); + } + + private void assertCounter(String name, double value) { + Double var = (Double) Stats.getVariable(name).read(); + assertEquals(var, value, 1e-6); + } +} + http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/stats/TimeSeriesRepositoryImplTest.java ---------------------------------------------------------------------- diff --git a/commons/src/test/java/com/twitter/common/stats/TimeSeriesRepositoryImplTest.java b/commons/src/test/java/com/twitter/common/stats/TimeSeriesRepositoryImplTest.java new file mode 100644 index 0000000..56e0bdf --- /dev/null +++ b/commons/src/test/java/com/twitter/common/stats/TimeSeriesRepositoryImplTest.java @@ -0,0 +1,132 @@ +// ================================================================================================= +// Copyright 2011 Twitter, Inc. +// ------------------------------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this work except in compliance with the License. +// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.stats; + +import com.google.common.collect.ImmutableList; +import com.twitter.common.quantity.Amount; +import com.twitter.common.quantity.Time; +import com.twitter.common.testing.easymock.EasyMockTest; +import com.twitter.common.util.testing.FakeClock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.easymock.EasyMock.createStrictControl; +import static org.easymock.EasyMock.expect; +import static org.junit.Assert.assertEquals; + +/** + * @author William Farner + */ +public class TimeSeriesRepositoryImplTest extends EasyMockTest { + + private static final Amount<Long, Time> RETENTION_PERIOD = Amount.of(10L, Time.MINUTES); + private static final Amount<Long, Time> SAMPLE_PERIOD = Amount.of(1L, Time.SECONDS); + + private StatRegistry statRegistry; + private TimeSeriesRepositoryImpl repo; + private FakeClock clock; + + @Before + public void setUp() { + control = createStrictControl(); + statRegistry = control.createMock(StatRegistry.class); + repo = new TimeSeriesRepositoryImpl(statRegistry, SAMPLE_PERIOD, RETENTION_PERIOD); + clock = new FakeClock(); + } + + @After + public void after() { + control.verify(); + } + + @Test + public void testSamplesInOrder() { + RecordingStat<Integer> statA = mockedStat(); + RecordingStat<Integer> statB = mockedStat(); + RecordingStat<Integer> statC = mockedStat(); + RecordingStat<Integer> statD = mockedStat(); + + expect(statRegistry.getStats()) + .andReturn(ImmutableList.<RecordingStat<? extends Number>>of(statB, statA, statC, statD)); + + expect(statB.getName()).andReturn("statB"); + expect(statB.sample()).andReturn(1); + + expect(statA.getName()).andReturn("statA"); + expect(statA.sample()).andReturn(2); + + expect(statC.getName()).andReturn("statC"); + expect(statC.sample()).andReturn(3); + + expect(statD.getName()).andReturn("statD"); + expect(statD.sample()).andReturn(4); + + control.replay(); + repo.runSampler(clock); + } + + @Test + public void testDelayedExport() throws InterruptedException { + RecordingStat<Integer> earlyExport = mockedStat(); + + for (int i = 1; i <= 4; i++) { + expect(statRegistry.getStats()) + .andReturn(ImmutableList.<RecordingStat<? extends Number>>of(earlyExport)); + expect(earlyExport.getName()).andReturn("early"); + expect(earlyExport.sample()).andReturn(i * 2); + } + + RecordingStat<Integer> delayedExport = mockedStat(); + expect(statRegistry.getStats()) + .andReturn(ImmutableList.<RecordingStat<? extends Number>>of(earlyExport, delayedExport)); + expect(earlyExport.getName()).andReturn("early"); + expect(earlyExport.sample()).andReturn(10); + expect(delayedExport.getName()).andReturn("delayed"); + expect(delayedExport.sample()).andReturn(100); + + control.replay(); + + clock.setNowMillis(1000); + + for (int i = 0; i < 4; i++) { + repo.runSampler(clock); + clock.waitFor(1000); + } + + expectTimestamps(1000L, 2000L, 3000L, 4000L); + expectSeriesData("early", 2, 4, 6, 8); + + repo.runSampler(clock); + + expectTimestamps(1000L, 2000L, 3000L, 4000L, 5000L); + expectSeriesData("early", 2, 4, 6, 8, 10); + expectSeriesData("delayed", 0L, 0L, 0L, 0L, 100); + } + + private RecordingStat<Integer> mockedStat() { + return createMock(new Clazz<RecordingStat<Integer>>() { }); + } + + private void expectTimestamps(Number... timestamps) { + assertEquals(ImmutableList.copyOf(timestamps), ImmutableList.copyOf(repo.getTimestamps())); + } + + private void expectSeriesData(String series, Number... values) { + assertEquals(ImmutableList.copyOf(values), ImmutableList.copyOf(repo.get(series).getSamples())); + } +}
