package com.code4reference.jmeter.threads;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.engine.TreeCloner;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.threads.AbstractThreadGroup;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterThread;
import org.apache.jmeter.threads.ListenerNotifier;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.ListedHashTree;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
public class HostThreadGroup extends AbstractThreadGroup {
/**
*
*/
private static final long serialVersionUID = 2406L;
private static final Logger log = LoggingManager.getLoggerForClass();
private static final long WAIT_TO_DIE = JMeterUtils.getPropDefault("jmeterengine.threadstop.wait", 5 * 1000); // 5 seconds
public static final String HOST_NAMES = "HostThreadGroup.hostNames";
public static final String RAMP_TIME = "HostThreadGroup.ramp_time";
public static final String DELAYED_START = "HostThreadGroup.delayedStart";
private volatile boolean hostNamePresent = false;
private String currentHostName;
/**
* Is test (still) running?
*/
private volatile boolean running = false;
// List of active threads
private final Map<JMeterThread, Thread> allThreads = new ConcurrentHashMap<JMeterThread, Thread>();
/**
* No-arg constructor.
*/
public HostThreadGroup() {
currentHostName = JMeterUtils.getLocalHostName();
log.debug(String.format("currentHostName : %s", currentHostName));
}
/**
* set the HostName value.
*
* @param hostName set the hostname value.
*
*/
public void setHostNames(String hostNames) {
if (hostNames == null || hostNames.trim().length() == 0 ) {
hostNamePresent = false;
}
else {
hostNamePresent = true;
}
setProperty(new StringProperty(HOST_NAMES, hostNames.trim()));
}
/**
* Get the Host names value.
*
* @return returns the comma separated host name.
*/
public String getHostNames() {
return getPropertyAsString(HostThreadGroup.HOST_NAMES);
}
/**
* Set the ramp-up value.
*
* @param rampUp
* the ramp-up value.
*/
public void setRampUp(int rampUp) {
setProperty(new IntegerProperty(RAMP_TIME, rampUp));
}
/**
* Get the ramp-up value.
*
* @return the ramp-up value.
*/
public int getRampUp() {
return getPropertyAsInt(HostThreadGroup.RAMP_TIME);
}
private boolean isDelayedStartup() {
return getPropertyAsBoolean(DELAYED_START);
}
@Override
public void threadFinished(JMeterThread thread) {
log.debug("Ending thread " + thread.getThreadName());
allThreads.remove(thread);
}
@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;
}
@Override
public int numberOfActiveThreads() {
return allThreads.size();
}
@Override
public void start(int groupCount,
ListenerNotifier notifier,
ListedHashTree threadGroupTree,
StandardJMeterEngine engine) {
String[] hostList = getHostNames().split(",");
boolean hostPresent = false;
log.info(String.format("hostList.length : %d, hostNamePresent : %s", hostList.length, hostNamePresent));
if (hostNamePresent == true) { //If hosts' name are present then try to match.
for(String host : hostList) {
if(currentHostName.equals(host.trim())){
log.debug(String.format("Found %s name in the given host list", host));
hostPresent = true;
break;
}
}
}
if (hostPresent || hostNamePresent == false) {
//If there is no host name provided that means this thread-group should run on all machine.
running = true;
int numThreads = getNumThreads();
float perThreadDelay = 1.0f;
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 * perThreadDelay));
Thread newThread = new Thread(jmThread, jmThread.getThreadName());
registerStartedThread(jmThread, newThread);
newThread.start();
}
}
}
/**
* This will schedule the time for the JMeterThread.
*
* @param thread JMeterThread
*/
private void scheduleThread(JMeterThread thread, long now) {
thread.setStartTime(System.currentTimeMillis());
thread.setEndTime(System.currentTimeMillis() + 600000); //1 minute delay.
thread.setScheduled(true);
}
private JMeterThread makeThread(int groupCount,
ListenerNotifier notifier, ListedHashTree threadGroupTree,
StandardJMeterEngine engine, int i,
JMeterContext context) { // N.B. Context needs to be fetched in the correct thread
boolean onErrorStopTest = getOnErrorStopTest();
boolean onErrorStopTestNow = getOnErrorStopTestNow();
boolean onErrorStopThread = getOnErrorStopThread();
boolean onErrorStartNextLoop = getOnErrorStartNextLoop();
String groupName = getName();
final JMeterThread jmeterThread = new JMeterThread(cloneTree(threadGroupTree), this, notifier);
jmeterThread.setThreadNum(i);
jmeterThread.setThreadGroup(this);
jmeterThread.setInitialContext(context);
final String threadName = groupName + " " + (groupCount) + "-" + (i + 1);
jmeterThread.setThreadName(threadName);
jmeterThread.setEngine(engine);
jmeterThread.setOnErrorStopTest(onErrorStopTest);
jmeterThread.setOnErrorStopTestNow(onErrorStopTestNow);
jmeterThread.setOnErrorStopThread(onErrorStopThread);
jmeterThread.setOnErrorStartNextLoop(onErrorStartNextLoop);
return jmeterThread;
}
private ListedHashTree cloneTree(ListedHashTree tree) {
TreeCloner cloner = new TreeCloner(true);
tree.traverse(cloner);
return cloner.getClonedTree();
}
/**
* Register Thread when it starts
* @param jMeterThread {@link JMeterThread}
* @param newThread Thread
*/
private void registerStartedThread(JMeterThread jMeterThread, Thread newThread) {
allThreads.put(jMeterThread, newThread);
}
/**
* @return boolean true if all threads stopped
*/
@Override
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
*/
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;
}
@Override
public void waitThreadsStopped() {
for (Thread t : allThreads.values()) {
waitThreadStopped(t);
}
}
private void waitThreadStopped(Thread thread) {
if (thread != null) {
while (thread.isAlive()) {
try {
thread.join(WAIT_TO_DIE);
} catch (InterruptedException e) {
}
}
}
}
@Override
public void tellThreadsToStop() {
running = false;
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
}
}
}
@Override
public void stop() {
running = false;
for (JMeterThread item : allThreads.keySet()) {
item.stop();
}
}
}