From 45996dbd69cf9ad1ee95e0cc522a10196e4e6e0f Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Tue, 24 Sep 2024 17:05:28 +0200 Subject: [PATCH 1/4] Use current time as start time for OMTG When we use the test start time via JMeterContextService#getTestStartTime, we might create a storm of test samples at the beginning, when a setup thread group made us wait. --- .../org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt b/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt index 167e17f628f..5948dc6d40b 100644 --- a/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt +++ b/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt @@ -204,7 +204,7 @@ public class OpenModelThreadGroup : val seed = randomSeed val rnd = if (seed == 0L) Random() else Random(seed) val gen = ThreadScheduleProcessGenerator(rnd, parsedSchedule) - val testStartTime = JMeterContextService.getTestStartTime() + val testStartTime = System.currentTimeMillis() val executorService = Executors.newCachedThreadPool() this.executorService = executorService val starter = ThreadsStarter(testStartTime, executorService, activeThreads, gen) { threadNumber -> From 9e2e2bcbd13d128c0d887a23e67e2e7a9dcf9355 Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Wed, 25 Sep 2024 12:28:54 +0200 Subject: [PATCH 2/4] Add startTime to AbstractThreadGroup and use it in OMTG and PreciseThroughputTimer A thread group might start later than the test. When we calculate delays on the start time of the test, we might get a stampede of samples run at the start of the thread group. --- .../PreciseThroughputTimer.java | 4 ++-- .../jmeter/engine/StandardJMeterEngine.java | 1 + .../jmeter/threads/AbstractThreadGroup.java | 20 +++++++++++++++++++ .../threads/openmodel/OpenModelThreadGroup.kt | 2 +- xdocs/changes.xml | 4 +++- 5 files changed, 27 insertions(+), 4 deletions(-) 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 4864e22117c..ee4998a62c7 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 @@ -99,7 +99,7 @@ public long delay() { nextEvent = events.next(); } long now = System.currentTimeMillis(); - long testStarted = JMeterContextService.getTestStartTime(); + long testStarted = JMeterContextService.getContext().getThreadGroup().getStartTime(); long delay = (long) (nextEvent * TimeUnit.SECONDS.toMillis(1) + testStarted - now); if (log.isDebugEnabled()) { log.debug("Calculated delay is {}", delay); @@ -117,7 +117,7 @@ public long delay() { } private EventProducer getEventProducer() { - long testStarted = JMeterContextService.getTestStartTime(); + long testStarted = JMeterContextService.getContext().getThreadGroup().getStartTime(); long prevStarted = PREV_TEST_STARTED.get(); if (prevStarted != testStarted && PREV_TEST_STARTED.compareAndSet(prevStarted, testStarted)) { // Reset counters if we are calculating throughput for a new test, see https://github.com/apache/jmeter/issues/6165 diff --git a/src/core/src/main/java/org/apache/jmeter/engine/StandardJMeterEngine.java b/src/core/src/main/java/org/apache/jmeter/engine/StandardJMeterEngine.java index a72602fb2e4..618f5b92124 100644 --- a/src/core/src/main/java/org/apache/jmeter/engine/StandardJMeterEngine.java +++ b/src/core/src/main/java/org/apache/jmeter/engine/StandardJMeterEngine.java @@ -574,6 +574,7 @@ private void startThreadGroup(AbstractThreadGroup group, int groupCount, SearchB threadGroupTree.add(group, testLevelElements); groups.add(group); + group.setStartTime(System.currentTimeMillis()); group.start(groupCount, notifier, threadGroupTree, this); } catch (JMeterStopTestException ex) { // NOSONAR Reported by log JMeterUtils.reportErrorToUser("Error occurred starting thread group :" + group.getName()+ ", error message:"+ex.getMessage() diff --git a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java index 37b3f104b25..63a4462a4d1 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.IdentityHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.apache.jmeter.control.Controller; import org.apache.jmeter.control.IteratingController; @@ -86,6 +87,8 @@ public abstract class AbstractThreadGroup extends AbstractTestElement private final AtomicInteger numberOfThreads = new AtomicInteger(0); // Number of active threads in this group + private final AtomicLong startTime = new AtomicLong(0); + @Override public AbstractThreadGroupSchema getSchema() { return AbstractThreadGroupSchema.INSTANCE; @@ -97,6 +100,23 @@ public AbstractThreadGroupSchema getSchema() { return new PropertiesAccessor<>(this, getSchema()); } + /** + * Get the time when this thread group has been started + * @return time in milliseconds since epoch + */ + public long getStartTime() { + return startTime.get(); + } + + /** + * Set the time when this thread group has been started.
+ * Will probably be set by StandardJMeterEngine. + * @param startTime time in milliseconds since epoch + */ + public void setStartTime(long startTime) { + this.startTime.set(startTime); + } + /** {@inheritDoc} */ @Override public boolean isDone() { diff --git a/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt b/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt index 5948dc6d40b..45c0f6cef6b 100644 --- a/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt +++ b/src/core/src/main/kotlin/org/apache/jmeter/threads/openmodel/OpenModelThreadGroup.kt @@ -204,7 +204,7 @@ public class OpenModelThreadGroup : val seed = randomSeed val rnd = if (seed == 0L) Random() else Random(seed) val gen = ThreadScheduleProcessGenerator(rnd, parsedSchedule) - val testStartTime = System.currentTimeMillis() + val testStartTime = this.startTime val executorService = Executors.newCachedThreadPool() this.executorService = executorService val starter = ThreadsStarter(testStartTime, executorService, activeThreads, gen) { threadNumber -> diff --git a/xdocs/changes.xml b/xdocs/changes.xml index 60836279fde..025af48bca1 100644 --- a/xdocs/changes.xml +++ b/xdocs/changes.xml @@ -57,7 +57,7 @@ JMeter 6.x requires Java 17 or later for execution (Java 21 is recommended). Summary

Changes @@ -65,6 +65,8 @@ Summary From 66c2df72ad3d8cb39aae8cef6d2b68cc17624a1e Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Wed, 25 Sep 2024 18:25:09 +0200 Subject: [PATCH 3/4] Use simple long, as we are initialized before the reading threads are started --- .../org/apache/jmeter/threads/AbstractThreadGroup.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java index 63a4462a4d1..5b7c6c68575 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java @@ -21,7 +21,6 @@ import java.time.Duration; import java.util.IdentityHashMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import org.apache.jmeter.control.Controller; import org.apache.jmeter.control.IteratingController; @@ -87,7 +86,7 @@ public abstract class AbstractThreadGroup extends AbstractTestElement private final AtomicInteger numberOfThreads = new AtomicInteger(0); // Number of active threads in this group - private final AtomicLong startTime = new AtomicLong(0); + private long startTime; @Override public AbstractThreadGroupSchema getSchema() { @@ -105,7 +104,7 @@ public AbstractThreadGroupSchema getSchema() { * @return time in milliseconds since epoch */ public long getStartTime() { - return startTime.get(); + return startTime; } /** @@ -114,7 +113,7 @@ public long getStartTime() { * @param startTime time in milliseconds since epoch */ public void setStartTime(long startTime) { - this.startTime.set(startTime); + this.startTime = startTime; } /** {@inheritDoc} */ From 7848ff4fd62283f8ed2fc752e3e5e9e5b5240d3f Mon Sep 17 00:00:00 2001 From: Felix Schumacher Date: Fri, 27 Sep 2024 10:07:56 +0200 Subject: [PATCH 4/4] Add since annotation to javadoc for new feature --- .../java/org/apache/jmeter/threads/AbstractThreadGroup.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java index 5b7c6c68575..f64cf41e7d1 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/AbstractThreadGroup.java @@ -101,6 +101,7 @@ public AbstractThreadGroupSchema getSchema() { /** * Get the time when this thread group has been started + * @since 6.0.0 * @return time in milliseconds since epoch */ public long getStartTime() { @@ -110,6 +111,7 @@ public long getStartTime() { /** * Set the time when this thread group has been started.
* Will probably be set by StandardJMeterEngine. + * @since 6.0.0 * @param startTime time in milliseconds since epoch */ public void setStartTime(long startTime) {