
org.perfcake.message.generator.DefaultMessageGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of perfcake Show documentation
Show all versions of perfcake Show documentation
A Lightweight Performance Testing Framework
/*
* -----------------------------------------------------------------------\
* 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.PerfCakeConst;
import org.perfcake.common.PeriodType;
import org.perfcake.reporting.ReportManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Generates maximal load using a given number of threads.
*
* @author Martin Večeřa
* @author Pavel Macík
*/
public class DefaultMessageGenerator extends AbstractMessageGenerator {
/**
* The generator's logger.
*/
private static final Logger log = LogManager.getLogger(DefaultMessageGenerator.class);
/**
* The period in milliseconds in which the thread queue is filled with new tasks.
*/
protected long monitoringPeriod = 1000; // default 1s
/**
* During a shutdown, the thread queue is regularly checked for the threads finishing their work.
* If the same amount of threads keeps running for this period, they are forcefully stopped.
* The unit of this value is milliseconds. The default value is 5000ms.
*/
protected long shutdownPeriod = 5000;
/**
* The size of internal queue of prepared sender tasks. The default value is 1000 tasks.
*/
protected int senderTaskQueueSize = 1000;
/**
* Gets the shutdown period.
* During a shutdown, the thread queue is regularly checked for the threads finishing their work.
* If the same amount of threads keeps running for this period, they are forcefully stopped.
*
* @return Current shutdown period in ms.
*/
public long getShutdownPeriod() {
return shutdownPeriod;
}
/**
* Sets the shutdown period which tells how frequently we should check for threads finishing their work.
* During a shutdown, the thread queue is regularly checked for the threads finishing their work.
* If the same amount of threads keeps running for this period, they are forcefully stopped.
*
* @param shutdownPeriod
* The new shutdown period.
* @return Instance of this to support fluent API.
*/
public DefaultMessageGenerator setShutdownPeriod(final long shutdownPeriod) {
this.shutdownPeriod = shutdownPeriod;
return this;
}
@Override
public void setReportManager(final ReportManager reportManager) {
super.setReportManager(reportManager);
}
/**
* Assigns nice names to threads that send messages and increases their default priority slightly.
* All threads are set at daemon by default for PerfCake to be able to finish even if some of then hung up.
*/
static class DaemonThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DaemonThreadFactory() {
final SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "PerfCake-" + poolNumber.getAndIncrement() + "-sender-thread-";
}
public Thread newThread(final Runnable r) {
final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
t.setPriority(8);
return t;
}
}
/**
* Places a new {@link SenderTask} implementing the message sending to an internal thread queue.
*
* @return True if and only if the task has been successfully submitted.
* @throws java.lang.InterruptedException
* When it was not possible to place another task because the queue was empty.
*/
protected boolean prepareTask() throws InterruptedException {
if (executorService.getQueue().remainingCapacity() > 0) {
executorService.submit(newSenderTask());
return true;
}
return false;
}
/**
* Adaptively terminates active sender tasks. Waits for tasks to be finished. While they are some tasks remaining and some of them get terminated, keep waiting.
*
* @throws InterruptedException
* When threads were interrupted during awaiting task termination.
*/
private void adaptiveTermination() throws InterruptedException {
executorService.shutdown();
if (shutdownPeriod == -1) {
shutdownPeriod = Math.max(5000, 5 * runInfo.getRunTime() * runInfo.getThreads() / runInfo.getIteration());
if (log.isInfoEnabled()) {
log.info(String.format("Shutdown period auto-tuned to " + shutdownPeriod + " ms."));
}
}
long active = getTasksInQueue();
long lastActive = 0;
while (active > 0 && lastActive != active) { // make sure the threads are finishing
lastActive = active;
executorService.awaitTermination(shutdownPeriod, TimeUnit.MILLISECONDS);
active = getTasksInQueue();
if (log.isDebugEnabled()) {
log.debug(String.format("Adaptive test execution termination in progress. Tasks finished in last round: %d", lastActive - active));
}
}
// some threads might have been finished by interrupted exception, let's give them the chance to live with that
executorService.awaitTermination(shutdownPeriod, TimeUnit.MILLISECONDS);
active = executorService.getActiveCount();
final long uncomplete = executorService.getTaskCount() - executorService.getCompletedTaskCount();
if (active > 0) {
log.warn("Cannot terminate all sender tasks. Set higher shutdownPeriod for the generator in your scenario. Remaining tasks/threads active: {}/{}", uncomplete, active);
}
}
/**
* Takes care of gentle shutdown of the generator based on the period type.
*
* @throws java.lang.InterruptedException
* When waiting for the termination was interrupted.
*/
protected void shutdown() throws InterruptedException {
if (runInfo.getDuration().getPeriodType() == PeriodType.ITERATION) { // in case of iterations, we wait for the tasks to be finished first
log.info("Waiting for all messages to be sent...");
adaptiveTermination();
setStopTime();
} else { // in case of time, we must stop measurement first
setStopTime();
log.info("Shutting down execution...");
adaptiveTermination();
}
executorService.shutdownNow();
}
@Override
public void generate() throws Exception {
log.info("Starting to generate...");
executorService = new ThreadPoolExecutor(getThreads(), getThreads(), 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(getSenderTaskQueueSize()), new DaemonThreadFactory());
runInfo.setThreads(getThreads());
setStartTime();
if (runInfo.getDuration().getPeriodType() == PeriodType.ITERATION) { // for iterations, we need a precise number of messages
long i = 0;
final long max = runInfo.getDuration().getPeriod();
boolean wasWarmUp = false;
while (i < max) {
boolean warmUpTag = runInfo.hasTag(PerfCakeConst.WARM_UP_TAG);
if (wasWarmUp && !warmUpTag) { // if we were in the warmUp phase and it ended, we start counting from 0 again
i = 0;
}
if (prepareTask()) {
i = i + 1; // long does not work with i++
}
wasWarmUp = warmUpTag;
}
} else {
while (runInfo.isRunning()) { // for time controlled run, we just go until the time is over
prepareTask();
}
}
log.info("Reached test end. All messages were prepared to be sent.");
shutdown();
}
/**
* Gets a monitoring period in which the sender task queue is filled with new tasks.
*
* @return The monitoring period in milliseconds.
*/
public long getMonitoringPeriod() {
return monitoringPeriod;
}
/**
* Gets a monitoring period in which the sender task queue is filled with new tasks.
*
* @param monitoringPeriod
* The monitoring period.
* @return Instance of this to support fluent API.
*/
public DefaultMessageGenerator setMonitoringPeriod(final long monitoringPeriod) {
this.monitoringPeriod = monitoringPeriod;
return this;
}
/**
* Gets the duration period for which the generator will generate the measured load.
*
* @return The duration in the units of run
type.
*/
public long getDuration() {
return runInfo.getDuration().getPeriod();
}
/**
* Gets the size of the internal sender task queue.
*
* @return The sender task queue size.
*/
public int getSenderTaskQueueSize() {
return senderTaskQueueSize;
}
/**
* Sets the the size of the internal sender task queue.
*
* @param senderTaskQueueSize
* The sender task queue size.
* @return Instance of this to support fluent API.
*/
public DefaultMessageGenerator setSenderTaskQueueSize(final int senderTaskQueueSize) {
this.senderTaskQueueSize = senderTaskQueueSize;
return this;
}
@Override
protected void validateRunInfo() {
if (runInfo.getDuration().getPeriodType() == PeriodType.PERCENTAGE) {
throw new IllegalStateException(String.format("%s can only be used with an iteration based run configuration.", this.getClass().getName()));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy