blob: 2f7d8a11cb595801ecdecffa9a97d450c93f34e2 [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.jmeter.timers.poissonarrivals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import org.junit.jupiter.api.Test;
public class PreciseThroughputTimerTest {
@Test
public void testTimer1() throws Exception {
// The case is to produce 2 samples per second, and we specify "test duration to be 5 seconds"
// It means every 5 seconds the sampler must produce 2*5=10 samples
// However, we generate 31 samples (to ensure generator overflows properly)
final int throughput = 2;
final int duration = 5;
final long seed = 42L;
final int batchSize = 1;
ConstantPoissonProcessGenerator gen = getConstantPoissonProcessGenerator(throughput, duration, seed, batchSize);
gen.generateNext();
double[] expected = new double[] {
1.3787403472085118, 1.3853924503706834,
1.5435972766632988, 1.8439145670565282,
2.3182678790457665, 3.327744758972868,
3.416117358799227, 3.6378184001643405,
3.914508893950179, 4.516861323360891,
// ^^ 10 samples for 5 seconds
5.861089688439262, 5.886892389546892,
6.04883784433166, 6.932834371796743,
7.182454872116432, 7.937136908931478,
7.9717495544484205, 8.749530906277236,
9.129829359439105, 9.596638914343584,
// ^^ 10 samples for the next 5 seconds
10.157091194132905, 11.789599597385642,
12.088437733764593, 12.855201742074335,
12.900124422510304, 13.567031289116144,
13.7564020338373, 13.762549742953254,
14.088984654178198, 14.870178407479408,
// ^^ 10 samples for the next 5 seconds
16.45828248705902
// ^^ it should exceed 15
};
double[] actual = new double[expected.length];
boolean ok = true;
for (int i = 0; i < actual.length; i++) {
actual[i] = gen.next();
ok = ok && Math.abs(actual[i] - expected[i]) < 0.01;
}
if (!ok) {
assertEquals(Arrays.toString(expected), Arrays.toString(actual), "Schedule does not match expectation, " +
"throughput=" + throughput + ", duration=" + duration +
"seed=" + seed + ", batchSize=" + batchSize);
}
}
@Test
public void testExactNumberOfSamples() throws Exception {
Random rnd = new Random();
for (int i = 0; i < 100; i++) {
long seed = rnd.nextLong();
final int testDuration = rnd.nextInt(100) + 5;
final int throughput = rnd.nextInt(40) + 1;
final int throughputInterval = rnd.nextInt(100) + 1;
verifyExactThroughput(seed, testDuration, throughput, throughputInterval);
}
}
@Test
public void testSingleExactNumberOfSamples() throws Exception {
long seed = 6217980321110818258L;
verifyExactThroughput(seed, 60, 5, 60);
}
@Test
public void testSingleExactNumberOfSamples6812190053835844998() throws Exception {
long seed = 6812190053835844998L;
verifyExactThroughput(seed, 60, 5, 60);
}
private void verifyExactThroughput(
long seed, int testDuration, int throughput, int throughputInterval) {
for (int batchSize = 1; batchSize < 3; batchSize++) {
verifyExactThroughput(seed, testDuration, throughput, throughputInterval, batchSize);
}
}
@Test
public void repro2110188512211996814L() {
verifyExactThroughput(2110188512211996814L, 27, 1, 85, 1);
}
@Test
public void reproduer4389853422207095555() {
verifyExactThroughput(/*seed=*/ 4389853422207095555L, /*testDuration=*/ 21, /*throughput=*/ 26, /*throughputInterval=*/ 79, /*batchSize=*/ 1);
}
private void verifyExactThroughput(
long seed, int testDuration, int throughput, int throughputInterval, int batchSize) {
// 5 per second, and we specify the test duration as 1 second
// The generator would prepare the input data for 1 second (~5 samples)
// However, then we continue sampling, and the generator should still produce
// exactly 5 items for each new second
ConstantPoissonProcessGenerator gen =
getConstantPoissonProcessGenerator(
throughput * batchSize * 1.0 / throughputInterval, testDuration, seed, batchSize);
int samplesPerTest = (int) Math.ceil(throughput * 1.0 / throughputInterval * testDuration) * batchSize;
try {
ArrayList<Double> delays = new ArrayList<>();
// The test will last 100 times longer than expected, so generator would have to re-generate
// values 100 times or so
double prev = 0;
for (int time = 0; time < 100; time++) {
for (int i = 0; i < samplesPerTest; i++) {
double next = gen.next();
if (prev > next) {
fail("Schedule should be monotonic, so each new event comes later. " +
"prev: " + prev + ", next: " + next +
". Full schedule so far: " + delays);
}
prev = next;
delays.add(next);
if (time * testDuration <= next && next < (time + 1) * testDuration) {
// OK
continue;
}
fail("Throughput violation at second #" + time + ". Event #" + delays.size() +
" is scheduled at " + next + ", however it should be " +
" between " + time * testDuration + " and " + (time + 1) * testDuration +
". Full schedule so far: " + delays);
}
}
} catch (Throwable t) {
// This adds reproducer right into the stacktrace
final String seedHex = Long.toUnsignedString(seed);
t.addSuppressed(new Throwable(
"@Test public void reproduer" + seedHex + "() {" +
"verifyExactThroughput(/*seed=*/ " + seed + "L, /*testDuration=*/ " + testDuration +
", /*throughput=*/ " + throughput + ", /*throughputInterval=*/ " + throughputInterval +
", /*batchSize=*/ " + batchSize + "); }")
);
throw t;
}
}
protected ConstantPoissonProcessGenerator getConstantPoissonProcessGenerator(
final double throughput, final int duration, long seed, int batchSize) {
return new ConstantPoissonProcessGenerator(
() -> throughput, // samples per second
batchSize,
0,
() -> duration, // "expected" test duration: 3 seconds
seed, // Seed
false
);
}
}