ASF Bugzilla – Attachment 27262 Details for
Bug 51480
ConstantThroughputTimer in shared mode all threads start with an incorrect delay of 0
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Fix with unit tests
jmeter-v2_4_bug_51480.patch (text/plain), 97.63 KB, created by
baerrach
on 2011-07-06 01:06:40 UTC
(
hide
)
Description:
Fix with unit tests
Filename:
MIME Type:
Creator:
baerrach
Created:
2011-07-06 01:06:40 UTC
Size:
97.63 KB
patch
obsolete
>Index: src/components/org/apache/jmeter/timers/ThroughputInfo.java >=================================================================== >--- src/components/org/apache/jmeter/timers/ThroughputInfo.java (revision 0) >+++ src/components/org/apache/jmeter/timers/ThroughputInfo.java (revision 0) >@@ -0,0 +1,103 @@ >+/* >+ * 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 java.io.Serializable; >+ >+/** >+ * Manages the throughput information in a thread safe way. >+ */ >+public class ThroughputInfo implements Serializable { >+ private static final long serialVersionUID = 1; >+ >+ /** >+ * Extracted out for unit testing. See class javadoc. >+ */ >+ private static SystemClock SYSTEM_CLOCK = new SystemClock(); >+ >+ /** >+ * Extracted out for unit testing. Java should optimize this away in >+ * production. >+ */ >+ protected static class SystemClock { >+ >+ /** >+ * @return <code>System.currentTimeMillis</code> >+ */ >+ public long getCurrentTime() { >+ return System.currentTimeMillis(); >+ } >+ } >+ >+ /** >+ * Only used for unit testing to set a simulation clock instead of >+ * java.lang.System. >+ * >+ * @param systemClock >+ * the system clock to use >+ */ >+ protected static void setSystemClock(SystemClock systemClock) { >+ SYSTEM_CLOCK = systemClock; >+ } >+ >+ /** >+ * The time, in milliseconds, when the last scheduled event occurred. >+ * >+ * @GuardedBy("this") >+ */ >+ private long lastScheduledTime = 0; >+ >+ /** >+ * <p> >+ * Calculate the delay needed to reach the target time of >+ * <code>lastScheduledTime + delay</code> >+ * </p> >+ * >+ * @param delay >+ * the delay before the next scheduled event >+ * @return return 0 if the current time has already past the target time, >+ * target time - current time otherwise. >+ */ >+ public synchronized long calculateDelay(long delay) { >+ final long currentTime = SYSTEM_CLOCK.getCurrentTime(); >+ >+ /* >+ * If lastScheduledTime is zero, then target will be in the past. This >+ * is what we want, so first sample is run without a delay. (This is >+ * only true if the currentTime is at greater than <code>delay</code> >+ * seconds after midnight, January 1, 1970 UTC) >+ */ >+ long targetTime = lastScheduledTime + delay; >+ if (currentTime > targetTime) { >+ // We're behind schedule -- try to catch up: >+ lastScheduledTime = currentTime; >+ return 0; >+ } >+ lastScheduledTime = targetTime; >+ return targetTime - currentTime; >+ >+ } >+ >+ /** >+ * Reset the <code>lastScheduledTime</code> to zero. >+ */ >+ public synchronized void reset() { >+ lastScheduledTime = 0; >+ } >+ >+} >\ No newline at end of file >Index: src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java >=================================================================== >--- src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java (revision 1140940) >+++ src/components/org/apache/jmeter/timers/ConstantThroughputTimer.java (working copy) >@@ -18,7 +18,6 @@ > > package org.apache.jmeter.timers; > >-import java.util.Map; > import java.util.concurrent.ConcurrentHashMap; > > import org.apache.jmeter.engine.event.LoopIterationEvent; >@@ -37,25 +36,29 @@ > * samples per unit of time approaches a given constant as much as possible. > * > * There are two different ways of pacing the requests: >- * - delay each thread according to when it last ran >- * - delay each thread according to when any thread last ran >+ * <ul> >+ * <li>delay each thread according to when it last ran</li> >+ * <li>delay each thread according to when any thread last ran</li> >+ * </ul> > */ > public class ConstantThroughputTimer extends AbstractTestElement implements Timer, TestListener, TestBean { >- private static final long serialVersionUID = 3; >+ private static final long serialVersionUID = 4; > >- private static class ThroughputInfo{ >- final Object MUTEX = new Object(); >- long lastScheduledTime = 0; >- } >+ private static final int CALC_MODE_1_THIS_THREAD_ONLY = 0; >+ private static final int CALC_MODE_2_ALL_ACTIVE_THREADS = 1; >+ private static final int CALC_MODE_3_ALL_ACTIVE_THREADS_IN_CURRENT_THREAD_GROUP = 2; >+ private static final int CALC_MODE_4_ALL_ACTIVE_THREADS_SHARED = 3; >+ private static final int CALC_MODE_5_ALL_ACTIVE_THREADS_IN_CURRENT_THREAD_GROUP_SHARED = 4; >+ > private static final Logger log = LoggingManager.getLoggerForClass(); > >- private static final double MILLISEC_PER_MIN = 60000.0; >+ protected static final double MILLISEC_PER_MIN = 60000.0; > > /** > * Target time for the start of the next request. The delay provided by the > * timer will be calculated so that the next request happens at this time. > */ >- private long previousTime = 0; >+ private ThroughputInfo throughputInfo = new ThroughputInfo(); > > private String calcMode; // String representing the mode > // (Locale-specific) >@@ -71,7 +74,7 @@ > private final static ThroughputInfo allThreadsInfo = new ThroughputInfo(); > > //For holding the ThrougputInfo objects for all ThreadGroups. Keyed by AbstractThreadGroup objects >- private final static Map<AbstractThreadGroup, ThroughputInfo> threadGroupsInfoMap = >+ private final static ConcurrentHashMap<AbstractThreadGroup, ThroughputInfo> threadGroupsInfoMap = > new ConcurrentHashMap<AbstractThreadGroup, ThroughputInfo>(); > > >@@ -109,10 +112,33 @@ > return modeInt; > } > >+ /** >+ * Setting this has the side effect of sharing <code>throughputInfo</code> if the mode is >+ * <em>shared</em>. >+ * >+ * @param mode >+ * the delay calculation mode >+ */ > public void setCalcMode(String mode) { > this.calcMode = mode; > // TODO find better way to get modeInt > this.modeInt = ConstantThroughputTimerBeanInfo.getCalcModeAsInt(calcMode); >+ >+ switch (modeInt) { >+ case CALC_MODE_4_ALL_ACTIVE_THREADS_SHARED: >+ throughputInfo = allThreadsInfo; >+ break; >+ >+ case CALC_MODE_5_ALL_ACTIVE_THREADS_IN_CURRENT_THREAD_GROUP_SHARED: >+ final org.apache.jmeter.threads.AbstractThreadGroup group = JMeterContextService >+ .getContext().getThreadGroup(); >+ /* >+ * Share the first thread's throughputInfo >+ */ >+ threadGroupsInfoMap.putIfAbsent(group, throughputInfo); >+ throughputInfo = threadGroupsInfoMap.get(group); >+ break; >+ } > } > > /** >@@ -121,89 +147,56 @@ > * @see org.apache.jmeter.timers.Timer#delay() > */ > public long delay() { >- long currentTime = System.currentTimeMillis(); >- >- /* >- * If previous time is zero, then target will be in the past. >- * This is what we want, so first sample is run without a delay. >- */ >- long currentTarget = previousTime + calculateDelay(); >- if (currentTime > currentTarget) { >- // We're behind schedule -- try to catch up: >- previousTime = currentTime; >- return 0; >- } >- previousTime = currentTarget; >- return currentTarget - currentTime; >+ return throughputInfo.calculateDelay(calculateDelayForMode()); > } > > /** >- * @param currentTime >- * @return new Target time >+ * <p> >+ * Calculate the delay based on the mode >+ * </p> >+ * >+ * @return the delay (how long before another request should be made) in >+ * milliseconds > */ >- // TODO - is this used? (apart from test code) >- protected long calculateCurrentTarget(long currentTime) { >- return currentTime + calculateDelay(); >- } >- >- // Calculate the delay based on the mode >- private long calculateDelay() { >+ private long calculateDelayForMode() { > long delay = 0; > // N.B. we fetch the throughput each time, as it may vary during a test > double msPerRequest = (MILLISEC_PER_MIN / getThroughput()); > switch (modeInt) { >- case 1: // Total number of threads >+ case CALC_MODE_2_ALL_ACTIVE_THREADS: >+ /* >+ * Each request is allowed to run every msPerRequest. Each thread >+ * can run a request, so each thread needs to be delayed by the >+ * total pool size of threads to keep the expected throughput. >+ */ > delay = (long) (JMeterContextService.getNumberOfThreads() * msPerRequest); > break; > >- case 2: // Active threads in this group >+ case CALC_MODE_3_ALL_ACTIVE_THREADS_IN_CURRENT_THREAD_GROUP: >+ /* >+ * Each request is allowed to run every msPerRequest. Each thread >+ * can run a request, so each thread needs to be delayed by the >+ * total pool size of threads to keep the expected throughput. >+ */ > delay = (long) (JMeterContextService.getContext().getThreadGroup().getNumberOfThreads() * msPerRequest); > break; > >- case 3: // All threads - alternate calculation >- delay = calculateSharedDelay(allThreadsInfo,(long) msPerRequest); >+ /* >+ * The following modes all fall through for the default >+ */ >+ case CALC_MODE_1_THIS_THREAD_ONLY: >+ case CALC_MODE_4_ALL_ACTIVE_THREADS_SHARED: >+ case CALC_MODE_5_ALL_ACTIVE_THREADS_IN_CURRENT_THREAD_GROUP_SHARED: >+ default: >+ delay = (long) msPerRequest; > break; >- >- case 4: //All threads in this group - alternate calculation >- final org.apache.jmeter.threads.AbstractThreadGroup group = >- JMeterContextService.getContext().getThreadGroup(); >- ThroughputInfo groupInfo; >- synchronized (threadGroupsInfoMap) { >- groupInfo = threadGroupsInfoMap.get(group); >- if (groupInfo == null) { >- groupInfo = new ThroughputInfo(); >- threadGroupsInfoMap.put(group, groupInfo); >- } >- } >- delay = calculateSharedDelay(groupInfo,(long) msPerRequest); >- break; >- >- default: // e.g. 0 >- delay = (long) msPerRequest; // i.e. * 1 >- break; > } > return delay; > } > >- private long calculateSharedDelay(ThroughputInfo info, long milliSecPerRequest) { >- final long now = System.currentTimeMillis(); >- final long calculatedDelay; >- >- //Synchronize on the info object's MUTEX to ensure >- //Multiple threads don't update the scheduled time simultaneously >- synchronized (info.MUTEX) { >- final long nextRequstTime = info.lastScheduledTime + milliSecPerRequest; >- info.lastScheduledTime = Math.max(now, nextRequstTime); >- calculatedDelay = info.lastScheduledTime - now; >- } >- >- return Math.max(calculatedDelay, 0); >- } >- > private synchronized void reset() { >- allThreadsInfo.lastScheduledTime = 0; >- threadGroupsInfoMap.clear(); >- previousTime = 0; >+ throughputInfo = new ThroughputInfo(); >+ setCalcMode(calcMode); > } > > /** >@@ -234,6 +227,14 @@ > * {@inheritDoc} > */ > public void testEnded() { >+ /* >+ * There is no way to clean up static variables. The best place to do >+ * that is in the testEnded() call as this wont affect a running test. >+ * If these are in testStarted then they affect already initialised >+ * objects. >+ */ >+ allThreadsInfo.reset(); >+ threadGroupsInfoMap.clear(); > } > > /** >Index: test/src/org/apache/jmeter/timers/ConstantThroughPutTimerSimulation.java >=================================================================== >--- test/src/org/apache/jmeter/timers/ConstantThroughPutTimerSimulation.java (revision 0) >+++ test/src/org/apache/jmeter/timers/ConstantThroughPutTimerSimulation.java (revision 0) >@@ -0,0 +1,357 @@ >+/* >+ * 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 java.io.IOException; >+import java.io.InputStream; >+import java.io.InputStreamReader; >+import java.io.LineNumberReader; >+import java.util.ArrayList; >+import java.util.List; >+import java.util.regex.Matcher; >+import java.util.regex.Pattern; >+ >+import org.apache.commons.lang.StringUtils; >+import org.apache.jorphan.logging.LoggingManager; >+import org.apache.log.Logger; >+ >+/** >+ * <p> >+ * Builds {@link ConstantThroughputTimerTestData} from a simulation file. >+ * </p> >+ * <p> >+ * See simulator_template.txt for an example file explaining the format >+ * </p> >+ */ >+public class ConstantThroughPutTimerSimulation { >+ >+ /** Logger */ >+ private static final Logger LOG = LoggingManager.getLoggerForClass(); >+ >+ /** >+ * The separator of the simulation file is comma >+ */ >+ private static final String SEPARATOR = ","; >+ >+ /** >+ * Regular expression pattern for the header >+ */ >+ private static final Pattern PATTERN_HEADER = Pattern >+ .compile("(\\d+)m(\\d)t(\\d+)\\[(\\d+)\\]"); >+ >+ /** >+ * Regular expression pattern for the columns >+ */ >+ private static final Pattern PATTERN_COLUMN = Pattern.compile("d=(\\d+)"); >+ >+ /** >+ * >+ * @param simulationFileName >+ * the file name of the simulation. Uses >+ * {@link ClassLoader#getResourceAsStream(String)} to find the >+ * file >+ * @return the InputStream containging the contents of the file >+ * @throws IOException >+ * failed to obtain file >+ */ >+ private InputStream getSimulationFile(String simulationFileName) >+ throws IOException { >+ return getClass().getClassLoader().getResourceAsStream( >+ simulationFileName); >+ } >+ >+ /** >+ * Load the specified <code>simulationFileName</code> (Using >+ * {@link ClassLoader#getResourceAsStream(String)}) and return a list of >+ * configured TestData representing the simulation. >+ * >+ * @param simulationFileName >+ * the file name of the simulation. Uses >+ * {@link ClassLoader#getResourceAsStream(String)} to find the >+ * file >+ * @return a list of configured TestData >+ * @throws IOException >+ * failed to obtain file >+ * @throws SimulationParseException >+ * failed to parse simulation file >+ */ >+ public List<ConstantThroughputTimerTestData> loadSimulationFile( >+ String simulationFileName) throws IOException, >+ SimulationParseException { >+ return parse(simulationFileName); >+ } >+ >+ /** >+ * @param simulationFileName >+ * the name of the simulation file >+ * @return the simulation <code>TestData</code> >+ * @throws IOException >+ * failed to load the simulation file >+ * @throws SimulationParseException >+ * failed to parse simulation file >+ */ >+ private List<ConstantThroughputTimerTestData> parse( >+ String simulationFileName) throws IOException, >+ SimulationParseException { >+ InputStream simulationFile = getSimulationFile(simulationFileName); >+ if (simulationFile == null) { >+ throw new SimulationParseException("Unable to locate simulation file: " + simulationFileName, 0); >+ } >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ >+ LineNumberReader simulationInput = new LineNumberReader( >+ new InputStreamReader(simulationFile)); >+ String line = null; >+ >+ boolean parsingBody = false; >+ try { >+ while ((line = simulationInput.readLine()) != null) { >+ line = stripComments(line); >+ if (StringUtils.isNotBlank(line)) { >+ if (!parsingBody) { >+ parseHeader(simulation, line); >+ parsingBody = true; >+ >+ } else { >+ parseBody(simulation, line); >+ } >+ } >+ } >+ } catch (SimulationParseException e) { >+ e.setLineNumber(simulationInput.getLineNumber()); >+ e.setFileName(simulationFileName); >+ throw e; >+ } >+ >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ return simulation; >+ } >+ >+ /** >+ * @param simulation >+ * the simulation list of <code>TestData</code> >+ * @param line >+ * the line to parse >+ * @throws SimulationParseException >+ * failed to parse simulation file >+ */ >+ private void parseBody(List<ConstantThroughputTimerTestData> simulation, >+ String line) throws SimulationParseException { >+ String[] columns = line.split(SEPARATOR); >+ if (columns.length <= 1) { >+ throw new SimulationParseException("No Events defined", >+ line.length() + 1); >+ } >+ >+ String clockTimeAsString = columns[0].trim(); >+ int clockTime = 0; >+ try { >+ clockTime = Integer.parseInt(clockTimeAsString); >+ } catch (NumberFormatException e) { >+ throw new SimulationParseException( >+ "Malformed Simulation Clock Time: " + clockTimeAsString, >+ computeColumnNumber(columns, 0)); >+ >+ } >+ >+ for (int i = 1; i < columns.length; i++) { >+ String column = columns[i].trim(); >+ if (StringUtils.isBlank(column)) { >+ // ignore blank columns >+ continue; >+ } >+ >+ if (i > simulation.size()) { >+ throw new SimulationParseException("Too many events defined", >+ computeColumnNumber(columns, i)); >+ } >+ >+ if (column.equalsIgnoreCase("X")) { >+ // Ignore optimal time boundary indicators >+ continue; >+ } >+ >+ Matcher matcher = PATTERN_COLUMN.matcher(column); >+ if (!matcher.matches()) { >+ throw new SimulationParseException( >+ "Malformed delay: " + column, computeColumnNumber( >+ columns, i)); >+ } >+ int delay = 0; >+ try { >+ delay = Integer.parseInt(matcher.group(1)); >+ } catch (NumberFormatException e) { >+ throw new SimulationParseException( >+ "Malformed delay: " + column, computeColumnNumber( >+ columns, i)); >+ } >+ >+ SimulationEvent event = new SimulationEvent(clockTime, delay); >+ int simulationIndex = i - 1; /* >+ * columnIndex includes clockTime, >+ * simulationIndex doesn't >+ */ >+ LOG.debug("Adding event to TestData: " >+ + simulation.get(simulationIndex).getTimerId() + ", " >+ + event); >+ simulation.get(simulationIndex).addEvent(event); >+ } >+ >+ } >+ >+ /** >+ * @param simulation >+ * the list to build for the simulation >+ * @param line >+ * the line to parse >+ * @throws SimulationParseException >+ * failed to parse simulation file >+ */ >+ private void parseHeader(List<ConstantThroughputTimerTestData> simulation, >+ String line) throws SimulationParseException { >+ String[] headers = line.split(SEPARATOR); >+ if (headers.length <= 1) { >+ throw new SimulationParseException("No Timers defined", >+ line.length() + 1); >+ } >+ // Ignore the first header as its the Simulation Clock name >+ for (int i = 1; i < headers.length; i++) { >+ String header = headers[i].trim(); >+ Matcher matcher = PATTERN_HEADER.matcher(header); >+ if (!matcher.matches()) { >+ throw new SimulationParseException("Header " + header >+ + " did not match expected format : " >+ + PATTERN_HEADER.toString(), computeColumnNumber( >+ headers, i)); >+ } >+ String timerId = matcher.group(1); >+ String mode = ConstantThroughputTimerTestData.RESOURCE_BUNDLE_KEY_CALC_MODE_PREFIX >+ + matcher.group(2); >+ String numberOfThreads = matcher.group(3); >+ String msPerRequest = matcher.group(4); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setTimerId(timerId); >+ testData.setCalcMode(mode); >+ try { >+ testData.setNumberOfThreadsInThreadGroup(Integer >+ .parseInt(numberOfThreads)); >+ } catch (NumberFormatException e) { >+ throw new SimulationParseException( >+ "Malformed number of threads in header: " + header, >+ computeColumnNumber(headers, i)); >+ } >+ try { >+ testData.setMsPerRequest(Integer.parseInt(msPerRequest)); >+ } catch (NumberFormatException e) { >+ throw new SimulationParseException( >+ "Malformed msPerRequest in header: " + header, >+ computeColumnNumber(headers, i)); >+ } >+ >+ LOG.debug(testData.toString()); >+ simulation.add(testData); >+ } >+ } >+ >+ /** >+ * @param columns >+ * array of columns >+ * @param columnIndex >+ * the column index >+ * @return >+ */ >+ private int computeColumnNumber(String[] columns, int columnIndex) { >+ int columnNumber = 1; >+ for (int i = 0; i < columnIndex; i++) { >+ // extra for the stripped separator character >+ columnNumber += columns[i].length() + 1; >+ } >+ return columnNumber; >+ } >+ >+ /** >+ * @param line >+ * the line to strip comments from >+ * @return the line with all characters from a # to the end stripped >+ */ >+ private String stripComments(String line) { >+ return line.replaceAll("#.*", ""); >+ } >+} >+ >+class SimulationParseException extends Exception { >+ >+ /** >+ * serialVersionUID >+ */ >+ private static final long serialVersionUID = 1L; >+ >+ /** >+ * Offset into the line where the parsing error occurred. >+ */ >+ private int offset; >+ >+ /** >+ * The lineNumber of where the parsing error occurred. >+ */ >+ private int lineNumber; >+ >+ /** >+ * The simulation filename of where the parsing error occurred. >+ */ >+ private String fileName; >+ >+ public SimulationParseException(String message, int offset) { >+ super(message); >+ this.offset = offset; >+ } >+ >+ @Override >+ public String getMessage() { >+ return "Simulation Parsing exception in " + fileName + ":" + lineNumber >+ + ":" + offset + " " + super.getMessage(); >+ } >+ >+ /** >+ * @param offset >+ * the offset to set >+ */ >+ public void setOffset(int offset) { >+ this.offset = offset; >+ } >+ >+ /** >+ * @param lineNumber >+ * the lineNumber to set >+ */ >+ public void setLineNumber(int lineNumber) { >+ this.lineNumber = lineNumber; >+ } >+ >+ /** >+ * @param fileName >+ * the fileName to set >+ */ >+ public void setFileName(String fileName) { >+ this.fileName = fileName; >+ } >+ >+} >\ No newline at end of file >Index: test/src/org/apache/jmeter/timers/simulator_mode3.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_mode3.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_mode3.txt (revision 0) >@@ -0,0 +1,34 @@ >+# >+# Constant Throughput Timer Simulator file >+# See simulator_template.txt for full explanation of files contents. >+# >+ >+# calcMode.3=all active threads in current thread group >+# Delay is <code>msPerRequest * ThreadsInThreadGroup Count</code>. >+# Timer 1 delay = 5000 * 1 = 5,000 >+# Timer 2 delay = 3000 * 3 = 9,000 >+# Timer 3 delay = 10000 * 5 = 50,000 >+ >+# delay = target time <= current time ? 0 : target time - simulation time >+# where target time = previous time + msPerRequest >+Simulation Time, 1m3t1[5000], 2m3t3[3000], 3m3t5[10000], >+ 0, d=0, d=0, d=0, # For non-shared Constant Throughput Timers the first request will be at time 0 with no delay. >+ 1000, d=4000, , , # timer 1 : target time = 0 + 5000 = 5000; delay = 5000 - 1000 = 4000 >+ 4000, , d=5000, , # timer 2 : target time = 0 + 9000 = 9000; delay = 9000 - 4000 = 5000 >+ 5000, X, , , >+ 9000, , X, , >+ 10000, X, , , >+ 15000, X, , , >+ 18000, , d=0, , # timer 2 : target time = 9000 + 9000 = 18000; delay = 0 >+ 20000, X, , , >+ 27000, , d=0, , # timer 2 : target time = 18000 + 9000 = 27000; delay = 0 >+ 30000, , d=6000, , # timer 2 : target time = 27000 + 9000 = 36000; delay = 36000 - 30000 = 6000 >+ 36000, , X, , >+ 45000, X, , , >+ 50000, X, , d=0, # timer 3 : target time = 0 + 50000 = 50000; delay = 0; >+ 70000, , , d=30000, # timer 3 : target time = 50000 + 50000 = 100000; delay = 100000 - 70000 = 30000 >+ 100000, X, , X, >+ 150500, X, , X, >+ 150500, , , d=0, # timer 3 : target time = 10000 + 50000 = 150000; delay = 0 >+ 200000, X, , X, >+ >Index: test/src/org/apache/jmeter/timers/ConstantThroughputTimerTestData.java >=================================================================== >--- test/src/org/apache/jmeter/timers/ConstantThroughputTimerTestData.java (revision 0) >+++ test/src/org/apache/jmeter/timers/ConstantThroughputTimerTestData.java (revision 0) >@@ -0,0 +1,356 @@ >+package org.apache.jmeter.timers; >+ >+import static org.apache.jmeter.timers.Copy_Commons_Lang_2_5_Reflect.getDeclaredField; >+import static org.apache.jmeter.timers.Copy_Commons_Lang_2_5_Reflect.writeDeclaredStaticField; >+import static org.junit.Assert.assertEquals; >+ >+import java.lang.reflect.Field; >+import java.util.ArrayList; >+import java.util.Collection; >+import java.util.List; >+import java.util.ResourceBundle; >+ >+import org.apache.jmeter.testbeans.BeanInfoSupport; >+import org.apache.jmeter.threads.AbstractThreadGroup; >+import org.apache.jmeter.threads.JMeterContext; >+import org.apache.jmeter.threads.JMeterContextService; >+import org.apache.jmeter.threads.MockJMeterContext; >+ >+/** >+ * Data class for bundling test data around. >+ */ >+public class ConstantThroughputTimerTestData { >+ private static ConstantThroughputTimerBeanInfo BEAN_INFO = new ConstantThroughputTimerBeanInfo(); >+ >+ /** >+ * this thread only >+ */ >+ public static final String CALC_MODE_1 = "calcMode.1"; >+ >+ /** >+ * all active threads >+ */ >+ public static final String CALC_MODE_2 = "calcMode.2"; >+ >+ /** >+ * all active threads in current thread group >+ */ >+ public static final String CALC_MODE_3 = "calcMode.3"; >+ >+ /** >+ * all active threads (shared) >+ */ >+ public static final String CALC_MODE_4 = "calcMode.4"; >+ >+ /** >+ * all active threads in current thread group (shared) >+ */ >+ public static final String CALC_MODE_5 = "calcMode.5"; >+ >+ /** >+ * Mock JMeterContext which requires a delegate to the actual context. >+ * Actual contexts are in <code>ConstantThroughputTimerTestData</code> >+ */ >+ private static MockJMeterContext mockJMeterContext = new MockJMeterContext(); >+ >+ private static ResourceBundle RESOURCE_BUNDLE = (ResourceBundle) BEAN_INFO >+ .getBeanDescriptor().getValue(BeanInfoSupport.RESOURCE_BUNDLE); >+ >+ public static final String RESOURCE_BUNDLE_KEY_CALC_MODE_PREFIX = "calcMode."; >+ >+ /** >+ * Go through the test data list and create additional <code>TestData</code> >+ * instances for each thread in the thread groups. >+ * >+ * @param testDataList >+ * the test data list >+ */ >+ public static void createAdditionalThreads( >+ List<ConstantThroughputTimerTestData> testDataList) { >+ List<ConstantThroughputTimerTestData> additional = new ArrayList<ConstantThroughputTimerTestData>(); >+ >+ for (int i = 0; i < testDataList.size(); i++) { >+ ConstantThroughputTimerTestData testData = testDataList.get(i); >+ additional.addAll(testData.createAddtionalThreads()); >+ } >+ >+ testDataList.addAll(additional); >+ } >+ >+ /** >+ * Setup <code>JMeterContextService.numberOfActiveThreads</code> based on >+ * the test data list provided. >+ * >+ * @param testDataList >+ * the test data list >+ * @throws IllegalAccessException >+ * failed to setup >+ * <code>JMeterContextService.numberOfActiveThreads</code> >+ */ >+ public static void setupJMeterContextService_numberOfActiveThreads( >+ List<ConstantThroughputTimerTestData> testDataList) >+ throws IllegalAccessException { >+ >+ int numberOfActiveThreads = testDataList.size(); >+ >+ writeDeclaredStaticField(JMeterContextService.class, >+ "numberOfActiveThreads", numberOfActiveThreads, true); >+ int numberOfThreads = JMeterContextService.getNumberOfThreads(); >+ assertEquals(numberOfActiveThreads, numberOfThreads); >+ } >+ >+ /** >+ * Setup JMeterContextService.threadContext to use the "delegate" >+ * MockJMeterContext >+ * >+ * @throws IllegalAccessException >+ * failed to setup JMeterContextService.threadContext >+ * @throws IllegalArgumentException >+ * failed to setup JMeterContextService.threadContext >+ */ >+ public static void setupJMeterContextService_threadContext() >+ throws IllegalArgumentException, IllegalAccessException { >+ Field threadContextField = getDeclaredField(JMeterContextService.class, >+ "threadContext", true); >+ @SuppressWarnings("unchecked") >+ // We know this is the correct type >+ ThreadLocal<JMeterContext> context = (ThreadLocal<JMeterContext>) threadContextField >+ .get(null); >+ context.set(mockJMeterContext); >+ } >+ >+ private String calcMode; >+ >+ private List<SimulationEvent> events = new ArrayList<SimulationEvent>(); >+ >+ private JMeterContext jmeterContext; >+ >+ private long msPerRequest; >+ >+ private double throughput; >+ >+ private ConstantThroughputTimer timer; >+ >+ private String timerId; >+ >+ public ConstantThroughputTimerTestData() { >+ this.jmeterContext = MockJMeterContext.newJMeterContext(); >+ this.jmeterContext >+ .setThreadGroup(new org.apache.jmeter.threads.ThreadGroup()); >+ >+ ConstantThroughputTimer timer = new ConstantThroughputTimer(); >+ setTimer(timer); >+ } >+ >+ /** >+ * <p> >+ * Create a copy of the specified instance. >+ * </p> >+ * <p> >+ * Some properties will be shared from the instance: >+ * <ul> >+ * <li><code>threadGroup</code> >+ * <li><code>events</code>, only if the <code>calcMode</code> is one of the >+ * shared modes. >+ * </ul> >+ * </p> >+ * >+ * @param original >+ * instance to make a copy from >+ * @param count >+ * the instance count being requests >+ */ >+ private ConstantThroughputTimerTestData( >+ ConstantThroughputTimerTestData original, int count) { >+ /** >+ * this() sets up jmeterContext and timer >+ */ >+ this(); >+ jmeterContext.setThreadGroup(original.jmeterContext.getThreadGroup()); >+ setCalcMode(original.calcMode); >+ if (CALC_MODE_4.equals(calcMode) || CALC_MODE_5.equals(calcMode)) { >+ events = original.events; >+ } else { >+ events = new ArrayList<SimulationEvent>(); >+ events.addAll(original.events); >+ } >+ setThroughput(original.throughput); // Also sets msPerRequest >+ timerId = original.timerId + "/" + count; >+ } >+ >+ /** >+ * Make this Test Data the currently active one. >+ */ >+ public void activate() { >+ mockJMeterContext.setDelegate(jmeterContext); >+ } >+ >+ /** >+ * @param event >+ * the event to add to the events list >+ */ >+ public void addEvent(SimulationEvent event) { >+ events.add(event); >+ } >+ >+ /** >+ * <p> >+ * Uses the JMeter <code>numberOfThreads</code> (i.e. active threads in the >+ * thread group) not the property <code>NumThreads</code>, which is on the >+ * test plan. >+ * </p> >+ * >+ * @return a list (potentially empty) for any additional threads, as defined >+ * in the <code>numberOfThreadsInThreadGroup</code> >+ */ >+ private Collection<ConstantThroughputTimerTestData> createAddtionalThreads() { >+ List<ConstantThroughputTimerTestData> additional = new ArrayList<ConstantThroughputTimerTestData>(); >+ >+ int numberOfThreadsInThreadGroup = jmeterContext.getThreadGroup() >+ .getNumberOfThreads(); >+ for (int i = 1; i < numberOfThreadsInThreadGroup; i++) { >+ additional.add(new ConstantThroughputTimerTestData(this, i + 1)); >+ } >+ >+ return additional; >+ } >+ >+ /** >+ * @return the timer >+ */ >+ public ConstantThroughputTimer getTimer() { >+ return timer; >+ } >+ >+ /** >+ * @return the timerId >+ */ >+ public String getTimerId() { >+ return timerId; >+ } >+ >+ /** >+ * @return peek at the first event without removing it >+ */ >+ public SimulationEvent peekFirstEvent() { >+ return events.isEmpty() ? null : events.get(0); >+ } >+ >+ /** >+ * @return remove the first event >+ */ >+ public SimulationEvent removeFirstEvent() { >+ return events.isEmpty() ? null : events.remove(0); >+ } >+ >+ /** >+ * Setting the calcMode will also <code>activate</code> this Test Data. >+ * >+ * @param mode >+ * the mode to set >+ */ >+ public void setCalcMode(String mode) { >+ activate(); >+ this.calcMode = mode; >+ String modeStringFromResourceBundle = RESOURCE_BUNDLE.getString(mode); >+ timer.setCalcMode(modeStringFromResourceBundle); >+ } >+ >+ /** >+ * <p> >+ * Note: Will also set <code>throughput</code> and update the >+ * <code>timer</code> >+ * </p> >+ * >+ * @param msPerRequest >+ * the msPerRequest to set >+ */ >+ public void setMsPerRequest(long msPerRequest) { >+ setThroughput(ConstantThroughputTimer.MILLISEC_PER_MIN / msPerRequest); >+ } >+ >+ /** >+ * <p> >+ * Sets the JMeter <code>numberOfThreads</code> (i.e. active threads in the >+ * thread group) not the property <code>NumThreads</code>, which is on the >+ * test plan. >+ * </p> >+ * >+ * @param numberOfThreadsInThreadGroup >+ * the number of threads in the thread group >+ * @throws IllegalAccessException >+ * failed to set AbstractThreadGroup.numberOfThreads property >+ */ >+ public void setNumberOfThreadsInThreadGroup(int numberOfThreadsInThreadGroup) { >+ Field numberOfThreadsField = Copy_Commons_Lang_2_5_Reflect >+ .getDeclaredField(AbstractThreadGroup.class, "numberOfThreads", >+ true); >+ try { >+ Copy_Commons_Lang_2_5_Reflect.writeField(numberOfThreadsField, >+ jmeterContext.getThreadGroup(), >+ numberOfThreadsInThreadGroup, false); >+ } catch (IllegalAccessException e) { >+ throw new RuntimeException( >+ "Unexpected failure to set property AbstractThreadGroup.numberOfThreads", >+ e); >+ } >+ } >+ >+ /** >+ * <p> >+ * Note: Will also set <code>msPerRequest</code> and update the >+ * <code>timer</code>. >+ * </p> >+ * >+ * @param throughput >+ * the throughput to set >+ */ >+ public void setThroughput(double throughput) { >+ this.throughput = throughput; >+ this.msPerRequest = (long) (ConstantThroughputTimer.MILLISEC_PER_MIN / throughput); >+ timer.setThroughput(throughput); >+ } >+ >+ /** >+ * @param timer >+ * the timer to set >+ */ >+ public void setTimer(ConstantThroughputTimer timer) { >+ this.timer = timer; >+ } >+ >+ /** >+ * @param timerId >+ * the timerId to set >+ */ >+ public void setTimerId(String timerId) { >+ this.timerId = timerId; >+ } >+ >+ /** >+ * {@inheritDoc} >+ */ >+ @Override >+ public String toString() { >+ StringBuilder builder = new StringBuilder(); >+ builder.append("TestData["); >+ builder.append("timerId="); >+ builder.append(timerId); >+ builder.append(",ConstantThroughputTimer=0x"); >+ builder.append(Integer.toHexString(System.identityHashCode(timer))); >+ builder.append(",calcMode="); >+ builder.append(calcMode); >+ builder.append(",throughput="); >+ builder.append(throughput); >+ builder.append(",msPerRequest="); >+ builder.append(msPerRequest); >+ builder.append(",threadCount="); >+ builder.append(jmeterContext.getThreadGroup().getNumberOfThreads()); >+ builder.append(",events="); >+ builder.append(events.toString()); >+ builder.append("]"); >+ >+ return builder.toString(); >+ } >+ >+} >\ No newline at end of file >Index: test/src/org/apache/jmeter/timers/simulator_mode4.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_mode4.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_mode4.txt (revision 0) >@@ -0,0 +1,81 @@ >+# >+# Constant Throughput Timer Simulator file >+# See simulator_template.txt for full explanation of files contents. >+# >+ >+# calcMode.4=all active threads (shared) >+# Delay is <code>msPerRequest</code>. >+# The difference with the simulator in calc mode "all active threads (shared mode)" >+# is that each thread group will share a single event stream instead of getting a copy of the event stream. >+# For "all active threads (shared mode)" each timer uses its own delay, >+# but the calculation is based on when the last thread was run at. >+# This makes the simulation file trickier to read since all columns need to be considered when calculating the expected delay. >+# If multiple columns all have values at the same simulation time, then the simulator runs them left to right >+# Optimal boundaries are not marked as its too complicated - each thread has their own optimal value based on the thread's start time. >+# It also gets confusing because threads can request a new start time any time before the actual thread start time needed. >+# Generally, if you are going to use this mode then all timers would use the same msPerRequest. >+Simulation Time, 1m4t1[5000], 2m4t3[3000], 3m4t5[10000], >+ 0, d=0, d=3000, d=13000, # For "all active threads (shared mode)" Constant Throughput Timers only the first thread to make the request will be at time 0 with no delay. >+ 1000, , , , # Subsequent Threads delays are calculated based on the previous time and each thread group shares the event stream. >+ 2000, , , , # delay = target time <= current time ? 0 : target time - simulation time >+ 3000, , , , # where target time = previous time + msPerRequest >+ 4000, , , , # timer 2 : target time = 0 + 3000 = 3000; delay = 3000 - 0 = 3000 >+ 5000, , , , # timer 3 : target time = 3000 + 10000 = 130000; delay = 13000 - 0 = 13000 >+ 6000, , , , >+ 7000, , , d=16000, # timer 3 : target time = 13000 + 10000 = 23000; delay = 23000 - 7000 = 16000 >+ 8000, , , , >+ 9000, d=19000, , , # timer 1 : target time = 23000 + 5000 = 28000; delay = 28000 - 9000 = 19000 >+ 10000, , , , >+ 11000, , , , >+ 12000, , , , >+ 13000, , , , # 3rd thread start time >+ 14000, , , , >+ 15000, , , , >+ 16000, , , , >+ 17000, , , , >+ 18000, , , , >+ 19000, , , , >+ 20000, , , , >+ 21000, , , , >+ 22000, , , , >+ 23000, , , , # 4th thread start time >+ 24000, , , , >+ 25000, , , , >+ 26000, , , , >+ 27000, , , , >+ 28000, , , , # 5th thread start time >+ 29000, , , , >+ 30000, , , , >+ 31000, , , , >+ 32000, , , , >+ 33000, , d=0, , # 6th thread start time, timer 2 : target time = 28000 + 5000 = 33000; delay = 0 >+ 34000, , d=2000, , # timer 2 : target time = 33000 + 3000 = 36000; delay = 36000 - 34000 = 2000 >+ 35000, , , , >+ 36000, , , , # 7th thread start time >+ 37000, , , , >+ 38000, , , d=8000, # timer 3 : target time = 36000 + 10000 = 46000; delay = 46000 - 38000 = 8000 >+ 39000, , , , >+ 40000, , , , >+ 41000, , , , >+ 42000, , , , >+ 43000, , , , >+ 44000, , , , >+ 45000, , , , >+ 46000, , , , # 8th thread start time >+ 47000, , , , >+ 48000, , , , >+ 49000, , , , >+ 50000, , , , >+ 50999, d=1, , , # timer 1 : target time = 46000 + 5000 = 51000; delay = 51000 - 50999 = 1 >+ 51000, , , , # 9th thread start time >+ 52000, , , , >+ 53000, , , , >+ 54000, , , , >+ 55000, , , , >+ 56000, , , , >+ 57000, , , , >+ 58000, , , , >+ 59000, , , , >+ 60000, , , , >+ 61000, , , , >+ 61001, , , d=0, # timer 3 : target time = 51000 + 10000 = 61000; delay = 0 >\ No newline at end of file >Index: test/src/org/apache/jmeter/timers/PackageTest.java >=================================================================== >--- test/src/org/apache/jmeter/timers/PackageTest.java (revision 1140940) >+++ test/src/org/apache/jmeter/timers/PackageTest.java (working copy) >@@ -32,18 +32,34 @@ > > private static final Logger log = LoggingManager.getLoggerForClass(); > >+ private SimulationClock simulationClock = new SimulationClock(); >+ > public PackageTest(String arg0) { > super(arg0); > } > >+ @Override >+ protected void setUp() throws Exception { >+ super.setUp(); >+ ThroughputInfo.setSystemClock(simulationClock); >+ } >+ >+ @Override >+ protected void tearDown() throws Exception { >+ ThroughputInfo.setSystemClock(new ThroughputInfo.SystemClock()); >+ super.tearDown(); >+ } >+ > public void testTimer1() throws Exception { > ConstantThroughputTimer timer = new ConstantThroughputTimer(); > assertEquals(0,timer.getCalcModeInt());// Assume this thread only > timer.setThroughput(60.0);// 1 per second > long delay = timer.delay(); // Initialise >- assertEquals(0,delay); >- Thread.sleep(500); >- long diff=Math.abs(timer.delay()-500); >+ assertEquals(0,delay); // First one runs immediately >+ simulationClock.increaseTime(500); >+ delay = timer.delay(); >+ log.info("testTimer1: new delay = " + delay); >+ long diff=Math.abs(delay-500); > assertTrue("Delay is approximately 500",diff<=50); > } > >@@ -51,9 +67,11 @@ > ConstantThroughputTimer timer = new ConstantThroughputTimer(); > assertEquals(0,timer.getCalcModeInt());// Assume this thread only > timer.setThroughput(60.0);// 1 per second >- assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second >+ assertEquals(0, timer.delay()); // First one runs immediately >+ assertEquals(1000,timer.delay()); // Should delay for 1 second >+ simulationClock.increaseTime(1000); > timer.setThroughput(60000.0);// 1 per milli-second >- assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second >+ assertEquals(1,timer.delay()); // Should delay for 1 milli-second > } > > public void testTimer3() throws Exception { >@@ -67,15 +85,18 @@ > } > assertEquals(10,JMeterContextService.getNumberOfThreads()); > timer.setThroughput(600.0);// 10 per second >- assertEquals(1000,timer.calculateCurrentTarget(0)); // Should delay for 1 second >+ assertEquals(0, timer.delay()); // First one runs immediately >+ assertEquals(1000,timer.delay()); // Should delay for 1 second >+ simulationClock.increaseTime(1000); > timer.setThroughput(600000.0);// 10 per milli-second >- assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second >+ assertEquals(1,timer.delay()); // Should delay for 1 milli-second >+ simulationClock.increaseTime(1); > for(int i=1; i<=990; i++){ > TestJMeterContextService.incrNumberOfThreads(); > } > assertEquals(1000,JMeterContextService.getNumberOfThreads()); > timer.setThroughput(60000000.0);// 1000 per milli-second >- assertEquals(1,timer.calculateCurrentTarget(0)); // Should delay for 1 milli-second >+ assertEquals(1,timer.delay()); // Should delay for 1 milli-second > } > > public void testTimerBSH() throws Exception { >Index: test/src/org/apache/jmeter/timers/simulator_mode1.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_mode1.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_mode1.txt (revision 0) >@@ -0,0 +1,31 @@ >+# >+# Constant Throughput Timer Simulator file >+# See simulator_template.txt for full explanation of files contents. >+# >+ >+# calcMode.1=this thread only >+Simulation Time, 1m1t1[5000], 2m1t1[3000], 3m1t2[10000], >+ 0, d=0, d=0, d=0, # For non-shared Constant Throughput Timers the first request will be at time 0 with no delay. >+ 1000, , , , >+ 2000, , , , >+ 3000, d=2000, X, , # Here timer 1's request has completed, so there is a delay of 2000ms until the next optimal request boundary. It is also timer 2's optimal boundary, but it has been missed this time. >+ 4000, , d=0, , # timer 2 finally finshes, having missed the optimal boundary there is no delay in starting the next request. >+ 5000, X, , , >+ 6000, d=4000, X, , # timer 1 finishes, there is a delay of 4000ms until the next optimal boundary. >+ 6999, , d=1, , # timer 2 finishes, with 1ms to spare before the next optimal boundary. >+ 7000, , , , # Note: at 3000 msPerRequest the second optimal boundary is 6000, BUT the last request finished at 4000, so this makes the bounday 7000 (4000 + 3000) >+ 8000, , , , >+ 9000, , X, , >+ 10000, X, , X, # Note 'X' is for readability only, from this point on only interested in timer 3 so no more X's have been marked for timer 1 and 2 >+ 50000, , , X, >+ 51000, , , d=0, # timer 3 finally finishes. A 10,000 msPerRequest means 6 per minute. But there are only 10s left in the current minute >+ 52000, , , , # Note that Constant Throughput Timer does not average out request times to achieve throughput, >+ 53000, , , , # if you miss more than one optimal boundary then that time slice is gone forever and will never be caught up. >+ 54000, , , d=7000, # i.e. Even though the Timer is requesting another slice, the delay wont be d=0 to ensure we fit more requests in the minute >+ 55000, , , , # but instead will be the difference between now and the next boundary. >+ 56000, , , , # And the delay is 10000 from the previous time, i.e. 54000 + 10000 = 61000 >+ 57000, , , , >+ 58000, , , , >+ 59000, , , , >+ 60000, , , X, >+ 61000, , , , >Index: test/src/org/apache/jmeter/timers/ConstantThroughputTimerTest.java >=================================================================== >--- test/src/org/apache/jmeter/timers/ConstantThroughputTimerTest.java (revision 0) >+++ test/src/org/apache/jmeter/timers/ConstantThroughputTimerTest.java (revision 0) >@@ -0,0 +1,612 @@ >+/* >+ * 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 java.lang.reflect.Field; >+import java.util.ArrayList; >+import java.util.List; >+ >+import org.apache.jmeter.junit.JMeterTestCase; >+import org.apache.jmeter.threads.JMeterContextService; >+import org.apache.jorphan.logging.LoggingManager; >+import org.apache.log.Logger; >+ >+public class ConstantThroughputTimerTest extends JMeterTestCase { >+ >+ private static final Logger LOG = LoggingManager.getLoggerForClass(); >+ >+ /** >+ * Simulation Clock used to manage "SystemClock" time during tests >+ */ >+ private SimulationClock simulationClock; >+ >+ public ConstantThroughputTimerTest(String arg0) { >+ super(arg0); >+ } >+ >+ /** >+ * <p> >+ * This "simulates" which test data has the next event. In order to handle >+ * shared mode appropriately the "nextEvent" needs to be moved to the end of >+ * the list otherwise the same test data will be returned each time and none >+ * of the other "threads" (which sit after this in the list) will get a >+ * turn. >+ * </p> >+ * >+ * @param testDataList >+ * the list of test data >+ * @return the test data that contains the next event in the simulation, or >+ * null if the are no more events. Non-null next events will be >+ * moved to the end of the <code>List</code> >+ */ >+ private ConstantThroughputTimerTestData getNextEvent( >+ List<ConstantThroughputTimerTestData> testDataList) { >+ >+ ConstantThroughputTimerTestData nextTestData = null; >+ long nextTestDataEventAt = 0; >+ for (ConstantThroughputTimerTestData testData : testDataList) { >+ SimulationEvent simulationEvent = testData.peekFirstEvent(); >+ if (simulationEvent != null) { >+ if (nextTestData == null >+ || simulationEvent.getClockOffset() < nextTestDataEventAt) { >+ nextTestData = testData; >+ nextTestDataEventAt = simulationEvent.getClockOffset(); >+ } >+ } >+ } >+ if (nextTestData != null) { >+ assertTrue("Moving nextTestData to end failed", >+ testDataList.remove(nextTestData)); >+ assertTrue("Moving nextTestData to end failed", >+ testDataList.add(nextTestData)); >+ } >+ return nextTestData; >+ } >+ >+ /** >+ * Run the simulation against the test data list provided. >+ * >+ * @param testDataList >+ * the test data for the simulation >+ * @throws Exception >+ * simulation failures. >+ */ >+ private void runSimulation( >+ List<ConstantThroughputTimerTestData> testDataList) >+ throws Exception { >+ >+ ConstantThroughputTimerTestData >+ .setupJMeterContextService_numberOfActiveThreads(testDataList); >+ LOG.info("Active Thread Count = " >+ + JMeterContextService.getNumberOfThreads()); >+ >+ testStarted(testDataList); >+ >+ while (true) { >+ ConstantThroughputTimerTestData nextTestData = getNextEvent(testDataList); >+ if (nextTestData == null) { >+ break; >+ } >+ nextTestData.activate(); >+ >+ SimulationEvent currentEvent = nextTestData.peekFirstEvent(); >+ simulationClock.setOffset(currentEvent.getClockOffset()); >+ >+ long expectedDelay = currentEvent.getDelay(); >+ >+ LOG.info("Invocation at SimulationClock.offset=" >+ + simulationClock.getOffset() + " expect delay of " >+ + expectedDelay + " for Test " + nextTestData); >+ long actualDelay = nextTestData.getTimer().delay(); >+ if (actualDelay > 9223300000000000000L) { >+ /* >+ * The above values roughly Double.INFINITY - "current time" >+ * failing to call setThroughput causes a division by zero >+ * (hence INFINITY) but the delay is based on the time as well >+ */ >+ fail("setThroughput() was not called on test data: " >+ + nextTestData); >+ } >+ assertEquals("Invocation at " + simulationClock.toString() >+ + ", for " + nextTestData, expectedDelay, actualDelay); >+ >+ nextTestData.removeFirstEvent(); >+ } >+ >+ testEnded(testDataList); >+ } >+ >+ /** >+ * Sets up the <code>ThroughputInfo.systemClock</code> to be a >+ * <code>SimulationClock</code>. >+ * >+ * @throws Exception >+ * setUp failures >+ */ >+ @Override >+ protected void setUp() throws Exception { >+ super.setUp(); >+ simulationClock = new SimulationClock(); >+ ThroughputInfo.setSystemClock(simulationClock); >+ >+ ConstantThroughputTimerTestData >+ .setupJMeterContextService_threadContext(); >+ } >+ >+ /** >+ * Resets the <code>ThroughputInfo.systemClock</code> to >+ * <code>ThroughputInfo.SystemClock</code> >+ * >+ * @throws Exception >+ * tearDown failures >+ */ >+ @Override >+ protected void tearDown() throws Exception { >+ ThroughputInfo.setSystemClock(new ThroughputInfo.SystemClock()); >+ super.tearDown(); >+ } >+ >+ /** >+ * Mode 1 "This Thead Only", In this test threads take >+ * <code>msPerRequest</code> to complete. i.e. on the optimal boundaries. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode1ThisThreadOnly() throws Exception { >+ LOG.info("testDelayMode1ThisThreadOnly"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(1); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_1); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(15000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(25000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(35000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(45000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(55000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 2 "All Active Threads", the delay is >+ * <code>msPerRequest * AllActiveThread Count</code>. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode2AllActiveThreads_ManyThreads() throws Exception { >+ LOG.info("testDelayMode2AllActiveThreads_ManyThreads"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(200); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_2); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 1, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 2, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 3, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 4, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 5, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 2 "All Active Threads", the delay is >+ * <code>msPerRequest * AllActiveThread Count</code>, for one thread is the >+ * same as Mode 1. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode2AllActiveThreads_OneThread() throws Exception { >+ LOG.info("testDelayMode2AllActiveThreads_OneThread"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(1); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_2); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(15000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(25000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(35000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(45000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(55000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 2 "All Active Threads", the delay is >+ * <code>msPerRequest * AllActiveThread Count</code>. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode2AllActiveThreads_TwoThreads() throws Exception { >+ LOG.info("testDelayMode2AllActiveThreads_TwoThreads"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(2); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_2); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 3 "All Active Threads in Thread Group", the delay is >+ * <code>msPerRequest * active ThreadsInThreadGroup Count</code>. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode3AllActiveThreadsInThreadGroup_ManyThreads() >+ throws Exception { >+ LOG.info("testDelayMode3AllActiveThreadsInThreadGroup_ManyThreads"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(200); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_3); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 1, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 2, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 3, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 4, 0)); >+ testData.addEvent(new SimulationEvent(5000 * 200 * 5, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 3 "All Active Threads in Thread Group", the delay is >+ * <code>msPerRequest * ThreadsInThreadGroup Count</code>. For 1 Thread this >+ * is the same as Mode 1. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode3AllActiveThreadsInThreadGroup_OneThread() >+ throws Exception { >+ LOG.info("testDelayMode3AllActiveThreadsInThreadGroup_OneThread"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(1); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_3); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(15000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(25000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(35000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(45000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(55000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 3 "All Active Threads in Thread Group", the delay is >+ * <code>msPerRequest * ThreadsInThreadGroup Count</code>. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode3AllActiveThreadsInThreadGroup_TwoThreads() >+ throws Exception { >+ LOG.info("testDelayMode3AllActiveThreadsInThreadGroup_TwoThreads"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(2); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_3); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 4 "All Active Threads (Shared)", the delay is a constant >+ * <code>msPerRequest</code> but each thread shares the >+ * <code>previousTime</code> a request was invoked. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode4AllActiveThreadsShared_OneThread() >+ throws Exception { >+ LOG.info("testDelayMode4AllActiveThreadsShared"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(1); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_4); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(15000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(25000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(35000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(45000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(55000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Mode 5 "All Active Threads In Thread Group(Shared)", the delay is a >+ * constant <code>msPerRequest</code> but each thread in the thread group >+ * shares the <code>previousTime</code> a request was invoked. >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testDelayMode5AllActiveThreadsInThreadGroupShared_OneThread() >+ throws Exception { >+ LOG.info("testDelayMode5AllActiveThreadsInThreadGroupShared"); >+ >+ ConstantThroughputTimerTestData testData = new ConstantThroughputTimerTestData(); >+ testData.setThroughput(12.0); >+ testData.setNumberOfThreadsInThreadGroup(1); >+ testData.setCalcMode(ConstantThroughputTimerTestData.CALC_MODE_5); >+ >+ testData.addEvent(new SimulationEvent(0, 0)); >+ testData.addEvent(new SimulationEvent(5000, 0)); >+ testData.addEvent(new SimulationEvent(10000, 0)); >+ testData.addEvent(new SimulationEvent(15000, 0)); >+ testData.addEvent(new SimulationEvent(20000, 0)); >+ testData.addEvent(new SimulationEvent(25000, 0)); >+ testData.addEvent(new SimulationEvent(30000, 0)); >+ testData.addEvent(new SimulationEvent(35000, 0)); >+ testData.addEvent(new SimulationEvent(40000, 0)); >+ testData.addEvent(new SimulationEvent(45000, 0)); >+ testData.addEvent(new SimulationEvent(50000, 0)); >+ testData.addEvent(new SimulationEvent(55000, 0)); >+ testData.addEvent(new SimulationEvent(60000, 0)); >+ >+ List<ConstantThroughputTimerTestData> simulation = new ArrayList<ConstantThroughputTimerTestData>(); >+ simulation.add(testData); >+ ConstantThroughputTimerTestData.createAdditionalThreads(simulation); >+ >+ runSimulation(simulation); >+ } >+ >+ /** >+ * Notify all the timers in the list of test data that the tests have ended. >+ * >+ * @param testDataList >+ * varargs list of test data to notify >+ */ >+ private void testEnded(List<ConstantThroughputTimerTestData> testDataList) { >+ for (ConstantThroughputTimerTestData testData : testDataList) { >+ testData.getTimer().testEnded(); >+ } >+ } >+ >+ /** >+ * Test Mode 1 timers from simulation file simulator_mode1.txt >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testMode1Timers() throws Exception { >+ ConstantThroughPutTimerSimulation simulation = new ConstantThroughPutTimerSimulation(); >+ List<ConstantThroughputTimerTestData> testDataList = simulation >+ .loadSimulationFile("org/apache/jmeter/timers/simulator_mode1.txt"); >+ >+ runSimulation(testDataList); >+ } >+ >+ /** >+ * Test Mode 2 timers from simulation file simulator_mode2.txt >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testMode2Timers() throws Exception { >+ ConstantThroughPutTimerSimulation simulation = new ConstantThroughPutTimerSimulation(); >+ List<ConstantThroughputTimerTestData> testDataList = simulation >+ .loadSimulationFile("org/apache/jmeter/timers/simulator_mode2.txt"); >+ >+ runSimulation(testDataList); >+ } >+ >+ /** >+ * Test Mode 3 timers from simulation file simulator_mode3.txt >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testMode3Timers() throws Exception { >+ ConstantThroughPutTimerSimulation simulation = new ConstantThroughPutTimerSimulation(); >+ List<ConstantThroughputTimerTestData> testDataList = simulation >+ .loadSimulationFile("org/apache/jmeter/timers/simulator_mode3.txt"); >+ >+ runSimulation(testDataList); >+ } >+ >+ /** >+ * Test Mode 4 timers from simulation file simulator_mode4.txt >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testMode4Timers() throws Exception { >+ ConstantThroughPutTimerSimulation simulation = new ConstantThroughPutTimerSimulation(); >+ List<ConstantThroughputTimerTestData> testDataList = simulation >+ .loadSimulationFile("org/apache/jmeter/timers/simulator_mode4.txt"); >+ >+ runSimulation(testDataList); >+ } >+ >+ /** >+ * Test Mode 5 timers from simulation file simulator_mode5.txt >+ * >+ * @throws Exception >+ * test failures >+ */ >+ public void testMode5Timers() throws Exception { >+ ConstantThroughPutTimerSimulation simulation = new ConstantThroughPutTimerSimulation(); >+ List<ConstantThroughputTimerTestData> testDataList = simulation >+ .loadSimulationFile("org/apache/jmeter/timers/simulator_mode5.txt"); >+ >+ runSimulation(testDataList); >+ } >+ >+ /** >+ * Notify all the timers in the list of test data that the tests are >+ * starting. >+ * >+ * @param testDataList >+ * list of test data to notify >+ */ >+ private void testStarted(List<ConstantThroughputTimerTestData> testDataList) { >+ for (ConstantThroughputTimerTestData testData : testDataList) { >+ testData.activate(); >+ testData.getTimer().testStarted(); >+ } >+ } >+} >+ >+/** >+ * JMeter depends on commons-lang:2.4. These methods are copied from 2.5 >+ * >+ * TODO: Delete when dependency upgraded. >+ */ >+class Copy_Commons_Lang_2_5_Reflect { >+ public static Field getDeclaredField(Class<?> cls, String fieldName, >+ boolean forceAccess) { >+ if (cls == null) { >+ throw new IllegalArgumentException("The class must not be null"); >+ } >+ if (fieldName == null) { >+ throw new IllegalArgumentException( >+ "The field name must not be null"); >+ } >+ try { >+ // only consider the specified class by using getDeclaredField() >+ Field field = cls.getDeclaredField(fieldName); >+ field.setAccessible(true); >+ return field; >+ } catch (NoSuchFieldException e) { >+ } >+ return null; >+ } >+ >+ public static void writeDeclaredStaticField(Class<?> cls, String fieldName, >+ Object value, boolean forceAccess) throws IllegalAccessException { >+ Field field = getDeclaredField(cls, fieldName, forceAccess); >+ if (field == null) { >+ throw new IllegalArgumentException("Cannot locate declared field " >+ + cls.getName() + "." + fieldName); >+ } >+ // already forced access above, don't repeat it here: >+ writeField(field, (Object) null, value, false); >+ } >+ >+ public static void writeField(Field field, Object target, Object value, >+ boolean forceAccess) throws IllegalAccessException { >+ if (field == null) { >+ throw new IllegalArgumentException("The field must not be null"); >+ } >+ field.set(target, value); >+ } >+} >Index: test/src/org/apache/jmeter/timers/simulator_mode5.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_mode5.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_mode5.txt (revision 0) >@@ -0,0 +1,58 @@ >+# >+# Constant Throughput Timer Simulator file >+# See simulator_template.txt for full explanation of files contents. >+# >+ >+# calcMode.5=all active threads in current thread group (shared) >+# Delay is <code>msPerRequest</code>. >+# The difference with the simulator in calc mode "all active threads in current thread group (shared)" >+# is that each thread group will get a single event stream shared amongst the threads in the thread group, >+# instead of each thread getting a copy of the event stream. >+# For "all active threads in current thread group (shared)" each timer uses its own delay, >+# but the calculation is based on when the last thread within the thread group was run at. >+ >+# delay = target time <= current time ? 0 : target time - simulation time >+# where target time = previous time + msPerRequest >+Simulation Time, 1m5t1[5000], 2m5t3[3000], 3m5t5[10000], >+ 0, d=0, d=0, d=0, # For "all active threads in current thread group (shared)" Constant Throughput Timers only the first thread in each thread group will be at time 0 with no delay. >+ 1000, , , , # Subsequent Threads delays are calculated based on the previous time and each thread in the thread group shares the event stream. >+ 2000, , d=1000, , # timer 2 : target time = 0 + 3000 = 3000; delay = 3000 - 2000 = 1000 >+ 3000, , X, , >+ 4000, d=1000, , , # timer 1 : target time = 0 + 5000 = 5000; delay = 5000 - 4000 = 1000 >+ 5000, X, , , >+ 6000, , d=0, , # timer 2 : target time = 3000 + 3000 = 6000; delay = 0 >+ 7000, , , d=3000, # timer 3 : target time = 0 + 10000 = 10000; delay = 10000 - 7000 = 3000 >+ 8000, , , , >+ 9000, , X, , >+ 10000, X, d=0, X, # timer 2 : target time = 6000 + 3000 = 9000; delay = 0 >+ 11000, d=0, d=2000, , # timer 2 : target time = 10000 + 3000 = 13000; delay = 13000 - 11000 = 2000 >+ 12000, , X, , >+ 13000, , d=3000, , # timer 2 : target time = 13000 + 3000 = 16000; delay = 16000 - 13000 = 3000 >+ 14000, , , , >+ 15000, X, X, , >+ 16000, , , d=4000, # timer 3 : target time = 10000 + 10000 = 20000; delay = 20000 - 16000 = 4000 >+ 17000, , , , >+ 18000, , X, d=12000, # timer 3 : target time = 20000 + 10000 = 30000; delay = 30000 - 18000 = 12000 >+ 19000, , , , >+ 20000, X, , X, >+ 21000, , X, , >+ 22000, , , , >+ 23000, , , , >+ 24000, , X, , >+ 25000, X, , , >+ 26000, , , , >+ 27000, , X, , >+ 28000, , , , >+ 29000, , , , >+ 30000, X, X, X, >+ 31000, , , , >+ 32000, , , , >+ 33000, , , , >+ 34000, , , , >+ 35000, , , , >+ 36000, , , , >+ 37000, , , , >+ 38000, , , , >+ 39000, , , , >+ 40000, , , X, >+ 41000, , , d=0, # timer 3 : target time = 30000 + 10000 = 40000; delay = 0 >\ No newline at end of file >Index: test/src/org/apache/jmeter/timers/SimulationClock.java >=================================================================== >--- test/src/org/apache/jmeter/timers/SimulationClock.java (revision 0) >+++ test/src/org/apache/jmeter/timers/SimulationClock.java (revision 0) >@@ -0,0 +1,130 @@ >+/* >+ * 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.jorphan.logging.LoggingManager; >+import org.apache.log.Logger; >+ >+/** >+ * A Simluation Clock used in unit testing instead of java.lang.System >+ */ >+public class SimulationClock extends ThroughputInfo.SystemClock { >+ >+ private static final Logger LOG = LoggingManager.getLoggerForClass(); >+ >+ /** >+ * <p> >+ * See {@link ThroughputInfo#calculateDelay(long)}. >+ * </p> >+ * This needs to be set to something larger than the first delay expected. >+ * Setting to Jan 1 2000 0:00 >+ */ >+ private long intialTime = 946647000000L; >+ >+ /** >+ * When was the current time last logged, so that the it is only logged when >+ * the time changes. >+ */ >+ private long lastLoggedCurrentTime = 0L; >+ >+ /** >+ * The offset from the initial time. >+ */ >+ private long offset = 0L; >+ >+ /** >+ * @return <code>initialTime + offset</code> >+ */ >+ @Override >+ public long getCurrentTime() { >+ logTime(); >+ return getCurrentTimeInternal(); >+ } >+ >+ /** >+ * @return the current time used internally by other code. >+ */ >+ private long getCurrentTimeInternal() { >+ return intialTime + offset; >+ } >+ >+ /** >+ * @return the offset >+ */ >+ public long getOffset() { >+ return offset; >+ } >+ >+ /** >+ * @param increment >+ * the amount in milliseconds to increment the simulation clock >+ * by >+ */ >+ public void increaseTime(long increment) { >+ if (increment < 0) { >+ throw new IllegalStateException( >+ "Negative increments are not allowed"); >+ } >+ offset += increment; >+ logTime(); >+ } >+ >+ /** >+ * Log the time, but only if it hasn't changed since the last time it was >+ * logged. >+ */ >+ private void logTime() { >+ long currentTime = getCurrentTimeInternal(); >+ if (lastLoggedCurrentTime != currentTime) { >+ LOG.info(toString()); >+ lastLoggedCurrentTime = currentTime; >+ } >+ } >+ >+ /** >+ * {@inheritDoc} >+ */ >+ @Override >+ public String toString() { >+ return "Offset = " + offset; >+ } >+ >+ /** >+ * @param initialTime >+ * new initial simulation time >+ */ >+ public void setInitialTime(long initialTime) { >+ this.intialTime = initialTime; >+ logTime(); >+ } >+ >+ /** >+ * @param offset >+ * the offset to set >+ */ >+ public void setOffset(long offset) { >+ if (offset < this.offset) { >+ throw new IllegalStateException("Offset (" + offset >+ + ") must be larger than the current offset )" >+ + this.offset + "."); >+ } >+ this.offset = offset; >+ logTime(); >+ } >+ >+} >Index: test/src/org/apache/jmeter/timers/simulator_template.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_template.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_template.txt (revision 0) >@@ -0,0 +1,75 @@ >+# >+# Constant Throughput Timer Simulator template file >+# >+# Lines starting with # are comments and ignored. >+# Lines can also include # and the rest of the line is considered a comment. >+# >+# The first line is the Timer declarations. >+# The first line's column is ignored as this is the header name for the clock time >+# Each other column is of the form >+# <n>'m'<m>'t'<t>'['<msPerRequest']' >+# where >+# n = the id of the timer >+# m = mode number (See Available Modes below) >+# t = number of threads in the ThreadGroup the Constant Throughput Timer belongs to >+# msPerRequest = the milliseconds per request this Timer is optimally trying to hit >+# (it's easier to think in milliseconds per request than throughput) >+# You can convert between the two with the following formula: >+# msPerRequest = 60000 / throughput >+# e.g. >+# 1[5000] (Timer id 1, with a 5000 milliseconds per request) >+# >+# Each subsequent line is a simluation clock event. >+# The first column is the elapsed time in milliseconds since the start of the simulation. >+# Each other column is of the form >+# d=<delay> | 'X' >+# where >+# delay = the delay in milliseconds expected when this timer requests >+# to be scheduled again by the Constant Throughput Timer. >+# i.e. All the previous time the Sampler was busy sending its Request >+# 'X' = a marker that shows where the optimal time for this timer would be. >+# Not used at all, just a visual marker for people reading the files. >+# e.g. >+# d=0 (A delay of zero, used when the next request happens after a millisecond per request length, >+# or if it hits the optimal time boundary) >+# >+# If a row does not have any d= values then it has no effect on the simulation. >+# These are included to make it easier for people reading the files. >+ >+# Available Modes >+# calcMode.1=this thread only >+# calcMode.2=all active threads >+# calcMode.3=all active threads in current thread group >+# calcMode.4=all active threads (shared) >+# calcMode.5=all active threads in current thread group (shared) >+ >+# Assumptions: >+# * There is only one Constant Throughput Timer per ThreadGroup (Having more than one probably doesn't work the way you think it will) >+ >+# delay = target time <= current time ? 0 : target time - simulation time >+# where target time = previous time + msPerRequest >+Simulation Time, 1m1t1[5000], 2m1t1[3000], 3m1t2[10000], >+ 0, d=0, d=0, d=0, # For non-shared Constant Throughput Timers the first request will be at time 0 with no delay. >+ 1000, , , , # The choice of time increments is up to you, and there is no need to keep them uniform. >+ 2000, , , , >+ 3000, d=2000, X, , # Here timer 1's request has completed, so there is a delay of 2000ms (target time = 0 + 5000 = 5000; delay = 5000 - 3000 = 2000) until the next optimal request boundary. It is also timer 2's optimal boundary, but it has been missed this time. >+ 4000, , d=0, , # timer 2 finally finshes, having missed the optimal boundary there is no delay (target time = 0 + 3000 = 3000; delay = 0) in starting the next request. >+ 5000, X, , , >+ 6000, d=4000, X, , # timer 1 : target time = 5000 + 5000 = 10000; delay = 10000 - 6000 = 4000 >+ 6999, , d=1, , # timer 2 finishes, with 1ms to spare before the next optimal boundary. : target time = 4000 + 3000 = 7000; delay = 7000 - 6999 = 1 >+ 7000, , , , # Note: at 3000 msPerRequest the second optimal boundary is 6000, BUT the last request finished at 4000, so this makes the bounday 7000 (4000 + 3000) >+ 8000, , , , >+ 9000, , , , >+ 10000, X, , X, # Note 'X' is for readability only, from this point on only interested in timer 3 so no more X's have been marked for timer 1 and 2 >+ 50000, , , X, >+ 51000, , , d=0, # timer 3 finally finishes. A 10,000 msPerRequest means 6 per minute. But there are only 10s left in the current minute >+ 52000, , , , # Note that Constant Throughput Timer does not average out request times to achieve throughput, >+ 53000, , , , # if you miss more than one optimal boundary then that time slice is gone forever and will never be caught up. >+ 54000, , , d=7000, # i.e. Even though the Timer is requesting another slice, the delay wont be d=0 to ensure we fit more requests in the minute >+ 55000, , , , # but instead will be the difference between now and the next boundary. >+ 56000, , , , # And the delay is 7000 : target time = 51000 + 10000 = 61000; delay = 61000 - 54000 = 7000 >+ 57000, , , , >+ 58000, , , , >+ 59000, , , , >+ 60000, , , X, >+ 61000, , , , >Index: test/src/org/apache/jmeter/timers/simulator_mode2.txt >=================================================================== >--- test/src/org/apache/jmeter/timers/simulator_mode2.txt (revision 0) >+++ test/src/org/apache/jmeter/timers/simulator_mode2.txt (revision 0) >@@ -0,0 +1,31 @@ >+# >+# Constant Throughput Timer Simulator file >+# See simulator_template.txt for full explanation of files contents. >+# >+ >+# calcMode.2=all active threads >+# Total Active threads in this simulation = 1 + 3 + 5 = 9 >+# Delay is <code>msPerRequest * AllActiveThread Count</code>. >+# Timer 1 delay = 5000 * 9 = 45,000 >+# Timer 2 delay = 3000 * 9 = 27,000 >+# Timer 3 delay = 10000 * 9 = 90,000 >+ >+# delay = target time <= current time ? 0 : target time - simulation time >+# where target time = previous time + msPerRequest >+Simulation Time, 1m2t1[5000], 2m2t3[3000], 3m2t5[10000], >+ 0, d=0, d=0, d=0, # For non-shared Constant Throughput Timers the first request will be at time 0 with no delay. >+ 4000, , d=23000, , # timer 2 : target time = 0 + 27000 = 270000; delay = 27000 - 4000 = 23000 >+ 5000, d=40000, , , # timer 1 : target time = 0 + 45000 = 45000; delay = 45000 - 5000 = 40000 >+ 27000, , X, , >+ 30000, , d=24000, , # timer 2 : target time = 27000 + 27000 = 54000; delay = 54000 - 30000 = 24000 >+ 45000, X, , , >+ 54000, , X, , >+ 70000, , , d=20000, # timer 3 : target time = 0 + 90000 = 90000; delay = 90000 - 70000 = 20000 >+ 81000, , X, , >+ 90000, X, , X, >+ 108000, , X, , >+ 135000, X, , , >+ 180000, X, , X, >+ 269500, , , d=0, # timer 3 : target time = 90000 + 90000 = 180000; delay = 0 >+ 270000, X, , X, >+ >Index: test/src/org/apache/jmeter/timers/SimulationEvent.java >=================================================================== >--- test/src/org/apache/jmeter/timers/SimulationEvent.java (revision 0) >+++ test/src/org/apache/jmeter/timers/SimulationEvent.java (revision 0) >@@ -0,0 +1,62 @@ >+/* >+ * 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; >+ >+/** >+ * <p> >+ * A Simulation Event for <code>ConstantThroughputTimer</code>. >+ * </p> >+ * <p> >+ * Events happen at the specified <code>clockOffset</code> and expect that the >+ * <code>ConstantThroughputTimer</code> will have a delay of <code>delay</code>. >+ * </p> >+ */ >+public class SimulationEvent { >+ private int clockOffset; >+ private int delay; >+ >+ public SimulationEvent(int clockOffset, int delay) { >+ this.clockOffset = clockOffset; >+ this.delay = delay; >+ } >+ >+ /** >+ * @return the clockOffset the events occurs on >+ */ >+ public int getClockOffset() { >+ return clockOffset; >+ } >+ >+ /** >+ * @return the delay expected by the <code>ConstantThroughputTimer</code> >+ * when calculating the next time an event will occur >+ */ >+ public int getDelay() { >+ return delay; >+ } >+ >+ /** >+ * {@inheritDoc} >+ */ >+ @Override >+ public String toString() { >+ return "SimulationEvent [clockOffset=" + clockOffset + ", delay=" >+ + delay + "]"; >+ } >+ >+} >\ No newline at end of file >Index: test/src/org/apache/jmeter/threads/MockJMeterContext.java >=================================================================== >--- test/src/org/apache/jmeter/threads/MockJMeterContext.java (revision 0) >+++ test/src/org/apache/jmeter/threads/MockJMeterContext.java (revision 0) >@@ -0,0 +1,242 @@ >+/* >+ * 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.threads; >+ >+import org.apache.jmeter.engine.StandardJMeterEngine; >+import org.apache.jmeter.samplers.SampleResult; >+import org.apache.jmeter.samplers.Sampler; >+ >+/** >+ * <p> >+ * A Mock implementation of <code>JMeterContext</code> >+ * </p> >+ * <p> >+ * The normal way of obtaining <code>JMeterContext</code> is via >+ * {@link JMeterContextService#getContext()} which relies on >+ * <code>ThreadLocal</code> variables. In unit tests setting up threads is >+ * difficult. This Mock allows you to "swap out" what should be returned during >+ * your unit tests so that one thread can be used to run the tests. Setting the >+ * <code>delegate</code> allows you to do the "swap out" >+ * </p> >+ * >+ */ >+public class MockJMeterContext extends JMeterContext { >+ >+ private JMeterContext delegate; >+ >+ /** >+ * @return Factory method for creating <code>JMeterContext</code> since its >+ * visibility is "default" and not available outside of this >+ * package. >+ */ >+ public static JMeterContext newJMeterContext() { >+ return new JMeterContext(); >+ } >+ >+ /** >+ * @return the delegate >+ */ >+ public JMeterContext getDelegate() { >+ return delegate; >+ } >+ >+ /** >+ * @param delegate >+ * the delegate to set >+ */ >+ public void setDelegate(JMeterContext delegate) { >+ this.delegate = delegate; >+ } >+ >+ /** >+ * @return >+ * @see java.lang.Object#hashCode() >+ */ >+ public int hashCode() { >+ return delegate.hashCode(); >+ } >+ >+ /** >+ * >+ * @see org.apache.jmeter.threads.JMeterContext#clear() >+ */ >+ public void clear() { >+ delegate.clear(); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getVariables() >+ */ >+ public JMeterVariables getVariables() { >+ return delegate.getVariables(); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getReadBuffer() >+ */ >+ public byte[] getReadBuffer() { >+ return delegate.getReadBuffer(); >+ } >+ >+ /** >+ * @param vars >+ * @see org.apache.jmeter.threads.JMeterContext#setVariables(org.apache.jmeter.threads.JMeterVariables) >+ */ >+ public void setVariables(JMeterVariables vars) { >+ delegate.setVariables(vars); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getPreviousResult() >+ */ >+ public SampleResult getPreviousResult() { >+ return delegate.getPreviousResult(); >+ } >+ >+ /** >+ * @param result >+ * @see org.apache.jmeter.threads.JMeterContext#setPreviousResult(org.apache.jmeter.samplers.SampleResult) >+ */ >+ public void setPreviousResult(SampleResult result) { >+ delegate.setPreviousResult(result); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getCurrentSampler() >+ */ >+ public Sampler getCurrentSampler() { >+ return delegate.getCurrentSampler(); >+ } >+ >+ /** >+ * @param sampler >+ * @see org.apache.jmeter.threads.JMeterContext#setCurrentSampler(org.apache.jmeter.samplers.Sampler) >+ */ >+ public void setCurrentSampler(Sampler sampler) { >+ delegate.setCurrentSampler(sampler); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getPreviousSampler() >+ */ >+ public Sampler getPreviousSampler() { >+ return delegate.getPreviousSampler(); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getThreadNum() >+ */ >+ public int getThreadNum() { >+ return delegate.getThreadNum(); >+ } >+ >+ /** >+ * @param threadNum >+ * @see org.apache.jmeter.threads.JMeterContext#setThreadNum(int) >+ */ >+ public void setThreadNum(int threadNum) { >+ delegate.setThreadNum(threadNum); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getThread() >+ */ >+ public JMeterThread getThread() { >+ return delegate.getThread(); >+ } >+ >+ /** >+ * @param obj >+ * @return >+ * @see java.lang.Object#equals(java.lang.Object) >+ */ >+ public boolean equals(Object obj) { >+ return delegate.equals(obj); >+ } >+ >+ /** >+ * @param thread >+ * @see org.apache.jmeter.threads.JMeterContext#setThread(org.apache.jmeter.threads.JMeterThread) >+ */ >+ public void setThread(JMeterThread thread) { >+ delegate.setThread(thread); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getThreadGroup() >+ */ >+ public AbstractThreadGroup getThreadGroup() { >+ return delegate.getThreadGroup(); >+ } >+ >+ /** >+ * @param threadgrp >+ * @see org.apache.jmeter.threads.JMeterContext#setThreadGroup(org.apache.jmeter.threads.AbstractThreadGroup) >+ */ >+ public void setThreadGroup(AbstractThreadGroup threadgrp) { >+ delegate.setThreadGroup(threadgrp); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#getEngine() >+ */ >+ public StandardJMeterEngine getEngine() { >+ return delegate.getEngine(); >+ } >+ >+ /** >+ * @param engine >+ * @see org.apache.jmeter.threads.JMeterContext#setEngine(org.apache.jmeter.engine.StandardJMeterEngine) >+ */ >+ public void setEngine(StandardJMeterEngine engine) { >+ delegate.setEngine(engine); >+ } >+ >+ /** >+ * @return >+ * @see org.apache.jmeter.threads.JMeterContext#isSamplingStarted() >+ */ >+ public boolean isSamplingStarted() { >+ return delegate.isSamplingStarted(); >+ } >+ >+ /** >+ * @param b >+ * @see org.apache.jmeter.threads.JMeterContext#setSamplingStarted(boolean) >+ */ >+ public void setSamplingStarted(boolean b) { >+ delegate.setSamplingStarted(b); >+ } >+ >+ /** >+ * @return >+ * @see java.lang.Object#toString() >+ */ >+ public String toString() { >+ return delegate.toString(); >+ } >+}
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 51480
: 27262 |
27287
|
27288
|
27289
|
27293