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 2016/12/30 17:59:35 UTC

svn commit: r1776612 - in /jmeter/trunk: src/core/org/apache/jmeter/threads/AbstractThreadGroup.java src/core/org/apache/jmeter/threads/JMeterThread.java src/core/org/apache/jmeter/threads/ThreadGroup.java xdocs/changes.xml

Author: pmouawad
Date: Fri Dec 30 17:59:35 2016
New Revision: 1776612

URL: http://svn.apache.org/viewvc?rev=1776612&view=rev
Log:
Bug 60530 - Add API to create JMeter threads while test is running
Based on a contribution by Logan Mauzaize & Maxime Chassagneux
Bugzilla Id: 60530

Modified:
    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/threads/AbstractThreadGroup.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java?rev=1776612&r1=1776611&r2=1776612&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/AbstractThreadGroup.java Fri Dec 30 17:59:35 2016
@@ -245,17 +245,54 @@ public abstract class AbstractThreadGrou
         return getPropertyAsString(AbstractThreadGroup.ON_SAMPLE_ERROR).equalsIgnoreCase(ON_SAMPLE_ERROR_STOPTEST_NOW);
     }
 
+    /**
+     * Hard or graceful stop depending on now flag
+     * @param threadName String thread name
+     * @param now if true interrupt {@link Thread} 
+     * @return boolean true if stop succeeded
+     */
     public abstract boolean stopThread(String threadName, boolean now);
 
+    /**
+     * @return int number of active threads 
+     */
     public abstract int numberOfActiveThreads();
 
+    /**
+     * Start the {@link ThreadGroup}
+     * @param groupCount group number
+     * @param notifier {@link ListenerNotifier}
+     * @param threadGroupTree {@link ListedHashTree}
+     * @param engine {@link StandardJMeterEngine}
+     */
     public abstract void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine);
 
+    /**
+     * Add a new {@link JMeterThread} to this {@link ThreadGroup} for engine
+     * @param delay Delay in milliseconds
+     * @param engine {@link StandardJMeterEngine}
+     * @return {@link JMeterThread}
+     */
+    public abstract JMeterThread addNewThread(int delay, StandardJMeterEngine engine);
+
+    /**
+     * @return true if threads were correctly stopped
+     */
     public abstract boolean verifyThreadsStopped();
 
+    /**
+     * Wait for all Group Threads to stop after a graceful stop
+     */
     public abstract void waitThreadsStopped();
 
+    /**
+     * Ask threads to stop gracefully
+     */
     public abstract void tellThreadsToStop();
 
+    /**
+     * This immediately stop threads of Group by interrupting them
+     * It differs from {@link AbstractThreadGroup#tellThreadsToStop()} by being a hard stop
+     */
     public abstract void stop();
 }

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=1776612&r1=1776611&r2=1776612&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/JMeterThread.java Fri Dec 30 17:59:35 2016
@@ -50,6 +50,7 @@ import org.apache.jmeter.timers.Timer;
 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;
@@ -675,6 +676,10 @@ public class JMeterThread implements Run
         return threadName;
     }
 
+    /**
+     * Set running flag to false which will interrupt JMeterThread on next flag test.
+     * This is a clean shutdown.
+     */
     public void stop() { // Called by StandardJMeterEngine, TestAction and AccessLogSampler
         running = false;
         log.info("Stopping: " + threadName);
@@ -977,4 +982,18 @@ public class JMeterThread implements Run
         this.threadGroup = group;
     }
 
