/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.store.statistics.histograms; import com.foundationdb.util.AssertUtils; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.foundationdb.server.store.statistics.histograms.BucketTestUtils.bucket; import static org.junit.Assert.assertEquals; public final class BucketSamplerTest { @Test public void medianPointAtEnd() { check( 4, "a b c d e f g h i j k l", bucketsList( bucket("a", 1, 0, 0), bucket("d", 1, 2, 2), bucket("h", 1, 3, 3), bucket("l", 1, 3, 3) ) ); } @Test public void medianPointNotEvenlyDistributed() { check( 5, "a b c d e f g h i j k l m n", bucketsList( bucket("a", 1, 0, 0), bucket("d", 1, 2, 2), bucket("g", 1, 2, 2), bucket("k", 1, 3, 3), bucket("n", 1, 2, 2) ) ); } @Test public void spanStartsAfterMedianPointEndsOn() { check( 5, "a b c d d d e f g h i j", bucketsList( bucket("a", 1, 0, 0), bucket("c", 1, 1, 1), bucket("d", 3, 0, 0), bucket("g", 1, 2, 2), bucket("j", 1, 2, 2) ) ); } @Test public void spanStartsOffMedianPointEndsOn() { check( 5, "a b c d e e e e e f g h", bucketsList( bucket("a", 1, 0, 0), bucket("c", 1, 1, 1), bucket("e", 5, 1, 1), bucket("h", 1, 2, 2) ) ); } @Test public void spanStartsOnMedianPointEndsOff() { check( 5, "a b c d d d d e f g h i", bucketsList( bucket("a", 1, 0, 0), bucket("c", 1, 1, 1), bucket("d", 4, 0, 0), bucket("f", 1, 1, 1), bucket("i", 1, 2, 2) ) ); } @Test public void spanStartsOfMedianPointEndsOff() { check( 5, "a b c d e e e e f g h i", bucketsList( bucket("a", 1, 0, 0), bucket("c", 1, 1, 1), bucket("e", 4, 1, 1), bucket("f", 1, 0, 0), bucket("i", 1, 2, 2) ) ); } @Test public void moreInputsThanBuckets() { check( 40, "a b c d e f", bucketsList( bucket("a", 1, 0, 0), bucket("b", 1, 0, 0), bucket("c", 1, 0, 0), bucket("d", 1, 0, 0), bucket("e", 1, 0, 0), bucket("f", 1, 0, 0) ) ); } @Test public void asManyInputsThanBuckets() { check( 6, "a b c d e f", bucketsList( bucket("a", 1, 0, 0), bucket("b", 1, 0, 0), bucket("c", 1, 0, 0), bucket("d", 1, 0, 0), bucket("e", 1, 0, 0), bucket("f", 1, 0, 0) ) ); } @Test public void moreSpansThanBuckets() { check( 40, "a a b b c d d e f f", bucketsList( bucket("a", 2, 0, 0), bucket("b", 2, 0, 0), bucket("c", 1, 0, 0), bucket("d", 2, 0, 0), bucket("e", 1, 0, 0), bucket("f", 2, 0, 0) ) ); } @Test public void maxIsTwo() { // The practical minimum number of buckets is 2, one for the min value, one for everything else. check( 2, "a a b b c d d e f f", bucketsList( bucket("a", 2, 0, 0), bucket("f", 2, 6, 4) ) ); } @Test public void emptyInputs() { check( 32, "", bucketsList() ); } @Test public void appendingKeepsSamplingUnchanged() { // pipe is median, V is inserted bucket V V | V|V StringToBuckets inputs = new StringToBuckets("a a a b c c c c d e e e e f f f g"); assertEquals("buckets count", 7, inputs.buckets().size()); BucketSampler<String> sampler = new BucketSampler<>(2, inputs.inputsCount(), true); // insert one before everyone sampler.appendToResults(bucket("FIRST", 17, 100, 1000)); // insert one right before a median sampler.add(inputs.popBucket()); // Bucket a sampler.add(inputs.popBucket()); // Bucket b sampler.appendToResults(bucket("SECOND", 23, 200, 2000)); // insert one right after a median sampler.add(inputs.popBucket()); // Bucket c sampler.add(inputs.popBucket()); // Bucket d sampler.add(inputs.popBucket()); // Bucket e sampler.add(inputs.popBucket()); // Bucket f sampler.appendToResults(bucket("THIRD", 37, 300, 3000)); // insert one at the end sampler.add(inputs.popBucket()); // Bucket 6 sampler.appendToResults(bucket("FOURTH", 41, 400, 4000)); assertEquals("emptied buckets() list", Collections.emptyList(), inputs.buckets()); assertEquals("equality std dev", 1.39728d, sampler.getEqualsStdDev(), 0.00001d); assertEquals("equality mean", 2.42857d, sampler.getEqualsMean(), 0.00001d); List<Bucket<String>> expected = bucketsList( bucket("FIRST", 17, 100, 1000), bucket("SECOND", 23, 204, 2002), bucket("d", 1, 4, 1), bucket("THIRD", 37, 307, 3002), bucket("g", 1, 0, 0), bucket("FOURTH", 41, 400, 4000) ); AssertUtils.assertCollectionEquals("compiled buckets", expected, sampler.buckets()); } @Test public void testEqualityMean() { BucketSampler<String> sampler = runSampler(2, "a a a b b c c c c d d d e f f f f f"); assertEquals("mean equality", 3.0d, sampler.getEqualsMean(), 0.0); } @Test public void testEqualityStdDev() { BucketSampler<String> sampler = runSampler(2, "a a a b b c c c c d d d e f f f f f "); assertEquals("equality std dev", 1.41421d, sampler.getEqualsStdDev(), 0.00001d); } @Test(expected = IllegalArgumentException.class) public void maxIsZero() { new BucketSampler<String>(0, 1); } @Test(expected = IllegalArgumentException.class) public void maxIsNegative() { new BucketSampler<String>(-1, 1); } @Test(expected = IllegalArgumentException.class) public void expectedInputsIsNegative() { new BucketSampler<String>(16, -1); } @Test(expected = IllegalStateException.class) public void stdDevRequestedButNotCalculated() { BucketSampler<String> sampler = runSampler(2, "a b c", false); sampler.getEqualsStdDev(); } private BucketSampler<String> runSampler(int maxBuckets, String inputString) { return runSampler(maxBuckets, inputString, true); } private BucketSampler<String> runSampler(int maxBuckets, String inputString, boolean calculateStdDev) { StringToBuckets inputs = new StringToBuckets(inputString); BucketSampler<String> sampler = new BucketSampler<>(maxBuckets, inputs.inputsCount(), calculateStdDev); for (Bucket<String> bucket : inputs.buckets()) sampler.add(bucket); return sampler; } private void check(int maxBuckets, String inputs, List<Bucket<String>> expected) { BucketSampler<String> sampler = runSampler(maxBuckets, inputs); AssertUtils.assertCollectionEquals("compiled buckets", expected, sampler.buckets()); } private List<Bucket<String>> bucketsList(Bucket<?>... buckets) { List<Bucket<String>> list = new ArrayList<>(); for (Bucket<?> bucket : buckets) { @SuppressWarnings("unchecked") Bucket<String> cast = (Bucket<String>) bucket; list.add(cast); } return list; } private class StringToBuckets { public StringToBuckets(String inputString) { String[] splits = inputString.length() == 0 ? new String[0] : inputString.split("\\s+"); buckets = new ArrayList<>(); Bucket<String> lastBucket = null; for (String split : splits) { if (lastBucket == null || !split.equals(lastBucket.value())) { lastBucket = new Bucket<>(); lastBucket.init(split, 1); buckets.add(lastBucket); } else { lastBucket.addEquals(); } } inputsCount = splits.length; } public int inputsCount() { return inputsCount; } public List<Bucket<String>> buckets() { return buckets; } public Bucket<String> popBucket() { return buckets.remove(0); } private int inputsCount; private List<Bucket<String>> buckets; } }