You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2012/07/13 23:49:30 UTC

svn commit: r1361410 - in /jmeter/trunk: src/core/org/apache/jmeter/engine/ src/core/org/apache/jmeter/resources/ src/core/org/apache/jmeter/threads/ src/core/org/apache/jmeter/threads/gui/ xdocs/

Author: pmouawad
Date: Fri Jul 13 21:49:30 2012
New Revision: 1361410

URL: http://svn.apache.org/viewvc?rev=1361410&view=rev
Log:
Bug 53418 - New OnDemandThreadGroup that creates threads when needed instead of creating them on Test startup
Bugzilla Id: 53418

Added:
    jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java   (with props)
Modified:
    jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java
    jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties
    jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties
    jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
    jmeter/trunk/xdocs/changes.xml

Modified: jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java Fri Jul 13 21:49:30 2012
@@ -21,14 +21,12 @@ package org.apache.jmeter.engine;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.jmeter.JMeter;
 import org.apache.jmeter.testbeans.TestBean;
@@ -36,14 +34,14 @@ import org.apache.jmeter.testbeans.TestB
 import org.apache.jmeter.testelement.TestElement;
 import org.apache.jmeter.testelement.TestListener;
 import org.apache.jmeter.testelement.TestPlan;
+import org.apache.jmeter.threads.AbstractThreadGroup;
 import org.apache.jmeter.threads.JMeterContextService;
 import org.apache.jmeter.threads.JMeterThread;
 import org.apache.jmeter.threads.JMeterThreadMonitor;
 import org.apache.jmeter.threads.ListenerNotifier;
-import org.apache.jmeter.threads.TestCompiler;
-import org.apache.jmeter.threads.AbstractThreadGroup;
-import org.apache.jmeter.threads.SetupThreadGroup;
 import org.apache.jmeter.threads.PostThreadGroup;
+import org.apache.jmeter.threads.SetupThreadGroup;
+import org.apache.jmeter.threads.TestCompiler;
 import org.apache.jmeter.util.JMeterUtils;
 import org.apache.jorphan.collections.HashTree;
 import org.apache.jorphan.collections.ListedHashTree;
@@ -58,8 +56,6 @@ import org.apache.log.Logger;
 public class StandardJMeterEngine implements JMeterEngine, JMeterThreadMonitor, Runnable {
     private static final Logger log = LoggingManager.getLoggerForClass();
 
-    private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds
-
     // Should we exit at end of the test? (only applies to server, because host is non-null)
     private static final boolean exitAfterTest =
         JMeterUtils.getPropDefault("server.exitaftertest", false);  // $NON-NLS-1$
@@ -92,10 +88,7 @@ public class StandardJMeterEngine implem
 
     /** Whether to call System.exit(1) if threads won't stop */
     private static final boolean SYSTEM_EXIT_ON_STOP_FAIL = JMeterUtils.getPropDefault("jmeterengine.stopfail.system.exit", true);
-
-    /** JMeterThread => its JVM thread */
-    private final Map<JMeterThread, Thread> allThreads;
-
+    
     /** Flag to show whether test is running. Set to false to stop creating more threads. */
     private volatile boolean running = false;
 
@@ -111,6 +104,8 @@ public class StandardJMeterEngine implem
 
     private final String host;
 
+    private List<AbstractThreadGroup> groups = Collections.synchronizedList(new ArrayList<AbstractThreadGroup>());
+
     public static void stopEngineNow() {
         if (engine != null) {// May be null if called from Unit test
             engine.stopTest(true);
@@ -139,22 +134,12 @@ public class StandardJMeterEngine implem
         if (engine == null) {
             return false;// e.g. not yet started
         }
+        boolean wasStopped = false;
         // ConcurrentHashMap does not need synch. here
-        for(Entry<JMeterThread, Thread> entry : engine.allThreads.entrySet()){
-            JMeterThread thrd = entry.getKey();
-            if (thrd.getThreadName().equals(threadName)){
-                thrd.stop();
-                thrd.interrupt();
-                if (now) {
-                    Thread t = entry.getValue();
-                    if (t != null) {
-                        t.interrupt();
-                    }
-                }
-                return true;
-            }
+        for (AbstractThreadGroup threadGroup : engine.groups) {
+            wasStopped = wasStopped || threadGroup.stopThread(threadName, now);
         }
-        return false;
+        return wasStopped;
     }
 
     // End of code to allow engine to be controlled remotely
@@ -165,7 +150,6 @@ public class StandardJMeterEngine implem
 
     public StandardJMeterEngine(String host) {
         this.host = host;
-        this.allThreads = new ConcurrentHashMap<JMeterThread, Thread>();
         // Hack to allow external control
         engine = this;
     }
@@ -268,7 +252,9 @@ public class StandardJMeterEngine implem
     // Called by JMeter thread when it finishes
     public synchronized void threadFinished(JMeterThread thread) {
         log.info("Ending thread " + thread.getThreadName());
-        allThreads.remove(thread);
+        for (AbstractThreadGroup threadGroup : groups) {
+            threadGroup.threadFinished(thread);
+        }
     }
 
     public synchronized void stopTest() {
@@ -292,7 +278,7 @@ public class StandardJMeterEngine implem
             engine = null;
             if (now) {
                 tellThreadsToStop();
-                pause(10 * allThreads.size());
+                pause(10 * countStillActiveThreads());
                 boolean stopped = verifyThreadsStopped();
                 if (!stopped) {  // we totally failed to stop the test
                     if (JMeter.isNonGUI()) {
@@ -381,9 +367,7 @@ public class StandardJMeterEngine implem
                 String groupName = startThreadGroup(group, groupCount, setupSearcher, testLevelElements, notifier);
                 if (serialized && setupIter.hasNext()) {
                     log.info("Waiting for setup thread group: "+groupName+" to finish before starting next setup group");
-                    while (running && allThreads.size() > 0) {
-                        pause(1000);
-                    }
+                    pauseWhileRemainingThreads();
                 }
             }    
             log.info("Waiting for all setup thread groups To Exit");
@@ -414,9 +398,7 @@ public class StandardJMeterEngine implem
             String groupName=startThreadGroup(group, groupCount, searcher, testLevelElements, notifier);
             if (serialized && iter.hasNext()) {
                 log.info("Waiting for thread group: "+groupName+" to finish before starting next group");
-                while (running && allThreads.size() > 0) {
-                    pause(1000);
-                }
+                pauseWhileRemainingThreads();
             }
         } // end of thread groups
         if (groupCount == 0){ // No TGs found
@@ -443,9 +425,7 @@ public class StandardJMeterEngine implem
                 String groupName = startThreadGroup(group, groupCount, postSearcher, testLevelElements, notifier);
                 if (serialized && postIter.hasNext()) {
                     log.info("Waiting for post thread group: "+groupName+" to finish before starting next post group");
-                    while (running && allThreads.size() > 0) {
-                        pause(1000);
-                    }
+                    pauseWhileRemainingThreads();
                 }
             }
             waitThreadsStopped(); // wait for Post threads to stop
@@ -454,6 +434,33 @@ public class StandardJMeterEngine implem
         notifyTestListenersOfEnd(testListenersSave);
     }
 
+    /**
+     * Loop with 1s pause while thread groups have threads running 
+     */
+    private void pauseWhileRemainingThreads() {
+        while (running && groupsHaveThreads()) {
+            pause(1000);
+        }
+    }
+
+    /**
+     * @return true if remaining threads
+     */
+    private boolean groupsHaveThreads() {
+        return countStillActiveThreads()>0;
+    }
+
+    /**
+     * @return total of active threads in all Thread Groups
+     */
+    private int countStillActiveThreads() {
+        int reminingThreads= 0;
+        for (AbstractThreadGroup threadGroup : groups) {
+            reminingThreads += threadGroup.numberOfActiveThreads();
+        }
+        return reminingThreads; 
+    }
+    
     private String startThreadGroup(AbstractThreadGroup group, int groupCount, SearchByClass<?> searcher, List<?> testLevelElements, ListenerNotifier notifier)
     {
             int numThreads = group.getNumThreads();
@@ -478,6 +485,9 @@ public class StandardJMeterEngine implem
             }
             ListedHashTree threadGroupTree = (ListedHashTree) searcher.getSubTree(group);
             threadGroupTree.add(group, testLevelElements);
+            
+            JMeterThread[] jmThreads = 
+                    new JMeterThread[numThreads];
             for (int i = 0; running && i < numThreads; i++) {
                 final JMeterThread jmeterThread = new JMeterThread(cloneTree(threadGroupTree), this, notifier);
                 jmeterThread.setThreadNum(i);
@@ -493,45 +503,33 @@ public class StandardJMeterEngine implem
 
                 group.scheduleThread(jmeterThread);
 
-                Thread newThread = new Thread(jmeterThread);
-                newThread.setName(threadName);
-                allThreads.put(jmeterThread, newThread);
-                newThread.start();
+                jmThreads[i] = jmeterThread;
             } // end of thread startup for this thread group
+            group.setJMeterThreads(jmThreads);
+            groups.add(group);
+            group.start();
             return groupName;
     }
 
+    /**
+     * @return boolean true if all threads of all Threead Groups stopped
+     */
     private boolean verifyThreadsStopped() {
         boolean stoppedAll = true;
         // ConcurrentHashMap does not need synch. here
-        for (Thread t : allThreads.values()) {
-            if (t != null) {
-                if (t.isAlive()) {
-                    try {
-                        t.join(WAIT_TO_DIE);
-                    } catch (InterruptedException e) {
-                    }
-                    if (t.isAlive()) {
-                        stoppedAll = false;
-                        log.warn("Thread won't exit: " + t.getName());
-                    }
-                }
-            }
+        for (AbstractThreadGroup threadGroup : groups) {
+            stoppedAll = stoppedAll && threadGroup.verifyThreadsStopped();
         }
         return stoppedAll;
     }
 
+    /**
+     * Wait for Group Threads to stop
+     */
     private void waitThreadsStopped() {
         // ConcurrentHashMap does not need synch. here
-        for (Thread t : allThreads.values()) {
-            if (t != null) {
-                while (t.isAlive()) {
-                    try {
-                        t.join(WAIT_TO_DIE);
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
+        for (AbstractThreadGroup threadGroup : groups) {
+            threadGroup.waitThreadsStopped();
         }
     }
 
@@ -545,14 +543,8 @@ public class StandardJMeterEngine implem
      */
     private void tellThreadsToStop() {
         // ConcurrentHashMap does not need protecting
-        for (Entry<JMeterThread, Thread> entry : allThreads.entrySet()) {
-            JMeterThread item = entry.getKey();
-            item.stop(); // set stop flag
-            item.interrupt(); // interrupt sampler if possible
-            Thread t = entry.getValue();
-            if (t != null ) { // Bug 49734
-                t.interrupt(); // also interrupt JVM thread
-            }
+        for (AbstractThreadGroup threadGroup : groups) {
+            threadGroup.tellThreadsToStop();
         }
     }
 
@@ -570,8 +562,8 @@ public class StandardJMeterEngine implem
      */
     private void stopAllThreads() {
         // ConcurrentHashMap does not need synch. here
-        for (JMeterThread item : allThreads.keySet()) {
-            item.stop();
+        for (AbstractThreadGroup threadGroup : groups) {
+            threadGroup.stop();
         }
     }
 

Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties Fri Jul 13 21:49:30 2012
@@ -625,6 +625,7 @@ no=Norwegian
 number_of_threads=Number of Threads (users)\:
 obsolete_test_element=This test element is obsolete
 once_only_controller_title=Once Only Controller
+ondemand_threadgroup=On Demand Thread Group
 opcode=opCode
 open=Open...
 option=Options

Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties Fri Jul 13 21:49:30 2012
@@ -619,6 +619,7 @@ no=Norv\u00E9gien
 number_of_threads=Nombre d'unit\u00E9s (utilisateurs) \:
 obsolete_test_element=Cet \u00E9l\u00E9ment de test est obsol\u00E8te
 once_only_controller_title=Contr\u00F4leur Ex\u00E9cution unique
+ondemand_threadgroup=Groupe d'unit\u00E9s \u00E0 la demande
 opcode=Code d'op\u00E9ration
 open=Ouvrir...
 option=Options

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java Fri Jul 13 21:49:30 2012
@@ -19,6 +19,9 @@
 package org.apache.jmeter.threads;
 
 import java.io.Serializable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.jmeter.control.Controller;
 import org.apache.jmeter.control.LoopController;
@@ -29,6 +32,9 @@ import org.apache.jmeter.testelement.Tes
 import org.apache.jmeter.testelement.property.IntegerProperty;
 import org.apache.jmeter.testelement.property.JMeterProperty;
 import org.apache.jmeter.testelement.property.TestElementProperty;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
 
 /**
  * ThreadGroup holds the settings for a JMeter thread group.
@@ -39,6 +45,10 @@ public abstract class AbstractThreadGrou
 
     private static final long serialVersionUID = 240L;
 
+    private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
     /** Action to be taken when a Sampler error occurs */
     public final static String ON_SAMPLE_ERROR = "ThreadGroup.on_sample_error"; // int
 
@@ -65,6 +75,10 @@ public abstract class AbstractThreadGrou
     // @GuardedBy("this")
     private int numberOfThreads = 0; // Number of active threads in this group
 
+    private JMeterThread[] jmThreads;
+
+    private Map<JMeterThread, Thread> allThreads = new ConcurrentHashMap<JMeterThread, Thread>();
+
     /** {@inheritDoc} */
     public boolean isDone() {
         return getSamplerController().isDone();
@@ -217,4 +231,172 @@ public abstract class AbstractThreadGrou
     }
 
     public abstract void scheduleThread(JMeterThread thread);
+
+    /**
+     * Default implementation starts threads immediately
+     */
+    public void start() {
+        for (int i = 0; i < jmThreads.length; i++) {
+            Thread newThread = new Thread(jmThreads[i]);
+            newThread.setName(jmThreads[i].getThreadName());
+            registerStartedThread(jmThreads[i], newThread);
+            newThread.start();            
+        }
+    }
+
+    /**
+     * Register Thread when it starts
+     * @param jMeterThread {@link JMeterThread}
+     * @param newThread Thread
+     */
+    protected final void registerStartedThread(JMeterThread jMeterThread, Thread newThread) {
+        allThreads.put(jMeterThread, newThread);
+    }
+
+    /**
+     * 
+     * @param jmThreads JMeterThread[]
+     */
+    public final void setJMeterThreads(JMeterThread[] jmThreads) {
+        this.jmThreads = jmThreads;
+    }
+
+    /**
+     * @return JMeterThread[]
+     */
+    protected final JMeterThread[] getJMeterThreads() {
+        return this.jmThreads;
+    }
+    
+    /**
+     * Stop thread called threadName:
+     * <ol>
+     *  <li>stop JMeter thread</li>
+     *  <li>interrupt JMeter thread</li>
+     *  <li>interrupt underlying thread</li>
+     * <ol>
+     * @param threadName String thread name
+     * @param now boolean for stop
+     * @return true if thread stopped
+     */
+    public boolean stopThread(String threadName, boolean now) {
+        for(Entry<JMeterThread, Thread> entry : allThreads.entrySet()){
+            JMeterThread thrd = entry.getKey();
+            if (thrd.getThreadName().equals(threadName)){
+                thrd.stop();
+                thrd.interrupt();
+                if (now) {
+                    Thread t = entry.getValue();
+                    if (t != null) {
+                        t.interrupt();
+                    }
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by JMeter thread when it finishes
+     */
+    public void threadFinished(JMeterThread thread) {
+        allThreads.remove(thread);
+    }
+
+    /**
+     * For each thread, invoke:
+     * <ul> 
+     * <li>{@link JMeterThread#stop()} - set stop flag</li>
+     * <li>{@link JMeterThread#interrupt()} - interrupt sampler</li>
+     * <li>{@link Thread#interrupt()} - interrupt JVM thread</li>
+     * </ul> 
+     */
+    public void tellThreadsToStop() {
+        for (Entry<JMeterThread, Thread> entry : allThreads.entrySet()) {
+            JMeterThread item = entry.getKey();
+            item.stop(); // set stop flag
+            item.interrupt(); // interrupt sampler if possible
+            Thread t = entry.getValue();
+            if (t != null ) { // Bug 49734
+                t.interrupt(); // also interrupt JVM thread
+            }
+        }
+    }
+
+    /**
+     * For each thread, invoke:
+     * <ul> 
+     * <li>{@link JMeterThread#stop()} - set stop flag</li>
+     * </ul> 
+     */
+    public void stop() {
+        for (JMeterThread item : allThreads.keySet()) {
+            item.stop();
+        }
+    }
+
+    /**
+     * @return number of active threads
+     */
+    public int numberOfActiveThreads() {
+        return allThreads.size();
+    }
+
+    /**
+     * @return boolean true if all threads stopped
+     */
+    public boolean verifyThreadsStopped() {
+        boolean stoppedAll = true;
+        for (Thread t : allThreads.values()) {
+            stoppedAll = stoppedAll && verifyThreadStopped(t);
+        }
+        return stoppedAll;
+    }
+
+    /**
+     * Verify thread stopped and return true if stopped successfully
+     * @param thread Thread
+     * @return boolean
+     */
+    protected final boolean verifyThreadStopped(Thread thread) {
+        boolean stoppedAll = true;
+        if (thread != null) {
+            if (thread.isAlive()) {
+                try {
+                    thread.join(WAIT_TO_DIE);
+                } catch (InterruptedException e) {
+                }
+                if (thread.isAlive()) {
+                    stoppedAll = false;
+                    log.warn("Thread won't exit: " + thread.getName());
+                }
+            }
+        }
+        return stoppedAll;
+    }
+
+    /**
+     * Wait for all Group Threads to stop
+     */
+    public void waitThreadsStopped() {
+        for (Thread t : allThreads.values()) {
+            waitThreadStopped(t);
+        }
+    }
+
+    /**
+     * Wait for thread to stop
+     * @param thread Thread
+     */
+    protected final void waitThreadStopped(Thread thread) {
+        if (thread != null) {
+            while (thread.isAlive()) {
+                try {
+                    thread.join(WAIT_TO_DIE);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
 }

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java Fri Jul 13 21:49:30 2012
@@ -196,7 +196,8 @@ public class JMeterThread implements Run
      *
      */
     private void stopScheduler() {
-        long delay = System.currentTimeMillis() - endTime;
+        long now = System.currentTimeMillis();
+        long delay = now - endTime;
         if ((delay >= 0)) {
             running = false;
         }
@@ -770,6 +771,10 @@ public class JMeterThread implements Run
 
     }
 
+    /**
+     * Set rampup delay for JMeterThread Thread
+     * @param delay Rampup delay for JMeterThread
+     */
     public void setInitialDelay(int delay) {
         initialDelay = delay;
     }

Added: jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java?rev=1361410&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java Fri Jul 13 21:49:30 2012
@@ -0,0 +1,196 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.logging.LoggingManager;
+import org.apache.log.Logger;
+
+/**
+ * Thread Group implementation that creates Threads progressively
+ */
+public class OnDemandThreadGroup extends ThreadGroup {
+    /** How often to check for shutdown during ramp-up, default 1000ms */
+    private static final int RAMPUP_GRANULARITY =
+            JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$
+
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1326448504092168570L;
+
+    private Thread threadStarter;
+
+    /**
+     * Was test stopped
+     */
+    private AtomicBoolean stopped = new AtomicBoolean(false);
+
+    /**
+     * 
+     */
+    public OnDemandThreadGroup() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jmeter.threads.AbstractThreadGroup#start()
+     */
+    @Override
+    public void start() {
+        stopped.set(false);
+        this.threadStarter = new Thread(new ThreadStarter(), getName()+"-ThreadStarter");
+        threadStarter.start();  
+        try {
+            threadStarter.join();
+        } catch (InterruptedException e) {
+            return;
+        }
+    }
+    
+    /**
+     * Starts Threads using ramp up
+     */
+    private class ThreadStarter implements Runnable {
+
+        public ThreadStarter() {
+            super();
+        }
+        
+        public void run() {
+            final JMeterThread[] jMeterThreads = getJMeterThreads();
+            
+            int rampUp = getRampUp();
+            float perThreadDelay = ((float) (rampUp * 1000) / (float) getNumThreads());
+            if (getScheduler()) {
+                long now = System.currentTimeMillis();
+                // set the start time for the Thread
+                if (getDelay() > 0) {// Duration is in seconds
+                    delayBy(getDelay() * 1000, "start");
+                } else {
+                    long start = getStartTime();
+                    if (start < now) {
+                        start = now; // Force a sensible start time
+                        // No delay
+                    } else {
+                        delayBy(start-now, "start");
+                    }
+                }
+            }
+            for (int i = 0; i < jMeterThreads.length; i++) {
+                try {
+                    if(!stopped.get()) {
+                        Thread.sleep(Math.round(perThreadDelay));
+                        Thread newThread = new Thread(jMeterThreads[i]);
+                        newThread.setName(jMeterThreads[i].getThreadName());
+                        registerStartedThread(jMeterThreads[i], newThread);
+                        newThread.start();
+                    }
+                } catch (InterruptedException e) {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Wait for delay with RAMPUP_GRANULARITY
+     * @param delay delay in ms
+     * @param type Delay type
+     */
+    protected final void delayBy(long delay, String type) {
+        if (delay > 0) {
+            long start = System.currentTimeMillis();
+            long end = start + delay;
+            long now=0;
+            long pause = RAMPUP_GRANULARITY;
+            while(!stopped.get() && (now = System.currentTimeMillis()) < end) {
+                long togo = end - now;
+                if (togo < pause) {
+                    pause = togo;
+                }
+                try {
+                    Thread.sleep(pause); // delay between checks
+                } catch (InterruptedException e) {
+                    break;
+                }
+            }
+        }
+    }
+    /**
+     * @see org.apache.jmeter.threads.AbstractThreadGroup#stop()
+     */
+    @Override
+    public void stop() {
+        stopped.set(true);
+        try {
+            threadStarter.interrupt();
+        } catch (Exception e) {
+            log.warn("Exception occured interrupting ThreadStarter");
+        }
+        super.stop();
+    }
+    
+    /**
+     * Schedule thread
+     */
+    @Override
+    public void scheduleThread(JMeterThread thread)
+    {
+        // No delay as OnDemandThreadGroup starts thread during rampup
+        thread.setInitialDelay(0);
+        super.scheduleThread(thread, this);
+    }
+    
+    /**
+     * Stop thread stopper and JMeterThread Threads
+     */
+    @Override
+    public void tellThreadsToStop() {
+        stopped.set(true);
+        try {
+            threadStarter.interrupt();
+        } catch (Exception e) {
+            log.warn("Exception occured interrupting ThreadStarter");
+        }
+        super.tellThreadsToStop();
+    }
+
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.threads.AbstractThreadGroup#verifyThreadsStopped()
+     */
+    @Override
+    public boolean verifyThreadsStopped() {
+        return verifyThreadStopped(threadStarter) && super.verifyThreadsStopped();
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.jmeter.threads.AbstractThreadGroup#waitThreadsStopped()
+     */
+    @Override
+    public void waitThreadsStopped() {
+        waitThreadStopped(threadStarter);
+        super.waitThreadsStopped();
+    }
+}
\ No newline at end of file

Propchange: jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java Fri Jul 13 21:49:30 2012
@@ -181,10 +181,10 @@ public class ThreadGroup extends Abstrac
     /**
      * This will schedule the time for the JMeterThread.
      *
-     * @param thread
-     * @param group
+     * @param thread JMeterThread
+     * @param group ThreadGroup
      */
-    private void scheduleThread(JMeterThread thread, ThreadGroup group) {
+    protected void scheduleThread(JMeterThread thread, ThreadGroup group) {
         // if true the Scheduler is enabled
         if (group.getScheduler()) {
             long now = System.currentTimeMillis();

Added: jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java?rev=1361410&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java Fri Jul 13 21:49:30 2012
@@ -0,0 +1,58 @@
+/*
+ * 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.gui;
+
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.threads.OnDemandThreadGroup;
+import org.apache.jmeter.threads.ThreadGroup;
+
+/**
+ * GUI for {@link OnDemandThreadGroup}
+ */
+public class OnDemandThreadGroupGui extends ThreadGroupGui {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 7903310220568526814L;
+
+    /**
+     * 
+     */
+    public OnDemandThreadGroupGui() {
+        super();
+    }
+
+    /**
+     * @see org.apache.jmeter.threads.gui.ThreadGroupGui#createTestElement()
+     */
+    @Override
+    public TestElement createTestElement() {
+        ThreadGroup tg = new OnDemandThreadGroup();
+        modifyTestElement(tg);
+        return tg;
+    }
+    
+    /**
+     * @return String label key
+     */
+    public String getLabelResource() {
+        return "ondemand_threadgroup"; // $NON-NLS-1$
+    }
+}
\ No newline at end of file

Propchange: jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1361410&r1=1361409&r2=1361410&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml (original)
+++ jmeter/trunk/xdocs/changes.xml Fri Jul 13 21:49:30 2012
@@ -147,6 +147,7 @@ JSR223 Test Elements using Script file a
 <h3>General</h3>
 <ul>
 <li><bugzilla>53364</bugzilla> - Sort list of Functions in Function Helper Dialog</li>
+<li><bugzilla>53418</bugzilla> - New OnDemandThreadGroup that creates threads when needed instead of creating them on Test startup</li>
 </ul>
 
 <h2>Non-functional changes</h2>