+    /**
+     * @return {@link ListedHashTree}
+     */
+    public ListedHashTree getTestTree() {
+        return (ListedHashTree) testTree;
+    }
+
+    /**
+     * @return {@link ListenerNotifier}
+     */
+    public ListenerNotifier getNotifier() {
+        return notifier;
+    }
+
 }

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=1776612&r1=1776611&r2=1776612&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java Fri Dec 30 17:59:35 2016
@@ -42,7 +42,7 @@ import org.apache.log.Logger;
  * This class is intended to be ThreadSafe.
  */
 public class ThreadGroup extends AbstractThreadGroup {
-    private static final long serialVersionUID = 280L;
+    private static final long serialVersionUID = 281L;
 
     private static final Logger log = LoggingManager.getLoggerForClass();
     
@@ -83,6 +83,8 @@ public class ThreadGroup extends Abstrac
 
     // List of active threads
     private final Map<JMeterThread, Thread> allThreads = new ConcurrentHashMap<>();
+    
+    private final Object addThreadLock = new Object();
 
     /**
      * Is test (still) running?
@@ -90,11 +92,26 @@ public class ThreadGroup extends Abstrac
     private volatile boolean running = false;
 
     /**
+     * Thread Group number
+     */
+    private int groupNumber;
+
+    /**
      * Are we using delayed startup?
      */
     private boolean delayedStartup;
 
     /**
+     * Thread safe class
+     */
+    private ListenerNotifier notifier;
+
+    /**
+     * This property will be cloned
+     */
+    private ListedHashTree threadGroupTree;
+
+    /**
      * No-arg constructor.
      */
     public ThreadGroup() {
@@ -222,6 +239,7 @@ public class ThreadGroup extends Abstrac
      * This will schedule the time for the JMeterThread.
      *
      * @param thread JMeterThread
+     * @param now in milliseconds
      */
     private void scheduleThread(JMeterThread thread, long now) {
 
@@ -257,39 +275,58 @@ public class ThreadGroup extends Abstrac
     }
 
     @Override
-    public void start(int groupCount, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) {
-        running = true;
+    public void start(int groupNum, ListenerNotifier notifier, ListedHashTree threadGroupTree, StandardJMeterEngine engine) {
+        this.running = true;
+        this.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);
     }
 
     /**
+     * Start a new {@link JMeterThread} and registers it
+     * @param notifier {@link ListenerNotifier}
+     * @param threadGroupTree {@link ListedHashTree}
+     * @param engine {@link StandardJMeterEngine}
+     * @param threadNum Thread number
+     * @param context {@link JMeterContext}
+     * @param now Nom in milliseconds
+     * @param delay int delay in milliseconds
+     * @return {@link JMeterThread} newly created
+     */
+    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}
      * @param newThread Thread
@@ -298,9 +335,18 @@ public class ThreadGroup extends Abstrac
         allThreads.put(jMeterThread, newThread);
     }
 
