Index: src/core/org/apache/jmeter/threads/AbstractThreadGroup.java =================================================================== --- src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (revision 1776125) +++ src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (working copy) @@ -251,6 +251,8 @@ public abstract void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine); + public abstract JMeterThread addNewThread(int delay, StandardJMeterEngine engine); + public abstract boolean verifyThreadsStopped(); public abstract void waitThreadsStopped(); Index: src/core/org/apache/jmeter/threads/JMeterThread.java =================================================================== --- src/core/org/apache/jmeter/threads/JMeterThread.java (revision 1776125) +++ src/core/org/apache/jmeter/threads/JMeterThread.java (working copy) @@ -50,6 +50,7 @@ import org.apache.jmeter.util.JMeterUtils; import org.apache.jorphan.collections.HashTree; import org.apache.jorphan.collections.HashTreeTraverser; +import org.apache.jorphan.collections.ListedHashTree; import org.apache.jorphan.collections.SearchByClass; import org.apache.jorphan.logging.LoggingManager; import org.apache.jorphan.util.JMeterError; @@ -977,4 +978,12 @@ this.threadGroup = group; } + public ListedHashTree getTestTree() { + return (ListedHashTree) testTree; + } + + public ListenerNotifier getNotifier() { + return notifier; + } + } Index: src/core/org/apache/jmeter/threads/ThreadGroup.java =================================================================== --- src/core/org/apache/jmeter/threads/ThreadGroup.java (revision 1776125) +++ src/core/org/apache/jmeter/threads/ThreadGroup.java (working copy) @@ -83,17 +83,24 @@ // List of active threads private final Map allThreads = new ConcurrentHashMap<>(); + private final Object addThreadLock = new Object(); /** * Is test (still) running? */ private volatile boolean running = false; + private int groupNumber; + /** * Are we using delayed startup? */ private boolean delayedStartup; + private ListenerNotifier notifier; + + private ListedHashTree threadGroupTree; + /** * No-arg constructor. */ @@ -257,38 +264,46 @@ } @Override - public void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { + public void start(int groupNum, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { running = true; + groupNumber = groupNum; + this.notifier = notifier; + this.threadGroupTree = threadGroupTree; int numThreads = getNumThreads(); int rampUpPeriodInSeconds = getRampUp(); float perThreadDelayInMillis = (float) (rampUpPeriodInSeconds * 1000) / (float) getNumThreads(); delayedStartup = isDelayedStartup(); // Fetch once; needs to stay constant - log.info("Starting thread group number " + groupCount + log.info("Starting thread group number " + groupNumber + " threads " + numThreads + " ramp-up " + rampUpPeriodInSeconds + " perThread " + perThreadDelayInMillis + " delayedStart=" + delayedStartup); if (delayedStartup) { - threadStarter = new Thread(new ThreadStarter(groupCount, notifier, threadGroupTree, engine), getName()+"-ThreadStarter"); + threadStarter = new Thread(new ThreadStarter(notifier, threadGroupTree, engine), getName()+"-ThreadStarter"); threadStarter.setDaemon(true); threadStarter.start(); // N.B. we don't wait for the thread to complete, as that would prevent parallel TGs } else { long now = System.currentTimeMillis(); // needs to be same time for all threads in the group final JMeterContext context = JMeterContextService.getContext(); - for (int i = 0; running && i < numThreads; i++) { - JMeterThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, i, context); - scheduleThread(jmThread, now); // set start and end time - jmThread.setInitialDelay((int)(i * perThreadDelayInMillis)); - Thread newThread = new Thread(jmThread, jmThread.getThreadName()); - registerStartedThread(jmThread, newThread); - newThread.start(); + for (int threadNum = 0; running && threadNum < numThreads; threadNum++) { + startNewThread(notifier, threadGroupTree, engine, threadNum, context, now, (int)(threadNum * perThreadDelayInMillis)); } } - log.info("Started thread group number "+groupCount); + log.info("Started thread group number "+groupNumber); } + private JMeterThread startNewThread(ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine, + int threadNum, final JMeterContext context, long now, int delay) { + JMeterThread jmThread = makeThread(notifier, threadGroupTree, engine, threadNum, context); + scheduleThread(jmThread, now); // set start and end time + jmThread.setInitialDelay(delay); + Thread newThread = new Thread(jmThread, jmThread.getThreadName()); + registerStartedThread(jmThread, newThread); + newThread.start(); + return jmThread; + } /** * Register Thread when it starts * @param jMeterThread {@link JMeterThread} @@ -298,7 +313,7 @@ allThreads.put(jMeterThread, newThread); } - private JMeterThread makeThread(int groupCount, + private JMeterThread makeThread( ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine, int i, JMeterContext context) { // N.B. Context needs to be fetched in the correct thread @@ -311,7 +326,7 @@ jmeterThread.setThreadNum(i); jmeterThread.setThreadGroup(this); jmeterThread.setInitialContext(context); - final String threadName = groupName + " " + (groupCount) + "-" + (i + 1); + final String threadName = groupName + " " + groupNumber + "-" + (i + 1); jmeterThread.setThreadName(threadName); jmeterThread.setEngine(engine); jmeterThread.setOnErrorStopTest(onErrorStopTest); @@ -321,6 +336,21 @@ return jmeterThread; } + @Override + public JMeterThread addNewThread(int delay, StandardJMeterEngine engine) { + long now = System.currentTimeMillis(); + JMeterContext context = JMeterContextService.getContext(); + JMeterThread newJmThread; + synchronized (addThreadLock) { + int numThreads = getNumThreads(); + newJmThread = startNewThread(notifier, threadGroupTree, engine, numThreads, context, now, delay); + setNumThreads(numThreads + 1); + } + JMeterContextService.addTotalThreads( 1 ); + log.info("Started new thread in group " + groupNumber ); + return newJmThread; + } + /** * Stop thread called threadName: *
    @@ -504,15 +534,13 @@ */ class ThreadStarter implements Runnable { - private final int groupCount; private final ListenerNotifier notifier; private final ListedHashTree threadGroupTree; private final StandardJMeterEngine engine; private final JMeterContext context; - public ThreadStarter(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { + public ThreadStarter(ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) { super(); - this.groupCount = groupCount; this.notifier = notifier; this.threadGroupTree = threadGroupTree; this.engine = engine; @@ -584,7 +612,7 @@ if (usingScheduler && System.currentTimeMillis() > endtime) { break; // no point continuing beyond the end time } - JMeterThread jmThread = makeThread(groupCount, notifier, threadGroupTree, engine, i, context); + JMeterThread jmThread = makeThread(notifier, threadGroupTree, engine, i, context); jmThread.setInitialDelay(0); // Already waited if (usingScheduler) { jmThread.setScheduled(true);