You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by se...@apache.org on 2012/08/13 17:17:56 UTC

svn commit: r1372462 - in /jmeter/trunk: bin/ docs/images/screenshots/ src/core/org/apache/jmeter/resources/ src/core/org/apache/jmeter/save/ src/core/org/apache/jmeter/threads/ src/core/org/apache/jmeter/threads/gui/ xdocs/images/screenshots/ xdocs/us...

Author: sebb
Date: Mon Aug 13 15:17:55 2012
New Revision: 1372462

URL: http://svn.apache.org/viewvc?rev=1372462&view=rev
Log:
Merge OnDemandThreadGroup into ThreadGroup
Bugzilla Id: 53418

Removed:
    jmeter/trunk/src/core/org/apache/jmeter/threads/OnDemandThreadGroup.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/gui/OnDemandThreadGroupGui.java
Modified:
    jmeter/trunk/bin/saveservice.properties
    jmeter/trunk/docs/images/screenshots/threadgroup.png
    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/save/SaveService.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java
    jmeter/trunk/xdocs/images/screenshots/threadgroup.png
    jmeter/trunk/xdocs/usermanual/component_reference.xml

Modified: jmeter/trunk/bin/saveservice.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/bin/saveservice.properties?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/bin/saveservice.properties (original)
+++ jmeter/trunk/bin/saveservice.properties Mon Aug 13 15:17:55 2012
@@ -43,8 +43,8 @@ _file_version=$Revision$
 # (Some version updates were missed here...)
 # 2.2 = 2.6
 # 2.3 = 2.7
-# 2.4 = 2.8
-_version=2.4
+#
+_version=2.3
 #
 #
 # Character set encoding used to read and write JMeter XML files
@@ -207,8 +207,6 @@ NamePanel=org.apache.jmeter.gui.NamePane
 ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui
 OnceOnlyController=org.apache.jmeter.control.OnceOnlyController
 OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui
-OnDemandThreadGroup=org.apache.jmeter.threads.OnDemandThreadGroup
-OnDemandThreadGroupGui=org.apache.jmeter.threads.gui.OnDemandThreadGroupGui
 ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask
 ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier
 ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui

Modified: jmeter/trunk/docs/images/screenshots/threadgroup.png
URL: http://svn.apache.org/viewvc/jmeter/trunk/docs/images/screenshots/threadgroup.png?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
Binary files - no diff available.

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=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties Mon Aug 13 15:17:55 2012
@@ -625,7 +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
+ondemand=Delay Thread creation until needed
 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=1372462&r1=1372461&r2=1372462&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 Mon Aug 13 15:17:55 2012
@@ -619,7 +619,6 @@ 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/save/SaveService.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/save/SaveService.java Mon Aug 13 15:17:55 2012
@@ -171,7 +171,7 @@ public class SaveService {
 
     // This is written to JMX files by ScriptWrapperConverter
     private static String propertiesVersion = "";// read from properties file; written to JMX files
-    private static final String PROPVERSION = "2.4";// Expected version $NON-NLS-1$
+    private static final String PROPVERSION = "2.3";// Expected version $NON-NLS-1$
 
     // Internal information only
     private static String fileVersion = ""; // read from saveservice.properties file// $NON-NLS-1$

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=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java Mon Aug 13 15:17:55 2012
@@ -19,9 +19,6 @@
 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;
@@ -32,9 +29,6 @@ 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.
@@ -45,10 +39,6 @@ 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
 
@@ -75,10 +65,6 @@ 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();
@@ -232,171 +218,21 @@ 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();            
-        }
-    }
+    public abstract boolean stopThread(String threadName, boolean now);
 
-    /**
-     * Register Thread when it starts
-     * @param jMeterThread {@link JMeterThread}
-     * @param newThread Thread
-     */
-    protected final void registerStartedThread(JMeterThread jMeterThread, Thread newThread) {
-        allThreads.put(jMeterThread, newThread);
-    }
+    public abstract void threadFinished(JMeterThread thread);
 
-    /**
-     * 
-     * @param jmThreads JMeterThread[]
-     */
-    public final void setJMeterThreads(JMeterThread[] jmThreads) {
-        this.jmThreads = jmThreads;
-    }
+    public abstract int numberOfActiveThreads();
 
-    /**
-     * @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;
-    }
+    public abstract void setJMeterThreads(JMeterThread[] jmThreads);
 
-    /**
-     * Called by JMeter thread when it finishes
-     */
-    public void threadFinished(JMeterThread thread) {
-        allThreads.remove(thread);
-    }
+    public abstract void start();
 
