All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jmeter.threads.JMeterThread Maven / Gradle / Ivy

/*
 * 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.threads;

import io.metersphere.constants.BackendListenerConstants;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.assertions.Assertion;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.control.Controller;
import org.apache.jmeter.control.IteratingController;
import org.apache.jmeter.control.TransactionSampler;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.samplers.*;
import org.apache.jmeter.testbeans.TestBeanHelper;
import org.apache.jmeter.testelement.*;
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
import org.apache.jmeter.timers.Timer;
import org.apache.jmeter.timers.TimerService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.HashTreeTraverser;
import org.apache.jorphan.collections.ListedHashTree;
import org.apache.jorphan.collections.SearchByClass;
import org.apache.jorphan.util.JMeterError;
import org.apache.jorphan.util.JMeterStopTestException;
import org.apache.jorphan.util.JMeterStopTestNowException;
import org.apache.jorphan.util.JMeterStopThreadException;
import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 * The JMeter interface to the sampling process, allowing JMeter to see the
 * timing, add listeners for sampling events and to stop the sampling process.
 */
public class JMeterThread implements Runnable, Interruptible {
    private static final Logger log = LoggerFactory.getLogger(JMeterThread.class);

    public static final String PACKAGE_OBJECT = "JMeterThread.pack"; // $NON-NLS-1$

    public static final String LAST_SAMPLE_OK = "JMeterThread.last_sample_ok"; // $NON-NLS-1$

    private static final String TRUE = Boolean.toString(true); // i.e. "true"

    /**
     * How often to check for shutdown during ramp-up, default 1000ms
     */
    private static final int RAMPUP_GRANULARITY =
            JMeterUtils.getPropDefault("jmeterthread.rampup.granularity", 1000); // $NON-NLS-1$

    private static final float TIMER_FACTOR = JMeterUtils.getPropDefault("timer.factor", 1.0f);

    private static final TimerService TIMER_SERVICE = TimerService.getInstance();

    private static final float ONE_AS_FLOAT = 1.0f;

    private static final boolean APPLY_TIMER_FACTOR = Float.compare(TIMER_FACTOR, ONE_AS_FLOAT) != 0;

    private final Controller threadGroupLoopController;

    private final HashTree testTree;

    private final TestCompiler compiler;

    private final JMeterThreadMonitor monitor;

    private final JMeterVariables threadVars;

    // Note: this is only used to implement TestIterationListener#testIterationStart
    // Since this is a frequent event, it makes sense to create the list once rather than scanning each time
    // The memory used will be released when the thread finishes
    private final Collection testIterationStartListeners;

    private final Collection sampleMonitors;

    private final ListenerNotifier notifier;

    /*
     * The following variables are set by StandardJMeterEngine.
     * This is done before start() is called, so the values will be published to the thread safely
     * TODO - consider passing them to the constructor, so that they can be made final
     * (to avoid adding lots of parameters, perhaps have a parameter wrapper object.
     */
    private String threadName;

    private int initialDelay = 0;

    private int threadNum = 0;

    private long startTime = 0;

    private long endTime = 0;

    private final boolean isSameUserOnNextIteration;

    // based on this scheduler is enabled or disabled
    private boolean scheduler = false;

    // Gives access to parent thread threadGroup
    private AbstractThreadGroup threadGroup;

    private StandardJMeterEngine engine = null; // For access to stop methods.

    /*
     * The following variables may be set/read from multiple threads.
     */
    private volatile boolean running; // may be set from a different thread

    private volatile boolean onErrorStopTest;

    private volatile boolean onErrorStopTestNow;

    private volatile boolean onErrorStopThread;

    private volatile boolean onErrorStartNextLoop;

    private volatile Sampler currentSamplerForInterruption;

    private final ReentrantLock interruptLock = new ReentrantLock(); // ensure that interrupt cannot overlap with shutdown

