/*
* -----------------------------------------------------------------------\
* 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.reporting.reporter;
import org.perfcake.PerfCakeConst;
import org.perfcake.common.PeriodType;
import org.perfcake.reporting.MeasurementUnit;
import org.perfcake.reporting.ReportingException;
import org.perfcake.reporting.destination.Destination;
import org.perfcake.reporting.reporter.accumulator.LastValueAccumulator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Determines when the tested system is warmed up. The warming is enabled/disabled by the presence of the {@link WarmUpReporter} in the scenario. The minimal iteration count and
* the warm-up period duration can be tweaked by the respective properties ({@link #minimalWarmUpCount} with the default value of 10,000 and {@link #minimalWarmUpDuration} with the default value of
* 15,000 ms).
* <p>
* The system is considered warmed up when all of the following conditions are satisfied: The iteration length is not changing much over the time, the minimal iteration count has been executed and the
* minimal duration from the very start has exceeded.
* </p>
*
* @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a>
* @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a>
*/
public class WarmUpReporter extends AbstractReporter {
/**
* The reporter's logger.
*/
private static final Logger log = LogManager.getLogger(WarmUpReporter.class);
/**
* Minimal warm-up period duration in milliseconds.
*/
private long minimalWarmUpDuration = 15000; // default 15s
/**
* Minimal iteration count executed during the warm-up period.
*/
private long minimalWarmUpCount = 10000; // by JIT
/**
* The relative difference threshold to determine whether the throughput is not changing much.
*/
private double relativeThreshold = 0.002d; // 0.2%
/**
* The absolute difference threshold to determine whether the throughput is not changing much.
*/
private double absoluteThreshold = 0.2d; // 0.2
/**
* The flag indicating whether the tested system is considered warmed up.
*/
private boolean warmed = false;
/**
* The unit in which we measure the maximal warm-up count. Can be iteration, time, or percentage.
*/
private PeriodType maximalWarmUpType = PeriodType.ITERATION;
/**
* Maximal tolerance of waiting for the end of the warm-up phase. If we run out of this time/iteration count (determined by {@link #maximalWarmUpType}),
* we simply break the test and do not waste more time. {@code -1} means that the check is disabled.
*/
private long maximalWarmUpDuration = -1;
/**
* The index number of the checking period in which the current run is.
*
* @see #CHECKING_PERIOD
*/
private final AtomicLong checkingPeriodIndex = new AtomicLong(0);
private final LastValueAccumulator lastThroughput = new LastValueAccumulator();
/**
* The period in milliseconds in which the checking if the tested system is warmed up.
*/
private static final long CHECKING_PERIOD = 1000;
@Override
public void start() {
runInfo.addTag(PerfCakeConst.WARM_UP_TAG);
if (log.isInfoEnabled()) {
log.info("Warming the tested system up (for at least " + minimalWarmUpDuration + " ms and " + minimalWarmUpCount + " iterations) ...");
}
super.start();
}
@Override
public void publishResult(final PeriodType periodType, final Destination destination) throws ReportingException {
throw new ReportingException("No destination is allowed on " + getClass().getSimpleName());
}
@Override
protected void doReset() {
// nothing to do
}
@Override
protected void doReport(final MeasurementUnit measurementUnit) throws ReportingException {
if (!warmed) {
if (runInfo.getRunTime() / CHECKING_PERIOD > checkingPeriodIndex.get()) { // make sure we are in the next time interval and we should check for warm up end
checkingPeriodIndex.incrementAndGet();
// The throughput unit is number of iterations per second
final Double currentThroughput = (double) CHECKING_PERIOD * getMaxIteration() / runInfo.getRunTime();
final Double lastThroughputValue = (Double) lastThroughput.getResult();
if (lastThroughputValue != null) {
final double relDelta = Math.abs(currentThroughput / lastThroughputValue - 1.0);
final double absDelta = Math.abs(currentThroughput - lastThroughputValue);
if (log.isTraceEnabled()) {
log.trace("checkingPeriodIndex=" + checkingPeriodIndex + ", currentThroughput=" + currentThroughput + ", lastThroughput=" + lastThroughputValue + ", absDelta=" + absDelta + ", relDelta=" + relDelta);
}
if ((runInfo.getRunTime() > minimalWarmUpDuration) && (getMaxIteration() > minimalWarmUpCount) && (absDelta < absoluteThreshold || relDelta < relativeThreshold)) {
if (log.isInfoEnabled()) {
log.info("The tested system is warmed up.");
}
reportManager.reset();
runInfo.removeTag(PerfCakeConst.WARM_UP_TAG);
warmed = true;
}
}
lastThroughput.add(currentThroughput);
}
// check whether we hit the maximal limit for warm-up duration
if (maximalWarmUpDuration > -1 && !warmed
&& ((maximalWarmUpType == PeriodType.ITERATION && maximalWarmUpDuration < getMaxIteration())
|| (maximalWarmUpType == PeriodType.TIME && maximalWarmUpDuration < runInfo.getRunTime())
|| (maximalWarmUpType == PeriodType.PERCENTAGE && maximalWarmUpDuration < runInfo.getPercentage()))) {
log.warn("The system did not warm-up until the maximal tolerance (" + maximalWarmUpType + ": " + maximalWarmUpDuration + "). Terminating the test.");
reportManager.stop();
}
}
}
@Override
protected boolean checkStart() {
return true;
}
/**
* Gets the value of minimal warm-up period duration.
*
* @return The minimal warm-up period duration.
*/
public long getMinimalWarmUpDuration() {
return minimalWarmUpDuration;
}
/**
* Sets the value of minimal warm-up period duration.
*
* @param minimalWarmUpDuration
* The minimal warm-up period duration to set.
* @return Instance of this for fluent API.
*/
public WarmUpReporter setMinimalWarmUpDuration(final long minimalWarmUpDuration) {
this.minimalWarmUpDuration = minimalWarmUpDuration;
return this;
}
/**
* Gets the value of minimal warm-up iteration count.
*
* @return The value of minimal warm-up iteration count.
*/
public long getMinimalWarmUpCount() {
return minimalWarmUpCount;
}
/**
* Sets the value of minimal warm-up iteration count.
*
* @param minimalWarmUpCount
* The value of minimal warm-up iteration count to set.
* @return Instance of this for fluent API.
*/
public WarmUpReporter setMinimalWarmUpCount(final long minimalWarmUpCount) {
this.minimalWarmUpCount = minimalWarmUpCount;
return this;
}
/**
* Gets the value of relative threshold.
*
* @return The value of relative threshold.
*/
public double getRelativeThreshold() {
return relativeThreshold;
}
/**
* Sets the value of relative threshold.
*
* @param relativeThreshold
* The value of relative threshold to set.
* @return Instance of this for fluent API.
*/
public WarmUpReporter setRelativeThreshold(final double relativeThreshold) {
this.relativeThreshold = relativeThreshold;
return this;
}
/**
* Gets the value of absolute threshold.
*
* @return The value of absolute threshold.
*/
public double getAbsoluteThreshold() {
return absoluteThreshold;
}
/**
* Sets the value of absolute threshold.
*
* @param absoluteThreshold
* The value of absolute threshold to set.
* @return Instance of this for fluent API.
*/
public WarmUpReporter setAbsoluteThreshold(final double absoluteThreshold) {
this.absoluteThreshold = absoluteThreshold;
return this;
}
/**
* Gets the unit in which we measure the maximal warm-up count. Can be iteration, time, or percentage.
*
* @return The unit in which we measure the maximal warm-up count.
*/
public PeriodType getMaximalWarmUpType() {
return maximalWarmUpType;
}
/**
* Sets the unit in which we measure the maximal warm-up count. Can be iteration, time, or percentage.
*
* @param maximalWarmUpType
* The unit in which we measure the maximal warm-up count.
* @return Instance of this to support fluent API.
*/
public WarmUpReporter setMaximalWarmUpType(final PeriodType maximalWarmUpType) {
this.maximalWarmUpType = maximalWarmUpType;
return this;
}
/**
* Gets the maximal tolerance of waiting for the end of the warm-up phase. If we run out of this time/iteration count (determined by {@link #maximalWarmUpType},
* we simply break the test and do not waste more time. {@code -1} means that the check is disabled.
*
* @return The maximal tolerance of waiting for the end of the warm-up phase.
*/
public long getMaximalWarmUpDuration() {
return maximalWarmUpDuration;
}
/**
* Sets the maximal tolerance of waiting for the end of the warm-up phase. If we run out of this time/iteration count (determined by {@link #maximalWarmUpType},
* we simply break the test and do not waste more time. {@code -1} means that the check is disabled.
*
* @param maximalWarmUpDuration
* The maximal tolerance of waiting for the end of the warm-up phase.
* @return Instance of this to support fluent API.
*/
public WarmUpReporter setMaximalWarmUpDuration(final long maximalWarmUpDuration) {
this.maximalWarmUpDuration = maximalWarmUpDuration;
return this;
}
}