Programming with JMeter-- Sampler and Listeners
? ? ? ?SampleListener和TestStateListener作为很重要的两个Listener贯穿了JMeter整个test的生命周期。
? ? ? ?先看TestStateListener,两个方法名就能让我们猜到作用:
1) 在测试开始时候调用 testStarted
2) ? ?在测试结束的时候调用testEnded ? ?
? ? ??
? ? ? ?这些生命周期函数的调用是在什么地方呢?这个又要回到JMeterEngine了,看StandardJMeterEngine#run()方法,在启动ThreadGroup之前以及整个测试结束之后,代码是这样的:
?
public void run(){ 。。。。。。 SearchByClass<TestStateListener> testListeners = new SearchByClass<TestStateListener>(TestStateListener.class); // TL - S&E test.traverse(testListeners); // Merge in any additional test listeners // currently only used by the function parser testListeners.getSearchResults().addAll(testList); testList.clear(); // no longer needed if (!startListenersLater ) { notifyTestListenersOfStart(testListeners); } test.traverse(new TurnElementsOn()); if (startListenersLater) { notifyTestListenersOfStart(testListeners); } 。。。。。。 ////此处省略 n 行代码 。。。。。。 notifyTestListenersOfEnd(testListeners); }?
?
? ? ? ? ? 代码解释如下:
? ? ? ? ? 1) 从HashTree中找出所有TestStateListener类型
? ? ? ? ? 2) 在测试正式开始之前,触发这些TestStateListener,notifyTestListenersOfStart(testListeners)这个方法中会调用testListener#testStarted方法
? ? ? ? ? 3) 在测试执行完之后,再触发这些TestStateListener,notifyTestListenersOfEnd(testListeners)这个方法会调用testListener#testEnded方法
?
? ? ? ? ? ?理解了这些,来看看具体三种我们会用到TestStateListener(如上面类图):TestPlan,JavaSampler,RresultCollector,看看他们的testStarted()分别做些什么
?1. TestPlan:主要设置一些文件路径,方便查找config, log等文件,不需要再去干预。
?2. JavaSampler: 主要是加载client类,类名是根据HashTree中的配置数据。这个比较重要,因为这里是整合测试逻辑的地方,看下代码
? ? ? ? ? ? ? ?
@Override public void testStarted() { log.debug(whoAmI() + "\ttestStarted"); initClass(); }private void initClass() { String name = getClassname().trim(); try { javaClass = Class.forName(name, false, Thread.currentThread().getContextClassLoader()); Method method = javaClass.getMethod("teardownTest", new Class[]{JavaSamplerContext.class}); isToBeRegistered = !method.getDeclaringClass().equals(AbstractJavaSamplerClient.class); log.info("Created class: " + name + ". Uses tearDownTest: " + isToBeRegistered); } catch (Exception e) { log.error(whoAmI() + "\tException initialising: " + name, e); } }/** * Gets the Classname attribute of the JavaConfig object * * @return the Classname value */ public String getClassname() { return getPropertyAsString(CLASSNAME); }?3) ResultCollector:主要是初始化File Output,做好写测试结果的准备。代码如下:@Override public void testStarted(String host) { synchronized(LOCK){ instanceCount++; try { initializeFileOutput(); if (getVisualizer() != null) { this.isStats = getVisualizer().isStats(); } } catch (Exception e) { log.error("", e); } } inTest = true; if(summariser != null) { summariser.testStarted(host); } }? ? ? ?/////In JmeterThread@Override public void run() {......process_sampler(sam, null, threadContext);......}private SampleResult process_sampler(Sampler current, Sampler parent, JMeterContext threadContext){...... SampleResult result = sampler.sample(null); 。。。。。。 List<SampleListener> sampleListeners = getSampleListeners(pack, transactionPack, transactionSampler); notifyListeners(sampleListeners, result);......}private void notifyListeners(List<SampleListener> listeners, SampleResult result) { SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); notifier.notifyListeners(event, listeners);}?? ? 如上代码,每一个JMeterThread在运行时,当一个Sampler#sample触发得到一个SampleResult,那么回到JavaSampler,前文所述在TestStarted触发时,JavaSampler作为TestStateListener已经加载了Client类,那么他到底有什么用呢?现在可以揭晓了:? ? ?////JavaSampler public SampleResult sample(Entry entry) { Arguments args = getArguments(); args.addArgument(TestElement.NAME, getName()); // Allow Sampler access // to test element name context = new JavaSamplerContext(args); if (javaClient == null) { log.debug(whoAmI() + "\tCreating Java Client"); javaClient = createJavaClient(); javaClient.setupTest(context); } SampleResult result = javaClient.runTest(context); // Only set the default label if it has not been set if (result != null && result.getSampleLabel().length() == 0) { result.setSampleLabel(getName()); } return result; }private JavaSamplerClient createJavaClient() { if (javaClass == null) { // failed to initialise the class return new ErrorSamplerClient(); } JavaSamplerClient client; try { client = (JavaSamplerClient) javaClass.newInstance(); if (log.isDebugEnabled()) { log.debug(whoAmI() + "\tCreated:\t" + getClassname() + "@" + Integer.toHexString(client.hashCode())); } if(isToBeRegistered) { TEAR_DOWN_SET.add(this); } } catch (Exception e) { log.error(whoAmI() + "\tException creating: " + getClassname(), e); client = new ErrorSamplerClient(); } return client; }? ? ? ? 其实没什么神奇的,读代码可以得到几点信息:@Override public void sampleOccurred(SampleEvent event) { SampleResult result = event.getResult(); if (isSampleWanted(result.isSuccessful())) { sendToVisualizer(result); if (out != null && !isResultMarked(result) && !this.isStats) { SampleSaveConfiguration config = getSaveConfig(); result.setSaveConfig(config); try { if (config.saveAsXml()) { SaveService.saveSampleResult(event, out); } else { // !saveAsXml String savee = CSVSaveService.resultToDelimitedString(event); out.println(savee); } } catch (Exception err) { log.error("Error trying to record a sample", err); // should throw exception back to caller } } } if(summariser != null) { summariser.sampleOccurred(event); } }? ? ? ? ?可以看到,ResultCollector会去保存xml或者csv格式的测试结果文件,还会触发汇总Summariser.sampleOccurred(event),另外还有GUI上的显示等等,这些其实已经不是太重要了,因为实际测试中很可能是一个集群的环境,测试结果可能需要通过Remote写到中央测试机上。也就是说实现自己的ResultCollector不可避免的。? ? ? ? 所有的JMeterThread结束之后,回到StandardJMeterEngine#run()最后一行notifyTestListenersOfEnd(testListeners),TestStateListener(ResultCollector, JavaSampler, TestPlan)的testEnded又被触发:1)TestPlan#testEnded(): 关闭测试开始时候打开的文件2)ResultCollector#testEnded():写文件结束符(xml的话必须谢结束的tag </...>),关闭stream,file等3)若实现了teardownTest方法,则调用JavaSampleClient#testdownTest(JavaSamplerContext)? ? ? ? 到此,JMeter的大致的生命周期介绍完毕,当然不是全部的生命周期都涵盖在这篇文章里了,比如在上一篇讲到过,JMeterThread运行结束退出前有个回调JMeterThreadMonitor#threadFinished(JMeterThread)在这里没有提及,这个JMeterThreadMonitor接口的实现类主要就是ThreadGroup。可以看前几篇文章去了解。?