
org.perfcake.reporting.destination.ChartDestination Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------\
* 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.destination;
import static org.perfcake.reporting.destination.ChartDestination.ChartType.LINE;
import org.perfcake.PerfCakeConst;
import org.perfcake.PerfCakeException;
import org.perfcake.common.PeriodType;
import org.perfcake.reporting.Measurement;
import org.perfcake.reporting.ReportingException;
import org.perfcake.reporting.destination.c3chart.C3ChartHelper;
import org.perfcake.reporting.destination.util.DataBuffer;
import org.perfcake.util.properties.MandatoryProperty;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Creates nice charts from the results. The charts are generated to the outputPath path. The charts in the same group can be later
* combined together based on the names of the columns. All previously generated charts in the outputPath path are placed in the final
* report. Use with caution as the big number of results can take too long to load in the browser. Each chart has a quick view file
* where the results can be seen while the test is still running.
*
* @author Martin Večeřa
*/
public class ChartDestination extends AbstractDestination {
/**
* A logger for this class.
*/
private static final Logger log = LogManager.getLogger(ChartDestination.class);
/**
* Attributes that should be stored in the chart.
*/
@MandatoryProperty
private List attributes;
/**
* Where to store the charts.
*/
private Path outputPath = Paths.get("perfcake-chart");
/**
* Name of the chart for this measurement.
*/
private String name = "PerfCake Results";
/**
* Group of this chart. Charts in the same group can be later matched for the column names.
*/
private String group = "default";
/**
* Y axis legend.
*/
private String yAxis = "Value";
/**
* X axis legend.
*/
private String xAxis = "Time";
/**
* The type of the X axis. It can display the overall progress of the test in Percents, Time, or Iteration numbers.
*/
private PeriodType xAxisType = PeriodType.TIME;
/**
* Height of the resulting chart in pixels.
*/
private int chartHeight = 400;
/**
* Helper to control the chart generation.
*/
private C3ChartHelper helper;
/**
* True when we will automatically combine previous chart reports with the new one.
*/
private boolean autoCombine = true;
/**
* Some attributes might end with an asterisk, in such a case, we are not able to create output until the end of the test.
*/
private boolean dynamicAttributes = false;
/**
* The chart can be either of line or bar type. Line is the default.
*/
private ChartType type = LINE;
/**
* Holds the data when the dynamic attributes are used and we cannot stream directly to a file.
*/
private DataBuffer buffer;
public enum ChartType {
LINE, BAR;
}
@Override
public void open() {
dynamicAttributes = attributes.stream().anyMatch(s -> s.endsWith("*"));
if (attributes.contains(PerfCakeConst.WARM_UP_TAG)) {
attributes.remove(PerfCakeConst.WARM_UP_TAG);
attributes.addAll(attributes.stream().map(attr -> attr + "_" + PerfCakeConst.WARM_UP_TAG).collect(Collectors.toList()));
}
if (dynamicAttributes) {
buffer = new DataBuffer(attributes);
} else {
helper = new C3ChartHelper(this);
}
}
@Override
public void close() {
if (dynamicAttributes) {
attributes = buffer.getAttributes();
helper = new C3ChartHelper(this);
}
if (!helper.isInitialized()) {
log.error("Chart destination was not properly initialized, skipping result creation.");
} else {
try {
if (dynamicAttributes) {
buffer.replay((measurement) -> {
try {
helper.appendResult(measurement);
} catch (ReportingException e) {
log.error("Unable to write all reported data: ", e);
}
});
}
helper.close();
helper.compileResults(autoCombine);
} catch (final PerfCakeException e) {
log.error("Unable to compile all collected results in a final report: ", e);
}
}
}
@Override
public void report(final Measurement measurement) throws ReportingException {
if (dynamicAttributes) {
buffer.record(measurement);
} else {
if (!helper.isInitialized()) {
throw new ReportingException("Chart destination was not properly initialized.");
}
helper.appendResult(measurement);
}
}
/**
* Gets the output directory for charts.
*
* @return The output directory location.
*/
public String getOutputDir() {
return outputPath.toString();
}
/**
* Sets the output directory for charts.
*
* @param outputDir
* The output directory location.
* @return Instance of this to support fluent API.
*/
public ChartDestination setOutputDir(final String outputDir) {
this.outputPath = Paths.get(outputDir);
return this;
}
/**
* Gets the output directory for charts as the Path data type.
*
* @return The output directory location.
*/
public Path getOutputDirAsPath() {
return outputPath;
}
/**
* Gets the name of the chart.
*
* @return The name of the chart.
*/
public String getName() {
return name;
}
/**
* Sets the name of the chart.
*
* @param name
* The name of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setName(final String name) {
this.name = name;
return this;
}
/**
* Gets the group of the resulting chart.
*
* @return The group of the chart.
*/
public String getGroup() {
return group;
}
/**
* Sets the group of the resulting chart.
*
* @param group
* The group of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setGroup(final String group) {
if ("".equals(group)) {
this.group = "default";
log.warn("Empty group name, renaming to: default");
} else if (!group.matches("[a-zA-Z][a-zA-Z0-9_]*")) {
final String postGroup = group.replaceAll("[^a-zA-Z0-9_]", "_");
final Matcher firstNumericCharMatcher = Pattern.compile("[0-9]").matcher(postGroup);
final int firstNumericCharIndex;
if (firstNumericCharMatcher.find()) {
firstNumericCharIndex = firstNumericCharMatcher.start();
} else {
firstNumericCharIndex = -1;
}
if (firstNumericCharIndex == 0) {
this.group = "_" + postGroup;
} else {
this.group = postGroup;
}
log.warn("Illegal characters found in the group name. Only allowed characters are letters, numbers and underscore. The group must not start with a number. Renaming to: " + this.group);
} else {
this.group = group;
}
return this;
}
/**
* Gets the height of the resulting chart in pixels.
*
* @return The height of the resulting chart in pixels.
*/
public int getChartHeight() {
return chartHeight;
}
/**
* Sets the height of the resulting chart in pixels.
*
* @param chartHeight
* The height of the resulting chart in pixels.
* @return The height of the resulting chart in pixels.
*/
public ChartDestination setChartHeight(final int chartHeight) {
this.chartHeight = chartHeight;
return this;
}
/**
* Gets the legend of the Y axis of the chart.
*
* @return The legend of the Y axis of the chart.
*/
public String getyAxis() {
return yAxis;
}
/**
* Gets the legend of the Y axis of the chart.
*
* @return The legend of the Y axis of the chart.
*/
public String getYAxis() {
return yAxis;
}
/**
* Sets the legend of the Y axis of the chart.
*
* @param yAxis
* The legend of the Y axis of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setyAxis(final String yAxis) {
this.yAxis = yAxis;
return this;
}
/**
* Sets the legend of the Y axis of the chart.
*
* @param yAxis
* The legend of the Y axis of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setYAxis(final String yAxis) {
this.yAxis = yAxis;
return this;
}
/**
* Gets the legend of the X axis of the chart.
*
* @return The legend of the X axis of the chart.
*/
public String getxAxis() {
return xAxis;
}
/**
* Gets the legend of the X axis of the chart.
*
* @return The legend of the X axis of the chart.
*/
public String getXAxis() {
return xAxis;
}
/**
* Sets the legend of the X axis of the chart.
*
* @param xAxis
* The legend of the X axis of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setxAxis(final String xAxis) {
this.xAxis = xAxis;
return this;
}
/**
* Sets the legend of the X axis of the chart.
*
* @param xAxis
* The legend of the X axis of the chart.
* @return Instance of this to support fluent API.
*/
public ChartDestination setXAxis(final String xAxis) {
this.xAxis = xAxis;
return this;
}
/**
* Gets the type of the X axis. It can be either Time, Percents, or Iteration number.
*
* @return The type of the X axis.
*/
public PeriodType getxAxisType() {
return xAxisType;
}
/**
* Sets the type of the X axis. It can be either Time, Percents, or Iteration number.
*
* @param xAxisType
* The type of the X axis.
* @return Instance of this to support fluent API.
*/
public ChartDestination setxAxisType(final PeriodType xAxisType) {
this.xAxisType = xAxisType;
return this;
}
/**
* Gets the attributes that will be written to the chart.
*
* @return The attributes separated by comma.
*/
public String getAttributes() {
return StringUtils.join(attributes, ",");
}
/**
* Sets the attributes that will be written to the chart.
*
* @param attributes
* The attributes separated by comma.
* @return Instance of this to support fluent API.
*/
public ChartDestination setAttributes(final String attributes) {
this.attributes = new ArrayList<>(Arrays.asList(attributes.split("\\s*,\\s*")));
return this;
}
/**
* Gets the attributes that will be written to the chart as a List.
*
* @return The attributes list.
*/
public List getAttributesAsList() {
return attributes;
}
/**
* Should we automatically combine previous chart reports with the new one?
*
* @return True if and only if the combining feature is turned on.
*/
public boolean isAutoCombine() {
return autoCombine;
}
/**
* Should we automatically combine previous chart reports with the new one?
*
* @param autoCombine
* True to turn the feature on, false to turn it off. Default is true/on.
* @return Instance of this to support fluent API.
*/
public ChartDestination setAutoCombine(final boolean autoCombine) {
this.autoCombine = autoCombine;
return this;
}
/**
* Gets the chart's graphics type - either line or bar. Line is the default.
*
* @return The chart's graphics type.
*/
public ChartType getType() {
return type;
}
/**
* Sets the chart's graphics type - either line or bar. Line is the default.
*
* @param type
* The chart's graphics type.
* @return Instance of this to support fluent API.
*/
public ChartDestination setType(final ChartType type) {
this.type = type;
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy