blob: 3434c1771c740763f268415ea918c0e9c9682a5d [file] [log] [blame]
/*
* 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.gossip.accrual;
import org.apache.gossip.GossipSettings;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RunWith(JUnitPlatform.class)
public class FailureDetectorTest {
@FunctionalInterface
interface TriConsumer<A, B, C> {
void accept(A a, B b, C c);
}
static final Double failureThreshold = new GossipSettings().getConvictThreshold();
List<Integer> generateTimeList(int begin, int end, int step) {
List<Integer> values = new ArrayList<>();
Random rand = new Random();
for (int i = begin; i < end; i += step) {
int delta = (int) ((rand.nextDouble() - 0.5) * step / 2);
values.add(i + delta);
}
return values;
}
@Test
public void normalDistribution() {
FailureDetector fd = new FailureDetector(1, 1000, "normal");
List<Integer> values = generateTimeList(0, 10000, 100);
Double deltaSum = 0.0;
Integer deltaCount = 0;
for (int i = 0; i < values.size() - 1; i++) {
fd.recordHeartbeat(values.get(i));
if (i != 0) {
deltaSum += values.get(i) - values.get(i - 1);
deltaCount++;
}
}
Integer lastRecorded = values.get(values.size() - 2);
//after "step" delay we need to be considered UP
Assert.assertTrue(fd.computePhiMeasure(values.get(values.size() - 1)) < failureThreshold);
//if we check phi-measure after mean delay we get value for 0.5 probability(normal distribution)
Assert.assertEquals(fd.computePhiMeasure(lastRecorded + Math.round(deltaSum / deltaCount)), -Math.log10(0.5), 0.1);
}
@Test
public void checkMinimumSamples() {
Integer minimumSamples = 5;
FailureDetector fd = new FailureDetector(minimumSamples, 1000, "normal");
for (int i = 0; i < minimumSamples + 1; i++) { // +1 because we don't place first heartbeat into structure
Assert.assertNull(fd.computePhiMeasure(100));
fd.recordHeartbeat(i);
}
Assert.assertNotNull(fd.computePhiMeasure(100));
}
@Test
public void checkMonotonicDead() {
final FailureDetector fd = new FailureDetector(5, 1000, "normal");
TriConsumer<Integer, Integer, Integer> checkAlive = (begin, end, step) -> {
List<Integer> times = generateTimeList(begin, end, step);
for (int i = 0; i < times.size(); i++) {
Double current = fd.computePhiMeasure(times.get(i));
if (current != null) {
Assert.assertTrue(current < failureThreshold);
}
fd.recordHeartbeat(times.get(i));
}
};
TriConsumer<Integer, Integer, Integer> checkDeadMonotonic = (begin, end, step) -> {
List<Integer> times = generateTimeList(begin, end, step);
Double prev = null;
for (int i = 0; i < times.size(); i++) {
Double current = fd.computePhiMeasure(times.get(i));
if (current != null && prev != null) {
Assert.assertTrue(current >= prev);
}
prev = current;
}
};
checkAlive.accept(0, 20000, 100);
checkDeadMonotonic.accept(20000, 20500, 5);
}
}