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

step.artefacts.handlers.ThreadGroupHandler Maven / Gradle / Ivy

There is a newer version: 3.27.0
Show newest version
/*******************************************************************************
 * Copyright (C) 2020, exense GmbH
 *  
 * This file is part of STEP
 *  
 * STEP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * STEP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *  
 * You should have received a copy of the GNU Affero General Public License
 * along with STEP.  If not, see .
 ******************************************************************************/
package step.artefacts.handlers;

import com.fasterxml.jackson.annotation.JsonIgnore;
import step.artefacts.*;
import step.artefacts.ThreadGroup;
import step.artefacts.handlers.functions.MultiplyingTokenForecastingContext;
import step.artefacts.handlers.functions.TokenForecastingContext;
import step.artefacts.handlers.loadtesting.Pacer;
import step.artefacts.reports.ThreadReportNode;
import step.core.artefacts.AbstractArtefact;
import step.core.artefacts.Artefact;
import step.core.artefacts.handlers.ArtefactHandler;
import step.core.artefacts.handlers.AtomicReportNodeStatusComposer;
import step.core.artefacts.reports.ReportNode;
import step.threadpool.IntegerSequenceIterator;
import step.threadpool.ThreadPool;
import step.threadpool.ThreadPool.WorkerController;
import step.threadpool.WorkerItemConsumerFactory;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import static step.artefacts.handlers.functions.TokenForecastingExecutionPlugin.getTokenForecastingContext;
import static step.artefacts.handlers.functions.TokenForecastingExecutionPlugin.pushNewTokenNumberCalculationContext;

public class ThreadGroupHandler extends ArtefactHandler {

	public void createReportSkeleton_(ReportNode node, ThreadGroup artefact) {
		Integer numberOfThreads = artefact.getUsers().get();

		TokenForecastingContext tokenForecastingContext = getTokenForecastingContext(context);
		pushNewTokenNumberCalculationContext(context, new MultiplyingTokenForecastingContext(tokenForecastingContext, numberOfThreads));

		// The skeleton phase has to be executed within a session to match the behaviour of the execution
		// and properly estimate the required number of tokens
		createReportNodeSkeletonInSession(artefact, node, (sessionArtefact, sessionReportNode) -> {
			SequentialArtefactScheduler scheduler = new SequentialArtefactScheduler(context);
			scheduler.createReportSkeleton_(sessionReportNode, sessionArtefact);
		});
	}

	private void createReportNodeSkeletonInSession(AbstractArtefact artefact, ReportNode node, BiConsumer consumer, String artefactName, Map newVariables) {
		FunctionGroup functionGroup = createWorkArtefact(FunctionGroup.class, artefact, artefactName, true);
		functionGroup.setConsumer(consumer);
		delegateCreateReportSkeleton(functionGroup, node, newVariables);
	}

	private void createReportNodeSkeletonInSession(AbstractArtefact artefact, ReportNode node, BiConsumer consumer) {
		createReportNodeSkeletonInSession(artefact, node, consumer, "", new HashMap<>());
	}

	@Override
	public void execute_(final ReportNode node, final ThreadGroup testArtefact) {		
		final int numberOfUsers = testArtefact.getUsers().getOrDefault(Integer.class, 0);
		final int numberOfIterations = testArtefact.getIterations().getOrDefault(Integer.class, 0);
		final int pacing = testArtefact.getPacing().getOrDefault(Integer.class, 0);
		final int rampup = testArtefact.getRampup().getOrDefault(Integer.class, pacing);
		final int pack = testArtefact.getPack().getOrDefault(Integer.class, 1);
		final int maxDuration = testArtefact.getMaxDuration().getOrDefault(Integer.class, 0);
		final int startOffset = testArtefact.getStartOffset().getOrDefault(Integer.class, 0);

		if (numberOfUsers <= 0) {
			throw new RuntimeException("Invalid argument: The number of threads has to be higher than 0.");
		}

		if (maxDuration == 0 && numberOfIterations == 0) {
			throw new RuntimeException(
					"Invalid argument: Either specify the maximum duration or the number of iterations of the thread group.");
		}

		if (pack <= 0) {
			throw new RuntimeException(
					"Invalid argument: The number of packed threads has to be higher than 0.");
		}
		
		// Attach global iteration counter & user counter
		LongAdder gcounter = new LongAdder();
		AtomicReportNodeStatusComposer reportNodeStatusComposer = new AtomicReportNodeStatusComposer(node);
		
		Iterator groupIterator = new IntegerSequenceIterator(1,numberOfUsers,1);
		
		final long groupStartTime = System.currentTimeMillis();
		
		ThreadPool threadPool = context.get(ThreadPool.class);
		threadPool.consumeWork(groupIterator, new WorkerItemConsumerFactory() {
			@Override
			public Consumer createWorkItemConsumer(WorkerController groupController) {
				return groupID -> {
					try {
						final long localStartOffset = startOffset + (long) (1.0 * pack*Math.floor((groupID - 1)/pack) / numberOfUsers * rampup);

						CancellableSleep.sleep(localStartOffset, context::isInterrupted, ThreadGroupHandler.class);

						Thread thread = createWorkArtefact(Thread.class, testArtefact, "Thread "+groupID, true);
						thread.setGcounter(gcounter);
						thread.setGroupController(groupController);
						thread.setGroupId(groupID);
						thread.setGroupStartTime(groupStartTime);
						thread.setNumberOfIterations(numberOfIterations);
						thread.setPacing(pacing);
						thread.setThreadGroup(testArtefact);
						thread.setMaxDuration(maxDuration);
						
						HashMap newVariable = new HashMap<>();
						newVariable.put(thread.threadGroup.getUserItem().get(), thread.groupId);
						ReportNode threadReportNode = delegateExecute(thread, node, newVariable);
						reportNodeStatusComposer.addStatusAndRecompose(threadReportNode);
					} catch (Throwable e) {
						failWithException(node, e);
						reportNodeStatusComposer.addStatusAndRecompose(node);
					}
				};
			}
		}, numberOfUsers);

		reportNodeStatusComposer.applyComposedStatusToParentNode(node);
	}

