You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by vl...@apache.org on 2020/01/27 22:13:25 UTC
[jmeter] branch master updated: Bug 64091 - Precise Throughput
Timer might produce less samples when low test duration is used (#553)
This is an automated email from the ASF dual-hosted git repository.
vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git
The following commit(s) were added to refs/heads/master by this push:
new 03693f7 Bug 64091 - Precise Throughput Timer might produce less samples when low test duration is used (#553)
03693f7 is described below
commit 03693f7c66707ddf84346430a979660abe4c58e3
Author: Vladimir Sitnikov <si...@gmail.com>
AuthorDate: Tue Jan 28 01:13:19 2020 +0300
Bug 64091 - Precise Throughput Timer might produce less samples when low test duration is used (#553)
This reworks the generation algorithm.
For now, the timer generates random numbers in 0..testDuration interval
and sorts them.
Note: the properties of the resulting sequence are still the same,
however, the algorithm is NlogN now.
---
.../ConstantPoissonProcessGenerator.java | 109 +++++--------
.../poissonarrivals/PreciseThroughputTimer.java | 11 +-
.../PreciseThroughputTimerBeanInfo.java | 10 +-
.../PreciseThroughputTimerTest.java | 169 +++++++++++++++++----
xdocs/changes.xml | 2 +
.../timers/precise_throughput_timer.png | Bin 36007 -> 49413 bytes
xdocs/usermanual/component_reference.xml | 23 +--
7 files changed, 206 insertions(+), 118 deletions(-)
diff --git a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
index 7a3601f..7205e79 100644
--- a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
+++ b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/ConstantPoissonProcessGenerator.java
@@ -18,6 +18,7 @@
package org.apache.jmeter.timers.poissonarrivals;
import java.nio.DoubleBuffer;
+import java.util.Arrays;
import java.util.Random;
import org.apache.jmeter.testelement.AbstractTestElement;
@@ -33,37 +34,34 @@ public class ConstantPoissonProcessGenerator implements EventProducer {
private static final double PRECISION = 0.00001;
- private Random rnd = new Random();
- private ThroughputProvider throughputProvider;
- private int batchSize;
- private int batchThreadDelay;
- private DurationProvider durationProvider;
+ private final Random rnd = new Random();
+ private final ThroughputProvider throughputProvider;
+ private final int batchSize;
+ private final int batchThreadDelay;
+ private final DurationProvider durationProvider;
+ private final boolean logFirstSamples;
+
+ private int batchItemIndex;
private double lastThroughput;
- private int exactLimit;
- private double allowedThroughputSurplus;
+ private double lastThroughputDurationFinish;
private DoubleBuffer events;
- private double lastEvent;
- private final boolean logFirstSamples;
public ConstantPoissonProcessGenerator(
ThroughputProvider throughput, int batchSize, int batchThreadDelay,
- DurationProvider duration, int exactLimit, double allowedThroughputSurplus,
+ DurationProvider duration,
Long seed, boolean logFirstSamples) {
this.throughputProvider = throughput;
this.batchSize = batchSize;
this.batchThreadDelay = batchThreadDelay;
this.durationProvider = duration;
- this.exactLimit = exactLimit;
- this.allowedThroughputSurplus = allowedThroughputSurplus;
this.logFirstSamples = logFirstSamples;
if (seed != null && seed.intValue() != 0) {
rnd.setSeed(seed);
}
- ensureCapacity();
+ ensureCapacity(0);
}
- private void ensureCapacity() {
- int size = (int) Math.round((throughputProvider.getThroughput() * durationProvider.getDuration() + 1) * 3);
+ private void ensureCapacity(int size) {
if (events != null && events.capacity() >= size) {
return;
}
@@ -76,47 +74,26 @@ public class ConstantPoissonProcessGenerator implements EventProducer {
if (batchSize > 1) {
throughput /= batchSize;
}
+ batchItemIndex = 0;
long duration = this.durationProvider.getDuration();
- ensureCapacity();
int samples = (int) Math.ceil(throughput * duration);
- double time;
- int i = 0;
+ ensureCapacity(samples);
long t = System.currentTimeMillis();
- int loops = 0;
- double currentAllowedThroughputSurplus = samples < exactLimit ? 0.0d : this.allowedThroughputSurplus / 100;
- do {
- time = 0;
- events.clear();
- if (throughput < 1e-5) {
- log.info("Throughput should exceed zero");
- break;
- }
- if (duration < 5) {
- log.info("Duration should exceed 5 seconds");
- break;
- }
- i = 0;
- while (time < duration) {
- double u = rnd.nextDouble();
- // https://en.wikipedia.org/wiki/Exponential_distribution#Generating_exponential_variates
- double delay = -Math.log(1 - u) / throughput;
- time += delay;
- events.put(time + lastEvent);
- i++;
- }
- loops++;
- } while (System.currentTimeMillis() - t < 5000 &&
- (i < samples + 1 // not enough samples
- || (i - 1 - samples) * 1.0f / samples > currentAllowedThroughputSurplus));
+ events.clear();
+ for (int i = 0; i < samples; i++) {
+ events.put(lastThroughputDurationFinish + rnd.nextDouble() * duration);
+ }
+ Arrays.sort(events.array(), events.arrayOffset(), events.position());
t = System.currentTimeMillis() - t;
if (t > 1000) {
log.warn("Spent {} ms while generating sequence of delays for {} samples, {} throughput, {} duration",
t, samples, throughput, duration);
}
+ lastThroughputDurationFinish += duration;
if (logFirstSamples) {
if (log.isDebugEnabled()) {
- log.debug("Generated {} events ({} required, rate {}) in {} ms, restart was issued {} times",
- events.position(), samples, throughput, t, loops);
+ log.debug("Generated {} events ({} required, rate {}) in {} ms",
+ events.position(), samples, throughput, t);
}
if (log.isInfoEnabled()) {
StringBuilder sb = new StringBuilder();
@@ -126,13 +103,10 @@ public class ConstantPoissonProcessGenerator implements EventProducer {
}
sb.append(" ").append(samples).append(" required, rate ").append(throughput)
.append(", duration ").append(duration)
- .append(", exact lim ").append(exactLimit)
- .append(", i").append(i)
- .append(") in ").append(t)
- .append(" ms, restart was issued ").append(loops).append(" times. ");
- sb.append("First 15 events will be fired at: ");
+ .append(") in ").append(t).append(" ms");
+ sb.append(". First 15 events will be fired at: ");
double prev = 0;
- for (i = 0; i < events.position() && i < 15; i++) {
+ for (int i = 0; i < events.position() && i < 15; i++) {
if (i > 0) {
sb.append(", ");
}
@@ -145,29 +119,28 @@ public class ConstantPoissonProcessGenerator implements EventProducer {
}
}
events.flip();
- if (batchSize > 1) {
- // If required to generate "pairs" of events, then just duplicate events in the buffer
- // TODO: for large batchSizes it makes sense to use counting instead
- DoubleBuffer tmpBuffer = DoubleBuffer.allocate(batchSize * events.remaining());
- while (events.hasRemaining()) {
- double curTime = events.get();
- for (int j = 0; j < batchSize; j++) {
- tmpBuffer.put(curTime + j * batchThreadDelay);
- }
- }
- tmpBuffer.flip();
- events = tmpBuffer;
- }
}
@Override
public double next() {
- if (!events.hasRemaining()
+ if ((batchItemIndex == 0 && !events.hasRemaining())
|| !valuesAreEqualWithPrecision(throughputProvider.getThroughput(),lastThroughput)) {
generateNext();
}
- lastEvent = events.get();
- return lastEvent;
+ if (batchSize == 1) {
+ return events.get();
+ }
+ batchItemIndex++;
+ if (batchItemIndex == 1) {
+ // The first item advances the position
+ return events.get();
+ }
+ if (batchItemIndex == batchSize) {
+ batchItemIndex = 0;
+ }
+ // All the other items in the batch refer to the previous position
+ // since #position() points to the next item to be returned
+ return events.get(events.position() - 1);
}
private boolean valuesAreEqualWithPrecision(double throughput, double lastThroughput) {
diff --git a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimer.java b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimer.java
index 37a42d2..df38ae0 100644
--- a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimer.java
+++ b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimer.java
@@ -28,6 +28,7 @@ import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.timers.Timer;
import org.apache.jorphan.util.JMeterStopThreadException;
+import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -134,7 +135,7 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
return
groupEvents.computeIfAbsent(tg, x -> new ConstantPoissonProcessGenerator(
() -> PreciseThroughputTimer.this.getThroughput() / throughputPeriod,
- batchSize, batchThreadDelay, this, exactLimit, allowedThroughputSurplus, seed, true));
+ batchSize, batchThreadDelay, this, seed, true));
}
/**
@@ -174,18 +175,26 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
this.duration = duration;
}
+ @Deprecated
+ @API(status = API.Status.DEPRECATED, since = "5.3.0")
public int getExactLimit() {
return exactLimit;
}
+ @Deprecated
+ @API(status = API.Status.DEPRECATED, since = "5.3.0")
public void setExactLimit(int exactLimit) {
this.exactLimit = exactLimit;
}
+ @Deprecated
+ @API(status = API.Status.DEPRECATED, since = "5.3.0")
public double getAllowedThroughputSurplus() {
return allowedThroughputSurplus;
}
+ @Deprecated
+ @API(status = API.Status.DEPRECATED, since = "5.3.0")
public void setAllowedThroughputSurplus(double allowedThroughputSurplus) {
this.allowedThroughputSurplus = allowedThroughputSurplus;
}
diff --git a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerBeanInfo.java b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerBeanInfo.java
index db83eb2..46b7ddf 100644
--- a/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerBeanInfo.java
+++ b/src/components/src/main/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerBeanInfo.java
@@ -66,21 +66,15 @@ public class PreciseThroughputTimerBeanInfo extends BeanInfoSupport {
p.setValue(DEFAULT, 0);
- createPropertyGroup(
- "accuracy", //$NON-NLS-1$
- new String[]{
- "exactLimit", //$NON-NLS-1$
- "allowedThroughputSurplus" //$NON-NLS-1$
- }
- );
-
p = property("exactLimit"); //$NON-NLS-1$
p.setValue(NOT_UNDEFINED, Boolean.TRUE);
p.setValue(DEFAULT, 10000);
+ p.setHidden(true);
p = property("allowedThroughputSurplus"); //$NON-NLS-1$
p.setValue(NOT_UNDEFINED, Boolean.TRUE);
p.setValue(DEFAULT, 1.0d);
+ p.setHidden(true);
createPropertyGroup(
"repeatability", //$NON-NLS-1$
diff --git a/src/components/src/test/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerTest.java b/src/components/src/test/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerTest.java
index df54b65..ed5e973 100644
--- a/src/components/src/test/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerTest.java
+++ b/src/components/src/test/java/org/apache/jmeter/timers/poissonarrivals/PreciseThroughputTimerTest.java
@@ -18,8 +18,10 @@
package org.apache.jmeter.timers.poissonarrivals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Random;
import org.junit.jupiter.api.Test;
@@ -31,51 +33,156 @@ public class PreciseThroughputTimerTest {
@Test
public void testTimer1() throws Exception {
- ConstantPoissonProcessGenerator gen = getConstantPoissonProcessGenerator(2, 5, 42L);
+ // 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();
- assertEquals(0.6501751901910952, gen.next(), 0.01);
- assertEquals(1.2249545461599474, gen.next(), 0.01);
- assertEquals(1.409559315928937, gen.next(), 0.01);
- assertEquals(1.5717866281130652, gen.next(), 0.01);
- assertEquals(2.1194190047658874, gen.next(), 0.01);
- assertEquals(3.2878637366551384, gen.next(), 0.01);
- assertEquals(3.517916456559849, gen.next(), 0.01);
- assertEquals(3.679224444929692, gen.next(), 0.01);
- assertEquals(3.9907119513763165, gen.next(), 0.01);
- assertEquals(4.754414649148714, gen.next(), 0.01);
- // ^^ 10 samples for 5 seconds
- assertEquals(6.013095167372755, gen.next(), 0.01);
+ 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(
+ "Schedule does not match expectation, " +
+ "throughput=" + throughput + ", duration=" + duration +
+ "seed=" + seed + ", batchSize=" + batchSize,
+ Arrays.toString(expected), Arrays.toString(actual)
+ );
+ }
}
@Test
public void testExactNumberOfSamples() throws Exception {
Random rnd = new Random();
- long seed = rnd.nextLong();
- // Log seed, so the test can be reproduced in case of failure
- LOG.info("testExactNumberOfSamples is using seed " + seed);
- rnd.setSeed(seed);
-
- int testDuration = 5;
- for (int i = 0; i < 1000; i++) {
- ConstantPoissonProcessGenerator gen =
- getConstantPoissonProcessGenerator(2, testDuration, rnd.nextLong());
- gen.generateNext();
- for (int j = 0; j < 10; j++) {
- double next = gen.next();
- assertTrue("Delay #" + j + " (0-based) exceeds " + testDuration + " seconds", next < 5.0);
+ 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) {
+ final double throughput, final int duration, long seed, int batchSize) {
return new ConstantPoissonProcessGenerator(
() -> throughput, // samples per second
- 1,
+ batchSize,
0,
() -> duration, // "expected" test duration: 3 seconds
- 10000,
- 0.1,
seed, // Seed
false
);
diff --git a/xdocs/changes.xml b/xdocs/changes.xml
index da246c0..23514ee 100644
--- a/xdocs/changes.xml
+++ b/xdocs/changes.xml
@@ -99,6 +99,7 @@ to view the last release notes of version 5.2.1.
<h3>Timers, Assertions, Config, Pre- & Post-Processors</h3>
<ul>
+ <li><bug>64091</bug>Precise Throughput Timer schedule generation is improved significantly (e.g. 2 seconds for 10M samples)</li>
</ul>
<h3>Functions</h3>
@@ -147,6 +148,7 @@ to view the last release notes of version 5.2.1.
<h3>Timers, Assertions, Config, Pre- & Post-Processors</h3>
<ul>
+ <li><bug>64091</bug>Precise Throughput Timer might produce less samples when low test duration is used</li>
</ul>
<h3>Functions</h3>
diff --git a/xdocs/images/screenshots/timers/precise_throughput_timer.png b/xdocs/images/screenshots/timers/precise_throughput_timer.png
index e6f6d22..142ef2a 100644
Binary files a/xdocs/images/screenshots/timers/precise_throughput_timer.png and b/xdocs/images/screenshots/timers/precise_throughput_timer.png differ
diff --git a/xdocs/usermanual/component_reference.xml b/xdocs/usermanual/component_reference.xml
index d8a1559..253cb7f 100644
--- a/xdocs/usermanual/component_reference.xml
+++ b/xdocs/usermanual/component_reference.xml
@@ -5095,7 +5095,7 @@ Note that the throughput value should not be changed too often during a test
</properties>
</component>
-<component name="Precise Throughput Timer" index="§-num;.6.5" width="523" height="318" screenshot="timers/precise_throughput_timer.png">
+<component name="Precise Throughput Timer" index="§-num;.6.5" width="573" height="407" screenshot="timers/precise_throughput_timer.png">
<description><p>This timer introduces variable pauses, calculated to keep the total throughput (e.g. in terms of samples per minute) as close as possible to a give figure. Of course the throughput will be lower if the server is not capable of handling it, or if other timers, or if there's not enough threads, or time-consuming test elements prevent it.</p>
<p>Although the Timer is called Precise Throughput Timer, it does not aim to produce precisely the same number of samples over one-second intervals during the test.</p>
<p>The timer works best for rates under 36000 requests/hour, however your mileage might vary (see monitoring section below if your goals are
@@ -5166,13 +5166,18 @@ the required load.</p>
<p>Note: when using multiple thread groups with same throughput rates and same non-zero seed it might result in unwanted firing the samples at the same time.</p>
<h4>Testing high rates and/or long test durations</h4>
-<p>When the number of samples is high (e.g. it exceeds 10'000), schedule generation might take noticeable time (e.g. seconds) as <code>Precise Throughput Timer</code> tries
- to produce the exact number of samples. There's memory consumption as well, however it should not matter much as every item in the schedule consumes 8 bytes.
- In order to reduce schedule generation overhead, <code>Precise Throughput Timer</code> allows some slack when generating long schedules. It is controlled by
- <code>Accuracy of generated delays</code> properties. By default, inexact schedules are allowed when number of samples exceeds 10'000.</p>
-<p>If you want to perform 2-week long test with 5'000 per hour rate, you do not need to create a schedule for that 2 weeks.
- You can set <code>Test duration (seconds)</code> property of the timer to 1 hour. The timer would create a schedule of 5'000 samples for an hour, and when the schedule is exhausted, the timer would generate
+<p><code>Precise Throughput Timer</code> generates the schedule and keeps it in memory. In most cases it should not be a problem,
+ however, remember that you might want to keep the schedule shorter than 1'000'000 samples.
+ It takes ~200ms to generate a schedule for 1'000'000 samples, and the schedule consumes 8 megabytes in the heap.
+ Schedule for 10 million entries takes 1-2 second to build and it consumes 80 megabytes in the heap.
+</p>
+<p>For instance, if you want to perform 2-week long test with 5'000 per hour rate, then you probably want to have exactly 5'000 samples
+ for each hour. You can set <code>Test duration (seconds)</code> property of the timer of the timer to 1 hour.
+ Then the timer would create a schedule of 5'000 samples for an hour, and when the schedule is exhausted, the timer would generate
a schedule for the next hour.</p>
+<p>At the same time, you can set <code>Test duration (seconds)</code> to 2 weeks, and the timer would generate a schedule with
+ <code>168'000 samples = 2 weeks * 5'000 samples/hour = 2*7*24*500</code>. The schedule would take ~30ms to generate, and it would
+ consume a little more than 1 megabyte.</p>
<h4>Bursty load</h4>
<p>There might be a case when all the samples should come in pairs, triples, etc. Certain cases might be solved via <complink name="Synchronizing Timer"/>, however
@@ -5189,7 +5194,7 @@ the required load.</p>
<h4>Monitoring</h4>
<p>As next schedule is generated, <code>Precise Throughput Timer</code> logs a message to <code>jmeter.log</code>:
<code>2018-01-04 17:34:03,635 INFO o.a.j.t.ConstantPoissonProcessGenerator: Generated 21 timings (... 20 required, rate 1.0, duration 20, exact lim 20000,
- i21) in 0 ms, restart was issued 3 times. First 15 events will be fired at: 1.1869653574244292 (+1.1869653574244292), 1.4691340403043207 (+0.2821686828798915),
+ i21) in 0 ms. First 15 events will be fired at: 1.1869653574244292 (+1.1869653574244292), 1.4691340403043207 (+0.2821686828798915),
3.638151706179226 (+2.169017665874905), 3.836357090410566 (+0.19820538423134026), 4.709330071408575 (+0.8729729809980085), 5.61330076999953 (+0.903970698590955),
...</code>
This shows that schedule generation took 0ms, and it shows absolute timestamps in seconds. In the case above, the rate was set to be 1 per second, and the actual timestamps
@@ -5202,8 +5207,6 @@ This shows that schedule generation took 0ms, and it shows absolute timestamps i
<property name="Test duration (seconds)" required="Yes">This is used to ensure you'll get throughput*duration samples during "test duration" timeframe.</property>
<property name="Number of threads in the batch (threads)" required="Yes">If the value exceeds 1, then multiple threads depart from the timer simultaneously. Average throughput still meets "throughput" value.</property>
<property name="Delay between threads in the batch (ms)" required="Yes">For instance, if set to 42, and the batch size is 3, then threads will depart at x, x+42ms, x+84ms.</property>
- <property name="Use approximate throughput when sequence length exceeds (samples)" required="Yes">When the required number of samples is less than this limit, timer will generate exact number of samples.</property>
- <property name="Allowed throughput surplus (percents)" required="Yes">When more than "max exact samples" samples is required, timer might generate slightly more events than specified by throughput.</property>
<property name="Random seed (change from 0 to random)" required="Yes">Note: different timers should better have different seed values. Constant seed ensures timer generates the same delays each test start. The value of "0" means the timer is truly random (non-repeatable from one execution to another)..</property>
</properties>
</component>