    public JMeterThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note) {
        this(test, monitor, note, false);
    }

    public JMeterThread(HashTree test, JMeterThreadMonitor monitor, ListenerNotifier note, Boolean isSameUserOnNextIteration) {
        this.monitor = monitor;
        threadVars = new JMeterVariables();
        testTree = test;
        compiler = new TestCompiler(testTree);
        threadGroupLoopController = (Controller) testTree.getArray()[0];
        SearchByClass threadListenerSearcher = new SearchByClass<>(TestIterationListener.class); // TL - IS
        test.traverse(threadListenerSearcher);
        testIterationStartListeners = threadListenerSearcher.getSearchResults();
        SearchByClass sampleMonitorSearcher = new SearchByClass<>(SampleMonitor.class);
        test.traverse(sampleMonitorSearcher);
        sampleMonitors = sampleMonitorSearcher.getSearchResults();
        notifier = note;
        running = true;
        this.isSameUserOnNextIteration = isSameUserOnNextIteration;
    }

    @Deprecated
    @API(status = API.Status.DEPRECATED, since = "5.5")
    public void setInitialContext(JMeterContext context) {
        putVariables(context.getVariables());
    }

    /**
     * Updates the variables with all entries found in the variables in {@code vars}
     *
     * @param variables {@link JMeterVariables} with the entries to be updated
     */
    @API(status = API.Status.STABLE, since = "5.5")
    public void putVariables(JMeterVariables variables) {
        threadVars.putAll(variables);
    }

    /**
     * Enable the scheduler for this JMeterThread.
     *
     * @param sche flag whether the scheduler should be enabled
     */
    public void setScheduled(boolean sche) {
        this.scheduler = sche;
    }

    /**
     * Set the StartTime for this Thread.
     *
     * @param stime the StartTime value.
     */
    public void setStartTime(long stime) {
        startTime = stime;
    }

    /**
     * Get the start time value.
     *
     * @return the start time value.
     */
    public long getStartTime() {
        return startTime;
    }

    /**
     * Set the EndTime for this Thread.
     *
     * @param etime the EndTime value.
     */
    public void setEndTime(long etime) {
        endTime = etime;
    }

    /**
     * Get the end time value.
     *
     * @return the end time value.
     */
    public long getEndTime() {
        return endTime;
    }

    /**
     * Check if the scheduled time is completed.
     */
    private void stopSchedulerIfNeeded() {
        long now = System.currentTimeMillis();
        if (now >= endTime) {
            running = false;
            log.info("Stopping because end time detected by thread: {}", threadName);
        }
    }

    /**
     * Wait until the scheduled start time if necessary
     */
    private void startScheduler() {
        long delay = startTime - System.currentTimeMillis();
        delayBy(delay, "startScheduler");
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    @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 = threadGroupLoopController.next();
                while (running && sam != null) {
                    processSampler(sam, null, threadContext);
                    threadContext.cleanAfterSample();

                    boolean lastSampleOk = TRUE.equals(threadContext.getVariables().get(LAST_SAMPLE_OK));
                    // restart of the next loop
                    // - was requested through threadContext
                    // - or the last sample failed AND the onErrorStartNextLoop option is enabled
                    if (threadContext.getTestLogicalAction() != TestLogicalAction.CONTINUE
                            || (onErrorStartNextLoop && !lastSampleOk)) {
                        if (log.isDebugEnabled() && onErrorStartNextLoop
                                && threadContext.getTestLogicalAction() != TestLogicalAction.CONTINUE) {
                            log.debug("Start Next Thread Loop option is on, Last sample failed, starting next thread loop");
                        }
                        if (onErrorStartNextLoop && !lastSampleOk) {
                            triggerLoopLogicalActionOnParentControllers(sam, threadContext, JMeterThread::continueOnThreadLoop);
                        } else {
                            switch (threadContext.getTestLogicalAction()) {
                                case BREAK_CURRENT_LOOP:
                                    triggerLoopLogicalActionOnParentControllers(sam, threadContext, JMeterThread::breakOnCurrentLoop);
                                    break;
                                case START_NEXT_ITERATION_OF_THREAD:
                                    triggerLoopLogicalActionOnParentControllers(sam, threadContext, JMeterThread::continueOnThreadLoop);
                                    break;
                                case START_NEXT_ITERATION_OF_CURRENT_LOOP:
                                    triggerLoopLogicalActionOnParentControllers(sam, threadContext, JMeterThread::continueOnCurrentLoop);
                                    break;
                                default:
                                    break;
                            }
                        }
                        threadContext.setTestLogicalAction(TestLogicalAction.CONTINUE);
                        sam = null;
                        setLastSampleOk(threadContext.getVariables(), true);
                    } else {
                        sam = threadGroupLoopController.next();
                    }
                }

                // It would be possible to add finally for Thread Loop here
                if (threadGroupLoopController.isDone()) {
                    running = false;
                    log.info("Thread is done: {}", threadName);
                }
            }
        }
        // Might be found by controller.next()
        catch (JMeterStopTestException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stopping Test: {}", e.toString());
            }
            shutdownTest();
        } catch (JMeterStopTestNowException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stopping Test Now: {}", e.toString());
            }
            stopTestNow();
        } catch (JMeterStopThreadException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stop Thread seen for thread {}, reason: {}", getThreadName(), e.toString());
            }
        } catch (Exception | JMeterError e) {
            log.error("Test failed!", e);
        } catch (ThreadDeath e) {
            throw e; // Must not ignore this one
        } finally {
            currentSamplerForInterruption = null; // prevent any further interrupts
            interruptLock.lock();  // make sure current interrupt is finished, prevent another starting yet
            try {
                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)
            }
        }
    }

    /**
     * Trigger break/continue/switch to next thread Loop  depending on consumer implementation
     *
     * @param sampler       Sampler Base sampler
     * @param threadContext
     * @param consumer      Consumer that will process the tree of elements up to root node
     */
    private void triggerLoopLogicalActionOnParentControllers(Sampler sampler, JMeterContext threadContext,
                                                             Consumer consumer) {
        TransactionSampler transactionSampler = null;
        if (sampler instanceof TransactionSampler) {
            transactionSampler = (TransactionSampler) sampler;
        }

        Sampler realSampler = findRealSampler(sampler);
        if (realSampler == null) {
            throw new IllegalStateException(
                    "Got null subSampler calling findRealSampler for:" +
                            (sampler != null ? sampler.getName() : "null") + ", sampler:" + sampler);
        }
        // Find parent controllers of current sampler
        FindTestElementsUpToRootTraverser pathToRootTraverser = new FindTestElementsUpToRootTraverser(realSampler);
        testTree.traverse(pathToRootTraverser);

        consumer.accept(pathToRootTraverser);

        // bug 52968
        // When using Start Next Loop option combined to TransactionController.
        // if an error occurs in a Sample (child of TransactionController)
        // then we still need to report the Transaction in error (and create the sample result)
        if (transactionSampler != null) {
            SamplePackage transactionPack = compiler.configureTransactionSampler(transactionSampler);
            doEndTransactionSampler(transactionSampler, null, transactionPack, threadContext);
        }
    }

    /**
     * Executes a continue of current loop, equivalent of "continue" in algorithm.
     * As a consequence it ends the first loop it finds on the path to root
     *
     * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser}
     */
    private static void continueOnCurrentLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) {
        List controllersToReinit = pathToRootTraverser.getControllersToRoot();
        for (Controller parentController : controllersToReinit) {
            if (parentController instanceof AbstractThreadGroup) {
                AbstractThreadGroup tg = (AbstractThreadGroup) parentController;
                tg.startNextLoop();
            } else if (parentController instanceof IteratingController) {
                ((IteratingController) parentController).startNextLoop();
                break;
            } else {
                parentController.triggerEndOfLoop();
            }
        }
    }

    /**
     * Executes a break of current loop, equivalent of "break" in algorithm.
     * As a consequence it ends the first loop it finds on the path to root
     *
     * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser}
     */
    private static void breakOnCurrentLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) {
        List controllersToReinit = pathToRootTraverser.getControllersToRoot();
        for (Controller parentController : controllersToReinit) {
            if (parentController instanceof AbstractThreadGroup) {
                AbstractThreadGroup tg = (AbstractThreadGroup) parentController;
                tg.breakThreadLoop();
            } else if (parentController instanceof IteratingController) {
                ((IteratingController) parentController).breakLoop();
                break;
            } else {
                parentController.triggerEndOfLoop();
            }
        }
    }

    /**
     * Executes a restart of Thread loop, equivalent of "continue" in algorithm but on Thread Loop.
     * As a consequence it ends all loop on the path to root
     *
     * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser}
     */
    private static void continueOnThreadLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) {
        List controllersToReinit = pathToRootTraverser.getControllersToRoot();
        for (Controller parentController : controllersToReinit) {
            if (parentController instanceof AbstractThreadGroup) {
                AbstractThreadGroup tg = (AbstractThreadGroup) parentController;
                tg.startNextLoop();
            } else {
                parentController.triggerEndOfLoop();
            }
        }
    }

    /**
     * Find the Real sampler (Not TransactionSampler) that really generated an error
     * The Sampler provided is not always the "real" one, it can be a TransactionSampler,
     * if there are some other controllers (SimpleController or other implementations) between this TransactionSampler and the real sampler,
     * triggerEndOfLoop will not be called for those controllers leaving them in "ugly" state.
     * the following method will try to find the sampler that really generate an error
     *
     * @return {@link Sampler}
     */
    private Sampler findRealSampler(Sampler sampler) {
        Sampler realSampler = sampler;
        while (realSampler instanceof TransactionSampler) {
            realSampler = ((TransactionSampler) realSampler).getSubSampler();
        }
        return realSampler;
    }

    /**
     * Process the current sampler, handling transaction samplers.
     *
     * @param current       sampler
     * @param parent        sampler
     * @param threadContext
     * @return SampleResult if a transaction was processed
     */
    private SampleResult processSampler(Sampler current, Sampler parent, JMeterContext threadContext) {
        SampleResult transactionResult = null;
        // Check if we are running a transaction
        TransactionSampler transactionSampler = null;
        // Find the package for the transaction
        SamplePackage transactionPack = null;
        try {
            if (current instanceof TransactionSampler) {
                transactionSampler = (TransactionSampler) current;
                transactionPack = compiler.configureTransactionSampler(transactionSampler);

                // Check if the transaction is done
                if (transactionSampler.isTransactionDone()) {
                    transactionResult = doEndTransactionSampler(transactionSampler,
                            parent,
                            transactionPack,
                            threadContext);
                    // Transaction is done, we do not have a sampler to sample
                    current = null;
                } else {
                    Sampler prev = current;
                    // It is the sub sampler of the transaction that will be sampled
                    current = transactionSampler.getSubSampler();
                    if (current instanceof TransactionSampler) {
                        SampleResult res = processSampler(current, prev, threadContext);// recursive call
                        threadContext.setCurrentSampler(prev);
                        current = null;
                        if (res != null) {
                            transactionSampler.addSubSamplerResult(res);
                        }
                    }
                }
            }

            // Check if we have a sampler to sample
            if (current != null) {
                executeSamplePackage(current, transactionSampler, transactionPack, threadContext);
            }

            if (scheduler) {
                // checks the scheduler to stop the iteration
                stopSchedulerIfNeeded();
            }

        } catch (JMeterStopTestException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stopping Test: {}", e.toString());
            }
            shutdownTest();
        } catch (JMeterStopTestNowException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stopping Test with interruption of current samplers: {}", e.toString());
            }
            stopTestNow();
        } catch (JMeterStopThreadException e) { // NOSONAR
            if (log.isInfoEnabled()) {
                log.info("Stopping Thread: {}", e.toString());
            }
            stopThread();
        } catch (Exception e) {
            if (current != null) {
                log.error("Error while processing sampler: '{}'.", current.getName(), e);
            } else {
                log.error("Error while processing sampler.", e);
            }
        }
        if (!running
                && transactionResult == null
                && transactionSampler != null
                && transactionPack != null) {
            transactionResult = doEndTransactionSampler(transactionSampler, parent, transactionPack, threadContext);
        }

        return transactionResult;
    }

    private void fillThreadInformation(SampleResult result,
                                       int nbActiveThreadsInThreadGroup,
                                       int nbTotalActiveThreads) {
        result.setGroupThreads(nbActiveThreadsInThreadGroup);
        result.setAllThreads(nbTotalActiveThreads);
        result.setThreadName(threadName);
    }

    /**
     * Execute the sampler with its pre/post processors, timers, assertions
     * Broadcast the result to the sample listeners
     */
    private void executeSamplePackage(Sampler current,
                                      TransactionSampler transactionSampler,
                                      SamplePackage transactionPack,
                                      JMeterContext threadContext) {

        threadContext.setCurrentSampler(current);
        // Get the sampler ready to sample
        SamplePackage pack = compiler.configureSampler(current);
        runPreProcessors(pack.getPreProcessors());

        // Hack: save the package for any transaction controllers
        threadVars.putObject(PACKAGE_OBJECT, pack);

        delay(pack.getTimers());
        SampleResult result = null;
        if (running) {
            Sampler sampler = pack.getSampler();

            // 执行前发给监听
            if (this.threadGroup != null && this.threadGroup.getPropertyAsBoolean(BackendListenerConstants.MS_DEBUG.name())) {
                List sampleListeners = pack.getSampleListeners();
                if (CollectionUtils.isNotEmpty(sampleListeners)) {
                    for (SampleListener sampleListener : sampleListeners) {
                        try {
                            if (sampleListener != null && StringUtils.equals(sampleListener.getClass().getSimpleName(), "MsDebugListener")) {
                                SampleEvent event = new SampleEvent(null, current.getPropertyAsString("MS-RESOURCE-ID"), threadVars);
                                sampleListener.sampleStarted(event);
                                break;
                            }
                        } catch (RuntimeException e) {
                            log.error("自定义提前发送监听失败.", e);
                        }
                    }
                }
            }
            //======
            result = doSampling(threadContext, sampler);
        }
        // If we got any results, then perform processing on the result
        if (result != null) {
            if (!result.isIgnore()) {
                int nbActiveThreadsInThreadGroup = threadGroup.getNumberOfThreads();
                int nbTotalActiveThreads = JMeterContextService.getNumberOfThreads();
                fillThreadInformation(result, nbActiveThreadsInThreadGroup, nbTotalActiveThreads);
                SampleResult[] subResults = result.getSubResults();
                if (subResults != null) {
                    for (SampleResult subResult : subResults) {
                        fillThreadInformation(subResult, nbActiveThreadsInThreadGroup, nbTotalActiveThreads);
                    }
                }
                threadContext.setPreviousResult(result);
                runPostProcessors(pack.getPostProcessors());
                checkAssertions(pack.getAssertions(), result, threadContext);
                // PostProcessors can call setIgnore, so reevaluate here
                if (!result.isIgnore()) {
                    // Do not send subsamples to listeners which receive the transaction sample
                    List sampleListeners = getSampleListeners(pack, transactionPack, transactionSampler);
                    notifyListeners(sampleListeners, result);
                }
                compiler.done(pack);
                // Add the result as subsample of transaction if we are in a transaction
                if (transactionSampler != null && !result.isIgnore()) {
                    transactionSampler.addSubSamplerResult(result);
                }
            } else {
                // This call is done by checkAssertions() , as we don't call it
                // for isIgnore, we explictely call it here
                setLastSampleOk(threadContext.getVariables(), result.isSuccessful());
                compiler.done(pack);
            }
            // Check if thread or test should be stopped
            if (result.isStopThread() || (!result.isSuccessful() && onErrorStopThread)) {
                stopThread();
            }
            if (result.isStopTest() || (!result.isSuccessful() && onErrorStopTest)) {
                shutdownTest();
            }
            if (result.isStopTestNow() || (!result.isSuccessful() && onErrorStopTestNow)) {
                stopTestNow();
            }
            if (result.getTestLogicalAction() != TestLogicalAction.CONTINUE) {
                threadContext.setTestLogicalAction(result.getTestLogicalAction());
            }
        } else {
            compiler.done(pack); // Finish up
        }
    }

    /**
     * Call sample on Sampler handling:
     * 
    *
  • setting up ThreadContext
  • *
  • initializing sampler if needed
  • *
  • positioning currentSamplerForInterruption for potential interruption
  • *
  • Playing SampleMonitor before and after sampling
  • *
  • resetting currentSamplerForInterruption
  • *
* * @param threadContext {@link JMeterContext} * @param sampler {@link Sampler} * @return {@link SampleResult} */ private SampleResult doSampling(JMeterContext threadContext, Sampler sampler) { sampler.setThreadContext(threadContext); sampler.setThreadName(threadName); TestBeanHelper.prepare(sampler); // Perform the actual sample currentSamplerForInterruption = sampler; if (!sampleMonitors.isEmpty()) { for (SampleMonitor sampleMonitor : sampleMonitors) { if (sampleMonitor instanceof TestElement) { TestBeanHelper.prepare((TestElement) sampleMonitor); } sampleMonitor.sampleStarting(sampler); } } try { return sampler.sample(null); } finally { if (!sampleMonitors.isEmpty()) { for (SampleMonitor sampleMonitor : sampleMonitors) { sampleMonitor.sampleEnded(sampler); } } currentSamplerForInterruption = null; } } private SampleResult doEndTransactionSampler( TransactionSampler transactionSampler, Sampler parent, SamplePackage transactionPack, JMeterContext threadContext) { // Get the transaction sample result SampleResult transactionResult = transactionSampler.getTransactionResult(); fillThreadInformation(transactionResult, threadGroup.getNumberOfThreads(), JMeterContextService.getNumberOfThreads()); // Check assertions for the transaction sample checkAssertions(transactionPack.getAssertions(), transactionResult, threadContext); // Notify listeners with the transaction sample result if (!(parent instanceof TransactionSampler)) { notifyListeners(transactionPack.getSampleListeners(), transactionResult); } compiler.done(transactionPack); return transactionResult; } /** * Get the SampleListeners for the sampler. Listeners who receive transaction sample * will not be in this list. * * @param samplePack * @param transactionPack * @param transactionSampler * @return the listeners who should receive the sample result */ private List getSampleListeners(SamplePackage samplePack, SamplePackage transactionPack, TransactionSampler transactionSampler) { List sampleListeners = samplePack.getSampleListeners(); // Do not send subsamples to listeners which receive the transaction sample if (transactionSampler != null) { List onlySubSamplerListeners = new ArrayList<>(); List transListeners = transactionPack.getSampleListeners(); for (SampleListener listener : sampleListeners) { // Check if this instance is present in transaction listener list boolean found = false; for (SampleListener trans : transListeners) { // Check for the same instance if (trans == listener) { found = true; break; } } if (!found) { onlySubSamplerListeners.add(listener); } } sampleListeners = onlySubSamplerListeners; } return sampleListeners; } /** * Store {@link JMeterThread#LAST_SAMPLE_OK} in JMeter Variables context */ private void setLastSampleOk(JMeterVariables variables, boolean value) { variables.put(LAST_SAMPLE_OK, Boolean.toString(value)); } /** * @param threadContext * @return the iteration listener */ private IterationListener initRun(JMeterContext threadContext) { threadVars.putObject(JMeterVariables.VAR_IS_SAME_USER_KEY, isSameUserOnNextIteration); threadContext.setVariables(threadVars); threadContext.setThreadNum(getThreadNum()); setLastSampleOk(threadVars, true); threadContext.setThread(this); threadContext.setThreadGroup(threadGroup); threadContext.setEngine(engine); testTree.traverse(compiler); if (scheduler) { // set the scheduler to start startScheduler(); } rampUpDelay(); // TODO - how to handle thread stopped here if (log.isInfoEnabled()) { log.info("Thread started: {}", Thread.currentThread().getName()); } /* * Setting SamplingStarted before the controllers are initialised allows * them to access the running values of functions and variables (however * it does not seem to help with the listeners) */ threadContext.setSamplingStarted(true); threadGroupLoopController.initialize(); IterationListener iterationListener = new IterationListener(); threadGroupLoopController.addIterationListener(iterationListener); threadStarted(); return iterationListener; } private void threadStarted() { JMeterContextService.incrNumberOfThreads(); threadGroup.incrNumberOfThreads(); GuiPackage gp = GuiPackage.getInstance(); if (gp != null) {// check there is a GUI gp.getMainFrame().updateCounts(); } ThreadListenerTraverser startup = new ThreadListenerTraverser(true); testTree.traverse(startup); // call ThreadListener.threadStarted() } private void threadFinished(LoopIterationListener iterationListener) { ThreadListenerTraverser shut = new ThreadListenerTraverser(false); testTree.traverse(shut); // call ThreadListener.threadFinished() JMeterContextService.decrNumberOfThreads(); threadGroup.decrNumberOfThreads(); GuiPackage gp = GuiPackage.getInstance(); if (gp != null) {// check there is a GUI gp.getMainFrame().updateCounts(); } if (iterationListener != null) { // probably not possible, but check anyway threadGroupLoopController.removeIterationListener(iterationListener); } } // N.B. This is only called at the start and end of a thread, so there is not // necessary to cache the search results, thus saving memory static class ThreadListenerTraverser implements HashTreeTraverser { private final boolean isStart; ThreadListenerTraverser(boolean start) { isStart = start; } @Override public void addNode(Object node, HashTree subTree) { if (node instanceof ThreadListener) { ThreadListener tl = (ThreadListener) node; if (isStart) { try { tl.threadStarted(); } catch (Exception e) { log.error("Error calling threadStarted", e); } } else { try { tl.threadFinished(); } catch (Exception e) { log.error("Error calling threadFinished", e); } } } } @Override public void subtractNode() { // NOOP } @Override public void processPath() { // NOOP } } public String getThreadName() { return threadName; } /** * Set running flag to false which will interrupt JMeterThread on next flag test. * This is a clean shutdown. */ public void stop() { // Called by StandardJMeterEngine, TestAction and AccessLogSampler running = false; log.info("Stopping: {}", threadName); } /** * {@inheritDoc} */ @Override public boolean interrupt() { interruptLock.lock(); try { Sampler samp = currentSamplerForInterruption; // fetch once; must be done under lock if (samp instanceof Interruptible) { // (also protects against null) if (log.isWarnEnabled()) { log.warn("Interrupting: {} sampler: {}", threadName, samp.getName()); } try { boolean found = ((Interruptible) samp).interrupt(); if (!found) { log.warn("No operation pending"); } return found; } catch (Exception e) { // NOSONAR if (log.isWarnEnabled()) { log.warn("Caught Exception interrupting sampler: {}", e.toString()); } } } else if (samp != null) { if (log.isWarnEnabled()) { log.warn("Sampler is not Interruptible: {}", samp.getName()); } } } finally { interruptLock.unlock(); } return false; } /** * Clean shutdown of test, which means wait for end of current running samplers */ private void shutdownTest() { running = false; log.info("Shutdown Test detected by thread: {}", threadName); if (engine != null) { engine.askThreadsToStop(); } } /** * Stop test immediately by interrupting running samplers */ private void stopTestNow() { running = false; log.info("Stop Test Now detected by thread: {}", threadName); if (engine != null) { engine.stopTest(); } } /** * Clean Exit of current thread */ private void stopThread() { running = false; log.info("Stop Thread detected by thread: {}", threadName); } private void checkAssertions(List assertions, SampleResult parent, JMeterContext threadContext) { for (Assertion assertion : assertions) { TestBeanHelper.prepare((TestElement) assertion); if (assertion instanceof AbstractScopedAssertion) { AbstractScopedAssertion scopedAssertion = (AbstractScopedAssertion) assertion; String scope = scopedAssertion.fetchScope(); if (scopedAssertion.isScopeParent(scope) || scopedAssertion.isScopeAll(scope) || scopedAssertion.isScopeVariable(scope)) { processAssertion(parent, assertion); } if (scopedAssertion.isScopeChildren(scope) || scopedAssertion.isScopeAll(scope)) { recurseAssertionChecks(parent, assertion, 3); } } else { processAssertion(parent, assertion); } } setLastSampleOk(threadContext.getVariables(), parent.isSuccessful()); } private void recurseAssertionChecks(SampleResult parent, Assertion assertion, int level) { if (level < 0) { return; } SampleResult[] children = parent.getSubResults(); boolean childError = false; for (SampleResult childSampleResult : children) { processAssertion(childSampleResult, assertion); recurseAssertionChecks(childSampleResult, assertion, level - 1); if (!childSampleResult.isSuccessful()) { childError = true; } } // If parent is OK, but child failed, add a message and flag the parent as failed if (childError && parent.isSuccessful()) { AssertionResult assertionResult = new AssertionResult(((AbstractTestElement) assertion).getName()); assertionResult.setResultForFailure("One or more sub-samples failed"); parent.addAssertionResult(assertionResult); parent.setSuccessful(false); } } private void processAssertion(SampleResult result, Assertion assertion) { AssertionResult assertionResult; try { assertionResult = assertion.getResult(result); } catch (AssertionError e) { log.debug("Error processing Assertion.", e); assertionResult = new AssertionResult("Assertion failed! See log file (debug level, only)."); assertionResult.setFailure(true); assertionResult.setFailureMessage(e.toString()); } catch (JMeterError e) { log.error("Error processing Assertion.", e); assertionResult = new AssertionResult("Assertion failed! See log file."); assertionResult.setError(true); assertionResult.setFailureMessage(e.toString()); } catch (Exception e) { log.error("Exception processing Assertion.", e); assertionResult = new AssertionResult("Assertion failed! See log file."); assertionResult.setError(true); assertionResult.setFailureMessage(e.toString()); } result.setSuccessful(result.isSuccessful() && !(assertionResult.isError() || assertionResult.isFailure())); result.addAssertionResult(assertionResult); } private void runPostProcessors(List extractors) { for (PostProcessor ex : extractors) { TestBeanHelper.prepare((TestElement) ex); ex.process(); } } private void runPreProcessors(List preProcessors) { for (PreProcessor ex : preProcessors) { if (log.isDebugEnabled()) { log.debug("Running preprocessor: {}", ((AbstractTestElement) ex).getName()); } TestBeanHelper.prepare((TestElement) ex); ex.process(); } } /** * Run all configured timers and sleep the total amount of time. *

* If the amount of time would amount to an ending after endTime, then * end the current thread by setting {@code running} to {@code false} and * return immediately. * * @param timers to be used for calculating the delay */ private void delay(List timers) { long totalDelay = 0; for (Timer timer : timers) { TestBeanHelper.prepare((TestElement) timer); long delay = timer.delay(); if (APPLY_TIMER_FACTOR && timer.isModifiable()) { if (log.isDebugEnabled()) { log.debug("Applying TIMER_FACTOR:{} on timer:{} for thread:{}", TIMER_FACTOR, ((TestElement) timer).getName(), getThreadName()); } delay = Math.round(delay * TIMER_FACTOR); } totalDelay += delay; } if (totalDelay > 0) { try { if (scheduler) { // We reduce pause to ensure end of test is not delayed by a sleep ending after test scheduled end // See Bug 60049 totalDelay = TIMER_SERVICE.adjustDelay(totalDelay, endTime, false); if (totalDelay < 0) { log.debug("The delay would be longer than the scheduled period, so stop thread now."); running = false; return; } } TimeUnit.MILLISECONDS.sleep(totalDelay); } catch (InterruptedException e) { log.warn("The delay timer was interrupted - probably did not wait as long as intended."); Thread.currentThread().interrupt(); } } } void notifyTestListeners() { threadVars.incIteration(); for (TestIterationListener listener : testIterationStartListeners) { listener.testIterationStart(new LoopIterationEvent(threadGroupLoopController, threadVars.getIteration())); if (listener instanceof TestElement) { ((TestElement) listener).recoverRunningVersion(); } } } private void notifyListeners(List listeners, SampleResult result) { SampleEvent event = new SampleEvent(result, threadGroup.getName(), threadVars); notifier.notifyListeners(event, listeners); } /** * Set rampup delay for JMeterThread Thread * * @param delay Rampup delay for JMeterThread */ public void setInitialDelay(int delay) { initialDelay = delay; } /** * Initial delay if ramp-up period is active for this threadGroup. */ private void rampUpDelay() { delayBy(initialDelay, "RampUp"); } /** * Wait for delay with RAMPUP_GRANULARITY * * @param delay delay in ms * @param type Delay type */ protected final void delayBy(long delay, String type) { if (delay > 0) { long start = System.currentTimeMillis(); long end = start + delay; long now; long pause = RAMPUP_GRANULARITY; while (running && (now = System.currentTimeMillis()) < end) { long togo = end - now; if (togo < pause) { pause = togo; } try { TimeUnit.MILLISECONDS.sleep(pause); // delay between checks } catch (InterruptedException e) { if (running) { // NOSONAR running may have been changed from another thread log.warn("{} delay for {} was interrupted. Waited {} milli-seconds out of {}", type, threadName, now - start, delay); } Thread.currentThread().interrupt(); break; } } } } /** * Returns the threadNum. * * @return the threadNum */ public int getThreadNum() { return threadNum; } /** * Sets the threadNum. * * @param threadNum the threadNum to set */ public void setThreadNum(int threadNum) { this.threadNum = threadNum; } private class IterationListener implements LoopIterationListener { /** * {@inheritDoc} */ @Override public void iterationStart(LoopIterationEvent iterEvent) { notifyTestListeners(); } } /** * Save the engine instance for access to the stop methods * * @param engine the engine which is used */ public void setEngine(StandardJMeterEngine engine) { this.engine = engine; } /** * Should Test stop on sampler error? * * @param b true or false */ public void setOnErrorStopTest(boolean b) { onErrorStopTest = b; } /** * Should Test stop abruptly on sampler error? * * @param b true or false */ public void setOnErrorStopTestNow(boolean b) { onErrorStopTestNow = b; } /** * Should Thread stop on Sampler error? * * @param b true or false */ public void setOnErrorStopThread(boolean b) { onErrorStopThread = b; } /** * Should Thread start next loop on Sampler error? * * @param b true or false */ public void setOnErrorStartNextLoop(boolean b) { onErrorStartNextLoop = b; } public void setThreadGroup(AbstractThreadGroup group) { this.threadGroup = group; } /** * @return {@link ListedHashTree} */ public ListedHashTree getTestTree() { return (ListedHashTree) testTree; } /** * @return {@link ListenerNotifier} */ public ListenerNotifier getNotifier() { return notifier; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy