/*
* -----------------------------------------------------------------------\
* PerfCake
*
* Copyright (C) 2010 - 2016 the original author or authors.
*
* 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 org.perfcake.message.generator;
import org.perfcake.PerfCakeException;
import org.perfcake.common.PeriodType;
import org.perfcake.message.MessageTemplate;
import org.perfcake.message.sender.MessageSenderManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Generates maximal load using a variable number of threads.
* <p>The generating starts with the number of threads set to the value of the {@link #preThreadCount} property.
* It continues to execute for the duration set by the {@link #preDuration} property.
* The period is called the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase.
* When {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase ends,
* the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase starts.</p>
*
* <p>In the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase
* the number of threads is changed by the value of the {@link #rampUpStep} property
* each period set by the {@link #rampUpStepPeriod} until it reaches the number of threads
* set by the value of the {@link #mainThreadCount} property.</p>
*
* <p>In that moment {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN MAIN} phase starts
* and the execution continues for the duration set by the {@link #mainDuration} property,
* after which the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase starts.</p>
*
* <p>In the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase
* the number of threads is again changed but this time in the opposite direction than
* in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase.
* It changes by the value of the {@link #rampDownStep} property each period specified
* by the {@link #rampDownStepPeriod} property until the final number of threads is reached.
* By that moment the final phase called {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#POST POST} starts.</p>
*
* <p>The {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#POST POST} phase ends by the end of the scenario execution.</p>
*
* <p>The outer borders of the number of threads and the duration is set by the maximum number of threads
* specified by the <code>threads</code> attribute of the generator and by the maximum duration set by the <code>run</code> element.</p>
*
* @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a>
*/
public class RampUpDownGenerator extends DefaultMessageGenerator {
/**
* The generator's logger.
*/
private static final Logger log = LogManager.getLogger(RampUpDownGenerator.class);
/**
* Phase of the generator.
*/
private enum Phase {
PRE, RAMP_UP, MAIN, RAMP_DOWN, POST
}
/**
* An initial number of threads.
*/
private int preThreadCount = -1;
/**
* A final number of threads.
*/
private int postThreadCount = -1;
/**
* A maximal number of threads.
*/
private int mainThreadCount = -1;
/**
* A duration period of the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE} phase,
* before the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP} phase starts.
*/
private long preDuration = Long.MAX_VALUE;
/**
* A number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP} phase.
*/
private int rampUpStep = 0;
/**
* A period after which the number of threads is changed by {@link org.perfcake.message.generator.RampUpDownGenerator#rampUpStep} value.
*/
private long rampUpStepPeriod = Long.MAX_VALUE;
/**
* A number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN} phase
*/
private int rampDownStep = 0;
/**
* A period after which the number of threads is changed by {@link org.perfcake.message.generator.RampUpDownGenerator#rampDownStep} value.
*/
private long rampDownStepPeriod = Long.MAX_VALUE;
/**
* A duration for which the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN} phase lasts.
*/
private long mainDuration = 0;
/**
* A current phase of the generator.
*/
private Phase currentPhase;
@Override
public void init(final MessageSenderManager messageSenderManager, final List<MessageTemplate> messageStore) throws PerfCakeException {
messageSenderManager.setSenderPoolSize(Arrays.asList(preThreadCount, mainThreadCount, postThreadCount, super.getThreads()).stream().reduce(Integer::max).get());
super.init(messageSenderManager, messageStore);
if (preThreadCount <= 0) {
preThreadCount = super.getThreads();
}
if (mainThreadCount <= 0) {
mainThreadCount = super.getThreads();
}
if (postThreadCount <= 0) {
postThreadCount = super.getThreads();
}
}
@Override
public void generate() throws Exception {
log.info("Starting to generate...");
executorService = new ThreadPoolExecutor(preThreadCount, preThreadCount, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(getSenderTaskQueueSize()), new DaemonThreadFactory());
currentPhase = Phase.PRE;
boolean phaseChanged = true;
setThreads(preThreadCount);
boolean threadCountChanged = true;
setStartTime();
long last = 0;
final PeriodType runTimeType = runInfo.getDuration().getPeriodType();
runInfo.addTag("");
mainLoop:
while (runInfo.isRunning()) {
final long runTime;
switch (runTimeType) {
case TIME:
runTime = runInfo.getRunTime();
break;
case ITERATION:
runTime = runInfo.getIteration();
break;
default:
// should not reach this code
break mainLoop;
}
switch (currentPhase) {
case PRE:
if (runTime >= preDuration) {
currentPhase = Phase.RAMP_UP;
phaseChanged = true;
setThreads(getThreads() + rampUpStep);
resizeExecutorService(getThreads());
last = runTime;
}
break;
case RAMP_UP:
if (runTime - last >= rampUpStepPeriod) {
final int newThreadCount = getThreads() + rampUpStep;
if (newThreadCount >= mainThreadCount) {
setThreads(mainThreadCount);
currentPhase = Phase.MAIN;
phaseChanged = true;
} else {
setThreads(newThreadCount);
}
last = runTime;
resizeExecutorService(getThreads());
threadCountChanged = true;
}
break;
case MAIN:
if (runTime - last >= mainDuration) {
currentPhase = Phase.RAMP_DOWN;
phaseChanged = true;
setThreads(getThreads() - rampDownStep);
resizeExecutorService(getThreads());
last = runTime;
}
break;
case RAMP_DOWN:
if (runTime - last >= rampDownStepPeriod) {
int newThreadCount = getThreads() - rampDownStep;
if (newThreadCount < postThreadCount) {
if (newThreadCount <= 1) {
if (log.isWarnEnabled()) {
log.warn("There was an attempt to decrease the thread count below 1 in RAMP_DOWN phase. Setting number of threads to 1.");
}
newThreadCount = 1;
} else {
newThreadCount = postThreadCount;
}
currentPhase = Phase.POST;
phaseChanged = true;
}
last = runTime;
setThreads(newThreadCount);
resizeExecutorService(getThreads());
threadCountChanged = true;
}
break;
case POST:
default:
break;
}
if (phaseChanged || threadCountChanged) {
if (log.isDebugEnabled()) {
log.debug(currentPhase.toString() + " phase [" + getThreads() + " threads]");
}
phaseChanged = false;
threadCountChanged = false;
}
super.prepareTask();
}
log.info("Reached test end. Shutting down execution...");
super.shutdown();
}
private void resizeExecutorService(final int threads) throws InterruptedException {
executorService.setCorePoolSize(threads);
executorService.setMaximumPoolSize(threads);
}
/**
* Gets an initial number of threads - the number of threads in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase.
*
* @return The final number of threads.
*/
public int getPreThreadCount() {
return preThreadCount;
}
/**
* Sets a final number of threads - the number of threads in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase.
*
* @param preThreadCount
* The initial number of threads.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setPreThreadCount(final int preThreadCount) {
this.preThreadCount = preThreadCount;
return this;
}
/**
* Gets a final number of threads - the number of threads in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#POST POST} phase.
*
* @return The final number of threads.
*/
public int getPostThreadCount() {
return postThreadCount;
}
/**
* Sets a final number of threads - the number of threads in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#POST POST} phase.
*
* @param postThreadCount
* The final number of threads.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setPostThreadCount(final int postThreadCount) {
this.postThreadCount = postThreadCount;
return this;
}
/**
* Gets duration period of the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase,
* before the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase starts.
*
* @return PRE phase duration period in the units of <code>run</code> type.
*/
public long getPreDuration() {
return preDuration;
}
/**
* Sets duration period of the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#PRE PRE} phase,
* before the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase starts.
*
* @param preDuration
* PRE phase duration period in the units of <code>run</code> type.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setPreDuration(final long preDuration) {
this.preDuration = preDuration;
return this;
}
/**
* Gets a number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase.
*
* @return The size of the step.
*/
public int getRampUpStep() {
return rampUpStep;
}
/**
* Sets a number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase.
*
* @param rampUpStep
* The size of the step.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setRampUpStep(final int rampUpStep) {
this.rampUpStep = rampUpStep;
return this;
}
/**
* Gets a duration period after which the number of threads is changed in {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase.
*
* @return The RAMP UP step duration period in a units of <code>run</code> type.
*/
public long getRampUpStepPeriod() {
return rampUpStepPeriod;
}
/**
* Sets a duration period after which the number of threads is changed in {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_UP RAMP UP} phase.
*
* @param rampUpStepPeriod
* The RAMP UP step duration period in a units of <code>run</code> type.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setRampUpStepPeriod(final long rampUpStepPeriod) {
this.rampUpStepPeriod = rampUpStepPeriod;
return this;
}
/**
* Gets a number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase.
*
* @return The size of the step.
*/
public int getRampDownStep() {
return rampDownStep;
}
/**
* Sets a number by which the number of threads is changed in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase.
*
* @param rampDownStep
* The size of the step.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setRampDownStep(final int rampDownStep) {
this.rampDownStep = rampDownStep;
return this;
}
/**
* Gets a duration period after which the number of threads is changed in {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase.
*
* @return The RAMP DOWN step duration period in a units of <code>run</code> type.
*/
public long getRampDownStepPeriod() {
return rampDownStepPeriod;
}
/**
* Sets a duration period after which the number of threads is changed in {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#RAMP_DOWN RAMP DOWN} phase.
*
* @param rampDownStepPeriod
* The RAMP DOWN step duration period in a units of <code>run</code> type.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setRampDownStepPeriod(final long rampDownStepPeriod) {
this.rampDownStepPeriod = rampDownStepPeriod;
return this;
}
/**
* Gets a duration period for which the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN MAIN} phase lasts.
*
* @return The MAIN phase duration period in the units of <code>run</code> type.
*/
public long getMainDuration() {
return mainDuration;
}
/**
* Sets a duration period for which the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN MAIN} phase lasts.
*
* @param mainDuration
* The MAIN phase duration period in the units of <code>run</code> type.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setMainDuration(final long mainDuration) {
this.mainDuration = mainDuration;
return this;
}
/**
* Gets a maximal number of threads - the number of threads used in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN MAIN} phase.
*
* @return The maximal number of threads.
*/
public int getMainThreadCount() {
return mainThreadCount;
}
/**
* Sets a maximal number of threads - the number of threads used in the {@link org.perfcake.message.generator.RampUpDownGenerator.Phase#MAIN MAIN} phase.
*
* @param mainThreadCount
* The maximal number of threads.
* @return Instance of this to support fluent API.
*/
public RampUpDownGenerator setMainThreadCount(final int mainThreadCount) {
this.mainThreadCount = mainThreadCount;
return this;
}
}