/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.bookkeeper.client; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.bookkeeper.client.WeightedRandomSelection.WeightedObject; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.Configuration; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestWeightedRandomSelection { static final Logger LOG = LoggerFactory.getLogger(TestWeightedRandomSelection.class); static class TestObj implements WeightedObject { long val; TestObj(long value) { this.val = value; } @Override public long getWeight() { return val; } } WeightedRandomSelection<String> wRS; Configuration conf = new CompositeConfiguration(); int multiplier = 3; @Before public void setUp() throws Exception { wRS = new WeightedRandomSelection<String>(); } @After public void tearDown() throws Exception { } @Test(timeout = 60000) public void testSelectionWithEqualWeights() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); Long val=100L; int numKeys = 50, totalTries = 1000000; Map<String, Integer> randomSelection = new HashMap<String, Integer>(); for (Integer i=0; i < numKeys; i++) { map.put(i.toString(), new TestObj(val)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } // there should be uniform distribution double expectedPct = ((double)1/(double)numKeys)*100; for (Map.Entry<String, Integer> e : randomSelection.entrySet()) { double actualPct = ((double)e.getValue()/(double)totalTries)*100; double delta = (Math.abs(expectedPct-actualPct)/expectedPct)*100; System.out.println("Key:" + e.getKey() + " Value:" + e.getValue() + " Expected: " + expectedPct + " Actual: " + actualPct); // should be within 5% of expected assertTrue("Not doing uniform selection when weights are equal", delta < 5); } } @Test(timeout = 60000) public void testSelectionWithAllZeroWeights() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); int numKeys = 50, totalTries = 1000000; Map<String, Integer> randomSelection = new HashMap<String, Integer>(); for (Integer i=0; i < numKeys; i++) { map.put(i.toString(), new TestObj(0L)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } // when all the values are zeros, there should be uniform distribution double expectedPct = ((double)1/(double)numKeys)*100; for (Map.Entry<String, Integer> e : randomSelection.entrySet()) { double actualPct = ((double)e.getValue()/(double)totalTries)*100; double delta = (Math.abs(expectedPct-actualPct)/expectedPct)*100; System.out.println("Key:" + e.getKey() + " Value:" + e.getValue() + " Expected: " + expectedPct + " Actual: " + actualPct); // should be within 5% of expected assertTrue("Not doing uniform selection when weights are equal", delta < 5); } } void verifyResult(Map<String, WeightedObject> map, Map<String, Integer> randomSelection, int multiplier, long minWeight, long medianWeight, long totalWeight, int totalTries) { List<Integer> values = new ArrayList<Integer>(randomSelection.values()); Collections.sort(values); double medianObserved, medianObservedWeight, medianExpectedWeight; int mid = values.size()/2; if ((values.size() % 2) == 1) { medianObserved = values.get(mid); } else { medianObserved = (double)(values.get(mid-1) + values.get(mid))/2; } medianObservedWeight = (double)medianObserved/(double)totalTries; medianExpectedWeight = (double)medianWeight/totalWeight; for (Map.Entry<String, Integer> e : randomSelection.entrySet()) { double observed = (((double)e.getValue()/(double)totalTries)); double expected; if (map.get(e.getKey()).getWeight() == 0) { // if the value is 0 for any key, we make it equal to the first non zero value expected = (double)minWeight/(double)totalWeight; } else { expected = (double)map.get(e.getKey()).getWeight()/(double)totalWeight; } if (multiplier > 0 && expected > multiplier*medianExpectedWeight) { expected = multiplier*medianExpectedWeight; } // We can't compare these weights because they are derived from different // values. But if we express them as a multiple of the min in each, then // they should be comparable double expectedMultiple = expected/medianExpectedWeight; double observedMultiple = observed/medianObservedWeight; double delta = (Math.abs(expectedMultiple-observedMultiple)/expectedMultiple)*100; System.out.println("Key:" + e.getKey() + " Value:" + e.getValue() + " Expected " + expectedMultiple + " actual " + observedMultiple + " delta " + delta + "%"); // the observed should be within 5% of expected assertTrue("Not doing uniform selection when weights are equal", delta < 5); } } @Test(timeout = 60000) public void testSelectionWithSomeZeroWeights() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); Map<String, Integer> randomSelection = new HashMap<String, Integer>(); int numKeys = 50; multiplier=3; long val=0L, total=0L, minWeight = 100L, medianWeight=minWeight; wRS.setMaxProbabilityMultiplier(multiplier); for (Integer i=0; i < numKeys; i++) { if (i < numKeys/3) { val = 0L; } else if (i < 2*(numKeys/3)){ val = minWeight; } else { val = 2*minWeight; } total += val; map.put(i.toString(), new TestObj(val)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); int totalTries = 10000000; for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } verifyResult(map, randomSelection, multiplier, minWeight, medianWeight, total, totalTries); } @Test(timeout = 60000) public void testSelectionWithUnequalWeights() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); Map<String, Integer> randomSelection = new HashMap<String, Integer>(); int numKeys = 50; multiplier=4; long val=0L, total=0L, minWeight=100L, medianWeight=2*minWeight; wRS.setMaxProbabilityMultiplier(multiplier); for (Integer i=0; i < numKeys; i++) { if (i < numKeys/3) { val = minWeight; } else if (i < 2*(numKeys/3)){ val = 2*minWeight; } else { val = 10*minWeight; } total += val; map.put(i.toString(), new TestObj(val)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); int totalTries = 10000000; for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } verifyResult(map, randomSelection, multiplier, minWeight, medianWeight, total, totalTries); } @Test(timeout = 60000) public void testSelectionWithHotNode() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); Map<String, Integer> randomSelection = new HashMap<String, Integer>(); multiplier=3; // no max int numKeys = 50; long total=0L, minWeight = 100L, val = minWeight, medianWeight=minWeight; wRS.setMaxProbabilityMultiplier(multiplier); for (Integer i=0; i < numKeys; i++) { if (i == numKeys-1) { // last one has 10X more weight than the rest put together val=10*(numKeys-1)*100L; } total += val; map.put(i.toString(), new TestObj(val)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); int totalTries = 10000000; for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } verifyResult(map, randomSelection, multiplier, minWeight, medianWeight, total, totalTries); } @Test(timeout = 60000) public void testSelectionWithHotNodeWithLimit() throws Exception { Map<String, WeightedObject> map = new HashMap<String, WeightedObject>(); Map<String, Integer> randomSelection = new HashMap<String, Integer>(); multiplier=3; // limit the max load on hot node to be 3X int numKeys = 50; long total=0L, minWeight = 100L, val = minWeight, medianWeight=minWeight; wRS.setMaxProbabilityMultiplier(multiplier); for (Integer i=0; i < numKeys; i++) { if (i == numKeys-1) { // last one has 10X more weight than the rest put together val=10*(numKeys-1)*100L; } total += val; map.put(i.toString(), new TestObj(val)); randomSelection.put(i.toString(), 0); } wRS.updateMap(map); int totalTries = 10000000; for (int i = 0; i < totalTries; i++) { String key = wRS.getNextRandom(); randomSelection.put(key, randomSelection.get(key)+1); } verifyResult(map, randomSelection, multiplier, minWeight, medianWeight, total, totalTries); } }