/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.control; import java.io.Serializable; import org.apache.jmeter.samplers.SampleEvent; import org.apache.jmeter.samplers.SampleListener; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.samplers.Sampler; import org.apache.jmeter.testelement.property.BooleanProperty; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterThread; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jmeter.threads.ListenerNotifier; import org.apache.jmeter.threads.SamplePackage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Transaction Controller to measure transaction times * * There are two different modes for the controller: * - generate additional total sample after nested samples (as in JMeter 2.2) * - generate parent sampler containing the nested samples * */ public class TransactionController extends GenericController implements SampleListener, Controller, Serializable { /** * Used to identify Transaction Controller Parent Sampler */ static final String NUMBER_OF_SAMPLES_IN_TRANSACTION_PREFIX = "Number of samples in transaction : "; private static final long serialVersionUID = 234L; private static final String TRUE = Boolean.toString(true); // i.e. "true" private static final String GENERATE_PARENT_SAMPLE = "TransactionController.parent";// $NON-NLS-1$ private static final String INCLUDE_TIMERS = "TransactionController.includeTimers";// $NON-NLS-1$ private static final Logger log = LoggerFactory.getLogger(TransactionController.class); private static final boolean DEFAULT_VALUE_FOR_INCLUDE_TIMERS = true; // default true for compatibility /** * Only used in parent Mode */ private transient TransactionSampler transactionSampler; /** * Only used in NON parent Mode */ private transient ListenerNotifier lnf; /** * Only used in NON parent Mode */ private transient SampleResult res; /** * Only used in NON parent Mode */ private transient int calls; /** * Only used in NON parent Mode */ private transient int noFailingSamples; /** * Cumulated pause time to excluse timer and post/pre processor times * Only used in NON parent Mode */ private transient long pauseTime; /** * Previous end time * Only used in NON parent Mode */ private transient long prevEndTime; /** * Creates a Transaction Controller */ public TransactionController() { lnf = new ListenerNotifier(); } @Override protected Object readResolve(){ super.readResolve(); lnf = new ListenerNotifier(); return this; } /** * @param generateParent flag whether a parent sample should be generated. */ public void setGenerateParentSample(boolean generateParent) { setProperty(new BooleanProperty(GENERATE_PARENT_SAMPLE, generateParent)); } /** * @return {@code true} if a parent sample will be generated */ public boolean isGenerateParentSample() { return getPropertyAsBoolean(GENERATE_PARENT_SAMPLE); } /** * @see org.apache.jmeter.control.Controller#next() */ @Override public Sampler next(){ if (isGenerateParentSample()){ return nextWithTransactionSampler(); } return nextWithoutTransactionSampler(); } ///////////////// Transaction Controller - parent //////////////// private Sampler nextWithTransactionSampler() { // Check if transaction is done if(transactionSampler != null && transactionSampler.isTransactionDone()) { if (log.isDebugEnabled()) { log.debug("End of transaction {}", getName()); } // This transaction is done transactionSampler = null; return null; } // Check if it is the start of a new transaction if (isFirst()) // must be the start of the subtree { if (log.isDebugEnabled()) { log.debug("Start of transaction {}", getName()); } transactionSampler = new TransactionSampler(this, getName()); } // Sample the children of the transaction Sampler subSampler = super.next(); transactionSampler.setSubSampler(subSampler); // If we do not get any sub samplers, the transaction is done if (subSampler == null) { transactionSampler.setTransactionDone(); } return transactionSampler; } @Override protected Sampler nextIsAController(Controller controller) throws NextIsNullException { if (!isGenerateParentSample()) { return super.nextIsAController(controller); } Sampler returnValue; Sampler sampler = controller.next(); if (sampler == null) { currentReturnedNull(controller); // We need to call the super.next, instead of this.next, which is done in GenericController, // because if we call this.next(), it will return the TransactionSampler, and we do not want that. // We need to get the next real sampler or controller returnValue = super.next(); } else { returnValue = sampler; } return returnValue; } ////////////////////// Transaction Controller - additional sample ////////////////////////////// private Sampler nextWithoutTransactionSampler() { if (isFirst()) // must be the start of the subtree { calls = 0; noFailingSamples = 0; res = new SampleResult(); res.setSampleLabel(getName()); // Assume success res.setSuccessful(true); res.sampleStart(); prevEndTime = res.getStartTime();//??? pauseTime = 0; } boolean isLast = current==super.subControllersAndSamplers.size(); Sampler returnValue = super.next(); if (returnValue == null && isLast) // Must be the end of the controller { if (res != null) { // See BUG 55816 if (!isIncludeTimers()) { long processingTimeOfLastChild = res.currentTimeInMillis() - prevEndTime; pauseTime += processingTimeOfLastChild; } res.setIdleTime(pauseTime+res.getIdleTime()); res.sampleEnd(); res.setResponseMessage(TransactionController.NUMBER_OF_SAMPLES_IN_TRANSACTION_PREFIX + calls + ", number of failing samples : " + noFailingSamples); if(res.isSuccessful()) { res.setResponseCodeOK(); } notifyListeners(); } } else { // We have sampled one of our children calls++; } return returnValue; } /** * @param res {@link SampleResult} * @return true if res is the ParentSampler transactions */ public static boolean isFromTransactionController(SampleResult res) { return res.getResponseMessage() != null && res.getResponseMessage().startsWith( TransactionController.NUMBER_OF_SAMPLES_IN_TRANSACTION_PREFIX); } /** * @see org.apache.jmeter.control.GenericController#triggerEndOfLoop() */ @Override public void triggerEndOfLoop() { if(!isGenerateParentSample()) { if (res != null) { res.setIdleTime(pauseTime + res.getIdleTime()); res.sampleEnd(); res.setSuccessful(TRUE.equals(JMeterContextService.getContext().getVariables().get(JMeterThread.LAST_SAMPLE_OK))); res.setResponseMessage(TransactionController.NUMBER_OF_SAMPLES_IN_TRANSACTION_PREFIX + calls + ", number of failing samples : " + noFailingSamples); notifyListeners(); } } else { Sampler subSampler = transactionSampler.getSubSampler(); // See Bug 56811 // triggerEndOfLoop is called when error occurs to end Main Loop // in this case normal workflow doesn't happen, so we need // to notify the childs of TransactionController and // update them with SubSamplerResult if(subSampler instanceof TransactionSampler) { TransactionSampler tc = (TransactionSampler) subSampler; transactionSampler.addSubSamplerResult(tc.getTransactionResult()); } transactionSampler.setTransactionDone(); // This transaction is done transactionSampler = null; } super.triggerEndOfLoop(); } /** * Create additional SampleEvent in NON Parent Mode */ protected void notifyListeners() { // TODO could these be done earlier (or just once?) JMeterContext threadContext = getThreadContext(); JMeterVariables threadVars = threadContext.getVariables(); SamplePackage pack = (SamplePackage) threadVars.getObject(JMeterThread.PACKAGE_OBJECT); if (pack == null) { // If child of TransactionController is a ThroughputController and TPC does // not sample its children, then we will have this // TODO Should this be at warn level ? log.warn("Could not fetch SamplePackage"); } else { SampleEvent event = new SampleEvent(res, threadContext.getThreadGroup().getName(),threadVars, true); // We must set res to null now, before sending the event for the transaction, // so that we can ignore that event in our sampleOccured method res = null; lnf.notifyListeners(event, pack.getSampleListeners()); } } @Override public void sampleOccurred(SampleEvent se) { if (!isGenerateParentSample()) { // Check if we are still sampling our children if(res != null && !se.isTransactionSampleEvent()) { SampleResult sampleResult = se.getResult(); res.setThreadName(sampleResult.getThreadName()); res.setBytes(res.getBytesAsLong() + sampleResult.getBytesAsLong()); res.setSentBytes(res.getSentBytes() + sampleResult.getSentBytes()); if (!isIncludeTimers()) {// Accumulate waiting time for later pauseTime += sampleResult.getEndTime() - sampleResult.getTime() - prevEndTime; prevEndTime = sampleResult.getEndTime(); } if(!sampleResult.isSuccessful()) { res.setSuccessful(false); noFailingSamples++; } res.setAllThreads(sampleResult.getAllThreads()); res.setGroupThreads(sampleResult.getGroupThreads()); res.setLatency(res.getLatency() + sampleResult.getLatency()); res.setConnectTime(res.getConnectTime() + sampleResult.getConnectTime()); } } } @Override public void sampleStarted(SampleEvent e) { } @Override public void sampleStopped(SampleEvent e) { } /** * Whether to include timers and pre/post processor time in overall sample. * @param includeTimers Flag whether timers and pre/post processor should be included in overall sample */ public void setIncludeTimers(boolean includeTimers) { setProperty(INCLUDE_TIMERS, includeTimers, DEFAULT_VALUE_FOR_INCLUDE_TIMERS); } /** * Whether to include timer and pre/post processor time in overall sample. * * @return boolean (defaults to true for backwards compatibility) */ public boolean isIncludeTimers() { return getPropertyAsBoolean(INCLUDE_TIMERS, DEFAULT_VALUE_FOR_INCLUDE_TIMERS); } }