/*
* -----------------------------------------------------------------------\
* 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.BinaryScalableQuantity;
import org.perfcake.reporting.Measurement;
import org.perfcake.reporting.MeasurementUnit;
import org.perfcake.reporting.Quantity;
import org.perfcake.reporting.ReportingException;
import org.perfcake.reporting.destination.Destination;
import org.perfcake.reporting.reporter.accumulator.Accumulator;
import org.perfcake.reporting.reporter.accumulator.AvgAccumulator;
import org.perfcake.reporting.reporter.accumulator.Histogram;
import org.perfcake.reporting.reporter.accumulator.LastValueAccumulator;
import org.perfcake.reporting.reporter.accumulator.MaxAccumulator;
import org.perfcake.reporting.reporter.accumulator.MinAccumulator;
import org.perfcake.reporting.reporter.accumulator.SlidingWindowAvgAccumulator;
import org.perfcake.reporting.reporter.accumulator.SlidingWindowMaxAccumulator;
import org.perfcake.reporting.reporter.accumulator.SlidingWindowMinAccumulator;
import org.perfcake.reporting.reporter.accumulator.SlidingWindowSumLongAccumulator;
import org.perfcake.reporting.reporter.accumulator.SumLongAccumulator;
import org.perfcake.reporting.reporter.accumulator.TimeSlidingWindowAvgAccumulator;
import org.perfcake.reporting.reporter.accumulator.TimeSlidingWindowMaxAccumulator;
import org.perfcake.reporting.reporter.accumulator.TimeSlidingWindowMinAccumulator;
import org.perfcake.reporting.reporter.accumulator.TimeSlidingWindowSumLongAccumulator;
/**
* <p>Reports the minimal, maximal and average value from the beginning
* of the measuring to the moment when the results are published including. The actual value about what
* the statistics are gathered is computed as a result of the {@link #computeResult(MeasurementUnit)} method.</p>
*
* <p>The default value of the reporter is a current value at the moment of publishing.</p>
*
* @author <a href="mailto:pavel.macik@gmail.com">Pavel Macík</a>
*/
public abstract class StatsReporter extends AbstractReporter {
/**
* A property that determines if the metric of a maximal value is enabled or disabled.
*/
private boolean maximumEnabled = true;
/**
* A property that determines if the metric of a minimal value is enabled or disabled.
*/
private boolean minimumEnabled = true;
/**
* A property that determines if the metric of an average value is enabled or disabled.
*/
private boolean averageEnabled = true;
/**
* True when request size reporting is enabled.
*/
private boolean requestSizeEnabled = true;
/**
* True when response size reporting is enabled.
*/
private boolean responseSizeEnabled = true;
/**
* A property that specifies a window size with the default value of {@link Integer#MAX_VALUE}.
*/
private int windowSize = Integer.MAX_VALUE;
/**
* The type of the window, either the number of iterations or a time duration.
*/
private WindowType windowType = WindowType.ITERATION;
/**
* The type of the window, either the number of iterations or a time duration.
*/
public enum WindowType {
/**
* A window of the number of latest iterations.
*/
ITERATION,
/**
* A window of a given time period.
*/
TIME
}
/**
* A comma separated list of values where the histogram is split to individual ranges.
*/
private String histogram = "";
/**
* String prefix used in the result map for histogram entries. This prefix is followed by the mathematical representation of the particular range.
*/
private String histogramPrefix = "in";
/**
* The actual histogram representation.
*/
private Histogram histogramCounter = null;
/**
* A String representation of a metric of a maximal value.
*/
public static final String MAXIMUM = "Maximum";
/**
* A String representation of a metric of a minimal value.
*/
public static final String MINIMUM = "Minimum";
/**
* A String representation of a metric of an average value.
*/
public static final String AVERAGE = "Average";
@SuppressWarnings("rawtypes")
@Override
protected Accumulator getAccumulator(final String key, final Class clazz) {
if (Double.class.equals(clazz) || PerfCakeConst.REQUEST_SIZE_TAG.equals(key) || PerfCakeConst.RESPONSE_SIZE_TAG.equals(key)) {
if (windowSize == Integer.MAX_VALUE) {
return getNonWindowedAccumulator(key);
} else {
return getWindowedAccumulator(key);
}
}
return super.getAccumulator(key, clazz);
}
/**
* Gets an appropriate accumulator for a given key from the Measurement Unit's results map for the case that the value of the {@link #windowSize} is
* different from the default value of {@link Integer#MAX_VALUE}.
*
* @param key
* Name of the key from the results map.
* @return An appropriate accumulator instance.
*/
@SuppressWarnings("rawtypes")
protected Accumulator getWindowedAccumulator(final String key) {
switch (windowType) {
case TIME:
switch (key) {
case MAXIMUM:
return new TimeSlidingWindowMaxAccumulator(windowSize);
case MINIMUM:
return new TimeSlidingWindowMinAccumulator(windowSize);
case Measurement.DEFAULT_RESULT:
return new LastValueAccumulator();
case PerfCakeConst.REQUEST_SIZE_TAG:
case PerfCakeConst.RESPONSE_SIZE_TAG:
return new TimeSlidingWindowSumLongAccumulator(windowSize);
case AVERAGE:
default:
return new TimeSlidingWindowAvgAccumulator(windowSize);
}
case ITERATION:
default:
switch (key) {
case MAXIMUM:
return new SlidingWindowMaxAccumulator(windowSize);
case MINIMUM:
return new SlidingWindowMinAccumulator(windowSize);
case Measurement.DEFAULT_RESULT:
return new LastValueAccumulator();
case PerfCakeConst.REQUEST_SIZE_TAG:
case PerfCakeConst.RESPONSE_SIZE_TAG:
return new SlidingWindowSumLongAccumulator(windowSize);
case AVERAGE:
default:
return new SlidingWindowAvgAccumulator(windowSize);
}
}
}
/**
* Gets an appropriate accumulator for a given key from the Measurement Unit's results map for the case that the value of the {@link #windowSize} is
* equal to the value of {@link Integer#MAX_VALUE}, which is the default value.
*
* @param key
* Name of the key from the results map.
* @return An appropriate accumulator instance.
*/
@SuppressWarnings("rawtypes")
protected Accumulator getNonWindowedAccumulator(final String key) {
switch (key) {
case MAXIMUM:
return new MaxAccumulator();
case MINIMUM:
return new MinAccumulator();
case Measurement.DEFAULT_RESULT:
return new LastValueAccumulator();
case PerfCakeConst.REQUEST_SIZE_TAG:
case PerfCakeConst.RESPONSE_SIZE_TAG:
return new SumLongAccumulator();
case AVERAGE:
default:
return new AvgAccumulator();
}
}
/**
* Computes the actual result value about what the reporter will collect the statistics.
*
* @param measurementUnit
* Provided {@link MeasurementUnit} with all the measured values.
* @return The processed result based on the input {@link MeasurementUnit}.
*/
protected abstract Double computeResult(final MeasurementUnit measurementUnit);
@Override
protected void doReport(final MeasurementUnit measurementUnit) throws ReportingException {
final Double result = computeResult(measurementUnit);
measurementUnit.appendResult(Measurement.DEFAULT_RESULT, result);
if (averageEnabled) {
measurementUnit.appendResult(AVERAGE, result);
}
if (minimumEnabled) {
measurementUnit.appendResult(MINIMUM, result);
}
if (maximumEnabled) {
measurementUnit.appendResult(MAXIMUM, result);
}
if (histogramCounter != null) {
histogramCounter.add(result);
}
}
@Override
public void publishResult(final PeriodType periodType, final Destination destination) throws ReportingException {
final Measurement m = newMeasurement();
publishAccumulatedResult(m);
final String unit = getResultUnit();
if (unit != null) {
wrapResultByQuantity(m, Measurement.DEFAULT_RESULT, unit);
wrapResultByQuantity(m, AVERAGE, unit);
wrapResultByQuantity(m, MINIMUM, unit);
wrapResultByQuantity(m, MAXIMUM, unit);
}
if (requestSizeEnabled) {
wrapResultByScalableQuantity(m, PerfCakeConst.REQUEST_SIZE_TAG, "B");
} else {
m.remove(PerfCakeConst.REQUEST_SIZE_TAG);
}
if (responseSizeEnabled) {
wrapResultByScalableQuantity(m, PerfCakeConst.RESPONSE_SIZE_TAG, "B");
} else {
m.remove(PerfCakeConst.RESPONSE_SIZE_TAG);
}
if (histogramCounter != null) {
histogramCounter.getHistogramInPercent().forEach((range, value) -> m.set(histogramPrefix + range.toString(), new Quantity<>(value, "%")));
}
destination.report(m);
}
private void wrapResultByScalableQuantity(final Measurement measurement, final String key, final String unit) {
final Long result = (Long) measurement.get(key);
if (result != null) {
measurement.set(key, new BinaryScalableQuantity(result, unit));
}
}
private void wrapResultByQuantity(final Measurement measurement, final String key, final String unit) {
final Number result = (Number) measurement.get(key);
if (result != null) {
measurement.set(key, new Quantity<>(result, unit));
}
}
@Override
protected void doReset() {
if (histogram != null && !histogram.isEmpty()) {
histogramCounter = new Histogram(histogram);
}
}
/**
* Gets the status of the metric of a maximal value
*
* @return Returns <code>true</code> if the metric of a maximal is enabled or <code>false</code> otherwise.
*/
public boolean isMaximumEnabled() {
return maximumEnabled;
}
/**
* Enables or disables the metric of a maximal value.
*
* @param maximumEnabled
* Set <code>true</code> to enable the metric of a maximal value or <code>false</code> to disable it.
* @return Instance of this for fluent API.
*/
public StatsReporter setMaximumEnabled(final boolean maximumEnabled) {
this.maximumEnabled = maximumEnabled;
return this;
}
/**
* Gets the status of the metric of a minimal value.
*
* @return <code>true</code> if the metric of a minimal value is enabled or <code>false</code> otherwise.
*/
public boolean isMinimumEnabled() {
return minimumEnabled;
}
/**
* Enables or disables the metric of a minimal value.
*
* @param minimumEnabled
* <code>true</code> to enable the metric of a minimal value or <code>false</code> to disable it.
* @return Instance of this for fluent API.
*/
public StatsReporter setMinimumEnabled(final boolean minimumEnabled) {
this.minimumEnabled = minimumEnabled;
return this;
}
/**
* Gets the status of the metric of an average value.
*
* @return <code>true</code> if the metric of an average value is enabled or <code>false</code> otherwise.
*/
public boolean isAverageEnabled() {
return averageEnabled;
}
/**
* Enables or disables the metric of an average value.
*
* @param averageEnabled
* <code>true</code> to enable the metric of an average value or <code>false</code> to disable it.
* @return Instance of this for fluent API.
*/
public StatsReporter setAverageEnabled(final boolean averageEnabled) {
this.averageEnabled = averageEnabled;
return this;
}
/**
* Gets the sliding window size if set. If the size is equal to {@link Integer#MAX_VALUE}, then it means the
* sliding window is not used at all and the statistics are taken from the whole run.
*
* @return The sliding window size.
*/
public int getWindowSize() {
return windowSize;
}
/**
* <p>Sets the size of the sliding window.</p>
*
* <p>If the size is equal to {@link Integer#MAX_VALUE} (which is the default value), then it means the
* sliding window is not used at all and the statistics are taken from the whole run.</p>
*
* @param windowSize
* The sliding window size.
* @return Instance of this for fluent API.
*/
public StatsReporter setWindowSize(final int windowSize) {
this.windowSize = windowSize;
return this;
}
/**
* Returns the unit of the results.
* If it returns a value that is not <code>null</code> the method {@link #publishResult(PeriodType, Destination)} will wrap the reporter's results by a {@link Quantity} with the return value of
* this method as units before sent to destinations.
* If a stats reporter is supposed to have results with units the reporter is to override this method.<br/>
* If the method returns a <code>null</code> value, the reporter's results remain untouched.
*
* @return The string representation of the reporter results' unit or a <code>null</code> value.
*/
protected String getResultUnit() {
return null;
}
/**
* Gets the string specifying where the histogram should be split.
*
* @return The string specifying where the histogram should be split.
*/
public String getHistogram() {
return histogram;
}
/**
* Sets the string specifying where the histogram should be split.
*
* @param histogram
* The string specifying where the histogram should be split.
* @return Instance of this to support fluent API.
*/
public StatsReporter setHistogram(String histogram) {
this.histogram = histogram;
return this;
}
/**
* Gets the string prefix used in the result map for histogram entries.
*
* @return The string prefix used in the result map for histogram entries.
*/
public String getHistogramPrefix() {
return histogramPrefix;
}
/**
* Sets the string prefix used in the result map for histogram entries.
*
* @param histogramPrefix
* The string prefix used in the result map for histogram entries.
* @return Instance of this to support fluent API.
*/
public StatsReporter setHistogramPrefix(String histogramPrefix) {
this.histogramPrefix = histogramPrefix;
return this;
}
/**
* Is the metric of the request size enabled?
*
* @return True if and only if the metric of the request size is enabled.
*/
public boolean isRequestSizeEnabled() {
return requestSizeEnabled;
}
/**
* Enables and disables the metric of the request size.
*
* @param requestSizeEnabled
* True to enable the metric.
* @return Instance of this to support fluent API.
*/
public StatsReporter setRequestSizeEnabled(final boolean requestSizeEnabled) {
this.requestSizeEnabled = requestSizeEnabled;
return this;
}
/**
* Is the metric of the response size enabled?
*
* @return True if and only if the metric of the response size is enabled.
*/
public boolean isResponseSizeEnabled() {
return responseSizeEnabled;
}
/**
* Enables and disables the metric of the response size.
*
* @param responseSizeEnabled
* True to enable the metric.
* @return Instance of this to support fluent API.
*/
public StatsReporter setResponseSizeEnabled(final boolean responseSizeEnabled) {
this.responseSizeEnabled = responseSizeEnabled;
return this;
}
/**
* Returns the type of the window.
*
* @return The window type.
*/
public WindowType getWindowType() {
return windowType;
}
/**
* Sets the type of the window.
*
* @param windowType
* Either ITERATION or TIME is supported.
* @return The instance of this for a fluent API.
*/
public StatsReporter setWindowType(final WindowType windowType) {
this.windowType = windowType;
return this;
}
}