
step.artefacts.handlers.ThreadGroupHandler Maven / Gradle / Ivy
/*******************************************************************************
* 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