/*
* -----------------------------------------------------------------------\
* PerfCake
*
* Copyright (C) 2010 - 2016 the original author or authors.
*
* Licensed 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.perfcake.reporting.reporter.accumulator;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Tests all {@link org.perfcake.reporting.reporter.accumulator.Accumulator Accumulators}.
*
* @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a>
* @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a>
*/
@Test(groups = { "unit" })
public class AccumulatorsTest {
private static final int STRESS_THREADS = 500;
@Test
public void lastValueAccumulatorTest() {
final LastValueAccumulator lva = new LastValueAccumulator();
Assert.assertNull(lva.getResult(), "Clean LastValueAccumulator must have null result.");
lva.add("Hello");
Assert.assertEquals(lva.getResult(), "Hello");
lva.add("World");
Assert.assertEquals(lva.getResult(), "World");
lva.reset();
Assert.assertNull(lva.getResult(), "LastValueAccumulator must have null result after reset.");
}
@Test
public void avgAccumulatorTest() {
final int START = 10, END = 100;
final AvgAccumulator aa = new AvgAccumulator();
Assert.assertEquals(aa.getResult(), 0d);
for (int i = START; i <= END; i++) {
aa.add((double) i);
}
Assert.assertEquals(aa.getResult(), (START + END) / 2d);
aa.reset();
Assert.assertEquals(aa.getResult(), 0d, "AvgAccumulator must be 0 after reset.");
}
@Test
public void sumAccumulatorTest() {
final int START = 10, END = 100;
final SumAccumulator aa = new SumAccumulator();
Assert.assertEquals(aa.getResult(), 0d);
for (int i = START; i <= END; i++) {
aa.add((double) i);
}
Assert.assertEquals(aa.getResult(), (START + END) * (END - START + 1) / 2d);
aa.reset();
Assert.assertEquals(aa.getResult(), 0d, "SumAccumulator must be 0 after reset.");
}
@Test
public void slidingWindowAvgAccumulatorTest() {
final int START = 10, END = 100, WINDOW = 16;
final SlidingWindowAvgAccumulator aa = new SlidingWindowAvgAccumulator(WINDOW);
Assert.assertEquals(aa.getResult(), 0d);
aa.add(5d);
Assert.assertEquals(aa.getResult(), 5d);
aa.reset();
Assert.assertEquals(aa.getResult(), 0d, "SlidingWindowAvgAccumulator must be 0 after reset.");
for (int i = START; i <= END; i++) {
aa.add((double) i);
}
Assert.assertEquals(aa.getResult(), (END - WINDOW + 1 + END) / 2d);
aa.reset();
Assert.assertEquals(aa.getResult(), 0d, "SlidingWindowAvgAccumulator must be 0 after reset.");
}
@Test
public void slidingWindowSumLongAccumulatorTest() {
final int START = 10, END = 100, WINDOW = 16;
final SlidingWindowSumLongAccumulator aa = new SlidingWindowSumLongAccumulator(WINDOW);
Assert.assertEquals(aa.getResult(), (Long) 0L);
aa.add(5L);
Assert.assertEquals(aa.getResult(), (Long) 5L);
aa.reset();
Assert.assertEquals(aa.getResult(), (Long) 0L, "SlidingWindowSumLongAccumulator must be 0 after reset.");
for (int i = START; i <= END; i++) {
aa.add((long) i);
}
Assert.assertEquals(aa.getResult(), (Long) (WINDOW * (WINDOW + 1L) / 2L + WINDOW * (END - WINDOW)));
aa.reset();
Assert.assertEquals(aa.getResult(), (Long) 0L, "SlidingWindowSumLongAccumulator must be 0 after reset.");
}
@Test
public void maxLongValueAccumulatorTest() {
final MaxLongValueAccumulator mlva = new MaxLongValueAccumulator();
Assert.assertEquals((long) mlva.getResult(), (long) Long.MIN_VALUE);
mlva.add(1l);
Assert.assertEquals((long) mlva.getResult(), 1l);
final Random r = new Random();
for (int i = 0; i < 5000; i++) {
mlva.add(r.nextLong() >> 32);
}
Assert.assertTrue(mlva.getResult() <= Long.MAX_VALUE >> 32);
mlva.add(Long.MAX_VALUE);
Assert.assertEquals((long) mlva.getResult(), Long.MAX_VALUE);
}
@DataProvider(name = "timeSlidingWindowAccumulatorsTest")
public Object[][] createDataForTimeSlidingWindowAccumulatorsTest() {
return new Object[][] {
{ TimeSlidingWindowMaxAccumulator.class, 5.0, 3.0, 2.0, new Object[] { 5.0, 4.0, 3.0, 2.0, 1.0 } },
{ TimeSlidingWindowMinAccumulator.class, 1.0, 3.0, 4.0, new Object[] { 1.0, 2.0, 3.0, 4.0, 5.0 } },
{ TimeSlidingWindowAvgAccumulator.class, 3.0, 4.0, 4.5, new Object[] { 1.0, 2.0, 3.0, 4.0, 5.0 } },
{ TimeSlidingWindowHarmonicMeanAccumulator.class, 5.0 / (1.0 + 1.0 / 2.0 + 1.0 / 3.0 + 1.0 / 4.0 + 1.0 / 5.0), 3.0 / (1.0 / 3.0 + 1.0 / 4.0 + 1.0 / 5.0), 2.0 / (1.0 / 4.0 + 1.0 / 5.0), new Object[] { 1.0, 2.0, 3.0, 4.0, 5.0 } },
{ TimeSlidingWindowSumAccumulator.class, 15.0, 12.0, 9.0, new Object[] { 1.0, 2.0, 3.0, 4.0, 5.0 } },
{ TimeSlidingWindowSumLongAccumulator.class, 15L, 12L, 9L, new Object[] { 1L, 2L, 3L, 4L, 5L } }
};
}
@SuppressWarnings("unchecked")
@Test(dataProvider = "timeSlidingWindowAccumulatorsTest")
public void timeSlidingWindowAccumulatorTest(final Class<Accumulator> accumulatorClass, final Object oneTickResult, final Object twoTicksResult, final Object finalTickResult, Object[] values) throws Exception {
final Constructor<Accumulator> constr = accumulatorClass.getDeclaredConstructor(int.class);
final Accumulator accumulator = constr.newInstance(600);
// || | 5 || 4 || 3 || 2 || 1 | |||||
long t1 = System.currentTimeMillis();
for (int i = 0; i < values.length; i++) {
Thread.sleep(50);
accumulator.add(values[i]);
Thread.sleep(50);
}
Assert.assertEquals(accumulator.getResult(), oneTickResult);
long t2 = System.currentTimeMillis();
Thread.sleep(250 + (525 - (t2 - t1)));
long t3 = System.currentTimeMillis();
Assert.assertEquals(accumulator.getResult(), twoTicksResult, "775ms took " + (t3-t1) + "ms");
Thread.sleep(100);
Assert.assertEquals(accumulator.getResult(), finalTickResult);
}
@DataProvider(name = "accumulatorsTest")
public Object[][] createDataForStressTest() {
final Long START = 1L, END = 100_000L;
final int WINDOW = 1000;
double harmonicMean = 0d;
for (long i = START; i <= END; i = i + 1) {
harmonicMean = harmonicMean + (1d / i);
}
harmonicMean = (END - START + 1d) / harmonicMean;
double slidingHarmonicMean = 0d;
for (long i = END - WINDOW + 1; i <= END; i = i + 1) {
slidingHarmonicMean = slidingHarmonicMean + (1d / i);
}
slidingHarmonicMean = (double) WINDOW / slidingHarmonicMean;
// accumulator, start, end, result, after reset
return new Object[][] { { new AvgAccumulator(), START, END, (START + END) / 2d, (START + END) / 2d, 0d },
{ new HarmonicMeanAccumulator(), START, END, harmonicMean, null, 0d },
{ new MaxAccumulator(), START, END, (double) END, (double) END, Double.NEGATIVE_INFINITY },
{ new MinAccumulator(), START, END, (double) START, (double) START, Double.POSITIVE_INFINITY },
{ new MaxLongValueAccumulator(), START, END, END, END, Long.MIN_VALUE },
{ new SumAccumulator(), START, END, (START + END) * (END - START + 1L) / 2d, STRESS_THREADS * (START + END) * (END - START + 1L) / 2d, 0d },
{ new LastValueAccumulator(), START, END, (double) END, (double) END, null },
{ new SlidingWindowAvgAccumulator(WINDOW), START, END, (END - WINDOW + 1 + END) / 2d, null, 0d },
{ new SlidingWindowHarmonicMeanAccumulator(WINDOW), START, END, slidingHarmonicMean, null, 0d },
{ new SlidingWindowMaxAccumulator(WINDOW), START, END, (double) END, null, Double.NaN },
{ new SlidingWindowMinAccumulator(WINDOW), START, END, (double) (END - WINDOW + 1), null, Double.NaN } };
}
@Test(dataProvider = "accumulatorsTest")
@SuppressWarnings({ "rawtypes", "unchecked" })
public void accumulatorGenericTest(final Accumulator a, final Long start, final Long end, final Number result, final Number stressResult, final Number zero) {
Assert.assertEquals(a.getResult(), zero);
try {
for (long i = start; i <= end; i = i + 1) {
a.add((double) i);
}
} catch (final ClassCastException cce) { // some accumulators accept only longs
for (long i = start; i <= end; i = i + 1) {
a.add(i);
}
}
Assert.assertEquals(a.getResult(), result);
a.reset();
Assert.assertEquals(a.getResult(), zero);
}
@Test(dataProvider = "accumulatorsTest", groups = { "ueber", "performance", "stress" })
@SuppressWarnings("rawtypes")
public void accumulatorStressTest(final Accumulator a, final Long start, final Long end, final Number result, final Number stressResult, final Number zero) throws InterruptedException {
final List<Thread> stressors = new ArrayList<>();
for (int i = 0; i < STRESS_THREADS; i++) {
stressors.add(new Thread(new AccumulatorStressor(a, start, end)));
}
long time = System.currentTimeMillis();
for (final Thread t : stressors) {
t.start();
}
for (final Thread t : stressors) {
t.join();
}
time = System.currentTimeMillis() - time;
Reporter.log("Stress test (" + a.getClass().getSimpleName() + ") length " + time + "ms.");
if (stressResult != null) {
Assert.assertEquals(a.getResult(), stressResult);
}
a.reset();
Assert.assertEquals(a.getResult(), zero);
}
@SuppressWarnings("rawtypes")
public static class AccumulatorStressor implements Runnable {
private final Accumulator a;
private final long start, end;
public AccumulatorStressor(final Accumulator a, final long start, final long end) {
this.a = a;
this.start = start;
this.end = end;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
try {
for (long i = start; i <= end; i = i + 1L) {
a.add((double) i);
}
} catch (final ClassCastException cce) { // some accumulators accept only longs
for (long i = start; i <= end; i = i + 1) {
a.add(i);
}
}
}
}
}