Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.property.DoubleProperty;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.JMeterProperty;
Expand All @@ -52,14 +52,15 @@
*/
@GUIMenuSortOrder(4)
@TestElementMetadata(labelResource = "displayName")
public class ConstantThroughputTimer extends AbstractTestElement implements Timer, TestStateListener, TestBean {
public class ConstantThroughputTimer extends AbstractTestElement implements Timer, TestBean {
private static final long serialVersionUID = 4;

private static class ThroughputInfo{
final Object MUTEX = new Object();
long lastScheduledTime = 0;
}
private static final Logger log = LoggerFactory.getLogger(ConstantThroughputTimer.class);
private static final AtomicLong PREV_TEST_STARTED = new AtomicLong(0L);

private static final double MILLISEC_PER_MIN = 60000.0;

Expand Down Expand Up @@ -180,6 +181,13 @@ protected long calculateCurrentTarget(long currentTime) {

// Calculate the delay based on the mode
private long calculateDelay() {
long testStarted = JMeterContextService.getTestStartTime();
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
reset();
}

long delay;
// N.B. we fetch the throughput each time, as it may vary during a test
double msPerRequest = MILLISEC_PER_MIN / getThroughput();
Expand Down Expand Up @@ -252,18 +260,6 @@ public String toString() {
return JMeterUtils.getResString("constant_throughput_timer_memo"); //$NON-NLS-1$
}

/**
* Get the timer ready to compute delays for a new test.
* <p>
* {@inheritDoc}
*/
@Override
public void testStarted()
{
log.debug("Test started - reset throughput calculation.");
reset();
}

/**
* Override the setProperty method in order to convert
* the original String calcMode property.
Expand Down Expand Up @@ -300,30 +296,6 @@ public void setProperty(JMeterProperty property) {
super.setProperty(property);
}

/**
* {@inheritDoc}
*/
@Override
public void testEnded() {
//NOOP
}

/**
* {@inheritDoc}
*/
@Override
public void testStarted(String host) {
testStarted();
}

/**
* {@inheritDoc}
*/
@Override
public void testEnded(String host) {
//NOOP
}

// For access from test code
Mode getMode() {
int mode = getCalcMode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.timers.Timer;
import org.apache.jorphan.collections.IdentityKey;
import org.apache.jorphan.util.JMeterStopThreadException;
Expand All @@ -41,7 +42,7 @@
*/
@GUIMenuSortOrder(3)
@TestElementMetadata(labelResource = "displayName")
public class PreciseThroughputTimer extends AbstractTestElement implements Cloneable, Timer, TestStateListener, TestBean, ThroughputProvider, DurationProvider {
public class PreciseThroughputTimer extends AbstractTestElement implements Cloneable, Timer, TestBean, ThroughputProvider, DurationProvider {
private static final Logger log = LoggerFactory.getLogger(PreciseThroughputTimer.class);

private static final long serialVersionUID = 4;
Expand All @@ -50,6 +51,8 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
private static final ConcurrentMap<IdentityKey<AbstractThreadGroup>, EventProducer> groupEvents =
new ConcurrentHashMap<>();

private static final AtomicLong PREV_TEST_STARTED = new AtomicLong(0L);

/**
* Desired throughput configured as {@code throughput/throughputPeriod} per second.
*/
Expand All @@ -63,8 +66,6 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
*/
private long duration;

private long testStarted;

/**
* When number of required samples exceeds {@code exactLimit}, random generator would resort to approximate match of
* number of generated samples.
Expand All @@ -87,31 +88,9 @@ public class PreciseThroughputTimer extends AbstractTestElement implements Clone
@Override
public Object clone() {
final PreciseThroughputTimer newTimer = (PreciseThroughputTimer) super.clone();
newTimer.testStarted = testStarted; // JMeter cloning does not clone fields
return newTimer;
}

@Override
public void testStarted() {
testStarted(null);
}

@Override
public void testStarted(String host) {
groupEvents.clear();
testStarted = System.currentTimeMillis();
}

@Override
public void testEnded() {
// NOOP
}

@Override
public void testEnded(String s) {
// NOOP
}

@Override
public long delay() {
double nextEvent;
Expand All @@ -120,6 +99,7 @@ public long delay() {
nextEvent = events.next();
}
long now = System.currentTimeMillis();
long testStarted = JMeterContextService.getTestStartTime();
long delay = (long) (nextEvent * TimeUnit.SECONDS.toMillis(1) + testStarted - now);
if (log.isDebugEnabled()) {
log.debug("Calculated delay is {}", delay);
Expand All @@ -137,6 +117,13 @@ public long delay() {
}

private EventProducer getEventProducer() {
long testStarted = JMeterContextService.getTestStartTime();
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
groupEvents.clear();
}

AbstractThreadGroup tg = getThreadContext().getThreadGroup();
IdentityKey<AbstractThreadGroup> key = new IdentityKey<>(tg);
EventProducer eventProducer = groupEvents.get(key);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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

import org.apache.jmeter.control.LoopController
import org.apache.jmeter.junit.JMeterTestCase
import org.apache.jmeter.sampler.DebugSampler
import org.apache.jmeter.test.assertions.executePlanAndCollectEvents
import org.apache.jmeter.threads.ThreadGroup
import org.apache.jmeter.treebuilder.TreeBuilder
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds

class ConstantThroughputTimerKtTest : JMeterTestCase() {
fun TreeBuilder.oneRequest(body: ThreadGroup.() -> Unit) {
ThreadGroup::class {
numThreads = 1
rampUp = 0
setSamplerController(
LoopController().apply {
loops = 1
}
)
body()
}
}

@Test
fun `throughput as variable`() {
val events = executePlanAndCollectEvents(5.seconds) {
oneRequest {
DebugSampler::class {
// This initializes the variable during the test execution
name = "\${__groovy( vars.put(\"throughput\"\\, \"10000\") )}"
}
DebugSampler::class {
ConstantThroughputTimer::class {
setProperty(
"throughput",
"\${__groovy( vars.get(\"throughput\").toDouble() )}"
)
setProperty("calcMode", 0)
}
}
}
}
assertEquals(
2,
events.size,
"Test should complete within reasonable time, and the test has 2 debug samplers, so we expect 2 events"
)
}
}