	@Override
	public ReportNode createReportNode_(ReportNode parentNode, ThreadGroup testArtefact) {
		return new ReportNode();
	}
	
	@Artefact(test = true)
	public static class Thread extends AbstractArtefact {
		
		@JsonIgnore
		int groupId;
		@JsonIgnore
		int numberOfIterations;
		@JsonIgnore
		int pacing;
		@JsonIgnore
		long groupStartTime;
		@JsonIgnore
		ThreadGroup threadGroup;
		@JsonIgnore
		WorkerController groupController;
		@JsonIgnore
		LongAdder gcounter;
		@JsonIgnore
		long maxDuration;
		
		public Thread() {
			super();
		}
		
		public int getGroupId() {
			return groupId;
		}

		public void setGroupId(int groupId) {
			this.groupId = groupId;
		}

		public int getNumberOfIterations() {
			return numberOfIterations;
		}

		public void setNumberOfIterations(int numberOfIterations) {
			this.numberOfIterations = numberOfIterations;
		}

		public int getPacing() {
			return pacing;
		}

		public void setPacing(int pacing) {
			this.pacing = pacing;
		}

		public long getGroupStartTime() {
			return groupStartTime;
		}

		public void setGroupStartTime(long groupStartTime) {
			this.groupStartTime = groupStartTime;
		}

		public ThreadGroup getThreadGroup() {
			return threadGroup;
		}

		public void setThreadGroup(ThreadGroup threadGroup) {
			this.threadGroup = threadGroup;
		}

		public WorkerController getGroupController() {
			return groupController;
		}

		public void setGroupController(WorkerController groupController) {
			this.groupController = groupController;
		}

		public LongAdder getGcounter() {
			return gcounter;
		}

		public void setGcounter(LongAdder gcounter) {
			this.gcounter = gcounter;
		}

		public long getMaxDuration() {
			return maxDuration;
		}

		public void setMaxDuration(long maxDuration) {
			this.maxDuration = maxDuration;
		}
	}
	
	public static class ThreadHandler extends AbstractSessionArtefactHandler {

		@Override
		protected void createReportSkeleton_(ThreadReportNode parentNode, Thread testArtefact) {
		}

		@Override
		protected void execute_(ThreadReportNode node, Thread thread) {
			int pacing = thread.pacing;
			int numberOfIterations = thread.numberOfIterations;
			long maxDuration = thread.maxDuration;
			
			ReportNode reportNode = executeInSession(thread, node, (sessionArtefact, sessionReportNode)->{
				context.getVariablesManager().putVariable(sessionReportNode, TEC_EXECUTION_REPORTNODES_PERSISTBEFORE, false);
				SequentialArtefactScheduler sequentialArtefactScheduler = new SequentialArtefactScheduler(context);
				sequentialArtefactScheduler.executeWithinBeforeAndAfter(sessionArtefact, sessionReportNode, newChildren->{
					AtomicReportNodeStatusComposer sessionReportNodeStatusComposer = new AtomicReportNodeStatusComposer(sessionReportNode);
					try {
						Pacer.scheduleAtConstantPacing(i->{
							ReportNode iterationReportNode = null;
							try {
								thread.gcounter.increment();
								
								Sequence iterationTestCase = createWorkArtefact(Sequence.class, sessionArtefact, "Iteration "+i);

								// Force the persistence of the iteration report node before its execution to have it
								// in the tree view (SED-1002)
								iterationTestCase.addCustomAttribute(ArtefactHandler.FORCE_PERSIST_BEFORE, true);
								for(AbstractArtefact child:newChildren) {
									iterationTestCase.addChild(child);
								}
								
								HashMap newVariable = new HashMap<>();
								newVariable.put(thread.threadGroup.getLocalItem().get(), i);
								//For Performance reasons, we might want to expose the LongAdder itself rather than calling "intValue()" every time
								newVariable.put(thread.threadGroup.getItem().get(), thread.gcounter.intValue());
								
								iterationReportNode = delegateExecute(iterationTestCase, sessionReportNode, newVariable);
								sessionReportNodeStatusComposer.addStatusAndRecompose(iterationReportNode);
							} catch (Throwable e) {
								if(iterationReportNode!=null) {
									failWithException(iterationReportNode, e);
									sessionReportNodeStatusComposer.addStatusAndRecompose(iterationReportNode);
								}
							}
							
						}, pacing,
								c -> !context.isInterrupted()
										&& (maxDuration == 0 || c.getDuration() < maxDuration)
										&& (numberOfIterations == 0 || c.getIterations() < numberOfIterations), context);
					} catch (InterruptedException e) {
						failWithException(sessionReportNode, e);
						sessionReportNodeStatusComposer.addStatusAndRecompose(sessionReportNode);
					}
					sessionReportNodeStatusComposer.applyComposedStatusToParentNode(sessionReportNode);
					return sessionReportNode;
				}, BeforeThread.class, AfterThread.class);
			});
			
			node.setStatus(reportNode.getStatus());
		}

		@Override
		public ThreadReportNode createReportNode_(ReportNode parentNode, Thread testArtefact) {
			ThreadReportNode threadReportNode = new ThreadReportNode();
			threadReportNode.setThreadGroupName((parentNode != null) ? parentNode.getName() : "Unnamed");
			return threadReportNode;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy