// ================================================================================================= // 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); } }