到上一篇ThreadGroup为止,基本上在项目中可以完成对JMeterEngine / ThreadGroup 的整合,现在要来分析下JMeterThread,以便更好的集成到项目中。
一个 JMeterThread 好比一个vu(虚拟用户),他的创建和启动已经在ThreadGroup这一篇中完整的分析,我们是将JMeterThread委托给一个叫做DaemonizableNamedRunnable(这是因为我需要整合 weblogic workmanager 统一管理线程池)的实例来运行的, 附DaemonizableNamedRunnable代码
//remote case
runnable = new DaemonizableNamedRunnable() {
@Override
public String getName()
{
return jmThread.getThreadName();
}
@Override
public boolean isDaemon()
{
return true;
}
@Override
public void release()
{
}
@Override
public void run()
{
Security.runAs(subj, new PrivilegedAction<Void>() {
@Override
public Void run()
{
jmThread.run();
return null;
}
});
}
};
//Local Case
runnable = new DaemonizableNamedRunnable() {
@Override
public String getName()
{
return jmThread.getThreadName();
}
@Override
public boolean isDaemon()
{
return true;
}
@Override
public void release()
{
}
@Override
public void run()
{
jmThread.run();
}
};
也就是说外围的一些工作都在ThreadGroup里完成了, 现在看看其内部的结构,首先看下大概的类图(不全,JMeterEngine没有画进去):
结合初始化创建代码:
//ThreadGroup创建JMeterThread
private CustomJMeterThread 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 CustomJMeterThread jmeterThread = new CustomJMeterThread(cloneTree(threadGroupTree), this, notifier);
jmeterThread.setThreadNum(i);
jmeterThread.setThreadGroup(this);//持有线程所属的 ThreadGroup 实例
jmeterThread.setInitialContext(context);
final String threadName = groupName + " " + groupCount + "-" + (i + 1);
jmeterThread.setThreadName(threadName);
jmeterThread.setEngine(engine); //持有一个 当前运行的 JMeterEngine 实例,这个以后会有用
jmeterThread.setOnErrorStopTest(onErrorStopTest);
jmeterThread.setOnErrorStopTestNow(onErrorStopTestNow);
jmeterThread.setOnErrorStopThread(onErrorStopThread);
jmeterThread.setOnErrorStartNextLoop(onErrorStartNextLoop);
return jmeterThread;
}
//JMeterThread 构造方法
public JMeterThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note) {
this.monitor = monitor;
threadVars = new JMeterVariables();
testTree = test;
compiler = new TestCompiler(testTree);
controller = (Controller) testTree.getArray()[0];
SearchByClass<TestIterationListener> threadListenerSearcher = new SearchByClass<TestIterationListener>(TestIterationListener.class); // TL - IS
test.traverse(threadListenerSearcher);
testIterationStartListeners = threadListenerSearcher.getSearchResults();
notifier = note;
running = true;
}
//JMeterThread保存线程上下文变量
public void setInitialContext(JMeterContext context) {
threadVars.putAll(context.getVariables());
}
非常清晰了,JMeterThread的创建和初始化基本上针对以下几点:
1) onError 设置,方便在出错时响应
2) 设置线程所在的ThreadGroup 和 JMeterEngine
3) 保存好上下文到 线程 Local 的 JMeterVariables
4) 保存好测试数据结构 HashTree, 解析出controller, listener. 之前我们提到过JMeter 提供了注入 LoopController / Runtime 来控制线程的循环,和注入ResultCollector之类的Listener来收集测试结果。
初始化完毕,我们再来看运行过程,即run()方法:
@Override
public void run() {
// threadContext is not thread-safe, so keep within thread
JMeterContext threadContext = JMeterContextService.getContext();
LoopIterationListener iterationListener=null;
try {
iterationListener = initRun(threadContext);
while (running) {
Sampler sam = controller.next();
while (running && sam != null) {
process_sampler(sam, null, threadContext);
threadContext.cleanAfterSample();
if(onErrorStartNextLoop || threadContext.isRestartNextLoop()) {
if(threadContext.isRestartNextLoop()) {
triggerEndOfLoopOnParentControllers(sam, threadContext);
sam = null;
threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE);
threadContext.setRestartNextLoop(false);
} else {
boolean lastSampleFailed = !TRUE.equals(threadContext.getVariables().get(LAST_SAMPLE_OK));
if(lastSampleFailed) {
if(log.isDebugEnabled()) {
log.debug("StartNextLoop option is on, Last sample failed, starting next loop");
}
triggerEndOfLoopOnParentControllers(sam, threadContext);
sam = null;
threadContext.getVariables().put(LAST_SAMPLE_OK, TRUE);
} else {
sam = controller.next();
}
}
}
else {
sam = controller.next();
}
}
if (controller.isDone()) {
running = false;
}
}
}
// Might be found by contoller.next()
catch (JMeterStopTestException e) {
log.info("Stopping Test: " + e.toString());
stopTest();
}
catch (JMeterStopTestNowException e) {
log.info("Stopping Test Now: " + e.toString());
stopTestNow();
} catch (JMeterStopThreadException e) {
log.info("Stop Thread seen: " + e.toString());
} catch (Exception e) {
log.error("Test failed!", e);
} catch (ThreadDeath e) {
throw e; // Must not ignore this one
} catch (Error e) {// Make sure errors are output to the log file
log.error("Test failed!", e);
} finally {
currentSampler = null; // prevent any further interrupts
try {
interruptLock.lock(); // make sure current interrupt is finished, prevent another starting yet
threadContext.clear();
log.info("Thread finished: " + threadName);
threadFinished(iterationListener);
monitor.threadFinished(this); // Tell the monitor we are done
JMeterContextService.removeContext(); // Remove the ThreadLocal entry
}
finally {
interruptLock.unlock(); // Allow any pending interrupt to complete (OK because currentSampler == null)
}
}
}
代码结构很清晰:
1) initRun(代码省略):
初始化Thread Context (主要是初始化好当前 Thread / threadgroup / engine 实例, 线程上下文变量, 线程数,线程循环控制器,监听器),
执行 线程正式运行前的delay(若有thread rampup 策略的话)工作
Thread计数,numberOfActiveThreads / numberOfThreadsStarted / numberOfThreads
2)循环运行,直到 running 为 false(一般manually 停止测试就必须会设置该flag为false) OR controller.next()为null,也就是说配置的循环控制器执行完毕
3)finally 清理线程:这里有行比较重要的代码 “monitor.threadFinished(this);” ,这个monitor是什么呢? 往下看。(Q1)
中间循环运行比较复杂的是process_sampler(sam, null, threadContext); 这个方法会真正触发客户端的测试代码的地方,代码很长就略去了,核心的逻辑大概如下:
1)分析Sampler是否是TransactionSampler, 若是解析Transaction 范围的一组Sampler Pack 进行递归process
2)Sampler.sample(Entry e)方法,产生一个SampleResult
3)把result包装成一个SampleEvent 并通知所有的监听器
所有的 SampleListener #sampleOccur 方法被触发, ResultCollector 就会有相应的措施来保存测试结果。
附接口 Sampler(主要有JavaSampler / TransationSampler / BeanShellSampler 等), SampleListener (最终要的是ResultCollector,另外还有ResultAction,Summariser等)代码:
public interface Sampler extends Serializable, TestElement {
/**
* Obtains statistics about the given Entry, and packages the information
* into a SampleResult.
*/
SampleResult sample(Entry e);
}
public interface SampleListener {
/**
* A sample has started and stopped.
*/
void sampleOccurred(SampleEvent e);
/**
* A sample has started.
*/
void sampleStarted(SampleEvent e);
/**
* A sample has stopped.
*/
void sampleStopped(SampleEvent e);
}
留下问题Q1: “monitor.threadFinished(this);” ,这个monitor是什么呢?
这个monitor其实就当前Thread 所在的 ThreadGroup,这里相当与对ThreadGroup的一个回调,如果有些资源清理必须在同一个线程中做的话就会很有用,比如一个case,我的测试代码中需要一个JNDI Context 进行lookup,那么这个JNDI Context 的close工作应该是在创建他的 同一个线程中去做,那么线程结束的时候去清理是最好不过了,所以可以重写ThreadGroup的threadFinished(JMeterThread)方法:
@Override
public void threadFinished(JMeterThread thread)
{
super.threadFinished(thread);
Context ctx = (Context) (thread instanceof ITestResourceHolder ? ((ITestResourceHolder) thread).getResourceMap().get(
JMeterTestResource.JNDI_CONTEXT) : null);
if (ctx != null) {
try {
log.debug("Thread " + Thread.currentThread().getName() + " closing InitialContext " + ctx);
ctx.close();
}
catch (NamingException e) {
log.error("Exception occurred when try to close the InitialContext ", e);
}
finally {
((ITestResourceHolder) thread).getResourceMap().clear();
}
}
}
和JMeterEngine一样,我在扩展JMeterThread时顺便实现了ITestResourceHolder接口用来维护额外的线程变量(比如JNDIContext).
- 大小: 54.9 KB
分享到:
相关推荐
基于springboot个人公务员考试管理系统源码数据库文档.zip
bimdata_api_client-4.2.1-py3-none-any.whl
numpy-1.20.2-cp39-cp39-linux_armv7l.whl
matplotlib-3.3.2-cp39-cp39-linux_armv7l.whl
bimdata_api_client-4.0.0-py3-none-any.whl
ta_lib-0.5.1-cp312-cp312-win32.whl
基于springboot的非学勿扰学习交流平台源码数据库文档.zip
基于springboot云平台的信息安全攻防实训平台源码数据库文档.zip
pillow-10.4.0-cp311-cp311-linux_armv7l.whl
论文描述:该论文研究了某一特定领域的问题,并提出了新的解决方案。论文首先对问题进行了详细的分析和理解,并对已有的研究成果进行了综述。然后,论文提出了一种全新的解决方案,包括算法、模型或方法。在整个研究过程中,论文使用了合适的实验设计和数据集,并进行了充分的实验验证。最后,论文对解决方案的性能进行了全面的评估和分析,并提出了进一步的研究方向。 源码内容描述:该源码实现了论文中提出的新的解决方案。源码中包含了算法、模型或方法的具体实现代码,以及相关的数据预处理、实验设计和性能评估代码。源码中还包括了合适的注释和文档,以方便其他研究者理解和使用。源码的实现应该具有可读性、可维护性和高效性,并能够复现论文中的实验结果。此外,源码还应该尽可能具有通用性,以便在其他类似问题上进行进一步的应用和扩展。
基于springboot+web的学生作业管理系统源码数据库文档.zip
论文描述:该论文研究了某一特定领域的问题,并提出了新的解决方案。论文首先对问题进行了详细的分析和理解,并对已有的研究成果进行了综述。然后,论文提出了一种全新的解决方案,包括算法、模型或方法。在整个研究过程中,论文使用了合适的实验设计和数据集,并进行了充分的实验验证。最后,论文对解决方案的性能进行了全面的评估和分析,并提出了进一步的研究方向。 源码内容描述:该源码实现了论文中提出的新的解决方案。源码中包含了算法、模型或方法的具体实现代码,以及相关的数据预处理、实验设计和性能评估代码。源码中还包括了合适的注释和文档,以方便其他研究者理解和使用。源码的实现应该具有可读性、可维护性和高效性,并能够复现论文中的实验结果。此外,源码还应该尽可能具有通用性,以便在其他类似问题上进行进一步的应用和扩展。
基于springboot网上书店源码数据库文档.zip
numpy-2.1.3-cp311-cp311-linux_armv7l.whl
基于springboot的校园消费点评系统源码数据库文档.zip
ta_lib-0.5.1-cp37-cp37m-win32.whl
Java高校学生信息管理系统源码 一、源码介绍 高校学生信息管理系统设计主要应用JAVA语言编程和mysql数据库连接等相关知识,需要熟练掌握Struts2、Spring、Hibernate基础 二、主要功能 高校学生信息管理系统设计主要应用JAVA语言编程和mysql数据库连接等相关知识,需要熟练掌握Struts2、Spring、Hibernate基础,将所 学知识在生活中灵活运用,高校学生信息管理系统的主要设计功能如下: (1)学生信息管理模块:包括所有学生信息的查询(用分页列表显示)、查看某个学生的详细信息、删除某学生信息、修改某学生信息以及学生信息的录入等子功能 (2)学生成绩管理模块:包括成绩信息录入、学生成绩查询、查看某个学生的成绩表以及删除学生
opencv_python-4.4.0.42-cp39-cp39-linux_armv7l.whl
基于springboot扶贫助农系统源码数据库文档.zip
论文描述:该论文研究了某一特定领域的问题,并提出了新的解决方案。论文首先对问题进行了详细的分析和理解,并对已有的研究成果进行了综述。然后,论文提出了一种全新的解决方案,包括算法、模型或方法。在整个研究过程中,论文使用了合适的实验设计和数据集,并进行了充分的实验验证。最后,论文对解决方案的性能进行了全面的评估和分析,并提出了进一步的研究方向。 源码内容描述:该源码实现了论文中提出的新的解决方案。源码中包含了算法、模型或方法的具体实现代码,以及相关的数据预处理、实验设计和性能评估代码。源码中还包括了合适的注释和文档,以方便其他研究者理解和使用。源码的实现应该具有可读性、可维护性和高效性,并能够复现论文中的实验结果。此外,源码还应该尽可能具有通用性,以便在其他类似问题上进行进一步的应用和扩展。