-    /**
-     * 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();
-        }
-    }
+    public abstract boolean verifyThreadsStopped();
 
-    /**
-     * @return number of active threads
-     */
-    public int numberOfActiveThreads() {
-        return allThreads.size();
-    }
+    public abstract void waitThreadsStopped();
 
-    /**
-     * @return boolean true if all threads stopped
-     */
-    public boolean verifyThreadsStopped() {
-        boolean stoppedAll = true;
-        for (Thread t : allThreads.values()) {
-            stoppedAll = stoppedAll && verifyThreadStopped(t);
-        }
-        return stoppedAll;
-    }
+    public abstract void tellThreadsToStop();
 
-    /**
-     * 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) {
-                }
-            }
-        }
-    }
+    public abstract void stop();
 }

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=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java Mon Aug 13 15:17:55 2012
@@ -18,9 +18,17 @@
 
 package org.apache.jmeter.threads;
 
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import org.apache.jmeter.testelement.property.BooleanProperty;
 import org.apache.jmeter.testelement.property.IntegerProperty;
 import org.apache.jmeter.testelement.property.LongProperty;
+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.
@@ -30,9 +38,18 @@ import org.apache.jmeter.testelement.pro
 public class ThreadGroup extends AbstractThreadGroup {
     private static final long serialVersionUID = 240L;
 
+    private static final Logger log = LoggingManager.getLoggerForClass();
+
+    private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds
+
+    //+ JMX entries - do not change the string values
+
     /** Ramp-up time */
     public final static String RAMP_TIME = "ThreadGroup.ramp_time";
 
+    /** Whether onDemand scheduler is being used */
+    public static final String ONDEMAND = "ThreadGroup.onDemand";
+
     /** Whether scheduler is being used */
     public final static String SCHEDULER = "ThreadGroup.scheduler";
 
@@ -48,6 +65,23 @@ public class ThreadGroup extends Abstrac
     /** Scheduler start delay, overrides start time */
     public final static String DELAY = "ThreadGroup.delay";
 
+    //- JMX entries
+
+    /** 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 Thread threadStarter;
+
+    private JMeterThread[] jmThreads;
+
+    private Map<JMeterThread, Thread> allThreads = new ConcurrentHashMap<JMeterThread, Thread>();
+
+    /**
+     * Was test stopped
+     */
+    private AtomicBoolean stopped = new AtomicBoolean(false);
+
     /**
      * No-arg constructor.
      */
@@ -64,6 +98,10 @@ public class ThreadGroup extends Abstrac
         setProperty(new BooleanProperty(SCHEDULER, Scheduler));
     }
 
+    public boolean getOnDemand() {
+        return getPropertyAsBoolean(ONDEMAND);
+    }
+
     /**
      * Get whether scheduler is being used
      *
@@ -171,10 +209,14 @@ public class ThreadGroup extends Abstrac
    @Override
    public void scheduleThread(JMeterThread thread)
    {
-       int rampUp = getRampUp();
-       float perThreadDelay = ((float) (rampUp * 1000) / (float) getNumThreads());
-       thread.setInitialDelay((int) (perThreadDelay * thread.getThreadNum()));
-
+       if (getOnDemand()) {
+           // No delay as OnDemandThreadGroup starts thread during rampup
+           thread.setInitialDelay(0);
+       } else {
+           int rampUp = getRampUp();
+           float perThreadDelay = ((float) (rampUp * 1000) / (float) getNumThreads());
+           thread.setInitialDelay((int) (perThreadDelay * thread.getThreadNum()));
+       }
        scheduleThread(thread, this);
    }
 
@@ -184,7 +226,7 @@ public class ThreadGroup extends Abstrac
      * @param thread JMeterThread
      * @param group ThreadGroup
      */
