// $Header: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/engine/StandardJMeterEngine.java,v 1.43.2.8 2004/10/12 23:45:21 sebb Exp $
/*
* Copyright 2000-2004 The Apache Software Foundation.
*
* Licensed 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.engine;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.testelement.TestPlan;
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.ThreadGroup;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
import org.apache.jorphan.collections.SearchByClass;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
/**
* @version $Revision: 1.43.2.8 $ Updated on: $Date: 2004/10/12 23:45:21 $
*/
public class StandardJMeterEngine
implements JMeterEngine, JMeterThreadMonitor, Runnable, Serializable
{
transient private static Logger log = LoggingManager.getLoggerForClass();
private transient Thread runningThread;
private static long WAIT_TO_DIE = 5 * 1000; //5 seconds
private transient Map allThreads;
private boolean running = false;
private boolean serialized = false;
private volatile boolean schcdule_run = false;
private HashTree test;
private transient SearchByClass testListeners;
private String host = null;
private transient ListenerNotifier notifier;
// Allow engine and threads to be stopped from outside a thread
// e.g. from beanshell server
// Assumes that there is only one instance of the engine
// at any one time so it is not guaranteed to work ...
private static transient Map allThreadNames;
private static StandardJMeterEngine engine;
private static Map allThreadsSave;
public static void stopEngineNow()
{
if (engine != null) // May be null if called from Unit test
engine.stopTest(true);
}
public static void stopEngine()
{
if (engine != null) // May be null if called from Unit test
engine.stopTest(false);
}
public static boolean stopThread(String threadName)
{
return stopThread(threadName,false);
}
public static boolean stopThreadNow(String threadName)
{
return stopThread(threadName,true);
}
private static boolean stopThread(String threadName, boolean now)
{
if (allThreadNames == null) return false;// e.g. not yet started
JMeterThread thrd;
try {
thrd = (JMeterThread)allThreadNames.get(threadName);
} catch (Exception e) {
log.warn("stopThread: "+e);
return false;
}
if (thrd!= null)
{
thrd.stop();
if (now)
{
Thread t = (Thread) allThreadsSave.get(thrd);
if (t != null)
{
t.interrupt();
}
}
return true;
}
else
{
return false;
}
}
// End of code to allow engine to be controlled remotely
/*
* Allow functions etc to register for testStopped notification
*/
private static List testList = null;
public static synchronized void register(TestListener tl)
{
testList.add(tl);
}
public StandardJMeterEngine()
{
allThreads = new HashMap();
engine=this;
allThreadNames = new HashMap();
allThreadsSave = allThreads;
}
public StandardJMeterEngine(String host)
{
this();
this.host = host;
}
public void configure(HashTree testTree)
{
test = testTree;
}
public void setHost(String host)
{
this.host = host;
}
protected HashTree getTestTree()
{
return test;
}
protected void compileTree()
{
PreCompiler compiler = new PreCompiler();
getTestTree().traverse(compiler);
}
public void runTest() throws JMeterEngineException
{
try
{
runningThread = new Thread(this);
runningThread.start();
}
catch (Exception err)
{
stopTest();
StringWriter string = new StringWriter();
PrintWriter writer = new PrintWriter(string);
err.printStackTrace(writer);
throw new JMeterEngineException(string.toString());
}
}
private void removeThreadGroups(List elements)
{
Iterator iter = elements.iterator();
while (iter.hasNext())
{
Object item = iter.next();
if (item instanceof ThreadGroup)
{
iter.remove();
}
else if (!(item instanceof TestElement))
{
iter.remove();
}
}
}
protected void setMode()
{
SearchByClass testPlan = new SearchByClass(TestPlan.class);
getTestTree().traverse(testPlan);
Object[] plan = testPlan.getSearchResults().toArray();
ResultCollector.enableFunctionalMode(
((TestPlan) plan[0]).isFunctionalMode());
}
protected void notifyTestListenersOfStart()
{
Iterator iter = testListeners.getSearchResults().iterator();
while (iter.hasNext())
{
TestListener it = (TestListener)iter.next();
log.info("Notifying test listener: " + it.getClass().getName());
if (host == null)
{
it.testStarted();
}
else
{
it.testStarted(host);
}
}
}
protected void notifyTestListenersOfEnd()
{
log.info("Notifying listeners of end of test");
Iterator iter = testListeners.getSearchResults().iterator();
while (iter.hasNext())
{
TestListener it = (TestListener)iter.next();
log.info("Notifying test listener: " + it.getClass().getName());
if (host == null)
{
it.testEnded();
}
else
{
it.testEnded(host);
}
}
log.info("Test has ended");
}
private ListedHashTree cloneTree(ListedHashTree tree)
{
TreeCloner cloner = new TreeCloner(true);
tree.traverse(cloner);
return cloner.getClonedTree();
}
public void reset()
{
if (running)
{
stopTest();
}
}
public synchronized void threadFinished(JMeterThread thread)
{
allThreads.remove(thread);
if (!serialized && allThreads.size() == 0 && !schcdule_run )
{
stopTest();
}
}
public synchronized void stopTest()
{
Thread stopThread = new Thread(new StopTest());
stopThread.start();
}
public synchronized void stopTest(boolean b)
{
Thread stopThread = new Thread(new StopTest(b));
stopThread.start();
}
public void askThreadsToStop()
{
engine.stopTest(false);
}
private class StopTest implements Runnable
{
boolean now;
private StopTest(){
now=true;
}
private StopTest(boolean b){
now=b;
}
public void run()
{
if (running)
{
running = false;
if (now){
tellThreadsToStop();
} else {
stopAllThreads();
}
try
{
Thread.sleep(10 * allThreads.size());
}
catch (InterruptedException e)
{}
boolean stopped=verifyThreadsStopped();
if (stopped || now){
notifyTestListenersOfEnd();
}
}
}
}
public void run()
{
log.info("Running the test!");
running = true;
testList = new ArrayList();
SearchByClass testPlan = new SearchByClass(TestPlan.class);
getTestTree().traverse(testPlan);
Object[] plan = testPlan.getSearchResults().toArray();
if (plan.length == 0){
System.err.println("Could not find the TestPlan!");
log.error("Could not find the TestPlan!");
System.exit(1);
}
if (((TestPlan) plan[0]).isSerialized())
{
serialized = true;
}
compileTree();
/**
* Notification of test listeners needs to happen after function replacement, but before
* setting RunningVersion to true.
*/
testListeners = new SearchByClass(TestListener.class);
getTestTree().traverse(testListeners);
log.info("About to call test listeners");
Collection col = testListeners.getSearchResults();
col.addAll(testList);
testList=null;
notifyTestListenersOfStart();
getTestTree().traverse(new TurnElementsOn());
List testLevelElements =
new LinkedList(getTestTree().list(getTestTree().getArray()[0]));
removeThreadGroups(testLevelElements);
SearchByClass searcher = new SearchByClass(ThreadGroup.class);
setMode();
getTestTree().traverse(searcher);
TestCompiler.initialize();
//for each thread group, generate threads
// hand each thread the sampler controller
// and the listeners, and the timer
JMeterThread[] threads;
Iterator iter = searcher.getSearchResults().iterator();
/*
* Here's where the test really starts. Run a Full GC now: it's no
* harm at all (just delays test start by a tiny amount) and
* hitting one too early in the test can impair results for short
* tests.
*/
System.gc();
notifier = new ListenerNotifier();
schcdule_run = true;
JMeterContextService.getContext().setSamplingStarted(true);
int groupCount = 0;
while (iter.hasNext())
{
groupCount++;
ThreadGroup group = (ThreadGroup) iter.next();
int numThreads = group.getNumThreads();
boolean onErrorStopTest = group.getOnErrorStopTest();
boolean onErrorStopThread = group.getOnErrorStopThread();
String groupName = group.getName();
int rampUp = group.getRampUp();
float perThreadDelay = ((float) (rampUp * 1000) / (float) numThreads);
threads = new JMeterThread[numThreads];
log.info("Starting " + numThreads + " threads for group "+ groupName
+ ". Ramp up = "+ rampUp + ".");
if (onErrorStopTest) {
log.info("Test will stop on error");
} else if (onErrorStopThread) {
log.info("Thread will stop on error");
} else {
log.info("Continue on error");
}
for (int i = 0; running && i < threads.length; i++)
{
ListedHashTree threadGroupTree =
(ListedHashTree) searcher.getSubTree(group);
threadGroupTree.add(group, testLevelElements);
threads[i] =
new JMeterThread(
cloneTree(threadGroupTree),
this,
notifier);
threads[i].setThreadNum(i);
threads[i].setInitialContext(JMeterContextService.getContext());
threads[i].setInitialDelay((int) (perThreadDelay * (float) i));
String threadName = groupName + " " + groupCount + "-" + (i + 1);
threads[i].setThreadName(threadName);
scheduleThread(threads[i], group);
// Set up variables for stop handling
threads[i].setEngine(this);
threads[i].setOnErrorStopTest(onErrorStopTest);
threads[i].setOnErrorStopThread(onErrorStopThread);
Thread newThread = new Thread(threads[i]);
newThread.setName(threadName);
allThreads.put(threads[i], newThread);
allThreadNames.put(threadName,threads[i]);
if (serialized
&& !iter.hasNext()
&& i == threads.length - 1) //last thread
{
serialized = false;
}
newThread.start();
}
schcdule_run = false;
if (serialized)
{
while (running && allThreads.size() > 0)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{}
}
}
}
}
/**
* This will schedule the time for the JMeterThread.
*
* @param thread
* @param group
*/
private void scheduleThread(JMeterThread thread, ThreadGroup group)
{
//if true the Scheduler is enabled
if (group.getScheduler())
{
//set the starttime for the Thread
if (group.getDelay() > 0 ){// Duration is in seconds
thread.setStartTime(group.getDelay()*1000+(new Date().getTime()));
} else {
thread.setStartTime(group.getStartTime());
}
//set the endtime for the Thread
if (group.getDuration() > 0){// Duration is in seconds
thread.setEndTime(group.getDuration()*1000+(thread.getStartTime()));
} else {
thread.setEndTime(group.getEndTime());
}
//Enables the scheduler
thread.setScheduled(true);
}
}
private boolean verifyThreadsStopped()
{
boolean stoppedAll=true;
Iterator iter = new HashSet(allThreads.keySet()).iterator();
while (iter.hasNext())
{
Thread t = (Thread) allThreads.get(iter.next());
if (t != null && t.isAlive())
{
try
{
t.join(WAIT_TO_DIE);
}
catch (InterruptedException e)
{}
if (t.isAlive())
{
stoppedAll=false;
log.info("Thread won't die: " + t.getName());
}
}
}
return stoppedAll;
}
private void tellThreadsToStop()
{
Iterator iter = new HashSet(allThreads.keySet()).iterator();
while (iter.hasNext())
{
JMeterThread item = (JMeterThread) iter.next();
item.stop();
Thread t = (Thread) allThreads.get(item);
if (t != null)
{
t.interrupt();
}
else
{
log.warn("Lost thread: " + item.getThreadName());
allThreads.remove(item);
}
}
}
private void stopAllThreads()
{
Iterator iter = new HashSet(allThreads.keySet()).iterator();
while (iter.hasNext())
{
JMeterThread item = (JMeterThread) iter.next();
item.stop();
}
}
// Remote exit
public void exit()
{
// Needs to be run in a separate thread to allow RMI call to return OK
Thread t = new Thread(){
public void run(){
//log.info("Pausing");
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
}
log.info("Bye");
System.exit(0);
};
};
log.info("Starting Closedown");
t.start();
}
}