-    private JMeterThread makeThread(int groupCount,
+    /**
+     * Create {@link JMeterThread} cloning threadGroupTree
+     * @param notifier {@link ListenerNotifier}
+     * @param threadGroupTree {@link ListedHashTree}
+     * @param engine {@link StandardJMeterEngine}
+     * @param threadNumber int thread number
+     * @param context {@link JMeterContext}
+     * @return {@link JMeterThread}
+     */
+    private JMeterThread makeThread(
             ListenerNotifier notifier, ListedHashTree threadGroupTree,
-            StandardJMeterEngine engine, int i, 
+            StandardJMeterEngine engine, int threadNumber, 
             JMeterContext context) { // N.B. Context needs to be fetched in the correct thread
         boolean onErrorStopTest = getOnErrorStopTest();
         boolean onErrorStopTestNow = getOnErrorStopTestNow();
@@ -308,10 +354,10 @@ public class ThreadGroup extends Abstrac
         boolean onErrorStartNextLoop = getOnErrorStartNextLoop();
         String groupName = getName();
         final JMeterThread jmeterThread = new JMeterThread(cloneTree(threadGroupTree), this, notifier);
-        jmeterThread.setThreadNum(i);
+        jmeterThread.setThreadNum(threadNumber);
         jmeterThread.setThreadGroup(this);
         jmeterThread.setInitialContext(context);
-        final String threadName = groupName + " " + (groupCount) + "-" + (i + 1);
+        final String threadName = groupName + " " + groupNumber + "-" + (threadNumber + 1);
         jmeterThread.setThreadName(threadName);
         jmeterThread.setEngine(engine);
         jmeterThread.setOnErrorStopTest(onErrorStopTest);
@@ -321,6 +367,22 @@ public class ThreadGroup extends Abstrac
         return jmeterThread;
     }
 
+    @Override
+    public JMeterThread addNewThread(int delay, StandardJMeterEngine engine) {
+        long now = System.currentTimeMillis();
+        JMeterContext context = JMeterContextService.getContext();
+        JMeterThread newJmThread;
+        int numThreads;
+        synchronized (addThreadLock) {
+            numThreads = getNumThreads();
+            setNumThreads(numThreads + 1);
+        }
+        newJmThread = startNewThread(notifier, threadGroupTree, engine, numThreads, context, now, delay);
+        JMeterContextService.addTotalThreads( 1 );
+        log.info("Started new thread in group " + groupNumber );
+        return newJmThread;
+    }
+
     /**
      * Stop thread called threadName:
      * <ol>
@@ -345,15 +407,16 @@ public class ThreadGroup extends Abstrac
     }
     
     /**
-     * @param thrd JMeterThread
-     * @param t Thread
-     * @param interrupt Interrup thread or not
-     */
-    private void stopThread(JMeterThread thrd, Thread t, boolean interrupt) {
-        thrd.stop();
-        thrd.interrupt(); // interrupt sampler if possible
-        if (interrupt && t != null) { // Bug 49734
-            t.interrupt(); // also interrupt JVM thread
+     * Hard Stop JMeterThread thrd and interrupt JVM Thread if interrupt is true
+     * @param jmeterThread {@link JMeterThread}
+     * @param jvmThread {@link Thread}
+     * @param interrupt Interrupt thread or not
+     */
+    private void stopThread(JMeterThread jmeterThread, Thread jvmThread, boolean interrupt) {
+        jmeterThread.stop();
+        jmeterThread.interrupt(); // interrupt sampler if possible
+        if (interrupt && jvmThread != null) { // Bug 49734
+            jvmThread.interrupt(); // also interrupt JVM thread
         }
     }
 
@@ -485,12 +548,20 @@ public class ThreadGroup extends Abstrac
         }
     }
 
+    /**
+     * @param tree {@link ListedHashTree}
+     * @return a clone of tree
+     */
     private ListedHashTree cloneTree(ListedHashTree tree) {
         TreeCloner cloner = new TreeCloner(true);
         tree.traverse(cloner);
         return cloner.getClonedTree();
     }
 
+    /**
+     * Pause ms milliseconds
+     * @param ms long milliseconds
+     */
     private void pause(long ms){
         try {
             TimeUnit.MILLISECONDS.sleep(ms);
@@ -504,15 +575,13 @@ public class ThreadGroup extends Abstrac
      */
     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;
@@ -577,14 +646,14 @@ public class ThreadGroup extends Abstrac
                 }
                 final int numThreads = getNumThreads();
                 final int perThreadDelayInMillis = Math.round((float) (getRampUp() * 1000) / (float) numThreads);
-                for (int i = 0; running && i < numThreads; i++) {
-                    if (i > 0) {
+                for (int threadNumber = 0; running && threadNumber < numThreads; threadNumber++) {
+                    if (threadNumber > 0) {
                         pause(perThreadDelayInMillis); // ramp-up delay (except first)
                     }
                     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, threadNumber, context);
                     jmThread.setInitialDelay(0);   // Already waited
                     if (usingScheduler) {
                         jmThread.setScheduled(true);

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1776612&r1=1776611&r2=1776612&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Fri Dec 30 17:59:35 2016
@@ -140,6 +140,7 @@ Fill in some detail.
 <h3>General</h3>
 <ul>
     <li><bug>54525</bug>Search Feature : Enhance it with ability to replace</li>
+    <li><bug>60530</bug>Add API to create JMeter threads while test is running. Based on a contribution by Logan Mauzaize and Maxime Chassagneux</li>
 </ul>
 
 <ch_section>Non-functional changes</ch_section>
@@ -208,6 +209,8 @@ Fill in some detail.
 <li>(gavin at 16degrees.com.au)</li>
 <li>Thomas Schapitz (ts-nospam12 at online.de)</li>
 <li>Murdecai777 (https://github.com/Murdecai777)</li>
+<li>Logan Mauzaize (https://github.com/loganmzz)</li>
+<li>Maxime Chassagneux (https://github.com/max3163)</li>
 </ul>
 <p>We also thank bug reporters who helped us improve JMeter. <br/>
 For this release we want to give special thanks to the following reporters for the clear reports and tests made after our fixes:</p>