-    protected void scheduleThread(JMeterThread thread, ThreadGroup group) {
+    private void scheduleThread(JMeterThread thread, ThreadGroup group) {
         // if true the Scheduler is enabled
         if (group.getScheduler()) {
             long now = System.currentTimeMillis();
@@ -210,4 +252,276 @@ public class ThreadGroup extends Abstrac
             thread.setScheduled(true);
         }
     }
+
+
+    /**
+     * Wait for delay with RAMPUP_GRANULARITY
+     * @param delay delay in ms
+     * @param type Delay type
+     */
+    private 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;
+                }
+            }
+        }
+    }
+
+    /**
+     * Default implementation starts threads immediately
+     */
+    @Override
+    public void start() {
+        if (getOnDemand()) {
+            stopped.set(false);
+            this.threadStarter = new Thread(new ThreadStarter(), getName()+"-ThreadStarter");
+            threadStarter.start();  
+            try {
+                threadStarter.join();
+            } catch (InterruptedException e) {
+            }            
+        } else {
+            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
+     */
+    private void registerStartedThread(JMeterThread jMeterThread, Thread newThread) {
+        allThreads.put(jMeterThread, newThread);
+    }
+
+    /**
+     * 
+     * @param jmThreads JMeterThread[]
+     */
+    @Override
+    public final void setJMeterThreads(JMeterThread[] jmThreads) {
+        this.jmThreads = 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
+     */
+    @Override
+    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
+     */
+    @Override
+    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> 
+     */
+    @Override
+    public void tellThreadsToStop() {
+        if (getOnDemand()) {
+            stopped.set(true);
+            try {
+                threadStarter.interrupt();
+            } catch (Exception e) {
+                log.warn("Exception occured interrupting ThreadStarter");
+            }            
+        }
+        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> 
+     */
+    @Override
+    public void stop() {
+        if (getOnDemand()) {
+            stopped.set(true);
+            try {
+                threadStarter.interrupt();
+            } catch (Exception e) {
+                log.warn("Exception occured interrupting ThreadStarter");
+            }            
+        }
+        for (JMeterThread item : allThreads.keySet()) {
+            item.stop();
+        }
+    }
+
+    /**
+     * @return number of active threads
+     */
+    @Override
+    public int numberOfActiveThreads() {
+        return allThreads.size();
+    }
+
+    /**
+     * @return boolean true if all threads stopped
+     */
+    @Override
+    public boolean verifyThreadsStopped() {
+        boolean stoppedAll = true;
+        if (getOnDemand()){
+            stoppedAll &= verifyThreadStopped(threadStarter);
+        }
+        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
+     */
+    private boolean verifyThreadStopped(Thread thread) {
+        boolean stopped = true;
+        if (thread != null) {
+            if (thread.isAlive()) {
+                try {
+                    thread.join(WAIT_TO_DIE);
+                } catch (InterruptedException e) {
+                }
+                if (thread.isAlive()) {
+                    stopped = false;
+                    log.warn("Thread won't exit: " + thread.getName());
+                }
+            }
+        }
+        return stopped;
+    }
+
+    /**
+     * Wait for all Group Threads to stop
+     */
+    @Override
+    public void waitThreadsStopped() {
+        if (getOnDemand()) {
+            waitThreadStopped(threadStarter);            
+        }
+        for (Thread t : allThreads.values()) {
+            waitThreadStopped(t);
+        }
+    }
+
+    /**
+     * Wait for thread to stop
+     * @param thread Thread
+     */
+    private void waitThreadStopped(Thread thread) {
+        if (thread != null) {
+            while (thread.isAlive()) {
+                try {
+                    thread.join(WAIT_TO_DIE);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Starts Threads using ramp up
+     */
+    class ThreadStarter implements Runnable {
+
+        public ThreadStarter() {
+            super();
+        }
+        
+        public void run() {
+            final JMeterThread[] jMeterThreads = jmThreads;
+            
+            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) {
+                        delayBy(start-now, "start");
+                    } 
+                    // else start immediately
+                }
+            }
+            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;
+                }
+            }
+        }
+    }
 }

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/gui/ThreadGroupGui.java Mon Aug 13 15:17:55 2012
@@ -59,6 +59,8 @@ public class ThreadGroupGui extends Abst
 
     private JDateField end;
 
+    private JCheckBox onDemand;
+
     private JCheckBox scheduler;
 
     private JTextField duration;
@@ -92,6 +94,7 @@ public class ThreadGroupGui extends Abst
         tg.setProperty(ThreadGroup.RAMP_TIME, rampInput.getText());
         tg.setProperty(new LongProperty(ThreadGroup.START_TIME, start.getDate().getTime()));
         tg.setProperty(new LongProperty(ThreadGroup.END_TIME, end.getDate().getTime()));
+        tg.setProperty(new BooleanProperty(ThreadGroup.ONDEMAND, onDemand.isSelected()));
         tg.setProperty(new BooleanProperty(ThreadGroup.SCHEDULER, scheduler.isSelected()));
         tg.setProperty(ThreadGroup.DURATION, duration.getText());
         tg.setProperty(ThreadGroup.DELAY, delay.getText());
@@ -103,6 +106,7 @@ public class ThreadGroupGui extends Abst
         threadInput.setText(tg.getPropertyAsString(AbstractThreadGroup.NUM_THREADS));
         rampInput.setText(tg.getPropertyAsString(ThreadGroup.RAMP_TIME));
         loopPanel.configure((TestElement) tg.getProperty(AbstractThreadGroup.MAIN_CONTROLLER).getObjectValue());
+        onDemand.setSelected(tg.getPropertyAsBoolean(ThreadGroup.ONDEMAND));
         scheduler.setSelected(tg.getPropertyAsBoolean(ThreadGroup.SCHEDULER));
 
         if (scheduler.isSelected()) {
@@ -214,6 +218,7 @@ public class ThreadGroupGui extends Abst
         threadInput.setText("1"); // $NON-NLS-1$
         rampInput.setText("1"); // $NON-NLS-1$
         loopPanel.clearGui();
+        onDemand.setSelected(false);
         scheduler.setSelected(false);
         Date today = new Date();
         end.setDate(today);
@@ -259,6 +264,8 @@ public class ThreadGroupGui extends Abst
         // mainPanel.add(threadPropsPanel, BorderLayout.NORTH);
         // add(mainPanel, BorderLayout.CENTER);
 
+        onDemand = new JCheckBox(JMeterUtils.getResString("ondemand")); // $NON-NLS-1$
+        threadPropsPanel.add(onDemand);
         scheduler = new JCheckBox(JMeterUtils.getResString("scheduler")); // $NON-NLS-1$
         scheduler.addItemListener(this);
         threadPropsPanel.add(scheduler);

Modified: jmeter/trunk/xdocs/images/screenshots/threadgroup.png
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/images/screenshots/threadgroup.png?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
Binary files - no diff available.

Modified: jmeter/trunk/xdocs/usermanual/component_reference.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/usermanual/component_reference.xml?rev=1372462&r1=1372461&r2=1372462&view=diff
==============================================================================
--- jmeter/trunk/xdocs/usermanual/component_reference.xml (original)
+++ jmeter/trunk/xdocs/usermanual/component_reference.xml Mon Aug 13 15:17:55 2012
@@ -5022,7 +5022,7 @@ In jmeter.properties, edit "user.classpa
 </description>
 </component>
 
-<component name="Thread Group" index="&sect-num;.9.2a"  width="609" height="387" screenshot="threadgroup.png">
+<component name="Thread Group" index="&sect-num;.9.2"  width="583" height="447" screenshot="threadgroup.png">
 <description>
 <p>A Thread Group defines a pool of users that will execute a particular test case against your server.  In the Thread Group GUI, you can control the number of users simulated (num of threads), the ramp up time (how long it takes to start all the threads), the number of times to perform the test, and optionally, a start and stop time for the test.</p>
 <p>
@@ -5051,6 +5051,15 @@ JMeter does not interrupt samplers which
         <property name="Number of Threads" required="Yes">Number of users to simulate.</property>
         <property name="Ramp-up Period" required="Yes">How long JMeter should take to get all the threads started.  If there are 10 threads and a ramp-up time of 100 seconds, then each thread will begin 10 seconds after the previous thread started, for a total time of 100 seconds to get the test fully up to speed.</property>
         <property name="Loop Count" required="Yes, unless forever is selected">Number of times to perform the test case.  Alternatively, "forever" can be selected causing the test to run until manually stopped.</property>
+        <property name="Delay Thread creation until needed" required="Yes">
+        If selected, threads are created only when the appropriate proportion of the ramp-up time has elapsed.
+        This is most appropriate for tests with a ramp-up time that is significantly longer than the time to execute a single thread.
+        I.e. where earlier threads finish before later ones start. 
+        <br></br>
+        If not selected, all threads are created when the test starts (they then pause for the appropriate proportion of the ramp-up time).
+        This is the original default, and is appropriate for tests where threads are active throughout most of the test.
+        </property>
+        <property name="Scheduler" required="Yes">If selected, enables the scheduler</property>
         <property name="Start Time" required="No">If the scheduler checkbox is selected, one can choose an absolute start time.  When you start your test, JMeter will wait until the specified start time to begin testing.
         	Note: the Startup Delay field over-rides this - see below.
         	</property>
@@ -5068,13 +5077,6 @@ JMeter does not interrupt samplers which
 </properties>
 </component>
 
-<component name="On Demand Thread Group" index="&sect-num;.9.2b"  width="609" height="387" screenshot="ondemandthreadgroup.png">
-<description>
-<p>An On Demand Thread Group has the same features as a Thread except that instead of starting threads at Test Startup, it does so when thread really starts sampling.</p>
-<p>See also <complink name="Thread Group"/>.</p>
-</description>
-</component>
-
 <component name="WorkBench" index="&sect-num;.9.3"  width="232" height="68" screenshot="workbench.png">
 <description>
 <p>The WorkBench simply provides a place to temporarily store test elements while not in use, for copy/paste purposes, or any other purpose you desire.