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

com.xceptance.xlt.agent.LoadTestRunner Maven / Gradle / Ivy

Go to download

XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.

There is a newer version: 8.6.0
Show newest version
/*
 * Copyright (c) 2005-2024 Xceptance Software Technologies GmbH
 *
 * Licensed 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 com.xceptance.xlt.agent;

import java.lang.reflect.Method;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xceptance.xlt.agentcontroller.TestUserConfiguration;
import com.xceptance.xlt.agentcontroller.TestUserStatus;
import com.xceptance.xlt.api.engine.DataManager;
import com.xceptance.xlt.api.engine.GlobalClock;
import com.xceptance.xlt.engine.DataManagerImpl;
import com.xceptance.xlt.engine.SessionImpl;
import com.xceptance.xlt.engine.util.TimerUtils;

/**
 * Load test runner.
 * 

* Group of autonomous threads that execute one or more tests according to their configuration. *

* * @author Jörg Werner (Xceptance Software Technologies GmbH) */ public class LoadTestRunner extends Thread { /** * Class logger instance. */ private static final Logger log = LoggerFactory.getLogger(LoadTestRunner.class); /** * Configuration. */ private final TestUserConfiguration config; /** * Status of runner. */ private final TestUserStatus status; /** * Timer used for synchronization and test execution controlling. */ private AbstractExecutionTimer timer; /** * Some information about this and other agents. */ private final AgentInfo agentInfo; /** * Whether this runner has been aborted. */ private volatile boolean aborted; /** * Creates a new LoadTestRunner object for the given load test configuration. Typically, multiple runners are * started for one test case configuration, so the number of the current runner is passed as well. * * @param config * the load test configuration * @param agentInfo * load test agent information * @param timer * the execution timer that controls this load test runner */ public LoadTestRunner(final TestUserConfiguration config, final AgentInfo agentInfo, final AbstractExecutionTimer timer) { // create a new thread group for each LoadTestRunner as a means // to keep the main thread and any supporting threads together super(new ThreadGroup(config.getUserId()), config.getUserId()); this.config = config; this.agentInfo = agentInfo; this.timer = timer; status = new TestUserStatus(); status.setUserName(config.getUserId()); } /** * Returns the current test case status. * * @return the status */ public TestUserStatus getTestUserStatus() { return status; } /** * Runs the test case as configured in the test case configuration. */ @Override public void run() { try { final long now = GlobalClock.get().millis(); // get and check the test case class Class testCaseClass = null; try { // try to get the corresponding Java class testCaseClass = Class.forName(config.getTestCaseClassName()); // class found -> validate it checkTestCaseClass(testCaseClass); } // no such Java class catch (final ClassNotFoundException cnf) { // failed to get resource URL -> indicate error throw new RuntimeException("Could not find java class '" + config.getTestCaseClassName() + "'."); } // get the other test case parameters final int iterations = config.getNumberOfIterations(); final int warmUpPeriod = config.getWarmUpPeriod(); final int measurementPeriod = config.getMeasurementPeriod(); final int initialDelay = config.getInitialDelay(); final int duration = warmUpPeriod + measurementPeriod; // initialize the master controller status status.setStartDate(now + initialDelay); status.setEndDate(now + initialDelay + duration); // initialize the session final SessionImpl session = SessionImpl.getCurrent(); session.setUserCount(config.getNumberOfUsers()); session.setUserName(config.getUserName()); session.setUserNumber(config.getInstance()); session.setAbsoluteUserNumber(config.getAbsoluteUserNumber()); session.setTotalUserCount(config.getTotalUserCount()); session.setLoadTest(true); session.setAgentID(agentInfo.getAgentID()); session.setAgentNumber(agentInfo.getAgentNumber()); session.setTotalAgentCount(agentInfo.getTotalAgentCount()); // set the logging window final DataManager dataMgr = session.getDataManager(); dataMgr.setStartOfLoggingPeriod(now + initialDelay + warmUpPeriod); dataMgr.setEndOfLoggingPeriod(now + initialDelay + warmUpPeriod + measurementPeriod); // run the test if (iterations != 0) { status.setMode(TestUserStatus.Mode.ITERATION); runIterations(testCaseClass, iterations); } else { status.setMode(TestUserStatus.Mode.TIME_PERIOD); if (duration != 0) { runDuration(testCaseClass, duration); } else { log.warn("Both number of iterations and computed duration are unspecified for test case: " + config.getTestCaseClassName()); } } // set the final state if (aborted) { status.setState(TestUserStatus.State.Aborted); } else { status.setPercentageComplete(100); status.setState(TestUserStatus.State.Finished); } } catch (final Exception ex) { log.error("Failed to run test as user: " + getName(), ex); status.setState(TestUserStatus.State.Failed); status.setException(ex); } } /** * Marks this load test runner as aborted. */ public void setAborted() { aborted = true; } /** * Checks whether the test case class is acceptable, i.e., whether it has exactly one active test method. This * means, there can be only one method in the class that is - at the same time - annotated with "@Test" and not * annotated with "@Ignore". Furthermore, the class itself must not be annotated with "@Ignore" * * @param testCaseClass * the test case class * @throws RuntimeException * if there is *not* exactly one active test method in this class */ private void checkTestCaseClass(final Class testCaseClass) { // check whether the test case class is annotated with @Ignore if (testCaseClass.isAnnotationPresent(Ignore.class)) { throw new RuntimeException("Test class is annotated with @Ignore: " + testCaseClass.getName()); } // check whether we have exactly one active test method int testMethodCount = 0; for (final Method method : testCaseClass.getMethods()) { if (method.isAnnotationPresent(Test.class) && !method.isAnnotationPresent(Ignore.class)) { testMethodCount++; } } if (testMethodCount != 1) { throw new RuntimeException("No or more than one active test method found in class: " + testCaseClass.getName()); } } /** * Runs the passed test case repeatedly until the specified number of iterations has been reached. The test case * status is updated after each iteration. * * @param testCaseClass * the test case to execute * @param iterations * the number of test iterations */ private void runIterations(final Class testCaseClass, final int iterations) { final String testClassName = (testCaseClass == null ? config.getTestCaseClassName() : testCaseClass.toString()); log.info("Load test thread started (" + testClassName + " / " + iterations + " iterations)"); for (int i = 0; i < iterations; i++) { try { status.setState(TestUserStatus.State.Waiting); timer.waitForNextExecution(); status.setState(TestUserStatus.State.Running); runTestCase(testCaseClass, status); } catch (final InterruptedException ex) { break; } status.setPercentageComplete((i + 1) * 100 / iterations); } log.info("Load test thread finished."); } /** * Runs the passed test case repeatedly until the specified number of milliseconds has passed. The test case status * is updated after each iteration. * * @param testCaseClass * the test case to execute * @param duration * the test period [ms] */ private void runDuration(final Class testCaseClass, final int duration) { final String testClassName = (testCaseClass == null ? config.getTestCaseClassName() : testCaseClass.toString()); log.info("Load test thread started (" + testClassName + " / " + duration / 1000 + " s)"); final long startTime = GlobalClock.get().millis(); while (true) { try { status.setState(TestUserStatus.State.Waiting); timer.waitForNextExecution(); status.setState(TestUserStatus.State.Running); runTestCase(testCaseClass, status); } catch (final InterruptedException ex) { break; } final long now = GlobalClock.get().millis(); status.setPercentageComplete((int) ((now - startTime) * 100 / duration)); } log.info("Load test thread finished."); } /** * Runs the passed test case *once*. Depending on the test result, the test case status is updated accordingly * (errors, iterations, runtime, etc.). * * @param testCaseClass * the test case to execute * @param status * the test case status */ protected void runTestCase(final Class testCaseClass, final TestUserStatus status) throws InterruptedException { // check whether we have to quit before attempting a new iteration final SessionImpl session = SessionImpl.getCurrent(); if (session.wasMarkedAsExpired()) { throw new InterruptedException("User aborted as the load test is over"); } // remember start time final long startTime = TimerUtils.get().getStartTime(); // make sure transaction data recording is initiated even if XltTestRunner is not used by the test session.startTransaction(); // execute the test via JUnit final Result result = new JUnitCore().run(testCaseClass); // check again whether we have to quit before dealing with the results if (session.wasMarkedAsExpired()) { throw new InterruptedException("User aborted as the load test is over"); } // reset the interrupted flag for a clean new transaction Thread.interrupted(); // remember runtime final long runTime = TimerUtils.get().getElapsedTime(startTime); final boolean failed = !result.wasSuccessful(); Throwable failure = null; if (failed) { failure = result.getFailures().get(0).getException(); if (log.isErrorEnabled()) { log.error(String.format("Failure while executing test (user: '%s', output: '%s'):", session.getUserID(), session.getID()), failure); } } // maintain the master controller status final long now = GlobalClock.get().millis(); final DataManagerImpl dataManager = session.getDataManager(); status.incrementIterations(); status.addToTotalRuntime(runTime); status.setLastRuntime(runTime); status.setElapsedTime(now - status.getStartDate()); status.setLastModifiedDate(now); status.setEvents(dataManager.getNumberOfEvents()); if (failed) { status.incrementErrors(); } // maintain the transaction statistics (if not already done) if (session.isTransactionPending()) { /* * If transaction data recording is still in progress the test does not use XltTestRunner (which stops the * transaction automatically when test method execution has finished). In this case, we cannot rely on the * failure information tracked by the session itself but use the failure information reported by JUnit * instead. */ // restore failure information at session... session.setFailed(failed); session.setFailReason(failure); // and stop the transaction session.stopTransaction(); } // clean up session.clearFailedActionName(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy