getConstraintDescriptors() {
return ConstraintDescriptor.all();
}
@Override
public boolean isApplicable(Class extends AbstractProject> jobType) {
return true;
}
/**
* Populate the comparison type dynamically based on the user selection from
* the previous time
*
* @return the name of the option selected in the previous run
*/
public ListBoxModel doFillConfigTypeItems() {
return getResponseTimeOptions();
}
public ListBoxModel doFillGraphTypeItems() {
return getResponseTimeOptions();
}
private ListBoxModel getResponseTimeOptions() {
ListBoxModel items = new ListBoxModel();
items.add("Average Response Time", "ART");
items.add("Median Response Time", "MRT");
items.add("Percentile Response Time", "PRT");
return items;
}
}
/**
* @return the filterRegex
*/
public String getFilterRegex() {
return filterRegex;
}
/**
* @param filterRegex the filterRegex to set
*/
@DataBoundSetter
public void setFilterRegex(String filterRegex) {
this.filterRegex = filterRegex;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/PerformanceReportMap.java
================================================
package hudson.plugins.performance;
import hudson.model.AbstractProject;
import hudson.model.Describable;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.actions.PerformanceProjectAction;
import hudson.plugins.performance.data.ReportValueSelector;
import hudson.plugins.performance.details.GraphConfigurationDetail;
import hudson.plugins.performance.parsers.JMeterParser;
import hudson.plugins.performance.parsers.PerformanceReportParser;
import hudson.plugins.performance.reports.AbstractReport;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.data.PerformanceReportPosition;
import hudson.plugins.performance.reports.ThroughputReport;
import hudson.plugins.performance.reports.UriReport;
import hudson.util.ChartUtil;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import hudson.util.DataSetBuilder;
import hudson.util.Graph;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.CategoryDataset;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* Root object of a performance report.
*/
public class PerformanceReportMap implements ModelObject {
/**
* The {@link PerformanceBuildAction} that this report belongs to.
*/
private transient PerformanceBuildAction buildAction;
/**
* {@link PerformanceReport}s are keyed by
* {@link PerformanceReport#reportFileName}
*
* Test names are arbitrary human-readable and URL-safe string that identifies
* an individual report.
*/
private Map performanceReportMap = new LinkedHashMap<>();
private static final String PERFORMANCE_REPORTS_DIRECTORY = "performance-reports";
private static final String PLUGIN_NAME = "performance";
private static final String TRENDREPORT_LINK = "trendReport";
public PerformanceReportMap(final PerformanceBuildAction buildAction,
TaskListener listener) throws IOException {
this(buildAction, listener, true);
}
/**
* Parses the reports and build a {@link PerformanceReportMap}.
*
* @throws IOException If a report fails to parse.
*/
public PerformanceReportMap(final PerformanceBuildAction buildAction,
TaskListener listener, boolean isTopLevel) throws IOException {
this.buildAction = buildAction;
parseReports(getBuild(), listener, new PerformanceReportCollector() {
public void addAll(Collection reports) {
for (PerformanceReport r : reports) {
r.setBuildAction(buildAction);
performanceReportMap.put(r.getReportFileName(), r);
}
}
}, null);
if (isTopLevel) {
addPreviousBuildReports();
}
}
private void addAll(Collection reports) {
for (PerformanceReport r : reports) {
r.setBuildAction(buildAction);
performanceReportMap.put(r.getReportFileName(), r);
}
}
public Run, ?> getBuild() {
return buildAction.getBuild();
}
PerformanceBuildAction getBuildAction() {
return buildAction;
}
public String getDisplayName() {
return Messages.Report_DisplayName();
}
public List getPerformanceListOrdered() {
List listPerformance = new ArrayList(
getPerformanceReportMap().values());
Collections.sort(listPerformance);
return listPerformance;
}
protected PerformancePublisher getPublisher() {
if (buildAction != null) {
Run, ?> build = buildAction.getBuild();
if (build != null) {
Job, ?> job = build.getParent();
if (job instanceof AbstractProject) {
AbstractProject, ?> project = (AbstractProject, ?>) job;
Describable> describable = project.getPublishersList().get(PerformancePublisher.class);
return (describable != null) ? (PerformancePublisher) describable : null;
}
}
}
return null;
}
public boolean ifModeThroughputUsed() {
PerformancePublisher publisher = getPublisher();
return publisher == null || publisher.isModeThroughput();
}
public boolean ifShowTrendGraphsUsed() {
PerformancePublisher publisher = getPublisher();
return publisher == null || publisher.isShowTrendGraphs();
}
public boolean ifModePerformancePerTestCaseUsed() {
PerformancePublisher publisher = getPublisher();
return publisher == null || publisher.isModePerformancePerTestCase();
}
public Map getPerformanceReportMap() {
return performanceReportMap;
}
/**
*
* Give the Performance report with the parameter for name in Bean
*
*
* @param performanceReportName
* @return
*/
public PerformanceReport getPerformanceReport(String performanceReportName) {
return performanceReportMap.get(performanceReportName);
}
/**
* Get a URI report within a Performance report file
*
* @param uriReport "Performance report file name";"URI name"
* @return
*/
public UriReport getUriReport(String uriReport) {
if (uriReport != null) {
String uriReportDecoded;
try {
uriReportDecoded = URLDecoder
.decode(uriReport.replace(UriReport.END_PERFORMANCE_PARAMETER, ""),
"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
StringTokenizer st = new StringTokenizer(uriReportDecoded,
GraphConfigurationDetail.SEPARATOR);
return getPerformanceReportMap().get(st.nextToken()).getUriReportMap()
.get(st.nextToken());
} else {
return null;
}
}
public String getUrlName() {
return PLUGIN_NAME;
}
public void setBuildAction(PerformanceBuildAction buildAction) {
this.buildAction = buildAction;
}
public void setPerformanceReportMap(
Map performanceReportMap) {
this.performanceReportMap = performanceReportMap;
}
public static String getPerformanceReportFileRelativePath(
String parserDisplayName, String reportFileName) {
return getRelativePath(parserDisplayName, reportFileName);
}
public static String getPerformanceReportDirRelativePath() {
return getRelativePath();
}
private static String getRelativePath(String... suffixes) {
StringBuilder sb = new StringBuilder(100);
sb.append(PERFORMANCE_REPORTS_DIRECTORY);
for (String suffix : suffixes) {
sb.append(File.separator).append(suffix);
}
return sb.toString();
}
/**
*
* Verify if the PerformanceReport exist the performanceReportName must to be
* like it is in the build
*
*
* @param performanceReportName
* @return boolean
*/
public boolean isFailed(String performanceReportName) {
return getPerformanceReport(performanceReportName) == null;
}
public void doRespondingTimeGraph(StaplerRequest request,
StaplerResponse response) throws IOException {
String parameter = request.getParameter("performanceReportPosition");
Run, ?> previousBuild = getBuild();
final Map, Map> buildReports = getBuildReports(parameter, previousBuild);
// Now we should have the data necessary to generate the graphs!
DataSetBuilder dataSetBuilder = new DataSetBuilder();
ReportValueSelector valueSelector = ReportValueSelector.get(getBuild().getParent());
String keyLabel = getKeyLabel(valueSelector.getGraphType());
for (Map.Entry, Map> entry : buildReports.entrySet()) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(entry.getKey());
PerformanceReport report = entry.getValue().get(parameter);
dataSetBuilder.add(valueSelector.getValue(report),
keyLabel, label);
}
String legendLimit = request.getParameter("legendLimit");
int limit = (legendLimit != null && !legendLimit.isEmpty()) ? Integer.parseInt(legendLimit) : Integer.MAX_VALUE;
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createRespondingTimeChart(dataSetBuilder.build(), limit);
}
}.doPng(request, response);
}
public void doThroughputGraph(StaplerRequest request, StaplerResponse response) throws IOException {
String parameter = request.getParameter("performanceReportPosition");
if (parameter == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
final DataSetBuilder dataSetBuilder = new DataSetBuilder<>();
List extends Run, ?>> builds = buildAction.getBuild().getParent().getBuilds();
for (final Run, ?> build : builds) {
final PerformanceBuildAction performanceBuildAction = build.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
final PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(parameter);
if (performanceReport == null) {
continue;
}
final ThroughputReport throughputReport = new ThroughputReport(performanceReport);
final NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
dataSetBuilder.add(throughputReport.get(), Messages.ProjectAction_RequestsPerSeconds(), label);
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createThroughputChart((dataSetBuilder.build()));
}
}.doPng(request, response);
}
protected JFreeChart createThroughputChart(CategoryDataset dataset) {
return PerformanceProjectAction.createThroughputChart(dataset);
}
public void doRespondingTimeGraphPerTestCaseMode(
StaplerRequest request, StaplerResponse response) throws IOException {
final String performanceReportNameFile = request.getParameter("performanceReportPosition");
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
final DataSetBuilder dataSetBuilder = new DataSetBuilder<>();
List extends Run, ?>> builds = buildAction.getBuild().getParent().getBuilds();
ReportValueSelector valueSelector = ReportValueSelector.get(getPublisher());
for (Run, ?> build : builds) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
final PerformanceBuildAction performanceBuildAction = build.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
final PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(performanceReportNameFile);
if (performanceReport == null) {
continue;
}
List uriListOrdered = performanceReport.getUriListOrdered();
for (UriReport uriReport : uriListOrdered) {
dataSetBuilder.add(valueSelector.getValue(uriReport), uriReport.getUri(), label);
}
}
String legendLimit = request.getParameter("legendLimit");
int limit = (legendLimit != null && !legendLimit.isEmpty()) ? Integer.parseInt(legendLimit) : Integer.MAX_VALUE;
new Graph(-1, 600, 200) {
@Override
protected JFreeChart createGraph() {
return createRespondingTimeChart(dataSetBuilder.build(), limit);
}
}.doPng(request, response);
}
public void doErrorsGraph(StaplerRequest request, StaplerResponse response)
throws IOException {
final String performanceReportNameFile = request.getParameter("performanceReportPosition");
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
DataSetBuilder dataSetBuilderErrors = new DataSetBuilder();
List extends Run, ?>> builds = buildAction.getBuild().getParent().getBuilds();
for (Run, ?> currentBuild : builds) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(currentBuild);
PerformanceBuildAction performanceBuildAction = currentBuild
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(
performanceReportNameFile);
if (performanceReport == null) {
continue;
}
dataSetBuilderErrors.add(performanceReport.errorPercent(),
Messages.ProjectAction_Errors(), label);
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createErrorsChart(dataSetBuilderErrors.build());
}
}.doPng(request, response);
}
protected JFreeChart createErrorsChart(CategoryDataset dataset) {
return PerformanceProjectAction.createErrorsChart(dataset);
}
protected JFreeChart createRespondingTimeChart(CategoryDataset dataset, int legendLimit) {
return PerformanceProjectAction.doCreateRespondingTimeChart(dataset, legendLimit);
}
private String getKeyLabel(String configType) {
if (configType.equals(PerformancePublisher.MRT))
return Messages.ProjectAction_Median();
if (configType.equals(PerformancePublisher.PRT))
return Messages.ProjectAction_Line90();
return Messages.ProjectAction_Average();
}
private Map, Map> getBuildReports(String parameter, Run, ?> previousBuild) throws IOException {
final Map, Map> buildReports = new LinkedHashMap, Map>();
while (previousBuild != null) {
final Run, ?> currentBuild = previousBuild;
parseReports(currentBuild, TaskListener.NULL,
new PerformanceReportCollector() {
public void addAll(Collection parse) {
for (PerformanceReport performanceReport : parse) {
if (buildReports.get(currentBuild) == null) {
Map map = new LinkedHashMap();
buildReports.put(currentBuild, map);
}
buildReports.get(currentBuild).put(
performanceReport.getReportFileName(), performanceReport);
}
}
}, parameter);
previousBuild = previousBuild.getPreviousCompletedBuild();
}
return buildReports;
}
public void doSummarizerGraph(StaplerRequest request, StaplerResponse response)
throws IOException {
String parameter = request.getParameter("performanceReportPosition");
Run, ?> previousBuild = getBuild();
Map, Map> buildReports = getBuildReports(parameter, previousBuild);
DataSetBuilder dataSetBuilderSummarizer = new DataSetBuilder();
ReportValueSelector valueSelector = ReportValueSelector.get(getBuild().getParent());
for (Map.Entry, Map> entry : buildReports.entrySet()) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(entry.getKey());
PerformanceReport report = entry.getValue().get(parameter);
// Now we should have the data necessary to generate the graphs!
for (Map.Entry secondEntry : report.getUriReportMap().entrySet()) {
long methodValue = valueSelector.getValue(secondEntry.getValue());
dataSetBuilderSummarizer.add(methodValue, label, secondEntry.getKey());
}
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createSummarizerChart(dataSetBuilderSummarizer.build());
}
}.doPng(request, response);
}
protected JFreeChart createSummarizerChart(CategoryDataset dataset) {
return PerformanceProjectAction.doCreateSummarizerChart(dataset, "ms", Messages.ProjectAction_RespondingTime());
}
protected void parseReports(Run, ?> build, TaskListener listener,
PerformanceReportCollector collector, final String filename)
throws IOException {
File repo = new File(build.getRootDir(),
PerformanceReportMap.getPerformanceReportDirRelativePath());
// files directly under the directory are for JMeter, for compatibility
// reasons.
File[] files = repo.listFiles(new FileFilter() {
public boolean accept(File f) {
return !f.isDirectory() && !f.getName().endsWith(".serialized");
}
});
// this may fail, if the build itself failed, we need to recover gracefully
if (files != null) {
addAll(new JMeterParser("", AbstractReport.DEFAULT_PERCENTILES).parse(build, Arrays.asList(files), listener));
}
// otherwise subdirectory name designates the parser ID.
File[] dirs = repo.listFiles(new FileFilter() {
public boolean accept(File f) {
return f.isDirectory();
}
});
// this may fail, if the build itself failed, we need to recover gracefully
if (dirs != null) {
for (File dir : dirs) {
PerformanceReportParser p = buildAction.getParserByDisplayName(dir
.getName());
if (p != null) {
File[] listFiles = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (filename == null && !name.endsWith(".serialized")) {
return true;
}
if (name.equals(filename)) {
return true;
}
return false;
}
});
if (listener != null && listener.getLogger() != null) {
try {
collector.addAll(p.parse(build, Arrays.asList(listFiles), listener));
} catch (IOException ex) {
listener.getLogger().println("Unable to process directory '" + dir + "'.");
ex.printStackTrace(listener.getLogger());
}
} else {
// Handle the situation when listener or its logger is null
}
}
}
}
//addPreviousBuildReports();
}
private void addPreviousBuildReports() {
for (Map.Entry item : getPerformanceReportMap().entrySet()) {
PerformanceReport curReport = item.getValue();
int baselineBuild = curReport.getBaselineBuild();
PerformanceReport reportForCompare = (baselineBuild == 0) ?
getPerformanceReportForBuild(getBuild().getPreviousCompletedBuild(), item.getKey()) :
getPerformanceReportForBuild(getBuild(baselineBuild), item.getKey());
if (reportForCompare != null) {
curReport.setLastBuildReport(reportForCompare);
}
}
}
protected PerformanceReportMap getReportMap(Run, ?> build) {
if (build == null) {
return null;
}
PerformanceBuildAction action = build.getAction(PerformanceBuildAction.class);
if (action == null) {
return null;
}
return action.getPerformanceReportMap(false);
}
protected PerformanceReport getPerformanceReportForBuild(Run, ?> build, String key) {
PerformanceReportMap reportMap = getReportMap(build);
if (reportMap == null) {
return null;
}
return reportMap.getPerformanceReportMap().get(key);
}
protected Run, ?> getBuild(int buildNumber) {
Run, ?> r = getBuild();
while (r != null && buildNumber != r.getNumber()) {
r = r.getPreviousBuild();
}
return r;
}
protected interface PerformanceReportCollector {
void addAll(Collection parse);
}
public Object getDynamic(final String link, final StaplerRequest request,
final StaplerRequest response) {
if (TRENDREPORT_LINK.equals(link)) {
return createTrendReportGraphs(request);
} else {
return null;
}
}
public Object createTrendReportGraphs(final StaplerRequest request) {
String filename = getTrendReportFilename(request);
PerformanceReport report = performanceReportMap.get(filename);
Run, ?> build = getBuild();
if (build == null) {
// Handle the situation when build is null
return null;
}
TrendReportGraphs trendReport = new TrendReportGraphs(build.getParent(),
build, request, filename, report);
return trendReport;
}
private String getTrendReportFilename(final StaplerRequest request) {
PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
return performanceReportPosition.getPerformanceReportPosition();
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/TrendReportGraphs.java
================================================
package hudson.plugins.performance;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.model.Run;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.data.PerformanceReportPosition;
import hudson.plugins.performance.reports.UriReport;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
import java.util.ArrayList;
public class TrendReportGraphs implements ModelObject {
private Run, ?> build;
private String filename;
private PerformanceReport performanceReport;
private Job, ?> project;
public TrendReportGraphs(final Job, ?> project,
final Run, ?> build, final StaplerRequest request,
String filename, PerformanceReport performanceReport) {
this.build = build;
this.filename = filename;
this.performanceReport = performanceReport;
this.project = project;
}
private UriReport getUriReportForRequest(StaplerRequest request) {
PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
PerformanceBuildAction performanceBuildAction = build
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction != null && performanceReport != null) {
String uri = performanceReportPosition.getSummarizerTrendUri();
if (uri != null) {
return performanceReport.getUriReportMap().get(uri);
}
}
return null;
}
public void doRespondingTimeGraph(StaplerRequest request,
StaplerResponse response) throws IOException {
UriReport uriReport = getUriReportForRequest(request);
if (uriReport != null) {
uriReport.doSummarizerTrendGraph(request, response);
}
}
public void doPercentileGraph(StaplerRequest request,
StaplerResponse response) throws IOException {
UriReport uriReport = getUriReportForRequest(request);
if (uriReport != null) {
uriReport.doPercentileGraph(request, response);
}
}
public void doThroughputGraph(StaplerRequest request,
StaplerResponse response) throws IOException {
UriReport uriReport = getUriReportForRequest(request);
if (uriReport != null) {
uriReport.doThroughputGraph(request, response);
}
}
public void doErrorGraph(StaplerRequest request,
StaplerResponse response) throws IOException {
UriReport uriReport = getUriReportForRequest(request);
if (uriReport != null) {
uriReport.doErrorGraph(request, response);
}
}
public ArrayList getUris() {
ArrayList uriList = new ArrayList<>();
PerformanceReport report = getPerformanceReport();
if (report != null) {
uriList.addAll(report.getUriReportMap().keySet());
}
return uriList;
}
public UriReport getUriReport(String uri) {
if (performanceReport != null) {
return performanceReport.getUriReportMap().get(uri);
} else {
return build.getAction(PerformanceBuildAction.class)
.getPerformanceReportMap().getUriReport(uri);
}
}
public String getDisplayName() {
return Messages.TrendReportDetail_DisplayName();
}
public String getFilename() {
return filename;
}
public Job, ?> getProject() {
return project;
}
public Run, ?> getBuild() {
return build;
}
public boolean hasSamples(String uri) {
UriReport report = getUriReport(uri);
return report != null && report.hasSamples();
}
public PerformanceReport getPerformanceReport() {
return performanceReport;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/actions/ExternalBuildReportAction.java
================================================
package hudson.plugins.performance.actions;
import hudson.model.Action;
import org.kohsuke.stapler.StaplerProxy;
public class ExternalBuildReportAction implements Action, StaplerProxy {
private final String reportURL;
public ExternalBuildReportAction(String reportURL) {
this.reportURL = reportURL;
}
@Override
public String getIconFileName() {
return "graph.gif";
}
@Override
public String getDisplayName() {
return "View External Report";
}
@Override
public String getUrlName() {
return reportURL;
}
@Override
public Object getTarget() {
return null;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/actions/PerformanceBuildAction.java
================================================
package hudson.plugins.performance.actions;
import hudson.model.Action;
import hudson.model.Run;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.PerformanceReportMap;
import hudson.plugins.performance.parsers.PerformanceReportParser;
import hudson.util.StreamTaskListener;
import org.kohsuke.stapler.StaplerProxy;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class PerformanceBuildAction implements Action, StaplerProxy {
private final Run, ?> build;
/**
* Configured parsers used to parse reports in this build.
* For compatibility reasons, this can be null.
*/
private final List parsers;
private transient final PrintStream hudsonConsoleWriter;
private transient WeakReference performanceReportMap;
private static final Logger logger = Logger.getLogger(PerformanceBuildAction.class.getName());
public PerformanceBuildAction(Run, ?> pBuild, PrintStream logger,
List parsers) {
build = pBuild;
hudsonConsoleWriter = logger;
this.parsers = parsers;
}
public PerformanceReportParser getParserByDisplayName(String displayName) {
if (parsers != null)
for (PerformanceReportParser parser : parsers)
if (parser.getDescriptor().getDisplayName().equals(displayName))
return parser;
return null;
}
public String getDisplayName() {
return Messages.BuildAction_DisplayName();
}
public String getIconFileName() {
return "graph.gif";
}
public String getUrlName() {
return "performance";
}
public PerformanceReportMap getTarget() {
return getPerformanceReportMap(true);
}
public Run, ?> getBuild() {
return build;
}
public PrintStream getHudsonConsoleWriter() {
return hudsonConsoleWriter;
}
public synchronized PerformanceReportMap getPerformanceReportMap() {
return getPerformanceReportMap(true);
}
public synchronized PerformanceReportMap getPerformanceReportMap(boolean isInitNextLevel) {
PerformanceReportMap reportMap = null;
synchronized(this) {
WeakReference wr = this.performanceReportMap;
if (wr != null) {
reportMap = wr.get();
if (reportMap != null) {
return reportMap;
}
}
}
try {
reportMap = new PerformanceReportMap(this, StreamTaskListener.fromStderr(), isInitNextLevel);
} catch (IOException e) {
logger.log(Level.SEVERE, "Error creating new PerformanceReportMap()", e);
}
this.performanceReportMap = new WeakReference<>(reportMap);
return reportMap;
}
public synchronized void setPerformanceReportMap(WeakReference performanceReportMap) {
synchronized(this) {
this.performanceReportMap = performanceReportMap;
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/actions/PerformanceProjectAction.java
================================================
package hudson.plugins.performance.actions;
import java.awt.BasicStroke;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYDotRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Job;
import hudson.model.Run;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.PerformancePublisher;
import hudson.plugins.performance.PerformanceReportMap;
import hudson.plugins.performance.data.PerformanceReportPosition;
import hudson.plugins.performance.data.ReportValueSelector;
import hudson.plugins.performance.details.GraphConfigurationDetail;
import hudson.plugins.performance.details.TestSuiteReportDetail;
import hudson.plugins.performance.details.TrendReportDetail;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.ThroughputReport;
import hudson.plugins.performance.reports.UriReport;
import hudson.util.ChartUtil;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.Graph;
import hudson.util.ShiftedCategoryAxis;
public class PerformanceProjectAction implements Action {
private static final String CONFIGURE_LINK = "configure";
private static final String TRENDREPORT_LINK = "trendReport";
private static final String TESTSUITE_LINK = "testsuiteReport";
private static final String PLUGIN_NAME = "performance";
@SuppressWarnings("unused")
private static final long serialVersionUID = 1L;
/**
* Logger.
*/
private static final Logger LOGGER = Logger
.getLogger(PerformanceProjectAction.class.getName());
public final Job, ?> job;
private List performanceReportList;
public String getDisplayName() {
return Messages.ProjectAction_DisplayName();
}
public String getIconFileName() {
return "graph.gif";
}
public String getUrlName() {
return PLUGIN_NAME;
}
public PerformanceProjectAction(Job, ?> job) {
this.job = job;
}
public static JFreeChart createErrorsChart(CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createLineChart(
Messages.ProjectAction_PercentageOfErrors(), // chart title
null, // unused
"%", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.BOTTOM);
chart.setBackgroundPaint(Color.WHITE);
final CategoryPlot plot = chart.getCategoryPlot();
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setUpperBound(100);
rangeAxis.setLowerBound(0);
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot
.getRenderer();
renderer.setBaseStroke(new BasicStroke(4.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0));
return chart;
}
public static JFreeChart doCreateRespondingTimeChart(CategoryDataset dataset, int legendLimit) {
final JFreeChart chart = ChartFactory.createLineChart(
Messages.ProjectAction_RespondingTime(), // charttitle
null, // unused
"ms", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.BOTTOM);
if (dataset.getRowCount() > legendLimit) {
chart.removeLegend();
}
chart.setBackgroundPaint(Color.WHITE);
final CategoryPlot plot = chart.getCategoryPlot();
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot
.getRenderer();
renderer.setBaseStroke(new BasicStroke(4.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0));
return chart;
}
public static JFreeChart createThroughputChart(final CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createLineChart(
Messages.ProjectAction_Throughput(), // chart title
null, // unused
Messages.ProjectAction_RequestsPerSeconds(), // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.BOTTOM);
chart.setBackgroundPaint(Color.WHITE);
final CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setBaseStroke(new BasicStroke(4.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0));
return chart;
}
public static JFreeChart doCreateSummarizerChart(CategoryDataset dataset,
String yAxis, String chartTitle) {
final JFreeChart chart = ChartFactory.createBarChart(chartTitle, // chart
// title
null, // unused
yAxis, // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
true // urls
);
chart.setBackgroundPaint(Color.WHITE);
final CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);
final BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDrawBarOutline(false);
renderer.setBaseStroke(new BasicStroke(4.0f));
renderer.setItemMargin(0);
renderer.setMaximumBarWidth(0.05);
return chart;
}
public static JFreeChart createSummarizerTrend(
ArrayList dataset, String uri) {
final JFreeChart chart = ChartFactory.createTimeSeriesChart(uri, Messages.TrendReportDetail_Time(),
Messages.TrendReportDetail_ResponseTime(), dataset.get(0), true, true, false);
chart.setBackgroundPaint(Color.WHITE);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.BLACK);
plot.setRangeGridlinePaint(Color.BLACK);
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
/*
* final NumberAxis axis2 = new NumberAxis("Errors"); axis2.isAutoRange();
* axis2.setLowerBound(0); plot.setRangeAxis(1, axis2); plot.setDataset(1,
* dataset.get(1)); plot.mapDatasetToRangeAxis(1, 1);
*
* final StandardXYItemRenderer renderer2 = new StandardXYItemRenderer();
* renderer2.setSeriesPaint(0, Color.black); plot.setRenderer(1, renderer2);
*/
final DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("HH:mm:ss"));
final XYDotRenderer renderer = new XYDotRenderer(); // scatter plot
plot.setRenderer(renderer);
renderer.setDotWidth(2);
renderer.setDotHeight(2);
renderer.setSeriesPaint(0, ColorPalette.RED);
return chart;
}
public static JFreeChart createUriPercentileChart(XYDataset dataset, String uri) {
final JFreeChart chart = ChartFactory.createXYLineChart(uri, Messages.TrendReportDetail_Percent(),
Messages.TrendReportDetail_ResponseTime(), dataset,
PlotOrientation.VERTICAL, true, true, false);
chart.setBackgroundPaint(Color.WHITE);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.BLACK);
plot.setRangeGridlinePaint(Color.BLACK);
// Exclude zeroes to make sure y-axis aligns with response time auto range:
final NumberAxis yaxis = (NumberAxis) plot.getRangeAxis();
yaxis.setAutoRange(true);
yaxis.setAutoRangeIncludesZero(false);
final NumberAxis xaxis = (NumberAxis) plot.getDomainAxis();
xaxis.setRange(0, 100); // avoid auto margin
final XYItemRenderer renderer = plot.getRenderer();
renderer.setSeriesPaint(0, ColorPalette.RED);
return chart;
}
public static JFreeChart createUriThroughputChart(IntervalXYDataset dataset, String uri) {
final JFreeChart chart = ChartFactory.createXYBarChart(uri, Messages.TrendReportDetail_Time(), true,
Messages.TrendReportDetail_RequestsPerMinute(), dataset,
PlotOrientation.VERTICAL, true, true, false);
chart.setBackgroundPaint(Color.WHITE);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.BLACK);
plot.setRangeGridlinePaint(Color.BLACK);
final DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("HH:mm:ss"));
final XYBarRenderer renderer = (XYBarRenderer) plot.getRenderer();
// As of Jenkins v2.198 jfreechart v1.0.19 paints bars with a gradient by
// default
// which can't be turned off in v1.0.9 the plugin is compiled against
// so instead use a red outline and fill with white:
renderer.setSeriesPaint(0, Color.WHITE);
renderer.setDrawBarOutline(true);
renderer.setSeriesOutlinePaint(0, ColorPalette.RED);
return chart;
}
private String getPerformanceReportNameFile(StaplerRequest request) {
PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
return getPerformanceReportNameFile(performanceReportPosition);
}
private String getPerformanceReportNameFile(final PerformanceReportPosition performanceReportPosition) {
String performanceReportNameFile = performanceReportPosition.getPerformanceReportPosition();
if (performanceReportNameFile == null && getPerformanceReportList().size() == 1) {
performanceReportNameFile = getPerformanceReportList().get(0);
}
return performanceReportNameFile;
}
public void doErrorsGraph(StaplerRequest request, StaplerResponse response)
throws IOException {
final String performanceReportNameFile = getPerformanceReportNameFile(request);
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
DataSetBuilder dataSetBuilderErrors = new DataSetBuilder<>();
List extends Run, ?>> builds = getJob().getBuilds();
Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (Run, ?> currentBuild : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
if (!buildsLimits.includedByStep(currentBuild.number)) {
continue;
}
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(currentBuild);
PerformanceBuildAction performanceBuildAction = currentBuild
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(
performanceReportNameFile);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
dataSetBuilderErrors.add(performanceReport.errorPercent(),
Messages.ProjectAction_Errors(), label);
}
nbBuildsToAnalyze--;
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createErrorsGraph(dataSetBuilderErrors.build());
}
}.doPng(request, response);
}
protected JFreeChart createErrorsGraph(CategoryDataset dataset) {
return createErrorsChart(dataset);
}
public void doRespondingTimeGraphPerTestCaseMode(
StaplerRequest request, StaplerResponse response) throws IOException {
final String performanceReportNameFile = getPerformanceReportNameFile(request);
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
DataSetBuilder dataSetBuilder = new DataSetBuilder<>();
ReportValueSelector valueSelector = ReportValueSelector.get(getJob());
List extends Run, ?>> builds = getJob().getBuilds();
Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (Run, ?> build : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
if (!buildsLimits.includedByStep(build.number)) {
continue;
}
PerformanceReport performanceReport = getPerformanceReport(build, performanceReportNameFile);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
List uriListOrdered = performanceReport.getUriListOrdered();
for (UriReport uriReport : uriListOrdered) {
dataSetBuilder.add(valueSelector.getValue(uriReport), uriReport.getUri(), label);
}
}
nbBuildsToAnalyze--;
}
String legendLimit = request.getParameter("legendLimit");
int limit = (legendLimit != null && !legendLimit.isEmpty()) ? Integer.parseInt(legendLimit) : Integer.MAX_VALUE;
new Graph(-1, 600, 200) {
@Override
protected JFreeChart createGraph() {
return createRespondingTimeChart(dataSetBuilder.build(), limit);
}
}.doPng(request, response);
}
protected PerformanceReport getPerformanceReport(Run, ?> build, String reportFileName) {
PerformanceBuildAction performanceBuildAction = build.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
return null;
}
return performanceBuildAction
.getPerformanceReportMap()
.getPerformanceReport(reportFileName);
}
protected JFreeChart createRespondingTimeChart(CategoryDataset dataset, int legendLimit) {
return doCreateRespondingTimeChart(dataset, legendLimit);
}
public void doRespondingTimeGraph(StaplerRequest request, StaplerResponse response) throws IOException {
final String performanceReportNameFile = getPerformanceReportNameFile(request);
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
DataSetBuilder dataSetBuilderAverage = new DataSetBuilder<>();
List extends Run, ?>> builds = getJob().getBuilds();
Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (Run, ?> build : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
if (!buildsLimits.includedByStep(build.number)) {
continue;
}
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
PerformanceBuildAction performanceBuildAction = build
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(
performanceReportNameFile);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
dataSetBuilderAverage.add(performanceReport.getMedian(),
Messages.ProjectAction_Median(), label);
dataSetBuilderAverage.add(performanceReport.getAverage(),
Messages.ProjectAction_Average(), label);
dataSetBuilderAverage.add(performanceReport.get90Line(),
Messages.ProjectAction_Line90(), label);
dataSetBuilderAverage.add(performanceReport.get95Line(),
Messages.ProjectAction_Line95(), label);
}
nbBuildsToAnalyze--;
}
String legendLimit = request.getParameter("legendLimit");
int limit = (legendLimit != null && !legendLimit.isEmpty()) ? Integer.parseInt(legendLimit) : Integer.MAX_VALUE;
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createRespondingTimeChart(dataSetBuilderAverage.build(), limit);
}
}.doPng(request, response);
}
public void doThroughputGraph(final StaplerRequest request, final StaplerResponse response) throws IOException {
final String performanceReportNameFile = getPerformanceReportNameFile(request);
if (performanceReportNameFile == null) {
return;
}
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
final DataSetBuilder dataSetBuilder = new DataSetBuilder<>();
final List extends Run, ?>> builds = getJob().getBuilds();
final Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (final Run, ?> build : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
if (!buildsLimits.includedByStep(build.number)) {
continue;
}
final PerformanceBuildAction performanceBuildAction = build.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
final PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(performanceReportNameFile);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
final ThroughputReport throughputReport = new ThroughputReport(performanceReport);
final NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
dataSetBuilder.add(throughputReport.get(), Messages.ProjectAction_RequestsPerSeconds(), label);
}
nbBuildsToAnalyze--;
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createThroughputGraph(dataSetBuilder.build());
}
}.doPng(request, response);
}
protected JFreeChart createThroughputGraph(CategoryDataset dataset) {
return createThroughputChart(dataset);
}
public void doSummarizerGraph(StaplerRequest request, StaplerResponse response) throws IOException {
final PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
final String performanceReportNameFile = getPerformanceReportNameFile(performanceReportPosition);
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
// response.sendRedirect2(request.getContextPath() +
// "/images/headless.png");
return;
}
DataSetBuilder dataSetBuilderSummarizer = new DataSetBuilder<>();
DataSetBuilder dataSetBuilderSummarizerErrors = new DataSetBuilder<>();
ReportValueSelector valueSelector = ReportValueSelector.get(getJob());
List> builds = getJob().getBuilds();
Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (Object build : builds) {
Run, ?> currentBuild = (Run, ?>) build;
if (buildsLimits.in(nbBuildsToAnalyze)) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(currentBuild);
PerformanceReport performanceReport = getPerformanceReport(currentBuild, performanceReportNameFile);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
for (Map.Entry entry : performanceReport.getUriReportMap().entrySet()) {
long methodValue = valueSelector.getValue(entry.getValue());
float methodErrors = entry.getValue().getSummarizerErrors();
dataSetBuilderSummarizer.add(methodValue, label, entry.getKey());
dataSetBuilderSummarizerErrors.add(methodErrors, label, entry.getKey());
}
}
nbBuildsToAnalyze--;
}
String summarizerReportType = performanceReportPosition
.getSummarizerReportType();
if (summarizerReportType != null) {
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createSummarizerChart(dataSetBuilderSummarizerErrors.build(), "%",
Messages.ProjectAction_PercentageOfErrors());
}
}.doPng(request, response);
} else {
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return createSummarizerChart(dataSetBuilderSummarizer.build(), "ms",
Messages.ProjectAction_RespondingTime());
}
}.doPng(request, response);
}
}
protected JFreeChart createSummarizerChart(CategoryDataset dataset, String yAxis, String chartTitle) {
return doCreateSummarizerChart(dataset, yAxis, chartTitle);
}
/**
*
* give a list of two Integer : the smallest build to use and the biggest.
*
*
* @param request
* @param builds
* @return outList
*/
private Range getFirstAndLastBuild(StaplerRequest request, List> builds) {
GraphConfigurationDetail graphConf = (GraphConfigurationDetail) createUserConfiguration(request);
if (graphConf.isNone()) {
return all(builds);
}
if (graphConf.isBuildCount()) {
if (graphConf.getBuildCount() <= 0) {
return all(builds);
} else {
int first = builds.size() - graphConf.getBuildCount();
return new Range(first > 0 ? first + 1 : 1, builds.size());
}
} else if (graphConf.isBuildNth()) {
if (graphConf.getBuildStep() <= 0) {
return all(builds);
} else {
return new Range(1, builds.size(), graphConf.getBuildStep());
}
} else if (graphConf.isDate()) {
if (graphConf.isDefaultDates()) {
return all(builds);
} else {
int firstBuild = -1;
int lastBuild = -1;
int var = builds.size();
GregorianCalendar firstDate = null;
GregorianCalendar lastDate = null;
try {
firstDate = GraphConfigurationDetail
.getGregorianCalendarFromString(graphConf.getFirstDayCount());
lastDate = GraphConfigurationDetail
.getGregorianCalendarFromString(graphConf.getLastDayCount());
lastDate.set(GregorianCalendar.HOUR_OF_DAY, 23);
lastDate.set(GregorianCalendar.MINUTE, 59);
lastDate.set(GregorianCalendar.SECOND, 59);
for (Object build : builds) {
Run, ?> currentBuild = (Run, ?>) build;
GregorianCalendar buildDate = new GregorianCalendar();
buildDate.setTime(currentBuild.getTimestamp().getTime());
if (firstDate.getTime().before(buildDate.getTime())) {
firstBuild = var;
}
if (lastBuild < 0 && lastDate.getTime().after(buildDate.getTime())) {
lastBuild = var;
}
var--;
}
return new Range(firstBuild, lastBuild);
} catch (ParseException e) {
LOGGER
.log(Level.SEVERE, "Error during the manage of the Calendar", e);
}
}
}
throw new IllegalArgumentException("unsupported configType + "
+ graphConf.getConfigType());
}
public Range all(List> builds) {
return new Range(1, builds.size());
}
public Job, ?> getJob() {
return job;
}
public final Run, ?> getSomeBuildWithWorkspace() {
byte cnt = 0;
for (Run, ?> run = job.getLastBuild(); cnt < 5 && run != null; run = run.getPreviousBuild()) {
if (!run.isBuilding()) {
if (run instanceof AbstractBuild) {
FilePath ws = ((AbstractBuild, ?>) run).getWorkspace();
if (ws != null) {
return run;
}
} else {
return run;
}
}
cnt++;
}
return null;
}
@NonNull
public List getPerformanceReportList() {
this.performanceReportList = new ArrayList<>(0);
if (null == this.job) {
return performanceReportList;
}
if (null == getSomeBuildWithWorkspace()) {
return performanceReportList;
}
File file = new File(getSomeBuildWithWorkspace().getRootDir(),
PerformanceReportMap.getPerformanceReportDirRelativePath());
if (!file.isDirectory()) {
return performanceReportList;
}
File[] files = file.listFiles();
if (files != null) {
for (File entry : files) {
if (entry.isDirectory()) {
File[] entryFiles = entry.listFiles();
if (entryFiles != null) {
for (File e : Objects.requireNonNull(entryFiles)) {
if (!e.getName().endsWith(".serialized") && !e.getName().endsWith(".serialized-v2")) {
this.performanceReportList.add(e.getName());
}
}
}
} else {
if (!entry.getName().endsWith(".serialized") && !entry.getName().endsWith(".serialized-v2")) {
this.performanceReportList.add(entry.getName());
}
}
}
} else {
// Handle the situation when files is null
return performanceReportList;
}
Collections.sort(performanceReportList);
return this.performanceReportList;
}
public void setPerformanceReportList(List performanceReportList) {
this.performanceReportList = performanceReportList;
}
public boolean isTrendVisibleOnProjectDashboard() {
return getPerformanceReportList().size() == 1;
}
/**
* Returns the graph configuration for this project.
*
* @param link not used
* @param request Stapler request
* @param response Stapler response
* @return the dynamic result of the analysis (detail page).
*/
public Object getDynamic(final String link, final StaplerRequest request,
final StaplerResponse response) {
if (CONFIGURE_LINK.equals(link)) {
return createUserConfiguration(request);
} else if (TRENDREPORT_LINK.equals(link)) {
return createTrendReport(request);
} else if (TESTSUITE_LINK.equals(link)) {
return createTestsuiteReport(request);
} else {
return null;
}
}
/**
* Creates a view to configure the trend graph for the current user.
*
* @param request Stapler request
* @return a view to configure the trend graph for the current user
*/
private Object createUserConfiguration(final StaplerRequest request) {
return new GraphConfigurationDetail(job, PLUGIN_NAME, request);
}
/**
* Creates a view to configure the trend graph for the current user.
*
* @param request Stapler request
* @return a view to configure the trend graph for the current user
*/
private Object createTrendReport(final StaplerRequest request) {
String filename = getTrendReportFilename(request);
CategoryDataset dataSet = getTrendReportData(request, filename).build();
return new TrendReportDetail(job, PLUGIN_NAME, request, filename, dataSet);
}
private Object createTestsuiteReport(final StaplerRequest request) {
String filename = getTestSuiteReportFilename(request);
Range buildsLimits = getFirstAndLastBuild(request, getJob().getBuilds());
return new TestSuiteReportDetail(job, filename, buildsLimits);
}
private String getTrendReportFilename(final StaplerRequest request) {
PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
return performanceReportPosition.getPerformanceReportPosition();
}
private String getTestSuiteReportFilename(final StaplerRequest request) {
PerformanceReportPosition performanceReportPosition = new PerformanceReportPosition();
request.bindParameters(performanceReportPosition);
return performanceReportPosition.getPerformanceReportPosition();
}
private DataSetBuilder getTrendReportData(final StaplerRequest request,
String performanceReportNameFile) {
DataSetBuilder dataSet = new DataSetBuilder<>();
List extends Run, ?>> builds = getJob().getBuilds();
Range buildsLimits = getFirstAndLastBuild(request, builds);
int nbBuildsToAnalyze = builds.size();
for (Run, ?> currentBuild : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(currentBuild);
PerformanceBuildAction performanceBuildAction = currentBuild
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport report = null;
report = performanceBuildAction.getPerformanceReportMap()
.getPerformanceReport(performanceReportNameFile);
if (report == null) {
nbBuildsToAnalyze--;
continue;
}
dataSet.add(report.getAverage(),
Messages.ProjectAction_Average(), label);
Map percentilesValues = report.getPercentilesValues();
for (Map.Entry entry : percentilesValues.entrySet()) {
dataSet.add(entry.getValue(),
report.getPercentileLabel(entry.getKey()), label);
}
dataSet.add(Math.round(report.errorPercent()),
Messages.ProjectAction_PercentageOfErrors(), label);
dataSet.add(report.countErrors(),
Messages.ProjectAction_Errors(), label);
dataSet.add(report.getTotalTrafficInKb(),
Messages.ProjectAction_TotalTrafficKB(), label);
dataSet.add(report.getAverageSizeInKb(),
Messages.ProjectAction_AverageKB(), label);
}
nbBuildsToAnalyze--;
}
return dataSet;
}
public boolean ifSummarizerParserUsed(String filename) {
return this.getJob().getBuilds().getLastBuild()
.getAction(PerformanceBuildAction.class).getPerformanceReportMap()
.getPerformanceReport(filename).ifSummarizerParserUsed(filename);
}
public boolean ifModePerformancePerTestCaseUsed() {
if (this.job instanceof AbstractProject) {
AbstractProject, ?> project = (AbstractProject, ?>) job;
PerformancePublisher publisher = (PerformancePublisher) project.getPublishersList()
.get(PerformancePublisher.class);
return publisher != null && publisher.isModePerformancePerTestCase();
} else {
return true;
}
}
public boolean ifModeThroughputUsed() {
if (this.job instanceof AbstractProject) {
AbstractProject, ?> project = (AbstractProject, ?>) job;
PerformancePublisher publisher = (PerformancePublisher) project.getPublishersList()
.get(PerformancePublisher.class);
return publisher == null || publisher.isModeThroughput();
} else {
return true;
}
}
public static class Range {
public int first;
public int last;
public int step;
public Range(int first, int last) {
this.first = first;
this.last = last;
this.step = 1;
}
public Range(int first, int last, int step) {
this(first, last);
this.step = step;
}
public boolean in(int nbBuildsToAnalyze) {
return nbBuildsToAnalyze <= last && first <= nbBuildsToAnalyze;
}
public boolean includedByStep(int buildNumber) {
return (buildNumber % step == 0);
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/build/PerformanceTestBuild.java
================================================
package hudson.plugins.performance.build;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Functions;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.PerformancePublisher;
import hudson.plugins.performance.actions.PerformanceProjectAction;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import jenkins.tasks.SimpleBuildStep;
import org.apache.commons.io.output.NullOutputStream;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.util.*;
import java.util.logging.Logger;
/**
* "Build step" for running performance test
*/
public class PerformanceTestBuild extends Builder implements SimpleBuildStep {
public static final Logger LOGGER = Logger.getLogger(PerformanceTestBuild.class.getName());
protected final static String PERFORMANCE_TEST_COMMAND = "bzt";
protected final static String VIRTUALENV_COMMAND = "virtualenv";
protected final static String HELP_OPTION = "--help";
protected final static String VIRTUALENV_PATH_UNIX = "/taurus-venv/bin/";
protected final static String VIRTUALENV_PATH_WINDOWS = "\\taurus-venv\\Scripts\\";
final static String[] CHECK_BZT_COMMAND = new String[]{PERFORMANCE_TEST_COMMAND, HELP_OPTION};
final static String[] CHECK_VIRTUALENV_COMMAND = new String[]{VIRTUALENV_COMMAND, HELP_OPTION};
final static String[] CREATE_LOCAL_PYTHON_COMMAND_WITH_SYSTEM_PACKAGES_OPTION =
new String[]{VIRTUALENV_COMMAND, "--clear", "--system-site-packages", "taurus-venv"};
final static String[] CREATE_LOCAL_PYTHON_COMMAND = new String[]{VIRTUALENV_COMMAND, "--clear", "taurus-venv"};
protected final static String DEFAULT_CONFIG_FILE = "jenkins-report.yml";
@Symbol({"bzt","performanceTest"})
@Extension
public static class Descriptor extends BuildStepDescriptor {
@Override
public boolean isApplicable(Class extends AbstractProject> jobType) {
return true;
}
@Override
public String getDisplayName() {
return Messages.PerformanceTest_Name();
}
}
private String params;
private boolean printDebugOutput = false;
private boolean alwaysUseVirtualenv = false;
private boolean useSystemSitePackages = true;
private boolean generatePerformanceTrend = true;
private boolean useBztExitCode = true;
private String bztVersion = "";
private String workingDirectory = "";
private String virtualEnvCommand = "";
/**
* Use 'workingDirectory' for set bzt working directory
*/
@Deprecated
private transient String workspace = "";
@DataBoundConstructor
public PerformanceTestBuild(String params) {
this.params = params;
}
/**
* This method, invoked after object is resurrected from persistence
*/
public Object readResolve() {
if (workspace != null && !workspace.isEmpty()) {
workingDirectory = workspace;
workspace = "";
}
return this;
}
@Override
public Action getProjectAction(AbstractProject, ?> project) {
return generatePerformanceTrend ? new PerformanceProjectAction(project) : null;
}
@Override
public void perform(@NonNull Run, ?> run, @NonNull FilePath workspace, @NonNull Launcher launcher, @NonNull TaskListener listener) throws InterruptedException, IOException {
PrintStream logger = listener.getLogger();
EnvVars envVars = run.getEnvironment(listener);
addPipelineEnvVars(run, envVars);
FilePath virtualenvWorkspace;
try {
virtualenvWorkspace = getVirtualenvWorkspace(run, workspace, logger);
} catch (Exception ex) {
logger.println("[ERROR] Performance test: " + ex.getMessage());
run.setResult(Result.FAILURE);
return;
}
boolean isVirtualenvInstallation = false;
if ((!alwaysUseVirtualenv && isGlobalBztInstalled(workspace, logger, launcher, envVars)) ||
(isVirtualenvInstallation = installBztAndCheck(virtualenvWorkspace, logger, launcher, envVars))) {
FilePath bztWorkingDirectory = getBztWorkingDirectory(workspace);
try {
bztWorkingDirectory.mkdirs();
} catch (IOException ex) {
logger.println("Performance test: Cannot create working directory because of error: " + ex.getMessage());
run.setResult(Result.FAILURE);
return;
}
int testExitCode = runPerformanceTest(bztWorkingDirectory, virtualenvWorkspace, logger, launcher, envVars, isVirtualenvInstallation);
run.setResult(useBztExitCode ?
getBztJobResult(testExitCode) :
getJobResult(testExitCode)
);
Result result = run.getResult();
if (generatePerformanceTrend && result != null && Result.FAILURE.isWorseThan(result)) {
generatePerformanceTrend(bztWorkingDirectory.getRemote(), run, workspace, launcher, listener);
}
return;
}
run.setResult(Result.FAILURE);
}
private void addPipelineEnvVars(Run, ?> run, EnvVars envVars) {
if (run.getClass().getCanonicalName().startsWith("org.jenkinsci.plugins.workflow")) {
List extends Action> allActions = run.getAllActions();
if (!allActions.isEmpty()) {
for (Action action : allActions) {
if ("org.jenkinsci.plugins.workflow.cps.EnvActionImpl".equals(action.getClass().getCanonicalName())) {
addEnvVars(action, envVars);
}
}
}
}
}
private void addEnvVars(Action action, EnvVars envVars) {
try {
Class extends Action> actionClass = action.getClass();
Method method = actionClass.getMethod("getOverriddenEnvironment");
Map map = (Map) method.invoke(action);
envVars.overrideAll(map);
} catch (Throwable ex) {
LOGGER.warning("Failed to add envVars from action: " + action.getClass());
}
}
private FilePath getVirtualenvWorkspace(Run, ?> run, FilePath workspace, PrintStream logger) throws Exception {
return workspace.getRemote().contains(" ") ?
createTemporaryWorkspace(run, workspace, logger) :
workspace;
}
private FilePath createTemporaryWorkspace(Run, ?> run, FilePath workspace, PrintStream logger) throws Exception {
logger.println("[WARNING] Performance test: Job workspace contains spaces in path. Virtualenv does not support such path. Creating temporary workspace for virtualenv.");
File baseTmpDir = new File(System.getProperty("java.io.tmpdir"));
if (baseTmpDir.getAbsolutePath().contains(" ")) {
logger.println("[WARNING] Performance test: Temporary folder contains spaces in path.");
throw new InvalidPathException(baseTmpDir.getAbsolutePath(), "Virtualenv cannot be installed in workspace that contains spaces in path.");
}
File tempDir = new File(baseTmpDir.getAbsolutePath(), "perf-test-virtualenv-workspace-" + configJobName(run.getParent().getName()));
FilePath tempWorkspace = new FilePath(workspace.getChannel(), tempDir.getAbsolutePath());
tempWorkspace.mkdirs();
return tempWorkspace;
}
private String configJobName(String displayName) {
return displayName.replaceAll(" ", "-");
}
protected FilePath getBztWorkingDirectory(FilePath jobWorkspace) {
return (workingDirectory != null && !workingDirectory.isEmpty()) ?
(isAbsoluteFilePath() ?
// absolute workspace
new FilePath(jobWorkspace.getChannel(), workingDirectory) :
//relative workspace
new FilePath(jobWorkspace, workingDirectory)
) :
jobWorkspace;
}
private boolean isAbsoluteFilePath() {
return new File(workingDirectory).isAbsolute();
}
protected void generatePerformanceTrend(String path, Run, ?> run, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
new PerformancePublisher(path + "/aggregate-results.xml", -1, -1, "", 0, 0, 0, 0, 0, false, "", false, false, false, false,true, null).
perform(run, workspace, launcher, listener);
}
private boolean installBztAndCheck(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
return installBzt(workspace, logger, launcher, envVars) &&
isVirtualenvBztInstalled(workspace, logger, launcher, envVars);
}
private boolean installBzt(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
return isVirtualenvInstalled(workspace, logger, launcher, envVars) &&
createVirtualenvAndInstallBzt(workspace, logger, launcher, envVars);
}
private boolean createVirtualenvAndInstallBzt(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
return createIsolatedPython(workspace, logger, launcher, envVars) &&
installBztInVirtualenv(workspace, logger, launcher, envVars);
}
// Step 1.1: Check bzt using "bzt --help".
private boolean isGlobalBztInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
logger.println("Performance test: Checking global bzt installation...");
boolean result = isSuccessCode(runCmd(CHECK_BZT_COMMAND, workspace, NullOutputStream.NULL_OUTPUT_STREAM, launcher, envVars));
logger.println(result ?
"Performance test: Found global bzt installation." :
"Performance test: You don't have global bzt installed on this Jenkins host. Installing it globally will speed up job. Run 'sudo pip install bzt' to install it."
);
return result;
}
// Step 1.2: If bzt not installed check virtualenv using "virtualenv --help".
private boolean isVirtualenvInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
logger.println("Performance test: Checking virtualenv tool availability...");
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
boolean result = isSuccessCode(runCmd(CHECK_VIRTUALENV_COMMAND, workspace, outputStream, launcher, envVars));
logger.println(result ?
"Performance test: Found virtualenv tool." :
"Performance test: No virtualenv found on this Jenkins host. Install it with 'sudo pip install virtualenv'."
);
if (!result || printDebugOutput) {
logger.write(outputStream.toByteArray());
}
return result;
}
// Step 1.3: Create local python using "virtualenv --clear taurus-venv".
private boolean createIsolatedPython(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
logger.println("Performance test: Creating virtualev at 'taurus-venv'...");
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
boolean result = isSuccessCode(runCmd(useSystemSitePackages ?
CREATE_LOCAL_PYTHON_COMMAND_WITH_SYSTEM_PACKAGES_OPTION :
CREATE_LOCAL_PYTHON_COMMAND,
workspace, outputStream, launcher, envVars));
logger.println(result ?
"Performance test: Done creating virtualenv." :
"Performance test: Failed to create virtualenv at 'taurus-venv'"
);
if (!result || printDebugOutput) {
logger.write(outputStream.toByteArray());
}
return result;
}
// Step 1.4: Install bzt in virtualenv using "taurus-venv/bin/pip install bzt".
private boolean installBztInVirtualenv(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
logger.println("Performance test: Installing bzt into 'taurus-venv'");
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
boolean result = isSuccessCode(runCmd(getBztInstallCommand(workspace), workspace, outputStream, launcher, envVars));
logger.println(result ?
"Performance test: bzt installed successfully." :
"Performance test: Failed to install bzt into 'taurus-venv'"
);
if (!result || printDebugOutput) {
logger.write(outputStream.toByteArray());
}
return result;
}
// Step 1.5: Check bzt using "taurus-venv/bin/bzt --help"
private boolean isVirtualenvBztInstalled(FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
logger.println("Performance test: Checking installed bzt...");
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
boolean result = isSuccessCode(runCmd(getBztCheckCommand(workspace), workspace, outputStream, launcher, envVars));
logger.println(result ?
"Performance test: bzt is operational." :
"Performance test: Failed to run bzt inside virtualenv."
);
if (!result || printDebugOutput) {
logger.write(outputStream.toByteArray());
}
return result;
}
// Step 2: Run performance test.
private int runPerformanceTest(FilePath bztWorkingDirectory, FilePath workspace, PrintStream logger, Launcher launcher, EnvVars envVars, boolean isVirtualenvInstallation) throws InterruptedException, IOException {
final List testCommand = new ArrayList<>();
testCommand.add((isVirtualenvInstallation ? getVirtualenvPath(workspace) : "") + PERFORMANCE_TEST_COMMAND);
String[] parsedParams;
try {
parsedParams = CommandLineUtils.translateCommandline(envVars.expand(this.params));
} catch (Exception e) {
logger.println("Failed parse Taurus parameters");
e.printStackTrace(logger);
return 1;
}
for (String param : parsedParams) {
if (!param.isEmpty()) {
testCommand.add(param);
}
}
if (generatePerformanceTrend) {
testCommand.add(extractDefaultReportToWorkingDirectory(bztWorkingDirectory));
}
logger.println("Performance test: run " + Arrays.toString(testCommand.toArray()));
return runCmd(testCommand.toArray(new String[testCommand.size()]), bztWorkingDirectory, logger, launcher, envVars);
}
public boolean isSuccessCode(int code) {
return code == 0;
}
public Result getJobResult(int code) {
if (code == 0) {
return Result.SUCCESS;
} else {
return Result.FAILURE;
}
}
public Result getBztJobResult(int code) {
if (code == 0) {
return Result.SUCCESS;
} else if (code == 1) {
return Result.FAILURE;
} else {
return Result.UNSTABLE;
}
}
private String getVirtualenvPath(FilePath workspace) {
return workspace.getRemote() + (Functions.isWindows() ?
VIRTUALENV_PATH_WINDOWS :
VIRTUALENV_PATH_UNIX);
}
// return bzt install command
private String[] getBztInstallCommand(FilePath workspace) throws IOException, InterruptedException {
return new String[]{
getVirtualenvPath(workspace) + "pip", "install", getInstallCommand(workspace)};
}
private String getInstallCommand(FilePath workspace) throws IOException, InterruptedException {
if (bztVersion != null && !bztVersion.isEmpty()) {
return (isPathToFile(workspace) || isURLToFile()) ?
bztVersion :
(PERFORMANCE_TEST_COMMAND + "==" + bztVersion);
} else {
return PERFORMANCE_TEST_COMMAND;
}
}
private boolean isURLToFile() {
try {
if (bztVersion.startsWith("git+")) {
new URL(bztVersion.substring(4));
} else {
new URL(bztVersion);
}
return true;
} catch (MalformedURLException e) {
return false;
}
}
private boolean isPathToFile(FilePath workspace) throws IOException, InterruptedException {
return new FilePath(workspace.getChannel(), bztVersion).exists();
}
// return bzt check command
private String[] getBztCheckCommand(FilePath workspace) {
return new String[]{getVirtualenvPath(workspace) + "bzt", HELP_OPTION};
}
private String getVirtualEnvCommand(EnvVars envVars) {
return virtualEnvCommand == null || virtualEnvCommand.trim().isEmpty() ? VIRTUALENV_COMMAND : envVars.expand(virtualEnvCommand);
}
public int runCmd(String[] commands, FilePath workspace, OutputStream logger, Launcher launcher, EnvVars envVars) throws InterruptedException, IOException {
if (commands[0].equals(VIRTUALENV_COMMAND)) {
commands[0] = getVirtualEnvCommand(envVars);
}
try {
return launcher.launch().cmds(commands).envs(envVars).stdout(logger).stderr(logger).pwd(workspace).start().join();
} catch (IOException ex) {
logger.write(ex.getMessage().getBytes(StandardCharsets.UTF_8));
if (printDebugOutput) {
logger.write(Functions.printThrowable(ex).getBytes(StandardCharsets.UTF_8));
}
return 1;
}
}
protected String extractDefaultReportToWorkingDirectory(FilePath workingDirectory) throws IOException, InterruptedException {
FilePath defaultConfig = workingDirectory.child(DEFAULT_CONFIG_FILE);
try (InputStream is = getClass().getResourceAsStream(DEFAULT_CONFIG_FILE)) {
assert is != null;
defaultConfig.copyFrom(is);
}
return defaultConfig.getRemote();
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
public boolean isPrintDebugOutput() {
return printDebugOutput;
}
public boolean isAlwaysUseVirtualenv() {
return alwaysUseVirtualenv;
}
@DataBoundSetter
public void setAlwaysUseVirtualenv(boolean alwaysUseVirtualenv) {
this.alwaysUseVirtualenv = alwaysUseVirtualenv;
}
@DataBoundSetter
public void setPrintDebugOutput(boolean printDebugOutput) {
this.printDebugOutput = printDebugOutput;
}
public boolean isUseSystemSitePackages() {
return useSystemSitePackages;
}
@DataBoundSetter
public void setUseSystemSitePackages(boolean useSystemSitePackages) {
this.useSystemSitePackages = useSystemSitePackages;
}
public boolean isGeneratePerformanceTrend() {
return generatePerformanceTrend;
}
@DataBoundSetter
public void setGeneratePerformanceTrend(boolean generatePerformanceTrend) {
this.generatePerformanceTrend = generatePerformanceTrend;
}
public boolean isUseBztExitCode() {
return useBztExitCode;
}
@DataBoundSetter
public void setUseBztExitCode(boolean useBztExitCode) {
this.useBztExitCode = useBztExitCode;
}
public String getWorkspace() {
return workspace;
}
@DataBoundSetter
public void setWorkspace(String workspace) {
this.workspace = workspace;
readResolve();
}
public String getBztVersion() {
return bztVersion;
}
@DataBoundSetter
public void setBztVersion(String bztVersion) {
this.bztVersion = bztVersion;
}
public String getWorkingDirectory() {
return workingDirectory;
}
@DataBoundSetter
public void setWorkingDirectory(String workingDirectory) {
this.workingDirectory = workingDirectory;
}
public String getVirtualEnvCommand() {
return virtualEnvCommand;
}
@DataBoundSetter
public void setVirtualEnvCommand(String virtualEnvCommand) {
this.virtualEnvCommand = virtualEnvCommand;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/AbsoluteConstraint.java
================================================
package hudson.plugins.performance.constraints;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import hudson.AbortException;
import hudson.Extension;
import hudson.model.Run;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.constraints.blocks.TestCaseBlock;
import hudson.plugins.performance.descriptors.ConstraintDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
import hudson.util.FormValidation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
/**
* Absolute Constraints compare the result of a new load test against some user defined values.
*
* @author Rene Kugel
*/
public class AbsoluteConstraint extends AbstractConstraint {
/**
* User defined absolute value which must not be exceeded
*/
private long value = 0;
@Symbol("absolute")
@Extension
public static class DescriptorImpl extends ConstraintDescriptor {
@Override
public String getDisplayName() {
return "Absolute Constraint";
}
public FormValidation doCheckRelatedPerfReport(@QueryParameter String relatedPerfReport) {
if (relatedPerfReport == null || relatedPerfReport.isEmpty()) {
return FormValidation.error("This field must not be empty");
}
return FormValidation.ok();
}
public FormValidation doCheckTestCase(@QueryParameter String testCase) {
if (testCase == null || testCase.isEmpty()) {
return FormValidation.error("This field must not be empty");
}
return FormValidation.ok();
}
}
@DataBoundConstructor
public AbsoluteConstraint(Metric meteredValue, Operator operator, String relatedPerfReport, Escalation escalationLevel, boolean success, TestCaseBlock testCaseBlock, long value) {
super(meteredValue, operator, relatedPerfReport, escalationLevel, success, testCaseBlock);
this.value = value;
}
/**
* Cloning of a AbsoluteConstraint Note that this is not from the Interface Clonable
*
* @return clone of this object
*/
@SuppressFBWarnings("CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE")
public AbsoluteConstraint clone() {
return new AbsoluteConstraint(this.getMeteredValue(), this.getOperator(),
this.getRelatedPerfReport(), this.getEscalationLevel(),
this.getSuccess(),
new TestCaseBlock(this.getTestCaseBlock().getTestCase()),
this.getValue());
}
@Override
public ConstraintEvaluation evaluate(List extends Run, ?>> builds) throws InvocationTargetException, AbortException {
if (builds.isEmpty()) {
throw new AbortException("Performance: No builds found to evaluate!");
}
checkForDefectiveParams(builds);
PerformanceReport pr = builds.get(0).getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceReport(getRelatedPerfReport());
// calculate the
double newValue = 0;
if (!isSpecifiedTestCase()) {
newValue = checkMetredValueforPerfReport(getMeteredValue(), pr);
} else {
List uriList = pr.getUriListOrdered();
for (UriReport ur : uriList) {
if (getTestCaseBlock().getTestCase().equals(ur.getUri())) {
newValue = checkMetredValueforUriReport(getMeteredValue(), ur);
break;
}
}
}
return check(newValue);
}
/**
* Compares the values and sets the success and a result message of a constraint.
*
* @param newValue measured value of a specified metric of the newly created test
* @return evaluated constraint
*/
private ConstraintEvaluation check(double newValue) {
switch (getOperator()) {
case NOT_LESS:
setSuccess(newValue >= getValue());
break;
case NOT_GREATER:
setSuccess(newValue <= getValue());
break;
case NOT_EQUAL:
setSuccess(newValue != getValue());
break;
default:
break;
}
ConstraintEvaluation evaluation = new ConstraintEvaluation(this, getValue(), newValue);
String measuredLevel = isSpecifiedTestCase() ? getTestCaseBlock().getTestCase() : "all test cases";
if (getSuccess()) {
setResultMessage("Absolute constraint successful! - Report: " + getRelatedPerfReport() + " \n" + "The constraint says: " + getMeteredValue() + " of " + measuredLevel + " must "
+ getOperator().text + " " + getValue() + "\n" + "Measured value for " + getMeteredValue() + ": " + newValue + "\n" + "Escalation Level: " + getEscalationLevel());
} else {
setResultMessage("Absolute constraint failed! - Report: " + getRelatedPerfReport() + " \n" + "The constraint says: " + getMeteredValue() + " of " + measuredLevel + " must "
+ getOperator().text + " " + getValue() + "\n" + "Measured value for " + getMeteredValue() + ": " + newValue + "\n" + "Escalation Level: " + getEscalationLevel());
}
String unit = getMeteredValue()==Metric.ERRORPRC ? "percent" : "milliseconds";
setJunitResult(String.format("%n",
getRelatedPerfReport(), getMeteredValue(), measuredLevel, getOperator().text, getValue(), unit)
+ (getSuccess() ? "" :
String.format(" Measured value for %s: %.0f %s%n",
getEscalationLevel(), getMeteredValue(), newValue, unit))
+ "\n");
return evaluation;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/AbstractConstraint.java
================================================
package hudson.plugins.performance.constraints;
import hudson.AbortException;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.model.Run;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.constraints.blocks.TestCaseBlock;
import hudson.plugins.performance.data.ConstraintSettings;
import hudson.plugins.performance.descriptors.ConstraintDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
import jenkins.model.Jenkins;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
/**
* Parent class for AbsoluteConstraint and RelativeConstraint
*
* @author Rene Kugel
*/
public abstract class AbstractConstraint implements Describable, ExtensionPoint {
public static final String ANY = "*";
protected static final String NOW = "now";
/**
* Holds the information whether constraint is fulfilled(true) or violated(false)
*/
private boolean success = false;
/**
* True if constraint refers to a test case False if constraint refers to a whole report
*/
private boolean isSpecifiedTestCase = false;
/**
* Metric which should be evaluated
*/
private Metric meteredValue;
/**
* Operator which is used to compare values
*/
private Operator operator;
/**
* Determines the build result if constraint is violated
*/
private Escalation escalationLevel;
/**
* Holds relevant information about the evaluation
*/
private String resultMessage = "";
/**
* Holds relevant information about the evaluation in Junit format
*/
private String junitResult = "";
/**
* The report file the constraint refers to
*/
private String relatedPerfReport;
/**
* null if isSpecifiedTestCase == false Holds the test case if isSpecifiedTestCase == true
*/
private TestCaseBlock testCaseBlock;
/**
* Reference for global constraint settings
*/
private ConstraintSettings settings;
public ConstraintDescriptor getDescriptor() {
return (ConstraintDescriptor) Jenkins.get().getDescriptorOrDie(getClass());
}
public static ExtensionList all() {
return Jenkins.get().getExtensionList(AbstractConstraint.class);
}
protected AbstractConstraint(Metric meteredValue, Operator operator, String relatedPerfReport, Escalation escalationLevel, boolean success, TestCaseBlock testCaseBlock) {
this.relatedPerfReport = relatedPerfReport;
this.success = success;
this.meteredValue = meteredValue;
this.operator = operator;
this.escalationLevel = escalationLevel;
if (testCaseBlock != null) {
this.setSpecifiedTestCase(true);
this.testCaseBlock = testCaseBlock;
} else {
this.setSpecifiedTestCase(false);
}
}
/**
* Cloning of a constraint Note that this is not from the Interface Clonable {@inheritDoc}
*/
public abstract AbstractConstraint clone();
/**
* Evaluates whether the constraint is fulfilled or violated
*
* @param builds all builds that are saved in Jenkins
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws AbortException
* @throws ParseException
*/
public abstract ConstraintEvaluation evaluate(List extends Run, ?>> builds) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, AbortException,
ParseException;
/**
* Grabs a specified Metric in a specified UriReport
*
* @param meteredValue the metric that should be evaluated
* @param ur the UriReport where the metric should be measured
* @return the value of the specified metric in the specified UriReport
*/
protected double checkMetredValueforUriReport(Metric meteredValue, UriReport ur) {
switch (meteredValue) {
case ERRORPRC:
return ur.errorPercent();
case AVERAGE:
return (double) ur.getAverage();
case LINE90:
return (double) ur.get90Line();
case LINE95:
return (double) ur.get95Line();
case MEDIAN:
return (double) ur.getMedian();
case MINIMUM:
return (double) ur.getMin();
case MAXIMUM:
return (double) ur.getMax();
default:
return (double) ur.getAverage();
}
}
/**
* Grabs a specified Metric in a specified PerformanceReport
*
* @param meteredValue the metric that should be evaluated
* @param pr the PerformanceReport where the metric should be measured
* @return the value of the specified metric in the specified PerformanceReport
*/
protected double checkMetredValueforPerfReport(Metric meteredValue, PerformanceReport pr) {
switch (meteredValue) {
case ERRORPRC:
return pr.errorPercent();
case AVERAGE:
return (double) pr.getAverage();
case LINE90:
return (double) pr.get90Line();
case LINE95:
return (double) pr.get95Line();
case MEDIAN:
return (double) pr.getMedian();
case MINIMUM:
return (double) pr.getMin();
case MAXIMUM:
return (double) pr.getMax();
default:
return (double) pr.getAverage();
}
}
/**
* Checks whether all parameters given in the UI are processable.
*
* @param builds all stored jenkins builds
* @throws AbortException if a parameter in the UI is not processable
*/
protected void checkForDefectiveParams(List extends Run, ?>> builds) throws AbortException {
boolean found = false;
if (builds.get(0).getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceReport(getRelatedPerfReport()) == null) {
throw new AbortException("Performance Plugin: Could't find a report specified in the performance constraints! Report: \"" + getRelatedPerfReport() + "\"");
} else {
PerformanceReport pr = builds.get(0).getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceReport(getRelatedPerfReport());
if (isSpecifiedTestCase()) {
for (UriReport ur : pr.getUriListOrdered()) {
if (ur.getUri().equals(getTestCaseBlock().getTestCase())) {
found = true;
}
}
if (!found) {
throw new AbortException("Performance Plugin: Could't find a test case specified in the performance constraints! TestCase: \"" + getTestCaseBlock().getTestCase() + "\" Report: \""
+ getRelatedPerfReport() + "\"");
}
}
}
if (this instanceof AbsoluteConstraint) {
AbsoluteConstraint ac = (AbsoluteConstraint) this;
if (ac.getValue() < 0) {
throw new AbortException("Performance Plugin: The value of a Absolute Constraint can't be negative!");
}
}
if (this instanceof RelativeConstraint) {
RelativeConstraint rc = (RelativeConstraint) this;
if (rc.getTolerance() < 0) {
throw new AbortException("Performance Plugin: The tolerance of a Relative Constraint can't be negative!");
}
if (rc.getTimeframeStart().after(rc.getTimeframeEnd())) {
throw new AbortException("Performance Plugin: The start date of a Relative Constraint can't be after the end date");
}
if (rc.getPreviousResultsBlock().isChoiceTimeframe()) {
final SimpleDateFormat dfLong = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try {
rc.setTimeframeStart(dfLong.parse(rc.getTimeframeStartString()));
if (!NOW.equals(rc.getTimeframeEndString())) {
rc.setTimeframeEnd(dfLong.parse(rc.getTimeframeEndString()));
}
} catch (ParseException e) {
throw new AbortException("Performance Plugin: Couldn't parse date in Relative Constraint! Please check the configuration of your constraints");
}
}
}
}
public enum Metric {
AVERAGE("Average", false), MEDIAN("Median", false), LINE90("90% Line", false), LINE95("95% Line", false), MAXIMUM("Maximum", false), MINIMUM("Minimum", false), ERRORPRC("Error %", false);
private final String text;
private final boolean isSelected;
private Metric(final String text, boolean isSelected) {
this.text = text;
this.isSelected = isSelected;
}
@Override
public String toString() {
return text;
}
public boolean isSelected() {
return isSelected;
}
}
public enum Escalation {
INFORMATION("Information", false), WARNING("Warning", false), ERROR("Error", false);
private final String text;
private final boolean isSelected;
private Escalation(final String text, boolean isSelected) {
this.text = text;
this.isSelected = isSelected;
}
@Override
public String toString() {
return text;
}
public boolean isSelected() {
return isSelected;
}
}
public enum Operator {
NOT_GREATER("not be greater than", false), NOT_LESS("not be less than", false), NOT_EQUAL("not be equal to", false);
public final String text;
private final boolean isSelected;
private Operator(final String text, boolean isSelected) {
this.text = text;
this.isSelected = isSelected;
}
@Override
public String toString() {
return text;
}
public boolean isSelected() {
return isSelected;
}
}
public void setSuccess(boolean success) {
this.success = success;
}
public boolean getSuccess() {
return this.success;
}
public String getResultMessage() {
return resultMessage;
}
public void setResultMessage(String resultMessage) {
this.resultMessage = resultMessage;
}
public String getJunitResult() {
return junitResult;
}
public void setJunitResult(String junitResult) {
this.junitResult = junitResult;
}
public String getRelatedPerfReport() {
return relatedPerfReport;
}
public void setRelatedPerfReport(String relatedPerfReport) {
this.relatedPerfReport = relatedPerfReport;
}
public Metric getMeteredValue() {
return meteredValue;
}
public void setMeteredValue(Metric meteredValue) {
this.meteredValue = meteredValue;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
public Escalation getEscalationLevel() {
return escalationLevel;
}
public void setEscalationLevel(Escalation escalationLevel) {
this.escalationLevel = escalationLevel;
}
public TestCaseBlock getTestCaseBlock() {
return testCaseBlock;
}
public void setTestCaseBlock(TestCaseBlock testCaseBlock) {
this.testCaseBlock = testCaseBlock;
}
public boolean isSpecifiedTestCase() {
return isSpecifiedTestCase;
}
public void setSpecifiedTestCase(boolean isSpecifiedTestCase) {
this.isSpecifiedTestCase = isSpecifiedTestCase;
}
public ConstraintSettings getSettings() {
return settings;
}
public void setSettings(ConstraintSettings settings) {
this.settings = settings;
}
public String getTestCase() {
if (getTestCaseBlock() != null) {
return getTestCaseBlock().getTestCase();
} else {
return null;
}
}
public void setTestCase(String testCase) {
this.getTestCaseBlock().setTestCase(testCase);
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/ConstraintChecker.java
================================================
package hudson.plugins.performance.constraints;
import hudson.AbortException;
import hudson.model.Run;
import hudson.plugins.performance.data.ConstraintSettings;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
/**
* Checks whether a list of constraints is fulfilled or violated
*
* @author Rene Kugel
*/
public class ConstraintChecker {
/**
* All builds that are saved in Jenkins
*/
private List extends Run, ?>> builds;
/**
* Global constraint settings
*/
private ConstraintSettings settings;
public ConstraintChecker(ConstraintSettings settings, List extends Run, ?>> builds) {
this.settings = settings;
this.builds = builds;
}
/**
* Evaluates a list of constraints defined by the user in the UI
*
* @param constraints constraints defined by the user
* @return ArrayList of evaluated constraints
* @throws AbortException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws ParseException
*/
public ArrayList checkAllConstraints(List extends AbstractConstraint> constraints) throws AbortException, SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException, ParseException {
ArrayList result = new ArrayList();
for (AbstractConstraint c : constraints) {
c.setSettings(settings);
try {
result.add(c.evaluate(builds));
} catch (Exception e) {
settings.getListener().getLogger().println(e.getMessage());
}
}
return result;
}
public ConstraintSettings getSettings() {
return settings;
}
public void setSettings(ConstraintSettings settings) {
this.settings = settings;
}
public List extends Run, ?>> getBuilds() {
return builds;
}
public void setBuilds(List extends Run, ?>> builds) {
this.builds = builds;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/ConstraintEvaluation.java
================================================
package hudson.plugins.performance.constraints;
/**
* Holds the values of a evaluated constraint.
*
* @author Rene Kugel
*/
public class ConstraintEvaluation {
private AbstractConstraint abstractConstraint;
private double constraintValue;
private double measuredValue;
public ConstraintEvaluation(AbstractConstraint constraint, double result, double calculatedValue) {
this.abstractConstraint = constraint;
this.constraintValue = result;
this.measuredValue = calculatedValue;
}
public ConstraintEvaluation() {
}
public double getConstraintValue() {
return constraintValue;
}
public void setConstraintValue(double constraintValue) {
this.constraintValue = constraintValue;
}
public double getMeasuredValue() {
return measuredValue;
}
public void setMeasuredValue(double measuredValue) {
this.measuredValue = measuredValue;
}
public AbstractConstraint getAbstractConstraint() {
return abstractConstraint;
}
public void setAbstractConstraint(AbstractConstraint abstractConstraint) {
this.abstractConstraint = abstractConstraint;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/ConstraintFactory.java
================================================
package hudson.plugins.performance.constraints;
import hudson.model.Run;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Evaluates the entries in the testCase field if test cases are specified and create clones of the
* constraint for every given test case There are two possibilities: 1. In the testCase field a
* comma seperated list of test cases is given 2. A '*' is given
*
* @author Rene Kugel
*/
public class ConstraintFactory {
/**
* Creates clones of a constraint if there is more than one test case specified in the UI
*
* @param build all builds that are saved in Jenkins
* @param constraints all constraints defined in the UI
* @return list of all constraint clones that will get evaluated
*/
public List extends AbstractConstraint> createConstraintClones(Run, ?> build, List extends AbstractConstraint> constraints) {
/*
* Checking the test case field and handle comma separated lists and wildcard
*/
List createdConstraints = new ArrayList<>();
for (AbstractConstraint c : constraints) {
List testCases = new ArrayList<>();
if (c.isSpecifiedTestCase()) {
String testCase = c.getTestCaseBlock().getTestCase();
if (AbstractConstraint.ANY.equals(testCase)) {
PerformanceReport pr = build.getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceReport(c.getRelatedPerfReport());
for (UriReport ur : pr.getUriListOrdered()) {
testCases.add(ur.getUri());
}
} else {
String[] tmpTestCases = testCase.split(",");
String[] trimmedTestCases = new String[tmpTestCases.length];
for (int i = 0; i < tmpTestCases.length; i++) {
trimmedTestCases[i] = tmpTestCases[i].trim();
}
testCases = Arrays.asList(trimmedTestCases);
}
/*
* Creating clones based on the test cases
*/
for (String s : testCases) {
AbstractConstraint constraint = c.clone();
constraint.getTestCaseBlock().setTestCase(s);
createdConstraints.add(constraint);
}
} else {
createdConstraints.add(c);
}
}
return createdConstraints;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/RelativeConstraint.java
================================================
package hudson.plugins.performance.constraints;
import java.io.PrintStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import hudson.AbortException;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.FreeStyleBuild;
import hudson.model.Result;
import hudson.model.Run;
import hudson.plugins.performance.PerformancePublisher;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.constraints.blocks.PreviousResultsBlock;
import hudson.plugins.performance.constraints.blocks.TestCaseBlock;
import hudson.plugins.performance.descriptors.ConstraintDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
import hudson.plugins.performance.tools.SafeMaths;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import hudson.util.RunList;
/**
* Compares new load test results with 1 or more load test results in the past in a dynamically
* manner.
*
* @author Rene Kugel
*/
public class RelativeConstraint extends AbstractConstraint {
/**
* Percentage value of the tolerance
*/
private double tolerance = 0;
/**
* True: relative constraint includes a user defined number of builds into the evaluation False:
* relative constraint includes all builds that have taken place in an user defined time frame
*/
private boolean choicePreviousResults = true;
/**
* Start of the time frame (for internal use)
*/
private Date timeframeStart = new Date();
/**
* End of the time frame (for internal use)
*/
private Date timeframeEnd = new Date();
/**
* Holds the relevant information to determine which builds get included into the evaluation
*/
private PreviousResultsBlock previousResultsBlock;
/**
* Start of the time frame (for UI use)
*/
private String timeframeStartString = "";
/**
* End of the time frame (for UI use)
*/
private String timeframeEndString = "";
/**
* Holds the user defined number of builds which are to include to the evaluation (for internal
* use)
*/
private int previousResults = 0;
/**
* Holds the user defined number of builds which are to include to the evaluation (for UI use)
*/
private String previousResultsString = "";
@Symbol("relative")
@Extension
public static class DescriptorImpl extends ConstraintDescriptor {
@Override
public String getDisplayName() {
return "Relative Constraint";
}
public FormValidation doCheckRelatedPerfReport(@QueryParameter String relatedPerfReport) {
if (relatedPerfReport == null || relatedPerfReport.isEmpty()) {
return FormValidation.error("This field must not be empty");
}
return FormValidation.ok();
}
public FormValidation doCheckTestCase(@QueryParameter String testCase) {
if (testCase == null || testCase.isEmpty()) {
return FormValidation.error("This field must not be empty");
}
return FormValidation.ok();
}
public FormValidation doCheckTimeframeStartString(@QueryParameter String timeframeStartString) {
return dateCheck(timeframeStartString);
}
public FormValidation doCheckTimeframeEndString(@QueryParameter String timeframeEndString) {
if (NOW.equals(timeframeEndString)) {
return FormValidation.ok();
}
return dateCheck(timeframeEndString);
}
private FormValidation dateCheck(String dateString) {
final SimpleDateFormat dfLong = new SimpleDateFormat("yyyy-MM-dd HH:mm");
final SimpleDateFormat dfShort = new SimpleDateFormat("yyyy-MM-dd");
dfLong.setLenient(false);
dfShort.setLenient(false);
try {
if (
(dfShort.parse(dateString) != null && dateString.length() == 10)
|| (dfLong.parse(dateString) != null && dateString.length() == 16)) {
return FormValidation.ok();
}
} catch (ParseException e1) {
return FormValidation.error("Not a valid date!");
}
return FormValidation.error("Not a valid date!");
}
public FormValidation doCheckPreviousResultsString(@QueryParameter String previousResultsString, @AncestorInPath AbstractProject, ?> project) {
if (ANY.equals(previousResultsString)) {
return FormValidation.ok();
}
int previousResults;
try {
previousResults = Integer.parseInt(previousResultsString);
} catch (NumberFormatException e) {
return FormValidation.error("This is not a valid number");
}
if (previousResults < 1) {
return FormValidation.error("This value can't be smaller 1");
}
/*
* Problem description: if you want to evaluate the 15 last builds in a relative
* constraint, but you only store 10 builds of your job you will get in trouble.
* similiar problem: you store 10 builds in you job and evaluate the last 7 SUCCESSFUL
* builds with a relative constraint. what if 5 of your 10 stored builds are in status
* FAILED or UNSTABLE? -> problem. this form validation solves this problem if your
* enter a number greater than the available builds (regarding your confiugration
* 'ignoreFailed' and 'ignoreUnstable') you will get a form validation error note: if
* you change 'ignoreFailed' or 'ignoreUnstable' you first have to save your
* configuration before you change the number of previous builds
*/
if (project == null) { // Counting builds makes no sense when in Pipeline snippet generator
return FormValidation.ok();
}
RunList> builds = project.getBuilds();
int buildsToAnalyze = 0;
int successBuilds = 0;
int failedBuilds = 0;
int unstableBuilds = 0;
String buildSizeMessage = "This value cant be bigger than the amount of stored builds with the status: SUCCESS";
ListIterator> it = builds.listIterator();
while (it.hasNext()) {
Object next = it.next();
if (next instanceof FreeStyleBuild) {
FreeStyleBuild b = (FreeStyleBuild) next;
Result buildResult = b.getResult();
if (buildResult != null) {
if (buildResult.equals(Result.FAILURE)) {
failedBuilds++;
} else if (buildResult.equals(Result.UNSTABLE)) {
unstableBuilds++;
} else if (buildResult.equals(Result.SUCCESS)) {
successBuilds++;
}
}
}
}
buildsToAnalyze = successBuilds;
boolean ignoreFailedBuilds = false;
boolean ignoreUnstableBuilds = false;
List list = project.getPublishersList().toList();
for (Publisher p : list) {
if (p instanceof PerformancePublisher) {
PerformancePublisher pp = (PerformancePublisher) p;
// MWA: uncomment and check
ignoreFailedBuilds = pp.isIgnoreFailedBuilds();
ignoreUnstableBuilds = pp.isIgnoreUnstableBuilds();
break;
}
}
if (!ignoreUnstableBuilds) {
buildsToAnalyze += unstableBuilds;
buildSizeMessage = buildSizeMessage + ", UNSTABLE";
}
if (!ignoreFailedBuilds) {
buildsToAnalyze += failedBuilds;
buildSizeMessage = buildSizeMessage + ", FAILED";
}
if (previousResults > buildsToAnalyze+1) { // at the time of evaluation there will be one more build (the next build that is run, which could be the very first or only build)
return FormValidation.error(buildSizeMessage);
} else {
return FormValidation.ok();
}
}
}
@DataBoundConstructor
public RelativeConstraint(Metric meteredValue, Operator operator, String relatedPerfReport, Escalation escalationLevel, boolean success, TestCaseBlock testCaseBlock,
PreviousResultsBlock previousResultsBlock, double tolerance) {
super(meteredValue, operator, relatedPerfReport, escalationLevel, success, testCaseBlock);
this.tolerance = tolerance;
this.previousResultsBlock = previousResultsBlock;
if (this.previousResultsBlock.isChoicePreviousResults()) {
if (ANY.equals(this.previousResultsBlock.getPreviousResultsString())) {
this.previousResults = -1;
} else {
try {
this.previousResults = Integer.parseInt(this.previousResultsBlock.getPreviousResultsString());
} catch (NumberFormatException ex) {
this.previousResults = -1;
}
}
this.previousResultsString = this.previousResultsBlock.getPreviousResultsString();
}
if (this.previousResultsBlock.isChoiceTimeframe()) {
this.timeframeStartString = this.previousResultsBlock.getTimeframeStartString();
this.timeframeEndString = this.previousResultsBlock.getTimeframeEndString();
if (this.timeframeStartString.length() == 10) {
this.timeframeStartString = this.timeframeStartString + " 00:00";
}
if (this.timeframeEndString.length() == 10) {
this.timeframeEndString = this.timeframeEndString + " 23:59";
}
try {
final SimpleDateFormat dfLong = new SimpleDateFormat("yyyy-MM-dd HH:mm");
this.timeframeStart = dfLong.parse(this.timeframeStartString);
if (!NOW.equals(this.timeframeEndString)) {
this.timeframeEnd = dfLong.parse(this.timeframeEndString);
}
} catch (ParseException e) {
PrintStream logger = getSettings().getListener().getLogger();
logger.print("Error occurred parsing one of those dates, timeframeStartString:"+timeframeStartString
+", timeframeEndString:"+timeframeEndString
+" using format:yyyy-MM-dd HH:mm, message:"+e.getMessage());
e.printStackTrace(logger);
}
}
if (this.previousResultsBlock.isChoiceBaselineBuild()) {
// nothing to do, baseline build number comes from settings
}
}
/**
* Cloning of a RelativeConstraint Note that this is not from the Interface Clonable
*
* @return clone of this object
*/
@SuppressFBWarnings("CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE")
public RelativeConstraint clone() {
return new RelativeConstraint(this.getMeteredValue(), this.getOperator(),
this.getRelatedPerfReport(), this.getEscalationLevel(), this.getSuccess(),
new TestCaseBlock(this.getTestCaseBlock().getTestCase()),
new PreviousResultsBlock(this.getPreviousResultsBlock().getValue(), this.getPreviousResultsString(),
this.getTimeframeStartString(), this.getTimeframeEndString()), this.getTolerance());
}
@Override
public ConstraintEvaluation evaluate(List extends Run, ?>> builds) throws AbortException, ParseException {
if (builds.isEmpty()) {
throw new AbortException("Performance: No builds found to evaluate!");
}
checkForDefectiveParams(builds);
PerformanceReport pr = builds.get(0).getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceReport(getRelatedPerfReport());
double calValue = 0;
if (!isSpecifiedTestCase()) {
calValue = checkMetredValueforPerfReport(getMeteredValue(), pr);
} else {
List uriList = pr.getUriListOrdered();
for (UriReport ur : uriList) {
if (getTestCaseBlock().getTestCase().equals(ur.getUri())) {
calValue = checkMetredValueforUriReport(getMeteredValue(), ur);
break;
}
}
}
return check(builds, calValue);
}
/**
* Compares the values and sets the success and a result message of a constraint.
*
* @param builds all builds that are saved in Jenkins
* @param newValue value of the measured metric of the new build
* @return evaluated constraint
*/
private ConstraintEvaluation check(List extends Run, ?>> builds, double newValue) {
double calculatedValue = calcAveOfReports(builds);
/*
* If calculatedValue == Long.MIN_VALUE there was no build found to evaluate this constraint
* The process should not get aborted, but this constraint should be marked as failed, unless it is the very first or only build.
*/
if (calculatedValue == Long.MIN_VALUE) {
boolean isVeryFirstBuild = (builds.size() == 1);
setSuccess(isVeryFirstBuild);
setResultMessage("Relative constraint " + (isVeryFirstBuild ? "skipped." : "failed!") + " - Report: " + getRelatedPerfReport() + "\n" + "There were no builds found to evaluate!");
return new ConstraintEvaluation(this, 0, 0);
}
double result = 0;
if (getOperator().equals(Operator.NOT_GREATER)) {
result = calculatedValue * (1 + getTolerance() / 100);
} else if (getOperator().equals(Operator.NOT_LESS)) {
result = calculatedValue * (1 - getTolerance() / 100);
} else {
try {
throw new AbortException("Performance Plugin: Relative Constraints can only handle \"not greater than\" and \"not less than\" operators. Please check your constraint configuration");
} catch (AbortException e) {
PrintStream logger = getSettings().getListener().getLogger();
e.printStackTrace(logger);
}
}
switch (getOperator()) {
case NOT_LESS:
setSuccess(result < newValue);
break;
case NOT_GREATER:
setSuccess(result >= newValue);
break;
default:
setSuccess(false);
break;
}
ConstraintEvaluation evaluation = new ConstraintEvaluation(this, result, calculatedValue);
String measuredLevel = isSpecifiedTestCase() ? getTestCaseBlock().getTestCase() : "all test cases";
if (getSuccess()) {
setResultMessage("Relative constraint successful! - Report: " + getRelatedPerfReport() + "\n" + "The constraint says: " + getMeteredValue() + " of " + measuredLevel + " must "
+ getOperator().text + " " + result + "\n" + "Measured value for " + getMeteredValue() + ": " + newValue + "\n" + "Included builds: " + getPreviousResults() + " builds \n"
+ "Escalation Level: " + getEscalationLevel());
} else {
setResultMessage("Relative constraint failed! - Report: " + getRelatedPerfReport() + "\n" + "The constraint says: " + getMeteredValue() + " of " + measuredLevel + " must "
+ getOperator().text + " " + result + "\n" + "Measured value for " + getMeteredValue() + ": " + newValue + "\n" + "Included builds: Last " + getPreviousResults() + " builds \n"
+ "Escalation Level: " + getEscalationLevel());
}
String unit = getMeteredValue()==Metric.ERRORPRC ? "percent" : "milliseconds";
setJunitResult(String.format("%n",
getRelatedPerfReport(), getMeteredValue(), measuredLevel, getOperator().text, getTolerance())
+ (getSuccess() ? "" :
String.format(" Measured value for %s: %.0f %s. Previous value: %.0f %s. Deviation: %.3f %%%n",
getEscalationLevel(), getMeteredValue(), newValue, unit, calculatedValue, unit, (newValue/calculatedValue-1)*100))
+ "\n");
return evaluation;
}
/**
* Calculates the average of an meteredValue from UriReports/PerfomanceReports over several
* builds.
*
* @param builds all builds that are saved in Jenkins
* @return average of measured metric over included builds
*/
private long calcAveOfReports(List extends Run, ?>> builds) {
List> buildsToAnalyze = new ArrayList<>();
long tmpResult = 0;
int counter = 0;
long result = 0;
Run, ?> newBuild = builds.get(0);
if (getPreviousResultsBlock().isChoiceTimeframe()) {
buildsToAnalyze.addAll(evaluateDate(builds));
}
if (getPreviousResultsBlock().isChoicePreviousResults()) {
buildsToAnalyze.addAll(evaluatePreviousBuilds(builds));
}
if (getPreviousResultsBlock().isChoiceBaselineBuild()) {
buildsToAnalyze.add(builds.get(getSettings().getBaselineBuild()));
}
setPreviousResults(buildsToAnalyze.size());
if (!buildsToAnalyze.isEmpty()) {
for (Run, ?> actBuild : buildsToAnalyze) {
if (actBuild.getAction(PerformanceBuildAction.class) != null && !actBuild.equals(newBuild)) {
List tmpList = actBuild.getAction(PerformanceBuildAction.class).getPerformanceReportMap().getPerformanceListOrdered();
for (PerformanceReport pr : tmpList) {
if (getRelatedPerfReport().equals(pr.getReportFileName())) {
if (!isSpecifiedTestCase()) {
tmpResult += checkMetredValueforPerfReport(getMeteredValue(), pr);
} else {
tmpResult += getUriValue(pr);
}
counter++;
}
}
} else {
PrintStream logger = getSettings().getListener().getLogger();
logger.println("Performance: There are no comaparable data available for build #" + actBuild.getNumber() + ". Skipping this build!");
setPreviousResults(getPreviousResults() - 1);
}
}
result = (long)SafeMaths.safeDivide(tmpResult, counter);
} else {
/*
* If no build was found to analyze return Long.MIN_VALUE. This will cause the
* constraint to be marked as failed (except for the first/only build).
*/
PrintStream logger = getSettings().getListener().getLogger();
logger.println("Performance: There were no builds found to evaluate for a relative constraint!");
return Long.MIN_VALUE;
}
return result;
}
/**
* Is executed when the RadioButton "Compare with builds in a timeframe" is choosen. Determines
* the builds that are included in the evaluation based on the constraint settings and the given
* timeframe.
*
* @param builds all builds that are saved in Jenkins
* @return builds list of builds that have taken place in a user defined time frame respecting
* the constraint settings
*/
private List> evaluateDate(List extends Run, ?>> builds) {
List> result = new ArrayList<>();
Calendar timeframeStartAsCalendar = Calendar.getInstance();
timeframeStartAsCalendar.setTime(getTimeframeStart());
Calendar timeframeEndAsCalendar = Calendar.getInstance();
timeframeEndAsCalendar.setTime(getTimeframeEnd());
if (NOW.equals(getTimeframeEndString())) {
timeframeEndAsCalendar.setTime(new Date());
}
for (Run, ?> build : builds) {
Result buildResult = build.getResult();
if (buildResult != null &&
(buildResult.equals(Result.SUCCESS)
|| (buildResult.equals(Result.UNSTABLE)
&& !getSettings().isIgnoreUnstableBuilds())
|| (buildResult.equals(Result.FAILURE)
&& !getSettings().isIgnoreFailedBuilds()))
&& (!build.getTimestamp().before(timeframeStartAsCalendar)
&& !build.getTimestamp().after(timeframeEndAsCalendar)
&& !build.equals(builds.get(0)))) {
result.add(build);
}
}
return result;
}
/**
* Is executed when the RadioButton "Compare with previous builds" is chosen. Determines the
* builds that are included in the evaluation based on the constraint settings
*
* @param builds all builds that are saved in Jenkins
* @return build list of previous builds that get included into the evaluation
*/
private List> evaluatePreviousBuilds(List extends Run, ?>> builds) {
List> result = new ArrayList<>();
if (getPreviousResults() == -1) {
setPreviousResults(builds.size() - 1);
}
int i = 1;
int j = 0;
while (j < getPreviousResults() && i < builds.size()) {
if (Objects.equals(builds.get(i).getResult(), Result.SUCCESS)
|| (Objects.equals(builds.get(i).getResult(), Result.UNSTABLE)
&& !getSettings().isIgnoreUnstableBuilds())
|| (Result.FAILURE.equals(builds.get(i).getResult()) && !getSettings().isIgnoreFailedBuilds())) {
result.add(builds.get(i));
j++;
}
i++;
}
return result;
}
/**
* Searches a value in a URIReport. Metric and PerformanceReport are defined in the constraint.
*
* @param pr performance report where to search for the URI report
* @return value of the specified metric
*/
private double getUriValue(PerformanceReport pr) {
double result = 0;
for (UriReport ur : pr.getUriListOrdered()) {
if (getTestCaseBlock().getTestCase().equals(ur.getUri())) {
result = checkMetredValueforUriReport(getMeteredValue(), ur);
}
}
return result;
}
public int getPreviousResults() {
return previousResults;
}
public void setPreviousResults(int previousResults) {
this.previousResults = previousResults;
}
public double getTolerance() {
return tolerance;
}
public void setTolerance(double d) {
this.tolerance = d;
}
public boolean getChoicePreviousResults() {
return choicePreviousResults;
}
public void setChoicePreviousResults(boolean choicePreviousResults) {
this.choicePreviousResults = choicePreviousResults;
}
public String getTimeframeStartString() {
return timeframeStartString;
}
public void setTimeframeStartString(String timeframeStartString) {
this.timeframeStartString = timeframeStartString;
}
public String getTimeframeEndString() {
return timeframeEndString;
}
public void setTimeframeEndString(String timeframeEndString) {
this.timeframeEndString = timeframeEndString;
}
public Date getTimeframeStart() {
return timeframeStart;
}
public void setTimeframeStart(Date timeframeStart) {
this.timeframeStart = timeframeStart;
}
public Date getTimeframeEnd() {
return timeframeEnd;
}
public void setTimeframeEnd(Date timeframeEnd) {
this.timeframeEnd = timeframeEnd;
}
public PreviousResultsBlock getPreviousResultsBlock() {
return previousResultsBlock;
}
public void setPreviousResultsBlock(PreviousResultsBlock previousResultsBlock) {
this.previousResultsBlock = previousResultsBlock;
}
public String getPreviousResultsString() {
return previousResultsString;
}
public void setPreviousResultsString(String previousResultsString) {
this.previousResultsString = previousResultsString;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/blocks/PreviousResultsBlock.java
================================================
package hudson.plugins.performance.constraints.blocks;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* Holds the informations which builds get included into the evaluation of relative constraints.
*
* @author Rene Kugel
*/
public class PreviousResultsBlock extends AbstractDescribableImpl {
/**
* True: relative constraint includes a user defined number of builds into the evaluation False:
* relative constraint includes all builds that have taken place in an user defined time frame
* BASELINE: relative constraint includes baseline build defined in the PerformancePublisher settings.
* True and false are retained for backward compatibility.
*/
public static final String PREVIOUS = "true", TIMEFRAME = "false", BASELINE = "BASELINE";
private String choicePreviousResults = TIMEFRAME; // keep field name for backward compatibility
/**
* Holds the user defined number of builds which are to include to the evaluation
*/
private String previousResultsString;
/**
* Start of the time frame
*/
private String timeframeStartString;
/**
* End of the time frame
*/
private String timeframeEndString;
@Symbol("previous")
@Extension
public static class DescriptorImpl extends Descriptor {
@Override
public String getDisplayName() {
return "PreviousResultsBlock";
}
}
@DataBoundConstructor
public PreviousResultsBlock(String value, String previousResultsString, String timeframeStartString, String timeframeEndString) {
this.setValue(value);
this.previousResultsString = previousResultsString;
this.timeframeStartString = timeframeStartString;
this.timeframeEndString = timeframeEndString;
}
public boolean isChoicePreviousResults() {
return PREVIOUS.equals(choicePreviousResults);
}
public void setChoicePreviousResults(boolean choicePreviousResults) {
this.choicePreviousResults = choicePreviousResults ? PREVIOUS : TIMEFRAME; // backward compatibility
}
public boolean isChoiceTimeframe() {
return TIMEFRAME.equals(choicePreviousResults);
}
public boolean isChoiceBaselineBuild() {
return BASELINE.equals(choicePreviousResults);
}
// Workaround for radioBlock sending 'value' instead of field name (JENKINS-45988):
public String getValue() {
return choicePreviousResults;
}
public void setValue(String value) {
this.choicePreviousResults = value;
}
public String getPreviousResultsString() {
return previousResultsString;
}
public void setPreviousResultsString(String previousResultsString) {
this.previousResultsString = previousResultsString;
}
public String getTimeframeStartString() {
return timeframeStartString;
}
public void setTimeframeStartString(String timeframeStartString) {
this.timeframeStartString = timeframeStartString;
}
public String getTimeframeEndString() {
return timeframeEndString;
}
public void setTimeframeEndString(String timeframeEndString) {
this.timeframeEndString = timeframeEndString;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/constraints/blocks/TestCaseBlock.java
================================================
package hudson.plugins.performance.constraints.blocks;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* Holds the testCase information for constraints.
*
* @author Rene Kugel
*/
public class TestCaseBlock extends AbstractDescribableImpl {
private String testCase;
@Symbol("testCase")
@Extension
public static class DescriptorImpl extends Descriptor {
@Override
public String getDisplayName() {
return "TestCaseBlock";
}
}
@DataBoundConstructor
public TestCaseBlock(String testCase) {
this.testCase = testCase;
}
public String getTestCase() {
return testCase;
}
public void setTestCase(String testCase) {
this.testCase = testCase;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/cookie/CookieHandler.java
================================================
package hudson.plugins.performance.cookie;
import org.kohsuke.stapler.Ancestor;
import javax.servlet.http.Cookie;
import java.util.List;
/**
* Creates and converts cookies.
*
* @author Ulli Hafner
*/
public class CookieHandler {
/**
* One year (in seconds).
*/
private static final int ONE_YEAR = 60 * 60 * 24 * 365;
/**
* The name of the cookie.
*/
private final String name;
/**
* Creates a new instance of {@link CookieHandler}.
*
* @param name the name of the cookie
*/
public CookieHandler(final String name) {
this.name = "hudson.plugins." + name;
}
/**
* Sends a cookie with the specified value.
*
* @param requestAncestors the ancestors of the request
* @param value the cookie value
* @return the created cookie
*/
public Cookie create(final List requestAncestors, final String value) {
Cookie cookie = new Cookie(name, value);
Ancestor ancestor = requestAncestors.get(requestAncestors.size() - 3);
cookie.setPath(ancestor.getUrl());
cookie.setMaxAge(ONE_YEAR);
return cookie;
}
/**
* Selects the correct cookie from the specified cookies and returns its
* value. If there is no such cookie, then an empty string is returned.
*
* @param cookies the cookies to scan
* @return the cookie value or an empty string if the cookie is not found
*/
public String getValue(final Cookie[] cookies) {
String values = "";
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
values = cookie.getValue();
}
}
}
return values;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/data/ConstraintSettings.java
================================================
package hudson.plugins.performance.data;
import hudson.model.TaskListener;
/**
* Holds the global settings for constraints.
*
* @author Rene Kugel
*/
public class ConstraintSettings {
/**
* Build listener which is used to print relevant information to the console while evaluating
* constraints
*/
private transient TaskListener listener;
/**
* If true: relative constraints won't include builds in the past with the status FAILURE into
* the evaluation
*/
private boolean ignoreFailedBuilds;
/**
* If true: relative constraints won't include builds in the past with the status UNSTABLE into
* the evaluation
*/
private boolean ignoreUnstableBuilds;
/**
* If true: the constraint log will get written into a log file
*/
private boolean persistConstraintLog;
/**
* Relative constraints may need access to globally configured baseline build number to evaluate against
*/
private int baselineBuild;
public ConstraintSettings(TaskListener listener, boolean ignoreFailedBuilds, boolean ignoreUnstableBuilds, boolean persistConstraintLog,
int baselineBuild) {
this.setListener(listener);
this.setIgnoreFailedBuilds(ignoreFailedBuilds);
this.setIgnoreUnstableBuilds(ignoreUnstableBuilds);
this.setPersistConstraintLog(persistConstraintLog);
this.setBaselineBuild(baselineBuild);
}
public TaskListener getListener() {
return listener;
}
private void setListener(TaskListener listener) {
this.listener = listener;
}
public boolean isIgnoreFailedBuilds() {
return ignoreFailedBuilds;
}
public void setIgnoreFailedBuilds(boolean ignoreFailedBuilds) {
this.ignoreFailedBuilds = ignoreFailedBuilds;
}
public boolean isIgnoreUnstableBuilds() {
return ignoreUnstableBuilds;
}
public void setIgnoreUnstableBuilds(boolean ignoreUnstableBuilds) {
this.ignoreUnstableBuilds = ignoreUnstableBuilds;
}
public boolean isPersistConstraintLog() {
return persistConstraintLog;
}
public void setPersistConstraintLog(boolean persistConstraintLog) {
this.persistConstraintLog = persistConstraintLog;
}
public int getBaselineBuild() {
return baselineBuild;
}
public void setBaselineBuild(int baselineBuild) {
this.baselineBuild = baselineBuild;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/data/HttpSample.java
================================================
package hudson.plugins.performance.data;
import hudson.plugins.performance.reports.UriReport;
import java.io.Serializable;
import java.util.Date;
import java.util.Objects;
/**
* Information about a particular HTTP request and how that went.
*
* This object belongs under {@link UriReport}.
*/
public class HttpSample implements Serializable, Comparable {
private static final long serialVersionUID = -3531990216789038711L;
private long duration;
private boolean successful;
private boolean errorObtained;
private Date date;
private String uri;
private String httpCode = "";
private double sizeInKb;
// Summarizer fields
private boolean isSummarizer;
private long summarizerMin;
private long summarizerMax;
private float summarizerErrors;
private long summarizerSamples;
public long getDuration() {
return duration;
}
public Date getDate() {
return date;
}
public String getUri() {
return uri;
}
public String getHttpCode() {
return httpCode;
}
public long getSummarizerSamples() {
return summarizerSamples;
}
public long getSummarizerMin() {
return summarizerMin;
}
public long getSummarizerMax() {
return summarizerMax;
}
public float getSummarizerErrors() {
return summarizerErrors;
}
public boolean isFailed() {
return !isSuccessful();
}
public boolean isSuccessful() {
return successful;
}
public void setDuration(long duration) {
this.duration = duration;
}
public void setSuccessful(boolean successful) {
this.successful = successful;
}
public void setErrorObtained(boolean errorObtained) {
this.errorObtained = errorObtained;
}
public boolean hasError() {
return errorObtained;
}
public void setDate(Date time) {
this.date = time;
}
public void setUri(String uri) {
this.uri = uri;
}
public void setHttpCode(String httpCode) {
this.httpCode = httpCode;
}
public void setSummarizerSamples(long summarizerSamples) {
this.summarizerSamples = summarizerSamples;
}
public void setSummarizerMin(long summarizerMin) {
this.summarizerMin = summarizerMin;
}
public void setSummarizerMax(long summarizerMax) {
this.summarizerMax = summarizerMax;
}
public void setSummarizerErrors(float summarizerErrors) {
this.summarizerErrors = summarizerErrors;
}
public int compareTo(HttpSample o) {
return (int) (getDuration() - o.getDuration());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
HttpSample that = (HttpSample) obj;
return getDuration() == that.getDuration();
}
@Override
public int hashCode() {
return Objects.hash(getDuration());
}
public double getSizeInKb() {
return sizeInKb;
}
public void setSizeInKb(double d) {
this.sizeInKb = d;
}
public boolean isErrorObtained() {
return errorObtained;
}
public boolean isSummarizer() {
return isSummarizer;
}
public void setSummarizer(boolean summarizer) {
isSummarizer = summarizer;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/data/PerformanceReportPosition.java
================================================
package hudson.plugins.performance.data;
public class PerformanceReportPosition {
private String performanceReportPosition;
private String summarizerReportType;
private String summarizerTrendUri;
public String getPerformanceReportPosition() {
return performanceReportPosition;
}
public String getSummarizerReportType() {
return summarizerReportType;
}
public String getSummarizerTrendUri() {
return summarizerTrendUri;
}
public void setPerformanceReportPosition(String performanceReportPosition) {
this.performanceReportPosition = performanceReportPosition;
}
public void setSummarizerReportType(String summarizerReportType) {
this.summarizerReportType = summarizerReportType;
}
public void setSummarizerTrendUri(String summarizerTrendUri) {
this.summarizerTrendUri = summarizerTrendUri;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/data/ReportValueSelector.java
================================================
package hudson.plugins.performance.data;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.plugins.performance.PerformancePublisher;
import hudson.plugins.performance.reports.AbstractReport;
public abstract class ReportValueSelector {
public abstract long getValue(AbstractReport report);
public abstract String getGraphType();
public static ReportValueSelector get(Job, ?> job) {
if (job instanceof AbstractProject) {
// can't get a AbstractProject reference from PerformanceReportMap :/
AbstractProject, ?> project = (AbstractProject, ?>) job;
return get(project.getPublishersList().get(PerformancePublisher.class));
}
return get((PerformancePublisher) null);
}
public static ReportValueSelector get(PerformancePublisher publisher) {
if (publisher == null)
return new SelectAverage();
String graphType = publisher.getGraphType();
if (graphType == null)
return new SelectAverage();
if (graphType.equals(PerformancePublisher.MRT))
return new SelectMedian();
if (graphType.equals(PerformancePublisher.PRT))
return new SelectPercentile();
return new SelectAverage(); // default
}
private static class SelectAverage extends ReportValueSelector {
@Override
public long getValue(AbstractReport report) {
return report.getAverage();
}
@Override
public String getGraphType() {
return PerformancePublisher.ART;
}
}
private static class SelectMedian extends ReportValueSelector {
@Override
public long getValue(AbstractReport report) {
return report.getMedian();
}
@Override
public String getGraphType() {
return PerformancePublisher.MRT;
}
}
private static class SelectPercentile extends ReportValueSelector {
@Override
public long getValue(AbstractReport report) {
return report.get90Line();
}
@Override
public String getGraphType() {
return PerformancePublisher.PRT;
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/data/TaurusFinalStats.java
================================================
package hudson.plugins.performance.data;
import java.io.Serializable;
public class TaurusFinalStats implements Serializable {
public static final String DEFAULT_TAURUS_LABEL = "SUMMARY";
private String label;
private int succ;
private int fail;
private long bytes;
// ALL TIME IN MILLISECONDS!
private double averageResponseTime;
private double perc0;
private double perc50;
private double perc90;
private double perc95;
private double perc100;
private Double testDuration;
private long throughput;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public int getSucc() {
return succ;
}
public void setSucc(int succ) {
this.succ = succ;
}
public int getFail() {
return fail;
}
public void setFail(int fail) {
this.fail = fail;
}
public double getAverageResponseTime() {
return averageResponseTime;
}
public void setAverageResponseTime(double averageResponseTime) {
this.averageResponseTime = averageResponseTime;
}
public long getBytes() {
return bytes;
}
public void setBytes(long bytes) {
this.bytes = bytes;
}
public double getPerc50() {
return perc50;
}
public void setPerc50(double perc50) {
this.perc50 = perc50;
}
public double getPerc90() {
return perc90;
}
public double getPerc95() {
return perc95;
}
public void setPerc90(double perc90) {
this.perc90 = perc90;
}
public void setPerc95(double perc95) {
this.perc95 = perc95;
}
public double getPerc0() {
return perc0;
}
public void setPerc0(double perc0) {
this.perc0 = perc0;
}
public double getPerc100() {
return perc100;
}
public void setPerc100(double perc100) {
this.perc100 = perc100;
}
public long getThroughput() {
return throughput;
}
public void setThroughput(long throughput) {
this.throughput = throughput;
}
public Double getTestDuration() {
return testDuration;
}
public void setTestDuration(Double testDuration) {
this.testDuration = testDuration;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/descriptors/ConstraintDescriptor.java
================================================
package hudson.plugins.performance.descriptors;
import hudson.DescriptorExtensionList;
import hudson.model.Descriptor;
import hudson.plugins.performance.constraints.AbstractConstraint;
import jenkins.model.Jenkins;
public abstract class ConstraintDescriptor extends Descriptor {
public final String getId() {
return getClass().getName();
}
public static DescriptorExtensionList all() {
return Jenkins.get().getDescriptorList(AbstractConstraint.class);
}
public static ConstraintDescriptor getById(String id) {
for (ConstraintDescriptor d : all())
if (d.getId().equals(id))
return d;
return null;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/descriptors/PerformanceReportParserDescriptor.java
================================================
package hudson.plugins.performance.descriptors;
import hudson.DescriptorExtensionList;
import hudson.model.Descriptor;
import hudson.plugins.performance.parsers.PerformanceReportParser;
import jenkins.model.Jenkins;
/**
* @author Kohsuke Kawaguchi
*/
public abstract class PerformanceReportParserDescriptor extends
Descriptor {
/**
* Internal unique ID that distinguishes a parser.
*/
public final String getId() {
return getClass().getName();
}
/**
* Returns all the registered {@link PerformanceReportParserDescriptor}s.
*/
public static DescriptorExtensionList all() {
return Jenkins.get().getDescriptorList(PerformanceReportParser.class);
}
public static PerformanceReportParserDescriptor getById(String id) {
for (PerformanceReportParserDescriptor d : all())
if (d.getId().equals(id))
return d;
return null;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/details/GraphConfigurationDetail.java
================================================
package hudson.plugins.performance.details;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import org.apache.commons.io.IOUtils;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.cookie.CookieHandler;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
/**
* Configures the trend graph of this plug-in.
*/
public class GraphConfigurationDetail implements ModelObject {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(GraphConfigurationDetail.class.getName());
public static final String LEGACY_SEPARATOR = ";";
public static final String SEPARATOR = ":";
/**
* The number of builds to consider.
*/
private int buildCount;
/**
* The first days to consider.
*/
private String firstDayCount;
/**
* The last days to consider.
*/
private String lastDayCount;
/**
* The type of config to use.
*/
private String configType;
/**
* The build step to consider.
*/
private int buildStep;
public static final int DEFAULT_COUNT = 0;
public static final int DEFAULT_STEP = 1;
public static final String DEFAULT_DATE = "dd/MM/yyyy";
public static final String NONE_CONFIG = "NONE";
public static final String BUILD_CONFIG = "BUILD";
public static final String DATE_CONFIG = "DATE";
public static final String BUILDNTH_CONFIG = "BUILDNTH";
public boolean isNone() {
return configType.compareToIgnoreCase(GraphConfigurationDetail.NONE_CONFIG) == 0;
}
public boolean isBuildCount() {
return configType.compareToIgnoreCase(GraphConfigurationDetail.BUILD_CONFIG) == 0;
}
public boolean isBuildNth() {
return configType.compareToIgnoreCase(GraphConfigurationDetail.BUILDNTH_CONFIG) == 0;
}
public boolean isDate() {
return configType.compareToIgnoreCase(GraphConfigurationDetail.DATE_CONFIG) == 0;
}
public boolean isDefaultDates() {
return DEFAULT_DATE.compareTo(firstDayCount) == 0
&& DEFAULT_DATE.compareTo(lastDayCount) == 0;
}
public GraphConfigurationDetail(final Job, ?> project,
final String pluginName, final StaplerRequest request) {
String value = createCookieHandler(pluginName).getValue(
request.getCookies());
List initializationListResult = initializeFrom(value);
if (!initializationListResult.isEmpty()) {
File defaultsFile = createDefaultsFile(project, pluginName);
if (defaultsFile.exists()) {
String defaultValue = readFromDefaultsFile(defaultsFile);
initializationListResult = initializeFrom(defaultValue);
if (!initializationListResult.isEmpty()) {
reset(initializationListResult);
}
} else {
reset(initializationListResult);
}
}
}
/**
* Saves the configured values. Subclasses need to implement the actual
* persistence.
*
* @param request Stapler request
* @param response Stapler response
*/
public void doSave(final StaplerRequest request,
final StaplerResponse response) {
try {
JSONObject formData = request.getSubmittedForm();
String buildCountString = formData.getString("buildCountString");
int buildCount = 0;
if (buildCountString != null && !buildCountString.isBlank()) {
buildCount = formData.getInt("buildCountString");
}
String firstDayCountString = formData.getString("firstDayCountString");
String firstDayCount = DEFAULT_DATE;
if (firstDayCountString != null && !firstDayCountString.isBlank()) {
firstDayCount = formData.getString("firstDayCountString");
}
String lastDayCountString = formData.getString("lastDayCountString");
String lastDayCount = DEFAULT_DATE;
if (lastDayCountString != null && !lastDayCountString.isBlank()) {
lastDayCount = formData.getString("lastDayCountString");
}
String radioConfigType = formData.getString("radioConfigType");
String configType = NONE_CONFIG;
if (radioConfigType != null && !radioConfigType.isBlank()) {
configType = formData.getString("radioConfigType");
}
int buildStep = DEFAULT_STEP;
if (formData.has("buildStepString")) {
String buildStepString = formData.getString("buildStepString");
if (buildStepString != null && !buildStepString.isBlank()) {
buildStep = formData.getInt("buildStepString");
}
}
String value = serializeToString(configType, buildCount, firstDayCount,
lastDayCount, buildStep);
persistValue(value, request, response);
} catch (JSONException exception) {
LOGGER.log(Level.SEVERE, "Can't parse the form data: " + request,
exception);
} catch (IllegalArgumentException exception) {
LOGGER.log(Level.SEVERE, "Can't parse the form data: " + request,
exception);
} catch (ServletException exception) {
LOGGER.log(Level.SEVERE, "Can't process the form data: " + request,
exception);
} finally {
try {
response.sendRedirect("../");
} catch (IOException exception) {
LOGGER.log(Level.SEVERE, "Can't redirect", exception);
}
}
}
public String getDisplayName() {
return Messages.GraphConfigurationDetail_DisplayName();
}
/**
* Creates a new cookie handler to convert the cookie to a string value.
*
* @param cookieName the suffix of the cookie name that is used to persist the
* configuration per user
* @return the new cookie handler
*/
private static CookieHandler createCookieHandler(final String cookieName) {
return new CookieHandler(cookieName);
}
protected void persistValue(final String value, final StaplerRequest request,
final StaplerResponse response) {
// First check for URL values
String buildCount = request.getParameter("buildCount");
if (buildCount != null) {
setBuildCount(Integer.parseInt(buildCount));
setConfigType(GraphConfigurationDetail.BUILD_CONFIG);
return;
}
String buildStep = request.getParameter("buildStep");
if (buildStep != null) {
setBuildStep(Integer.parseInt(buildStep));
setConfigType(GraphConfigurationDetail.BUILDNTH_CONFIG);
return;
}
// If not found, check cookie
Cookie cookie = createCookieHandler("performance").create(
request.getAncestors(), value);
response.addCookie(cookie);
}
protected String serializeToString(final String configType,
final int buildCount, final String firstDayCount,
final String lastDayCount, final int buildStep) {
return configType + SEPARATOR + buildCount + SEPARATOR + firstDayCount
+ SEPARATOR + lastDayCount + SEPARATOR + buildStep;
}
/**
* Creates a file with for the default values.
*
* @param project the project used as directory for the file
* @param pluginName the name of the plug-in
* @return the created file
*/
protected static File createDefaultsFile(final Job, ?> project,
final String pluginName) {
return new File(project.getRootDir(), pluginName + ".txt");
}
/**
* Parses the provided string and initializes the members. If the string is
* not in the expected format, a list containing -1, 1, 2 and/or 3 is
* returned. -1 is a global error, 1 is a dayCount error, 2 is a first date
* error, 3 is a last date error. Return an empty list if all is good.
*
* @param value the initialization value stored in the format
* configType;buildCount;firstDayCount;lastDayCount
* @return an empty list is the initialization was successful, a list
* containing -1, 1, 2 or 3 otherwise
*/
private List initializeFrom(final String value) {
List listErrors = new ArrayList(0);
if (value == null || value.isBlank()) {
listErrors.add(-1);
return listErrors;
}
String[] values;
if (value.contains(LEGACY_SEPARATOR))
values = value.split(LEGACY_SEPARATOR);
else
values = value.split(SEPARATOR);
if ((values.length != 4) && (values.length != 5)) {
listErrors.add(-1);
return listErrors;
}
configType = values[0];
if (BUILD_CONFIG.compareToIgnoreCase(configType) != 0
&& BUILDNTH_CONFIG.compareToIgnoreCase(configType) != 0
&& DATE_CONFIG.compareToIgnoreCase(configType) != 0
&& NONE_CONFIG.compareToIgnoreCase(configType) != 0) {
listErrors.add(-1);
}
try {
buildCount = Integer.parseInt(values[1]);
} catch (JSONException e) {
listErrors.add(1);
e.printStackTrace();
}
firstDayCount = values[2];
lastDayCount = values[3];
GregorianCalendar firstDate = null;
GregorianCalendar lastDate = null;
if (firstDayCount.compareTo(DEFAULT_DATE) == 0
&& DATE_CONFIG.compareToIgnoreCase(configType) == 0) {
listErrors.add(2);
}
if (lastDayCount.compareTo(DEFAULT_DATE) == 0
&& DATE_CONFIG.compareToIgnoreCase(configType) == 0) {
listErrors.add(3);
}
if (firstDayCount.compareTo(DEFAULT_DATE) != 0) {
try {
firstDate = getGregorianCalendarFromString(firstDayCount);
} catch (IllegalArgumentException e) {
listErrors.add(2);
e.printStackTrace();
} catch (ParseException e) {
listErrors.add(2);
e.printStackTrace();
}
}
if (lastDayCount.compareTo(DEFAULT_DATE) != 0) {
try {
lastDate = getGregorianCalendarFromString(lastDayCount);
} catch (IllegalArgumentException e) {
listErrors.add(3);
e.printStackTrace();
} catch (ParseException e) {
listErrors.add(3);
e.printStackTrace();
}
}
if (firstDate != null && lastDate != null && firstDate.after(lastDate)) {
listErrors.add(2);
listErrors.add(3);
}
try {
if (values.length == 5) {
buildStep = Integer.parseInt(values[4]);
}
} catch (JSONException e) {
listErrors.add(4);
e.printStackTrace();
}
// clean the error list
if (!listErrors.isEmpty()) {
Collections.sort(listErrors);
if (listErrors.get(0) == -1) {
listErrors = new ArrayList(1);
listErrors.add(-1);
} else {
int actualErrorType = 0;
List realListErrors = new ArrayList(0);
for (Integer typeError : listErrors) {
if (actualErrorType != typeError) {
actualErrorType = typeError;
realListErrors.add(typeError);
}
}
listErrors = realListErrors;
}
}
return listErrors;
}
/**
*
* Get a gregorian calendar from a String of type : DD/MM/YYYY
*
*
* @param dateString
* @return GregorianCalendar
* @throws ParseException
*/
public static GregorianCalendar getGregorianCalendarFromString(
String dateString) throws ParseException {
DateFormat format = new SimpleDateFormat(DEFAULT_DATE);
Date date = format.parse(dateString);
GregorianCalendar outCalendar = new GregorianCalendar();
outCalendar.setTime(date);
return outCalendar;
}
/**
* Resets the graph configuration to the default values.
*/
private void reset(List initializationResult) {
configType = NONE_CONFIG;
for (Integer errorNumber : initializationResult) {
if (errorNumber == -1) {
buildCount = DEFAULT_COUNT;
firstDayCount = DEFAULT_DATE;
lastDayCount = DEFAULT_DATE;
buildStep = DEFAULT_STEP;
} else if (errorNumber == 1) {
buildCount = DEFAULT_COUNT;
} else if (errorNumber == 2) {
firstDayCount = DEFAULT_DATE;
} else if (errorNumber == 3) {
lastDayCount = DEFAULT_DATE;
} else if (errorNumber == 4) {
buildStep = DEFAULT_STEP;
}
}
}
/**
* Reads the default values from file.
*
* @param defaultsFile the file with the default values
* @return the default values from file.
*/
private String readFromDefaultsFile(final File defaultsFile) {
String defaultValue = "";
FileInputStream input = null;
try {
input = new FileInputStream(defaultsFile);
defaultValue = IOUtils.toString(input, Charset.defaultCharset());
} catch (IOException exception) {
// ignore
} finally {
IOUtils.closeQuietly(input);
}
return defaultValue;
}
public int getBuildCount() {
return buildCount;
}
public void setBuildCount(int buildCount) {
this.buildCount = buildCount;
}
public int getBuildStep() {
return buildStep;
}
public void setBuildStep(int buildStep) {
this.buildStep = buildStep;
}
public String getFirstDayCount() {
return firstDayCount;
}
public void setFirstDayCount(String firstDayCount) {
this.firstDayCount = firstDayCount;
}
public String getLastDayCount() {
return lastDayCount;
}
public void setLastDayCount(String lastDayCount) {
this.lastDayCount = lastDayCount;
}
public String getConfigType() {
return configType;
}
public void setConfigType(String configType) {
this.configType = configType;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/details/TestSuiteReportDetail.java
================================================
package hudson.plugins.performance.details;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.model.Run;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.actions.PerformanceProjectAction.Range;
import hudson.plugins.performance.data.ReportValueSelector;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
import hudson.util.ChartUtil;
import hudson.util.ChartUtil.NumberOnlyBuildLabel;
import hudson.util.ColorPalette;
import hudson.util.DataSetBuilder;
import hudson.util.Graph;
import hudson.util.ShiftedCategoryAxis;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.awt.Color;
import java.awt.BasicStroke;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Configures the trend graph of this plug-in.
*/
public class TestSuiteReportDetail implements ModelObject {
private final Job, ?> project;
private final String filename;
private final Range buildsLimits;
private transient List performanceReportTestCaseList;
public TestSuiteReportDetail(final Job, ?> project, String filename,
Range buildsLimits) {
this.project = project;
this.filename = filename;
this.buildsLimits = buildsLimits;
}
public void doRespondingTimeGraphPerTestCaseMode(StaplerRequest request,
StaplerResponse response) throws IOException {
if (ChartUtil.awtProblemCause != null) {
// not available. send out error message
response.sendRedirect2(request.getContextPath() + "/images/headless.png");
return;
}
String testUri = request.getParameter("performanceReportTest");
new Graph(-1, 600, 200) {
@Override
protected JFreeChart createGraph() {
return createRespondingTimeChart(
getChartDatasetBuilderForBuilds(testUri, getProject().getBuilds()).build());
}
}.doPng(request, response);
}
DataSetBuilder getChartDatasetBuilderForBuilds(String testUri, List extends Run, ?>> builds) {
DataSetBuilder dataSetBuilder = new DataSetBuilder();
ReportValueSelector valueSelector = ReportValueSelector.get(getProject());
int nbBuildsToAnalyze = builds.size();
for (Run, ?> build : builds) {
if (buildsLimits.in(nbBuildsToAnalyze)) {
if (!buildsLimits.includedByStep(build.number)) {
continue;
}
NumberOnlyBuildLabel label = new NumberOnlyBuildLabel(build);
PerformanceBuildAction performanceBuildAction = build
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(this.filename);
if (performanceReport == null) {
nbBuildsToAnalyze--;
continue;
}
String testStaplerUri = PerformanceReport.asStaplerURI(testUri);
UriReport reportForTestUri = performanceReport.getUriReportMap().get(testStaplerUri);
if (reportForTestUri != null) {
dataSetBuilder.add(valueSelector.getValue(reportForTestUri), testUri, label);
}
}
nbBuildsToAnalyze--;
}
return dataSetBuilder;
}
protected static JFreeChart createRespondingTimeChart(CategoryDataset dataset) {
final JFreeChart chart = ChartFactory.createLineChart(
Messages.ProjectAction_RespondingTime(), // charttitle
null, // unused
"ms", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // orientation
true, // include legend
true, // tooltips
false // urls
);
// NOW DO SOME OPTIONAL CUSTOMISATION OF THE CHART...
final LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.BOTTOM);
chart.setBackgroundPaint(Color.white);
final CategoryPlot plot = chart.getCategoryPlot();
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlinePaint(null);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.black);
CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
plot.setDomainAxis(domainAxis);
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
domainAxis.setLowerMargin(0.0);
domainAxis.setUpperMargin(0.0);
domainAxis.setCategoryMargin(0.0);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
final LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot
.getRenderer();
renderer.setBaseStroke(new BasicStroke(4.0f));
ColorPalette.apply(renderer);
// crop extra space around the graph
plot.setInsets(new RectangleInsets(5.0, 0, 0, 5.0));
return chart;
}
public List getPerformanceReportTestCaseList() {
this.performanceReportTestCaseList = new ArrayList(0);
String performanceReportNameFile = this.getFilename();
List extends Run, ?>> builds = getProject().getBuilds();
for (Run, ?> build : builds) {
PerformanceBuildAction performanceBuildAction = build
.getAction(PerformanceBuildAction.class);
if (performanceBuildAction == null) {
continue;
}
PerformanceReport performanceReport = performanceBuildAction
.getPerformanceReportMap().getPerformanceReport(
performanceReportNameFile);
if (performanceReport == null) {
continue;
}
for (UriReport currentReport : performanceReport.getUriReportMap().values()) {
if (!performanceReportTestCaseList.contains(currentReport.getUri())) {
performanceReportTestCaseList.add(currentReport.getUri());
}
}
}
Collections.sort(performanceReportTestCaseList);
return this.performanceReportTestCaseList;
}
public Job, ?> getProject() {
return project;
}
public String getFilename() {
return filename;
}
public String getDisplayName() {
return Messages.TestSuiteReportDetail_DisplayName();
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/details/TrendReportDetail.java
================================================
package hudson.plugins.performance.details;
import hudson.model.Job;
import hudson.model.ModelObject;
import hudson.plugins.performance.Messages;
import org.jfree.data.category.CategoryDataset;
import org.kohsuke.stapler.StaplerRequest;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Configures the trend graph of this plug-in.
*/
public class TrendReportDetail implements ModelObject,
Iterable {
private Job, ?> project;
private String filename;
private CategoryDataset dataSet;
public TrendReportDetail(final Job, ?> project,
final String pluginName, final StaplerRequest request, String filename,
CategoryDataset dataSet) {
this.project = project;
this.filename = filename;
this.dataSet = dataSet;
}
public Job, ?> getProject() {
return project;
}
public String getFilename() {
return filename;
}
public String getDisplayName() {
return Messages.TrendReportDetail_DisplayName();
}
public Iterator iterator() {
return new RowIterator();
}
public Iterator getIterator() {
return iterator();
}
public List> getColumnLabels() {
return dataSet.getRowKeys();
}
public class RowIterator implements Iterator {
private int entry = 0;
public boolean hasNext() {
return (entry < dataSet.getColumnCount());
}
public Row next() {
return new Row(entry++);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public class Row {
private int entry;
public Row(int entry) {
this.entry = entry;
}
public Object getLabel() {
return dataSet.getColumnKey(entry);
}
public List> getLabels() {
return dataSet.getRowKeys();
}
public List getValues() {
int count = dataSet.getRowCount();
List list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(dataSet.getValue(dataSet.getRowKey(i),
dataSet.getColumnKey(entry)));
}
return list;
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/AbstractParser.java
================================================
package hudson.plugins.performance.parsers;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.reports.UriReport;
/**
* An abstraction for parsing data to PerformanceReport instances. This class
* provides functionality that optimizes the parsing process, such as caching as
* well as saving/loaded parsed data in serialized form to/from disc.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public abstract class AbstractParser extends PerformanceReportParser {
private static final Logger LOGGER = Logger.getLogger(JMeterParser.class.getName());
/**
* A suffix to be used for files in which a serialized PerformanceReport instance is stored.
*/
private static final String SERIALIZED_DATA_FILE_SUFFIX = ".serialized";
/**
* A cache that contains serialized PerformanceReport instances. This cache intends to limit disc IO.
*/
private static final Cache CACHE = CacheBuilder.newBuilder().maximumSize(1000).softValues().build();
protected boolean isNumberDateFormat = false;
protected transient SimpleDateFormat format;
static final String[] DATE_FORMATS = new String[]{
"yyyy/MM/dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd HH:mm:ss,SSS", "yyyy/mm/dd HH:mm:ss"
};
protected String percentiles;
protected String filterRegex;
public AbstractParser(String glob, String percentiles, String filterRegex) {
super(glob);
this.percentiles = percentiles;
this.filterRegex = filterRegex;
}
@Override
public Collection parse(Run, ?> build, Collection reports, TaskListener listener) throws IOException {
final List result = new ArrayList<>();
for (File reportFile : reports) {
// Attempt to load previously serialized instances from file or cache.
final PerformanceReport deserializedReport = loadSerializedReport(reportFile);
if (deserializedReport != null) {
result.add(deserializedReport);
continue;
}
// When serialized data cannot be used, the original JMeter files are to be processed.
try {
listener.getLogger().println("Performance: Parsing report file '" + reportFile + "' with filterRegex '"+filterRegex+"'.");
final PerformanceReport report = parse(reportFile);
result.add(report);
passBaselineBuild(report);
saveSerializedReport(reportFile, report);
} catch (Throwable e) {
listener.getLogger().println("Performance: Failed to parse file '" + reportFile + "': " + e.getMessage());
e.printStackTrace(listener.getLogger());
}
}
return result;
}
private void passBaselineBuild(PerformanceReport report) {
report.setBaselineBuild(baselineBuild);
}
/**
* Performs the actual parsing of data. When the implementation throws any
* exception, the input file is ignored. This does not abort parsing of
* subsequent files.
*
* @param reportFile The source file (cannot be null).
* @return The parsed data (never null).
* @throws Throwable On any exception.
*/
abstract PerformanceReport parse(File reportFile) throws Exception;
/**
* Returns a PerformanceReport instance for the provided report file, based on
* previously serialized data.
*
* This method first attempts to load data from an internal cache. If the data
* is not in cache, data is obtained from a file on disc.
*
* When no PerformanceReport instance has previously been serialized (or when
* such data cannot be read, for instance because of class file changes), this
* method returns null.
*
* @param reportFile Report for which to return data. Cannot be null.
* @return deserialized data, possibly null.
*/
protected static PerformanceReport loadSerializedReport(File reportFile) {
if (reportFile == null) {
throw new IllegalArgumentException("Argument 'reportFile' cannot be null.");
}
final String serialized = reportFile.getPath() + SERIALIZED_DATA_FILE_SUFFIX;
File file = new File(serialized);
synchronized (CACHE) {
PerformanceReport report = CACHE.getIfPresent(serialized);
if (report == null && file.exists() && file.canRead()) {
try (FileInputStream fis = new FileInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(fis);
ObjectInputStream in = new ObjectInputStreamWithClassMapping(bis)) {
report = (PerformanceReport) in.readObject();
CACHE.put(serialized, report);
return report;
} catch (FileNotFoundException ex) {
// That's OK
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Reading serialized PerformanceReport instance from file '" + serialized + "' failed.", ex);
}
}
return report;
}
}
/**
* Saves a PerformanceReport instance as serialized data into a file on disc.
*
* @param reportFile The file from which the original data is obtained (not
* the file into which serialized data is to be saved!) Cannot be
* null.
* @param report The instance to serialize. Cannot be null.
*/
protected static void saveSerializedReport(File reportFile, PerformanceReport report) {
if (reportFile == null) {
throw new IllegalArgumentException("Argument 'reportFile' cannot be null.");
}
if (report == null) {
throw new IllegalArgumentException("Argument 'report' cannot be null.");
}
final String serialized = reportFile.getPath() + SERIALIZED_DATA_FILE_SUFFIX;
synchronized (CACHE) {
CACHE.put(serialized, report);
}
try (FileOutputStream fos = new FileOutputStream(serialized);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream out = new ObjectOutputStream(bos)) {
out.writeObject(report);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, "Saving serialized PerformanceReport instance to file '" + serialized + "' failed.", ex);
}
}
public static class ObjectInputStreamWithClassMapping extends ObjectInputStream {
protected Hashtable> classMapping = new Hashtable<>();
public ObjectInputStreamWithClassMapping(InputStream in) throws IOException {
super(in);
classMapping.put("hudson.plugins.performance.PerformanceReport", PerformanceReport.class);
classMapping.put("hudson.plugins.performance.UriReport", UriReport.class);
classMapping.put("hudson.plugins.performance.UriReport$Sample", UriReport.Sample.class);
}
@Override
protected Class> resolveClass(ObjectStreamClass desc) throws IOException,
ClassNotFoundException {
return (classMapping.containsKey(desc.getName())) ?
classMapping.get(desc.getName()) :
super.resolveClass(desc);
}
}
public void clearDateFormat() {
this.format = null;
this.isNumberDateFormat = false;
}
public Date parseTimestamp(String timestamp) {
if (this.format == null) {
initDateFormat(timestamp);
}
try {
return isNumberDateFormat ?
new Date(Long.parseLong(timestamp)) :
format.parse(timestamp);
} catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse timestamp: " + timestamp +
". Please, use one of supported formats: " + Arrays.toString(DATE_FORMATS), e);
}
}
private void initDateFormat(String timestamp) {
Date result = null;
for (String format : DATE_FORMATS) {
try {
this.format = new SimpleDateFormat(format);
result = this.format.parse(timestamp);
} catch (ParseException ex) {
// ok
this.format = null;
}
if (result != null) {
break;
}
}
if (result == null) {
try {
Long.valueOf(timestamp);
isNumberDateFormat = true;
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Cannot parse timestamp: " + timestamp +
". Please, use one of supported formats: " + Arrays.toString(DATE_FORMATS), ex);
}
}
}
protected PerformanceReport createPerformanceReport() {
return new PerformanceReport(percentiles, filterRegex);
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/IagoParser.java
================================================
package hudson.plugins.performance.parsers;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.kohsuke.stapler.DataBoundConstructor;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
/**
* Parses Iago results as dumped by the server.
*
* @author jwstric2
*/
public class IagoParser extends AbstractParser {
private static final String STATS_DATE_FORMAT = "yyyymmdd-HH:mm:ss.SSS";
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "Iago";
}
}
public IagoParser(String glob, String percentiles) {
this(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public IagoParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Override
public String getDefaultGlobPattern() {
//Normally just a parrot server result file; if using multiple
//user will need to add their own recognizable glob extensions
return "parrot-server-stats.log";
}
@Override
PerformanceReport parse(File reportFile) throws Exception {
final PerformanceReport report = createPerformanceReport();
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
report.setReportFileName(reportFile.getName());
try (FileReader fr = new FileReader(reportFile, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(fr)){
String line = reader.readLine();
while (line != null) {
final HttpSample sample = this.getSample(line, reportFile.getName());
String nextLine = reader.readLine();
if (sample != null) {
report.addSample(sample);
}
line = nextLine;
}
}
return report;
}
/**
* Parses a line and return a HttpSample
* INF [20140611-21:34:01.224] stats: {"400":84,"client\/available":1,"client\/cancelled_connects":0,"client\/closechans":85,"client\/closed":85,"client\/closes":84,"client\/codec_connection_preparation_latency_ms_average":3,"client\/codec_connection_preparation_latency_ms_count":85,"client\/codec_connection_preparation_latency_ms_maximum":142,"client\/codec_connection_preparation_latency_ms_minimum":1,"client\/codec_connection_preparation_latency_ms_p50":2,"client\/codec_connection_preparation_latency_ms_p90":4,"client\/codec_connection_preparation_latency_ms_p95":4,"client\/codec_connection_preparation_latency_ms_p99":142,"client\/codec_connection_preparation_latency_ms_p999":142,"client\/codec_connection_preparation_latency_ms_p9999":142,"client\/codec_connection_preparation_latency_ms_sum":316,"client\/connect_latency_ms_average":2,"client\/connect_latency_ms_count":85,"client\/connect_latency_ms_maximum":142,"client\/connect_latency_ms_minimum":0,"client\/connect_latency_ms_p50":1,"client\/connect_latency_ms_p90":2,"client\/connect_latency_ms_p95":4,"client\/connect_latency_ms_p99":142,"client\/connect_latency_ms_p999":142,"client\/connect_latency_ms_p9999":142,"client\/connect_latency_ms_sum":238,"client\/connection_duration_average":5,"client\/connection_duration_count":85,"client\/connection_duration_maximum":173,"client\/connection_duration_minimum":2,"client\/connection_duration_p50":3,"client\/connection_duration_p90":6,"client\/connection_duration_p95":7,"client\/connection_duration_p99":173,"client\/connection_duration_p999":173,"client\/connection_duration_p9999":173,"client\/connection_duration_sum":477,"client\/connection_received_bytes_average":604,"client\/connection_received_bytes_count":85,"client\/connection_received_bytes_maximum":576,"client\/connection_received_bytes_minimum":576,"client\/connection_received_bytes_p50":576,"client\/connection_received_bytes_p90":576,"client\/connection_received_bytes_p95":576,"client\/connection_received_bytes_p99":576,"client\/connection_received_bytes_p999":576,"client\/connection_received_bytes_p9999":576,"client\/connection_received_bytes_sum":51340,"client\/connection_requests_average":1,"client\/connection_requests_count":85,"client\/connection_requests_maximum":1,"client\/connection_requests_minimum":1,"client\/connection_requests_p50":1,"client\/connection_requests_p90":1,"client\/connection_requests_p95":1,"client\/connection_requests_p99":1,"client\/connection_requests_p999":1,"client\/connection_requests_p9999":1,"client\/connection_requests_sum":85,"client\/connection_sent_bytes_average":140,"client\/connection_sent_bytes_count":85,"client\/connection_sent_bytes_maximum":142,"client\/connection_sent_bytes_minimum":142,"client\/connection_sent_bytes_p50":142,"client\/connection_sent_bytes_p90":142,"client\/connection_sent_bytes_p95":142,"client\/connection_sent_bytes_p99":142,"client\/connection_sent_bytes_p999":142,"client\/connection_sent_bytes_p9999":142,"client\/connection_sent_bytes_sum":11919,"client\/connections":0,"client\/connects":85,"client\/failed_connect_latency_ms_count":0,"client\/failfast":0,"client\/failfast\/unhealthy_for_ms":0,"client\/failfast\/unhealthy_num_tries":0,"client\/failures":1,"client\/failures\/com.twitter.finagle.ChannelClosedException":1,"client\/idle":0,"client\/jonatstr-dt-otc_80\/available":1,"client\/jonatstr-dt-otc_80\/cancelled_connects":0,"client\/jonatstr-dt-otc_80\/closechans":85,"client\/jonatstr-dt-otc_80\/closed":85,"client\/jonatstr-dt-otc_80\/closes":84,"client\/jonatstr-dt-otc_80\/connect_latency_ms_average":2,"client\/jonatstr-dt-otc_80\/connect_latency_ms_count":85,"client\/jonatstr-dt-otc_80\/connect_latency_ms_maximum":142,"client\/jonatstr-dt-otc_80\/connect_latency_ms_minimum":0,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p50":1,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p90":2,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p95":4,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p99":142,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p999":142,"client\/jonatstr-dt-otc_80\/connect_latency_ms_p9999":142,"client\/jonatstr-dt-otc_80\/connect_latency_ms_sum":238,"client\/jonatstr-dt-otc_80\/connection_duration_average":5,"client\/jonatstr-dt-otc_80\/connection_duration_count":85,"client\/jonatstr-dt-otc_80\/connection_duration_maximum":173,"client\/jonatstr-dt-otc_80\/connection_duration_minimum":2,"client\/jonatstr-dt-otc_80\/connection_duration_p50":3,"client\/jonatstr-dt-otc_80\/connection_duration_p90":6,"client\/jonatstr-dt-otc_80\/connection_duration_p95":7,"client\/jonatstr-dt-otc_80\/connection_duration_p99":173,"client\/jonatstr-dt-otc_80\/connection_duration_p999":173,"client\/jonatstr-dt-otc_80\/connection_duration_p9999":173,"client\/jonatstr-dt-otc_80\/connection_duration_sum":477,"client\/jonatstr-dt-otc_80\/connection_received_bytes_average":604,"client\/jonatstr-dt-otc_80\/connection_received_bytes_count":85,"client\/jonatstr-dt-otc_80\/connection_received_bytes_maximum":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_minimum":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p50":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p90":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p95":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p99":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p999":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_p9999":576,"client\/jonatstr-dt-otc_80\/connection_received_bytes_sum":51340,"client\/jonatstr-dt-otc_80\/connection_requests_average":1,"client\/jonatstr-dt-otc_80\/connection_requests_count":85,"client\/jonatstr-dt-otc_80\/connection_requests_maximum":1,"client\/jonatstr-dt-otc_80\/connection_requests_minimum":1,"client\/jonatstr-dt-otc_80\/connection_requests_p50":1,"client\/jonatstr-dt-otc_80\/connection_requests_p90":1,"client\/jonatstr-dt-otc_80\/connection_requests_p95":1,"client\/jonatstr-dt-otc_80\/connection_requests_p99":1,"client\/jonatstr-dt-otc_80\/connection_requests_p999":1,"client\/jonatstr-dt-otc_80\/connection_requests_p9999":1,"client\/jonatstr-dt-otc_80\/connection_requests_sum":85,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_average":140,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_count":85,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_maximum":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_minimum":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p50":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p90":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p95":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p99":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p999":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_p9999":142,"client\/jonatstr-dt-otc_80\/connection_sent_bytes_sum":11919,"client\/jonatstr-dt-otc_80\/connections":0,"client\/jonatstr-dt-otc_80\/connects":85,"client\/jonatstr-dt-otc_80\/failed_connect_latency_ms_count":0,"client\/jonatstr-dt-otc_80\/failfast":0,"client\/jonatstr-dt-otc_80\/failfast\/unhealthy_for_ms":0,"client\/jonatstr-dt-otc_80\/failfast\/unhealthy_num_tries":0,"client\/jonatstr-dt-otc_80\/failures":1,"client\/jonatstr-dt-otc_80\/failures\/com.twitter.finagle.ChannelClosedException":1,"client\/jonatstr-dt-otc_80\/idle":0,"client\/jonatstr-dt-otc_80\/lifetime":0,"client\/jonatstr-dt-otc_80\/load":0,"client\/jonatstr-dt-otc_80\/pending":0,"client\/jonatstr-dt-otc_80\/pool_cached":0,"client\/jonatstr-dt-otc_80\/pool_num_waited":0,"client\/jonatstr-dt-otc_80\/pool_size":0,"client\/jonatstr-dt-otc_80\/pool_waiters":0,"client\/jonatstr-dt-otc_80\/received_bytes":51340,"client\/jonatstr-dt-otc_80\/request_latency_ms_average":3,"client\/jonatstr-dt-otc_80\/request_latency_ms_count":85,"client\/jonatstr-dt-otc_80\/request_latency_ms_maximum":105,"client\/jonatstr-dt-otc_80\/request_latency_ms_minimum":1,"client\/jonatstr-dt-otc_80\/request_latency_ms_p50":2,"client\/jonatstr-dt-otc_80\/request_latency_ms_p90":4,"client\/jonatstr-dt-otc_80\/request_latency_ms_p95":4,"client\/jonatstr-dt-otc_80\/request_latency_ms_p99":105,"client\/jonatstr-dt-otc_80\/request_latency_ms_p999":105,"client\/jonatstr-dt-otc_80\/request_latency_ms_p9999":105,"client\/jonatstr-dt-otc_80\/request_latency_ms_sum":276,"client\/jonatstr-dt-otc_80\/requests":85,"client\/jonatstr-dt-otc_80\/sent_bytes":11919,"client\/jonatstr-dt-otc_80\/socket_unwritable_ms":0,"client\/jonatstr-dt-otc_80\/socket_writable_ms":207,"client\/jonatstr-dt-otc_80\/success":84,"client\/lifetime":0,"client\/load":0,"client\/loadbalancer\/adds":0,"client\/loadbalancer\/available":1,"client\/loadbalancer\/load":0,"client\/loadbalancer\/removes":0,"client\/loadbalancer\/size":1,"client\/pending":0,"client\/pool_cached":0,"client\/pool_num_waited":0,"client\/pool_size":0,"client\/pool_waiters":0,"client\/received_bytes":51340,"client\/request_latency_ms_average":3,"client\/request_latency_ms_count":85,"client\/request_latency_ms_maximum":105,"client\/request_latency_ms_minimum":1,"client\/request_latency_ms_p50":2,"client\/request_latency_ms_p90":4,"client\/request_latency_ms_p95":4,"client\/request_latency_ms_p99":105,"client\/request_latency_ms_p999":105,"client\/request_latency_ms_p9999":105,"client\/request_latency_ms_sum":276,"client\/requests":85,"client\/sent_bytes":11919,"client\/socket_unwritable_ms":0,"client\/socket_writable_ms":207,"client\/success":84,"clock_error":0,"jvm_buffer_direct_count":4,"jvm_buffer_direct_max":133120,"jvm_buffer_direct_used":133120,"jvm_buffer_mapped_count":0,"jvm_buffer_mapped_max":0,"jvm_buffer_mapped_used":0,"jvm_current_mem_CMS_Old_Gen_max":3657433088,"jvm_current_mem_CMS_Old_Gen_used":12173984,"jvm_current_mem_CMS_Perm_Gen_max":85983232,"jvm_current_mem_CMS_Perm_Gen_used":44355376,"jvm_current_mem_Code_Cache_max":50331648,"jvm_current_mem_Code_Cache_used":2425792,"jvm_current_mem_Eden_Space_max":429522944,"jvm_current_mem_Eden_Space_used":169518376,"jvm_current_mem_Survivor_Space_max":53673984,"jvm_current_mem_Survivor_Space_used":53673984,"jvm_current_mem_used":282147512,"jvm_fd_count":142,"jvm_fd_limit":4096,"jvm_gc_ConcurrentMarkSweep_cycles":0,"jvm_gc_ConcurrentMarkSweep_msec":0,"jvm_gc_Copy_cycles":0,"jvm_gc_Copy_msec":0,"jvm_gc_cycles":0,"jvm_gc_msec":0,"jvm_heap_committed":4140630016,"jvm_heap_max":4140630016,"jvm_heap_used":235366344,"jvm_nonheap_committed":47120384,"jvm_nonheap_max":136314880,"jvm_nonheap_used":46777704,"jvm_num_cpus":1,"jvm_post_gc_CMS_Old_Gen_max":3657433088,"jvm_post_gc_CMS_Old_Gen_used":0,"jvm_post_gc_CMS_Perm_Gen_max":85983232,"jvm_post_gc_CMS_Perm_Gen_used":0,"jvm_post_gc_Eden_Space_max":429522944,"jvm_post_gc_Eden_Space_used":0,"jvm_post_gc_Survivor_Space_max":53673984,"jvm_post_gc_Survivor_Space_used":53673984,"jvm_post_gc_used":53673984,"jvm_start_time":1402536778818,"jvm_thread_count":18,"jvm_thread_daemon_count":12,"jvm_thread_peak_count":18,"jvm_uptime":62216,"queue_depth":41,"records-read":126,"requests_sent":85,"service":"parrot_web","source":"jonatstr-dt-oneconnector","timestamp":1402536841,"unexpected_error":1,"unexpected_error\/com.twitter.finagle.ChannelClosedException":1}
*/
protected HttpSample getSample(String line, String key) throws ParseException, IllegalArgumentException {
HttpSample sample = new HttpSample();
Pattern pattern = Pattern.compile("^INF \\[(.+)\\] stats: (\\{.+\\})$");
Matcher matcher = pattern.matcher(line);
//Should have group count of 2, the date and the stats json
if (!matcher.find()) {
throw new ParseException("Invalid line " + line, 0);
}
String dateString = matcher.group(1);
String statsString = matcher.group(2);
//Get date object
SimpleDateFormat dateFormat = new SimpleDateFormat(STATS_DATE_FORMAT);
Date dateObject = dateFormat.parse(dateString);
//Now we need to parse the stats json
GsonBuilder gsonBuilder = new GsonBuilder();
StatsDeserializer deserializer = new StatsDeserializer();
gsonBuilder.registerTypeAdapter(Stats.class, deserializer);
Gson gson = gsonBuilder.create();
Stats statsObject = null;
try {
statsObject = gson.fromJson(statsString, Stats.class);
} catch (JsonParseException e) {
throw new IllegalArgumentException("Invalid stat data " + statsString + ":" + e.getLocalizedMessage());
}
//Set the sample data
sample.setDate(dateObject);
sample.setSummarizer(true);
sample.setSummarizerSamples(statsObject.getClientRequests()); // set SamplesCount
sample.setDuration(statsObject.getClientRequestLatencyMsAverage());
sample.setSuccessful(true);
sample.setSummarizerMin(statsObject.getClientRequestLatencyMsMinimum());
sample.setSummarizerMax(statsObject.getClientRequestLatencyMsMaximum());
sample.setSummarizerErrors((statsObject.getClientRequests() - statsObject.getClientSuccess())
+ statsObject.getSumValidationErrors());
sample.setUri(key);
return sample;
}
protected static class Stats {
@SerializedName("client/request_latency_ms_minimum")
private long clientRequestLatencyMsMinimum = 0;
@SerializedName("client/request_latency_ms_maximum")
private long clientRequestLatencyMsMaximum = 0;
@SerializedName("client/request_latency_ms_average")
private long clientRequestLatencyMsAverage = 0;
@SerializedName("client/sent_bytes")
private long clientSendBytes = 0;
@SerializedName("client/requests")
private long clientRequests = 0;
@SerializedName("client/success")
private long clientSuccess = 0;
//User defined validation errors
private Map validationErrors = new HashMap<>();
public Stats() {
super();
}
public long getClientRequestLatencyMsMinimum() {
return clientRequestLatencyMsMinimum;
}
public void setClientRequestLatencyMsMinimum(
long clientRequestLatencyMsMinimum) {
this.clientRequestLatencyMsMinimum = clientRequestLatencyMsMinimum;
}
public long getClientRequestLatencyMsMaximum() {
return clientRequestLatencyMsMaximum;
}
public void setClientRequestLatencyMsMaximum(
long clientRequestLatencyMsMaximum) {
this.clientRequestLatencyMsMaximum = clientRequestLatencyMsMaximum;
}
public long getClientRequestLatencyMsAverage() {
return clientRequestLatencyMsAverage;
}
public void setClientRequestLatencyMsAverage(
long clientRequestLatencyMsAverage) {
this.clientRequestLatencyMsAverage = clientRequestLatencyMsAverage;
}
public long getClientSendBytes() {
return clientSendBytes;
}
public void setClientSendBytes(long clientSendBytes) {
this.clientSendBytes = clientSendBytes;
}
public long getClientRequests() {
return clientRequests;
}
public void setClientRequests(long clientRequests) {
this.clientRequests = clientRequests;
}
public long getClientSuccess() {
return clientSuccess;
}
public void setClientSuccess(long clientSuccess) {
this.clientSuccess = clientSuccess;
}
public void addValidationError(String name, long value) {
synchronized (validationErrors) {
this.validationErrors.put(name, Long.valueOf(value));
}
}
public long getSumValidationErrors() {
long sumValidationErrors = 0;
synchronized (validationErrors) {
for (Long value : validationErrors.values()) {
sumValidationErrors += value;
}
}
return sumValidationErrors;
}
}
/**
* A Stats Deserializer to verify during deserialization that
* all needed stats are available for the IagoParser and handle
* any special error fields that a user specified
*
* @author jwstric2
*/
private static class StatsDeserializer implements JsonDeserializer {
private static final String[] requiredFields = new String[]{
"client/request_latency_ms_minimum",
"client/request_latency_ms_maximum",
"client/request_latency_ms_average",
"client/sent_bytes",
"client/requests",
"client/success"};
public Stats deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) {
JsonObject jsonObject = (JsonObject) json;
//First, check we have all our required fields ..
for (String fieldName : requiredFields) {
if (jsonObject.get(fieldName) == null) {
throw new JsonParseException("Required Field Not Found: " + fieldName);
}
}
return new Gson().fromJson(json, Stats.class);
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/JMeterCsvParser.java
================================================
package hudson.plugins.performance.parsers;
import java.io.*;
import java.nio.charset.StandardCharsets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.kohsuke.stapler.DataBoundConstructor;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
public class JMeterCsvParser extends AbstractParser {
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public char delimiter;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int timestampIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int elapsedIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int responseCodeIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int successIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int urlIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int bytesIdx = -1;
@SuppressFBWarnings("PA_PUBLIC_PRIMITIVE_ATTRIBUTE")
public int sentBytesIdx = -1;
public JMeterCsvParser(String glob, String percentiles) {
this(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public JMeterCsvParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "JMeterCSV";
}
}
@Override
public String getDefaultGlobPattern() {
return "**/*.csv";
}
@Override
PerformanceReport parse(File reportFile) throws Exception {
clearDateFormat();
final PerformanceReport report = createPerformanceReport();
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
report.setReportFileName(reportFile.getName());
String[] header = null;
try (FileReader fr = new FileReader(reportFile, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(fr)) {
String line = reader.readLine();
if (line != null) {
header = readCSVHeader(line);
}
}
try (Reader fileReader = new InputStreamReader(new FileInputStream(reportFile), StandardCharsets.UTF_8)) {
parseCSV(fileReader, header, report);
}
return report;
}
protected void parseCSV(Reader in, String[] header, PerformanceReport report) throws IOException {
CSVFormat csvFormat = CSVFormat.Builder.create().setDelimiter(delimiter).setHeader(header).setQuote('"')
.setSkipHeaderRecord(true).build();
Iterable records = csvFormat.parse(in);
for (CSVRecord record : records) {
final HttpSample sample = getSample(record);
report.addSample(sample);
}
}
protected String[] readCSVHeader(String line) {
this.delimiter = lookingForDelimiter(line);
final String[] header = line.split(String.valueOf(delimiter));
for (int i = 0; i < header.length; i++) {
String field = header[i];
if ("timestamp".equalsIgnoreCase(field)) {
timestampIdx = i;
} else if ("elapsed".equalsIgnoreCase(field)) {
elapsedIdx = i;
} else if ("responseCode".equalsIgnoreCase(field)) {
responseCodeIdx = i;
} else if ("success".equalsIgnoreCase(field)) {
successIdx = i;
} else if ("bytes".equalsIgnoreCase(field)) {
bytesIdx = i;
} else if ("sentBytes".equalsIgnoreCase(field)) {
sentBytesIdx = i;
} else if ("URL".equalsIgnoreCase(field) && urlIdx < 0) {
urlIdx = i;
} else if ("label".equalsIgnoreCase(field) && urlIdx < 0) {
urlIdx = i;
}
}
if (timestampIdx < 0 || elapsedIdx < 0 || responseCodeIdx < 0
|| successIdx < 0 || urlIdx < 0 || bytesIdx < 0
// || sentBytesIdx < 0 // sentBytes was introduced in 3.1
) {
throw new IllegalStateException("Missing required column");
}
return header;
}
protected static char lookingForDelimiter(String line) {
for (char ch : line.toCharArray()) {
if (!Character.isLetter(ch)) {
return ch;
}
}
throw new IllegalStateException("Cannot find delimiter in header " + line);
}
/**
* Parses a single HttpSample instance from a single CSV Record.
*
* @param record csv record from report file (cannot be null).
* @return An sample instance (never null).
*/
private HttpSample getSample(CSVRecord record) {
final HttpSample sample = new HttpSample();
sample.setDate(parseTimestamp(record.get(timestampIdx)));
sample.setDuration(Long.parseLong(record.get(elapsedIdx)));
sample.setHttpCode(record.get(responseCodeIdx));
sample.setSuccessful(Boolean.parseBoolean(record.get(successIdx)));
long bytes = Long.parseLong(record.get(bytesIdx));
if (sentBytesIdx != -1) {
bytes += Long.parseLong(record.get(sentBytesIdx));
}
sample.setSizeInKb((double) bytes / 1024d);
sample.setUri(record.get(urlIdx));
return sample;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/JMeterParser.java
================================================
package hudson.plugins.performance.parsers;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import javax.xml.parsers.SAXParserFactory;
import org.kohsuke.stapler.DataBoundConstructor;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
/**
* Parser for JMeter.
*
* @author Kohsuke Kawaguchi
*/
public class JMeterParser extends AbstractParser {
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "JMeter";
}
}
public JMeterParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public JMeterParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Override
public String getDefaultGlobPattern() {
return "**/*.jtl";
}
PerformanceReport parse(File reportFile) throws Exception {
// JMeter stores either CSV or XML in .JTL files.
final boolean isXml = isXmlFile(reportFile);
if (isXml) {
return parseXml(reportFile);
} else {
return parseCsv(reportFile);
}
}
/**
* Utility method that checks if the provided file has XML content.
*
* This implementation looks for the first non-empty file. If an XML prolog appears there, this method returns true, otherwise false is returned.
*
* @param file File from which the content is to e analyzed. Cannot be null.
* @return true if the file content has been determined to be XML, otherwise false.
*/
public static boolean isXmlFile(File file) throws IOException {
try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(fr)) {
String line;
boolean isXml = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue; // skip empty lines.
}
if (line.toLowerCase().trim().startsWith("time, then parses it into a long.
*/
static long parseDuration(final String time) {
if (time == null || time.isEmpty()) {
return 0;
} else {
// don't want commas or else will break on parse.
final double duration = Double.parseDouble(time.replaceAll(",", ""));
return (long) (duration * 1000);
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/JmeterSummarizerParser.java
================================================
package hudson.plugins.performance.parsers;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.regex.Pattern;
import org.kohsuke.stapler.DataBoundConstructor;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
/**
* Parses JMeter Summarized results
*
* @author Agoley
*/
public class JmeterSummarizerParser extends AbstractParser {
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "JmeterSummarizer";
}
}
public JmeterSummarizerParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public JmeterSummarizerParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Override
public String getDefaultGlobPattern() {
return "**/*.log";
}
@Override
PerformanceReport parse(File reportFile) throws Exception {
clearDateFormat();
final PerformanceReport report = createPerformanceReport();
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
report.setReportFileName(reportFile.getName());
try (Scanner fileScanner = new Scanner(reportFile, StandardCharsets.UTF_8)){
String line;
String lastEqualsLine = null;
while (fileScanner.hasNextLine()) {
line = fileScanner.nextLine();
if (line.contains("=") && line.contains("Summariser:")) {
lastEqualsLine = line;
}
}
long reportSamples = Long.MIN_VALUE;
long reportAvg = Long.MIN_VALUE;
long reportMin = Long.MAX_VALUE;
long reportMax = Long.MIN_VALUE;
String reportErrorPercent = "";
if (lastEqualsLine != null) {
try (Scanner lineScanner = new Scanner(lastEqualsLine)) {
final Pattern delimiter = lineScanner.delimiter();
lineScanner.useDelimiter("INFO"); // as jmeter logs INFO mode
final HttpSample sample = new HttpSample();
final String dateString = lineScanner.next();
sample.setDate(parseTimestamp(dateString));
lineScanner.findInLine("Summariser:");
lineScanner.useDelimiter("\\=");
String key = lineScanner.next().trim();
lineScanner.useDelimiter(delimiter);
lineScanner.next();
reportSamples = lineScanner.nextLong();
sample.setSummarizerSamples(reportSamples); // set SamplesCount
sample.setSummarizer(true);
lineScanner.findInLine("Avg:"); // set response time
sample.setDuration(lineScanner.nextLong());
reportAvg = sample.getDuration();
sample.setSuccessful(true);
lineScanner.findInLine("Min:"); // set MIN
long sampleMin = lineScanner.nextLong();
sample.setSummarizerMin(sampleMin);
reportMin = Math.min(reportMin, sampleMin);
lineScanner.findInLine("Max:"); // set MAX
long sampleMax = lineScanner.nextLong();
sample.setSummarizerMax(sampleMax);
reportMax = Math.max(reportMax, sampleMax);
lineScanner.findInLine("Err:"); // set errors count
lineScanner.findInLine("\\("); // set errors count
lineScanner.useDelimiter("%");
reportErrorPercent = lineScanner.next();
sample.setSummarizerErrors(Float.parseFloat(reportErrorPercent));
// sample.setSummarizerErrors(
// Float.valueOf(scanner.next().replaceAll("[()%]","")));
sample.setUri(key);
report.addSample(sample);
}
}
report.setSummarizerSize(reportSamples);
report.setSummarizerAvg(reportAvg);
report.setSummarizerMin(reportMin);
report.setSummarizerMax(reportMax);
report.setSummarizerErrors(reportErrorPercent);
return report;
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/LoadRunnerParser.java
================================================
package hudson.plugins.performance.parsers;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Date;
import java.util.TimeZone;
/** Parser for LoadRunner Analysis results stored in an MS Access database (*.mdb file).
*
* Reference https://community.saas.hpe.com/t5/Performance-Center-Practitioners/Regarding-Event-meter-table-in-LoadRunner-session-MS-ACCESS/td-p/566738
*/
public class LoadRunnerParser extends AbstractParser {
private String resultQuery =
"select "+
" cast(([Start Time] + e.[End Time] - e.Value)*1000 as decimal) as timeStamp, "+
" cast(e.Value*1000 as decimal) as elapsed, "+
" [Event Name] as label, "+
" case [Transaction End Status] when 'Pass' then 'true' end as success "+
"from Event_meter e "+
"join Event_map on Event_map.[Event ID] = e.[Event ID] "+
"join TransactionEndStatus on TransactionEndStatus.Status1 = e.Status1 "+
"join Result on Result.[Result ID] = e.[Result ID]"+
"where [Event Type] = 'Transaction'";
public LoadRunnerParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public LoadRunnerParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "LoadRunner";
}
}
@Override
public String getDefaultGlobPattern() {
return "**/*.mdb";
}
protected String jdbcUrlForFile(File reportFile) throws ClassNotFoundException {
Class.forName("net.ucanaccess.jdbc.UcanaccessDriver");
return String.format("jdbc:ucanaccess://%s;mirrorFolder=java.io.tmpdir;immediatelyReleaseResources=true", reportFile.getAbsolutePath());
}
protected HttpSample getSample(ResultSet res) throws SQLException {
HttpSample sample = new HttpSample();
Date sampleTime = new Date(res.getLong(1));
// Fix LoadRunner times that are off by 1 hour when in DST:
if (TimeZone.getTimeZone(System.getProperty("user.timezone")).inDaylightTime(sampleTime)) {
sampleTime = new Date(res.getLong(1)-3600000L);
}
sample.setDate(sampleTime);
sample.setDuration(res.getLong(2));
sample.setUri(res.getString(3));
sample.setSuccessful(Boolean.parseBoolean(res.getString(4)));
return sample;
}
@Override
PerformanceReport parse(File reportFile) throws Exception {
final PerformanceReport report = createPerformanceReport();
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
report.setReportFileName(reportFile.getName());
try (Connection con = DriverManager.getConnection(jdbcUrlForFile(reportFile));
Statement stmt = con.createStatement();
ResultSet res = stmt.executeQuery(getResultQuery())) {
while (res.next()) {
report.addSample(getSample(res));
}
}
return report;
}
protected String getResultQuery() {
return resultQuery;
}
protected void setResultQuery(String resultQuery) {
this.resultQuery = resultQuery;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/LocustParser.java
================================================
package hudson.plugins.performance.parsers;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import org.kohsuke.stapler.DataBoundConstructor;
public class LocustParser extends AbstractParser {
enum ReportColumns {
Type(0), Name(1), Requests(2), Failures(3), Median(4),
Average(5), Min(6), Max(7), AvgContentSize(8), Rps(9);
int column;
ReportColumns(final int order) {
this.column = order;
}
int getColumn() {
return column;
}
}
public LocustParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public LocustParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "Locust";
}
}
@Override
PerformanceReport parse(final File reportFile) throws Exception {
PerformanceReport report = createPerformanceReport();
report.setReportFileName(reportFile.getName());
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
List reportData = getCsvData(reportFile);
final Date now = new Date();
for (CSVRecord record : reportData) {
String name = record.get(ReportColumns.Name.getColumn());
long average = Double.valueOf(record.get(ReportColumns.Average.getColumn())).longValue();
long min = Double.valueOf(record.get(ReportColumns.Min.getColumn())).longValue();
long max = Double.valueOf(record.get(ReportColumns.Max.getColumn())).longValue();
long failures = Double.valueOf(record.get(ReportColumns.Failures.getColumn())).longValue();
long success = Double.valueOf(record.get(ReportColumns.Requests.getColumn())).longValue();
long errors = (long) ((double) failures / success);
long avgContentSize = Double.valueOf(record.get(ReportColumns.AvgContentSize.getColumn())).longValue();
if (name.equals("Aggregated")) {
report.setSummarizerSize(reportData.size() - 1);
report.setSummarizerAvg(average);
report.setSummarizerMin(min);
report.setSummarizerMax(max);
report.setSummarizerErrors(Float.toString(errors));
} else {
HttpSample sample = new HttpSample();
sample.setSuccessful(failures == 0);
sample.setSummarizer(true);
sample.setUri(name);
sample.setSummarizerMax(max);
sample.setSummarizerMin(min);
sample.setDuration(average);
sample.setSummarizerSamples(success);
sample.setSummarizerErrors(errors);
sample.setSizeInKb(avgContentSize * success);
sample.setDate(now);
report.addSample(sample);
}
}
return report;
}
List getCsvData(final File reportFile) {
List records = null;
try (Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream(reportFile), StandardCharsets.UTF_8));
CSVParser csvParser = new CSVParser(reader, CSVFormat.Builder.create(CSVFormat.DEFAULT).setHeader().build())) {
records = csvParser.getRecords();
} catch (IOException e) {
e.printStackTrace();
}
return records;
}
@Override
public String getDefaultGlobPattern() {
return "**/*_stats.csv";
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/ParserDetector.java
================================================
package hudson.plugins.performance.parsers;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import com.google.common.annotations.VisibleForTesting;
/**
* Auto-detect parser for file
*/
public class ParserDetector {
private ParserDetector() {
super();
}
/**
* Detect report file type using file content.
* @return report file type.
*/
public static String detect(String reportPath) throws IOException {
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(reportPath)), StandardCharsets.UTF_8))) {
String line = reader.readLine();
if (line == null) {
throw new IllegalArgumentException("File " + reportPath + " is empty");
}
if (line.startsWith(" pattern.length() &&
pattern.equals(line.substring(0, pattern.length()));
}
/**
* Detect XML report type using the search of the first opening xml-tag.
* - JMETER;
* - JUNIT;
* - TAURUS.
*/
private static String detectXMLFileType(String reportPath) throws IOException {
try (InputStream in = new FileInputStream(reportPath)) {
return detectXMLFileType(in);
} catch (Exception ex) {
throw new IllegalStateException("XML parsing error: ", ex);
}
}
@VisibleForTesting
protected static String detectXMLFileType(final InputStream in) throws XMLStreamException {
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLEventReader eventReader = inputFactory.createXMLEventReader(in);
while (eventReader.hasNext()) {
XMLEvent event = eventReader.nextEvent();
if (event.isStartElement()) {
StartElement startElement = event.asStartElement();
String name = startElement.getName().getLocalPart();
switch (name) {
case "testResults":
return JMeterParser.class.getSimpleName();
case "testsuite":
case "testsuites":
return JUnitParser.class.getSimpleName();
case "FinalStatus":
return TaurusParser.class.getSimpleName();
default:
throw new IllegalArgumentException("Unknown xml file format");
}
}
}
throw new IllegalStateException("XML parsing error: no start element");
}
/**
* Detect Locust report type using verification of csv header. Header names and order is asserted.
* @param line - single report file line
* @return true if Locust expected header found
*/
private static boolean isLocustFileType(String line) {
String[] fileLineHeader = line.replaceAll("\"", "").split(",");
String[] expectedHeaderFields = new String[]{"Type", "Name", "Request Count", "Failure Count",
"Median Response Time", "Average Response Time", "Min Response Time", "Max Response Time",
"Average Content Size", "Requests/s"};
return (Arrays.asList(fileLineHeader).containsAll(Arrays.asList(expectedHeaderFields)));
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/ParserFactory.java
================================================
package hudson.plugins.performance.parsers;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.Run;
public class ParserFactory {
private static final Logger LOGGER = Logger.getLogger(ParserFactory.class.getName());
protected static final Map defaultGlobPatterns = Collections.synchronizedMap(new HashMap());
private static final String TEMP_FOLDER = "/temp/";
static {
defaultGlobPatterns.put("parrot-server-stats.log", IagoParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.csv", JMeterCsvParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.jtl", JMeterParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.log", JmeterSummarizerParser.class.getSimpleName());
defaultGlobPatterns.put("**/TEST-*.xml", JUnitParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.xml", TaurusParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.wrk", WrkSummarizerParser.class.getSimpleName());
defaultGlobPatterns.put("**/*.mdb", LoadRunnerParser.class.getSimpleName());
defaultGlobPatterns.put("**/*_stats.csv", LocustParser.class.getSimpleName());
}
public static List getParser(Run, ?> build, FilePath workspace, PrintStream logger, String glob,
EnvVars env, String percentiles, String filterRegex) throws IOException, InterruptedException {
String expandGlob = env.expand(glob);
if (defaultGlobPatterns.containsKey(expandGlob)) {
return Collections.singletonList(getParser(defaultGlobPatterns.get(expandGlob), expandGlob, percentiles, filterRegex));
}
File path = new File(expandGlob);
return path.isAbsolute() ? getParserWithAbsolutePath(build, workspace, logger, path, percentiles, filterRegex) :
getParserWithRelativePath(build, workspace, logger, expandGlob, percentiles, filterRegex);
}
private static List getParserWithRelativePath(Run, ?> build, FilePath workspace, PrintStream logger, String glob, String percentiles, String filterRegex) throws IOException, InterruptedException {
List result = getParserUsingAntPatternRelativePath(build, workspace, logger, glob, percentiles, filterRegex);
if (result != null && !result.isEmpty()) {
return result;
}
File report = new File(workspace.getRemote() + '/' + glob);
if (!report.exists()) {
// if report on remote slave
FilePath localReport = new FilePath(new File(build.getRootDir(), TEMP_FOLDER + glob));
localReport.copyFrom(new FilePath(workspace, glob));
return Collections.singletonList(getParser(ParserDetector.detect(localReport.getRemote()), glob, percentiles, filterRegex));
}
return Collections.singletonList(getParser(ParserDetector.detect(workspace.getRemote() + '/' + glob), workspace.getRemote() + '/' + glob, percentiles, filterRegex));
}
private static List getParserUsingAntPatternRelativePath(Run, ?> build, FilePath workspace, PrintStream logger,
String glob, String percentiles, String filterRegex) throws InterruptedException {
try {
FilePath[] pathList = workspace.list(glob);
List result = new ArrayList<>();
for (FilePath src : pathList) {
// copy file (it can be on remote slave) to "../build/../temp/" folder
final File localReport = new File(build.getRootDir(), TEMP_FOLDER + src.getName());
if (src.isDirectory()) {
logger.println("Performance: File '" + src.getName() + "' is a directory, not a Performance Report");
continue;
}
src.copyTo(new FilePath(localReport));
result.add(getParser(ParserDetector.detect(localReport.getPath()), glob, percentiles, filterRegex));
}
return result;
} catch (IOException ignored) {
LOGGER.log(Level.FINE, "Cannot find report file using Ant pattern", ignored);
}
return Collections.emptyList();
}
private static List getParserWithAbsolutePath(Run, ?> build, FilePath workspace, PrintStream logger, File path, String percentiles, String filterRegex) throws IOException, InterruptedException {
List result = getParserUsingAntPatternAbsolutePath(build, workspace, logger, path, percentiles, filterRegex);
if (result != null && !result.isEmpty()) {
return result;
}
if (!path.exists()) {
// if report on remote slave
FilePath localReport = new FilePath(new File(build.getRootDir(), TEMP_FOLDER + path.getName()));
localReport.copyFrom(new FilePath(workspace.getChannel(), path.getAbsolutePath()));
return Collections.singletonList(getParser(ParserDetector.detect(localReport.getRemote()), path.getName(), percentiles, filterRegex));
}
return Collections.singletonList(getParser(ParserDetector.detect(path.getAbsolutePath()), path.getAbsolutePath(), percentiles, filterRegex));
}
private static List getParserUsingAntPatternAbsolutePath(Run, ?> build, FilePath wsp, PrintStream logger, File path, String percentiles, String filterRegex) throws InterruptedException {
try {
File parent = path.getParentFile();
FilePath workspace = new FilePath(wsp.getChannel(), parent.getAbsolutePath());
while (!workspace.exists()) {
parent = parent.getParentFile();
if (parent != null) {
workspace = new FilePath(wsp.getChannel(), parent.getAbsolutePath());
} else {
return Collections.emptyList();
}
}
String glob = path.getAbsolutePath().substring(parent.getAbsolutePath().length() + 1);
FilePath[] pathList = workspace.list(glob);
List parsers = new ArrayList<>();
for (FilePath src : pathList) {
// copy file (it can be on remote slave) to "../build/../temp/" folder
final File localReport = new File(build.getRootDir(), TEMP_FOLDER + src.getName());
if (src.isDirectory()) {
logger.println("Performance: File '" + src.getName() + "' is a directory, not a Performance Report");
continue;
}
src.copyTo(new FilePath(localReport));
parsers.add(getParser(ParserDetector.detect(localReport.getPath()), localReport.getPath(), percentiles, filterRegex));
}
return parsers;
} catch (IOException ignored) {
LOGGER.log(Level.FINE, "Cannot find report file using Ant pattern", ignored);
}
return Collections.emptyList();
}
private static PerformanceReportParser getParser(String parserName, String glob, String percentiles, String filterRegex) {
if (parserName.equals(JMeterParser.class.getSimpleName())) {
return new JMeterParser(glob, percentiles, filterRegex);
} else if (parserName.equals(JMeterCsvParser.class.getSimpleName())) {
return new JMeterCsvParser(glob, percentiles, filterRegex);
} else if (parserName.equals(JUnitParser.class.getSimpleName())) {
return new JUnitParser(glob, percentiles, filterRegex);
} else if (parserName.equals(TaurusParser.class.getSimpleName())) {
return new TaurusParser(glob, percentiles, filterRegex);
} else if (parserName.equals(WrkSummarizerParser.class.getSimpleName())) {
return new WrkSummarizerParser(glob, percentiles, filterRegex);
} else if (parserName.equals(JmeterSummarizerParser.class.getSimpleName())) {
return new JmeterSummarizerParser(glob, percentiles, filterRegex);
} else if (parserName.equals(IagoParser.class.getSimpleName())) {
return new IagoParser(glob, percentiles, filterRegex);
} else if (parserName.equals(LoadRunnerParser.class.getSimpleName())) {
return new LoadRunnerParser(glob, percentiles, filterRegex);
} else if (parserName.equals(LocustParser.class.getSimpleName())) {
return new LocustParser(glob, percentiles, filterRegex);
} else {
throw new IllegalArgumentException("Unknown parser type: " + parserName);
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/PerformanceReportParser.java
================================================
package hudson.plugins.performance.parsers;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Describable;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.performance.PerformancePublisher;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import jenkins.model.Jenkins;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
/**
* Parses performance result files into {@link PerformanceReport}s. This object
* is persisted with {@link PerformancePublisher} into the project
* configuration.
*
* Subtypes can define additional parser-specific parameters as instance fields.
*
* @author Kohsuke Kawaguchi
*/
public abstract class PerformanceReportParser implements
Describable, ExtensionPoint {
/**
* GLOB patterns that specify the performance report.
*/
public final String glob;
public String reportURL;
/**
* Exclude response time of errored samples
*/
protected boolean excludeResponseTime;
protected boolean showTrendGraphs;
protected int baselineBuild;
protected PerformanceReportParser(String glob) {
this.glob = (glob == null || glob.length() == 0) ? getDefaultGlobPattern()
: glob;
}
public PerformanceReportParserDescriptor getDescriptor() {
return (PerformanceReportParserDescriptor) Jenkins.get()
.getDescriptorOrDie(getClass());
}
/**
* Parses the specified reports into {@link PerformanceReport}s.
*/
public abstract Collection parse(
Run, ?> build, Collection reports, TaskListener listener)
throws IOException;
public abstract String getDefaultGlobPattern();
/**
* All registered implementations.
*/
public static ExtensionList all() {
return Jenkins.get().getExtensionList(PerformanceReportParser.class);
}
public String getReportName() {
return this.getClass().getName().replaceAll("^.*\\.(\\w+)Parser.*$", "$1");
}
public boolean isExcludeResponseTime() {
return excludeResponseTime;
}
public void setExcludeResponseTime(boolean excludeResponseTime) {
this.excludeResponseTime = excludeResponseTime;
}
public boolean isShowTrendGraphs() {
return showTrendGraphs;
}
public void setShowTrendGraphs(boolean showTrendGraphs) {
this.showTrendGraphs = showTrendGraphs;
}
public void setBaselineBuild(int baselineBuild) {
this.baselineBuild = baselineBuild;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/TaurusParser.java
================================================
package hudson.plugins.performance.parsers;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
import hudson.plugins.performance.data.TaurusFinalStats;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import org.kohsuke.stapler.DataBoundConstructor;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
/**
* Parser for Taurus
*/
public class TaurusParser extends AbstractParser {
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "Taurus";
}
}
public TaurusParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public TaurusParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Override
public String getDefaultGlobPattern() {
return "**/*.xml";
}
@Override
protected PerformanceReport parse(File reportFile) throws Exception {
return readFromXML(reportFile);
}
private PerformanceReport readFromXML(File reportFile) throws Exception {
final PerformanceReport report = createPerformanceReport();
report.setExcludeResponseTime(excludeResponseTime);
report.setShowTrendGraphs(showTrendGraphs);
report.setReportFileName(reportFile.getName());
DocumentBuilderFactory dbFactory
= DocumentBuilderFactory.newInstance();
dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(reportFile);
doc.getDocumentElement().normalize();
Node urlNode = doc.getElementsByTagName("ReportURL").item(0);
if (urlNode != null) {
reportURL = urlNode.getTextContent();
}
Node testDurationNode = doc.getElementsByTagName("TestDuration").item(0);
Double testDuration = null;
if (testDurationNode != null) {
testDuration = Double.parseDouble(testDurationNode.getTextContent()) * 1000; // to ms
}
NodeList nList = doc.getElementsByTagName("Group");
for (int temp = 0; temp < nList.getLength(); temp++) {
Node nNode = nList.item(temp);
TaurusFinalStats statusReport = getTaurusFinalStats((Element) nNode);
statusReport.setTestDuration(testDuration);
if (!((Element) nNode).getAttribute("label").isEmpty()) {
statusReport.setLabel(((Element) nNode).getAttribute("label"));
report.addSample(statusReport, false);
} else {
statusReport.setLabel(TaurusFinalStats.DEFAULT_TAURUS_LABEL);
report.addSample(statusReport, true);
}
}
return report;
}
@SuppressFBWarnings("DM_BOXED_PRIMITIVE_FOR_PARSING")
private TaurusFinalStats getTaurusFinalStats(Element group) {
final TaurusFinalStats report = new TaurusFinalStats();
report.setBytes(Long.parseLong(getValueAttribute("bytes", group)));
report.setFail(Integer.parseInt(getValueAttribute("fail", group)));
report.setSucc(Integer.parseInt(getValueAttribute("succ", group)));
if (group.getElementsByTagName("throughput").getLength() > 0) {
report.setThroughput(Long.parseLong(getValueAttribute("throughput", group)));
}
report.setAverageResponseTime(Double.parseDouble(getValueAttribute("avg_rt", group)) * 1000); // to ms
NodeList perc = group.getElementsByTagName("perc");
for (int i = 0; i < perc.getLength(); i++) {
Node nNode = perc.item(i);
String attributeParam = ((Element) nNode).getAttribute("param");
Double valueInMs = Double.valueOf(((Element) nNode).getAttribute("value")) * 1000;
if ("50.0".equals(attributeParam)) {
report.setPerc50(valueInMs); // to ms
} else if ("90.0".equals(attributeParam)) {
report.setPerc90(valueInMs); // to ms
} else if ("95.0".equals(attributeParam)) {
report.setPerc95(valueInMs); // to ms
} else if ("0.0".equals(attributeParam)) {
report.setPerc0(valueInMs); // to ms
} else if ("100.0".equals(attributeParam)) {
report.setPerc100(valueInMs); // to ms
}
}
return report;
}
private String getValueAttribute(String elementName, Element group) {
return ((Element) group.getElementsByTagName(elementName).item(0)).getAttribute("value");
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/parsers/WrkSummarizerParser.java
================================================
package hudson.plugins.performance.parsers;
import hudson.Extension;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.descriptors.PerformanceReportParserDescriptor;
import hudson.plugins.performance.reports.PerformanceReport;
import hudson.plugins.performance.tools.SafeMaths;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Scanner;
/**
* Parser for wrk (https://github.com/wg/wrk)
*
* Note that Wrk does not produce request-level data, and can only be processed
* in it's summarized form (unless extended to do otherwise).
*
* @author John Murray me@johnmurray.io
*/
public class WrkSummarizerParser extends AbstractParser {
private enum LineType {
RUNNING,
THREAD_CONN_COUNT,
OUTPUT_HEADER,
LATENCY_DIST,
LATENCY_DIST_BUCKET_HEADER,
LATENCY_DIST_BUCKET,
REQ_SEC_DIST,
SUMMARY,
REQ_SEC,
TRANSFER_SEC,
ERROR_COUNT,
UNKNOWN
}
public enum TimeUnit {
MILLISECOND(1),
SECOND(1000),
MINUTE(1000 * 60),
HOUR(1000 * 60 * 60);
private final int factor;
TimeUnit(int factor) {
this.factor = factor;
}
public int getFactor() {
return this.factor;
}
}
@Extension
public static class DescriptorImpl extends PerformanceReportParserDescriptor {
@Override
public String getDisplayName() {
return "wrk";
}
}
public WrkSummarizerParser(String glob, String percentiles) {
super(glob, percentiles, PerformanceReport.INCLUDE_ALL);
}
@DataBoundConstructor
public WrkSummarizerParser(String glob, String percentiles, String filterRegex) {
super(glob, percentiles, filterRegex);
}
@Override
public String getDefaultGlobPattern() {
return "**/*.wrk";
}
@Override
PerformanceReport parse(File reportFile) throws Exception {
final PerformanceReport r = createPerformanceReport();
r.setExcludeResponseTime(excludeResponseTime);
r.setShowTrendGraphs(showTrendGraphs);
r.setReportFileName(reportFile.getName());
Scanner s = null;
try {
s = new Scanner(reportFile, StandardCharsets.UTF_8);
HttpSample sample = new HttpSample();
while (s.hasNextLine()) {
Scanner scanner = null;
try {
String line = s.nextLine();
scanner = new Scanner(line.toLowerCase().replaceAll(
"(\\d)s|ms|%|mb|kb(\\b)", "$1$2"));
String firstToken = scanner.next();
String secondToken = scanner.next();
switch (determineLineType(firstToken, secondToken)) {
case RUNNING:
// extract URI
scanner.next();
scanner.next();
String uri = scanner.next();
sample.setUri(uri);
break;
case LATENCY_DIST:
Scanner latencyScanner = new Scanner(line.toLowerCase());
latencyScanner.next(); // header (skip)
long latencyAvg = getTime(latencyScanner.next(),
TimeUnit.MILLISECOND);
latencyScanner.next(); // stdDev (skipping)
long latencyMax = getTime(latencyScanner.next(),
TimeUnit.MILLISECOND);
sample.setDuration(latencyAvg);
sample.setSummarizerMax(latencyMax);
break;
case REQ_SEC_DIST:
// float reqSecAvg = Float.parseFloat(secondToken);
// float reqSecStdDev = scanner.nextFloat();
// float reqSecMax = scanner.nextFloat();
// float reqSecPercentInOneStdDev = scanner.nextFloat();
break;
case SUMMARY:
long totalReq = Long.parseLong(firstToken);
Scanner summaryScanner = new Scanner(line.toLowerCase());
summaryScanner.next();
summaryScanner.next();
summaryScanner.next();
// long totalTime = getTime(summaryScanner.next(), logger,
// TimeUnit.SECOND);
sample.setSummarizer(true);
sample.setSummarizerSamples(totalReq);
summaryScanner.close();
break;
case ERROR_COUNT:
scanner.next();
scanner.next();
int numErrors = scanner.nextInt();
sample.setSummarizerErrors(numErrors);
break;
case REQ_SEC:
case TRANSFER_SEC:
// not currently used by performance-plugin
break;
case THREAD_CONN_COUNT:
case OUTPUT_HEADER:
case LATENCY_DIST_BUCKET_HEADER:
case LATENCY_DIST_BUCKET:
case UNKNOWN:
// do nothing, don't need output
break;
}
} finally {
if (scanner != null)
scanner.close();
}
}
sample.setSuccessful(true);
sample.setDate(new Date());
r.addSample(sample);
} finally {
if (s != null)
s.close();
}
return r;
}
/**
* Given a time string (eg: 0ms, 1m, 2s, 3h, etc.) parse and yield the time in
* a specified time unit (millisecond, second, minute, hour)
*
* If no result can be returned, a 0 value will result and any errors
* encountered will be logged.
*
* @param timeString String representation from `wrk` command-output
* @param tu Time unit to return time string in
* @return Time in seconds, as parsed from input
*/
public long getTime(String timeString, TimeUnit tu) {
double factor = 0;
timeString = timeString.trim().replaceAll("[^\\d\\.smh]", "");
String timeUnitString = timeString.replaceAll("[\\d\\.]", "");
String timeValueString = timeString.replaceAll("[smh]", "");
/*
* Calculate 'factor' so that we can get the input time in ms (eg: 5m =
* 300000, 3s = 3000)
*/
if ("ms".equals(timeUnitString)) {
factor = 1;
} else if ("s".equals(timeUnitString)) {
factor = 1000;
} else if ("m".equals(timeUnitString)) {
factor = 1000 * 60d;
} else if ("h".equals(timeUnitString)) {
factor = 1000 * 60 * 60d;
}
double timeValue = Double.parseDouble(timeValueString);
double timeInMilliSeconds = timeValue * factor;
double timeInReturnFormat = SafeMaths.safeDivide(timeInMilliSeconds, tu.getFactor());
return (int) Math.floor(timeInReturnFormat);
}
/**
* Given the first couple of tokens from a line, determine what type of line
* it is (by returning a LineType) so that is can be processed accordingly.
*
* @param t1 First token in the processed line
* @param t2 Second token in the processed line
* @return LineType indicating how the rest of the line should be processed
*/
public LineType determineLineType(String t1, String t2) {
if ("running".equals(t1)) {
return LineType.RUNNING;
} else if ("thread".equals(t1)) {
return LineType.OUTPUT_HEADER;
} else if ("latency".equals(t1)) {
if ("distribution".equals(t2))
return LineType.LATENCY_DIST_BUCKET_HEADER;
else
return LineType.LATENCY_DIST;
} else if ("req/sec".equals(t1)) {
return LineType.REQ_SEC_DIST;
} else if ("requests/sec:".equals(t1)) {
return LineType.REQ_SEC;
} else if ("transfer/sec:".equals(t1)) {
return LineType.TRANSFER_SEC;
} else if ("non-2xx".equals(t1)) {
return LineType.ERROR_COUNT;
} else {
try {
Long.parseLong(t1);
if ("threads".equals(t2)) {
return LineType.THREAD_CONN_COUNT;
} else if ("requests".equals(t2)) {
return LineType.SUMMARY;
} else {
try {
Float.parseFloat(t2);
return LineType.LATENCY_DIST_BUCKET;
} catch (NumberFormatException e) {
return LineType.UNKNOWN;
}
}
} catch (NumberFormatException e) {
return LineType.UNKNOWN;
}
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/reports/AbstractReport.java
================================================
package hudson.plugins.performance.reports;
import hudson.plugins.performance.data.HttpSample;
import org.kohsuke.stapler.Stapler;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Abstract class for classes with samplesCount, error, mean, average, 90 line, 95 line, min and max attributes
*/
public abstract class AbstractReport {
public static final Logger LOGGER = Logger.getLogger(AbstractReport.class.getName());
public static final double ZERO_PERCENT = 0;
public static final double ONE_HUNDRED_PERCENT = 100;
public static final double NINETY_PERCENT = 90;
public static final double NINETY_FIVE_PERCENT = 95;
public static final double FIFTY_PERCENT = 50;
public static final String DEFAULT_PERCENTILES = "0,50,90,95,100";
protected final ThreadLocal percentFormat;
protected final ThreadLocal dataFormat; // three decimals
protected Map percentilesValues = new TreeMap<>();
protected Map percentilesDiffValues = new TreeMap<>();
protected boolean isCalculatedPercentilesValues = false;
/**
* Exclude response time of errored samples
*/
protected boolean excludeResponseTime;
protected boolean showTrendGraphs;
public abstract int countErrors();
public abstract double errorPercent();
public abstract void calculatePercentiles();
public abstract void calculateDiffPercentiles();
public AbstractReport() {
final Locale useThisLocale = (Stapler.getCurrentRequest() != null) ? Stapler.getCurrentRequest().getLocale() : Locale.getDefault();
percentFormat = new ThreadLocal() {
@Override
protected DecimalFormat initialValue() {
return new DecimalFormat("0.0", DecimalFormatSymbols.getInstance(useThisLocale));
}
};
dataFormat = new ThreadLocal() {
@Override
protected DecimalFormat initialValue() {
return new DecimalFormat("#,###", DecimalFormatSymbols.getInstance(useThisLocale));
}
};
}
public String errorPercentFormated() {
Stapler.getCurrentRequest().getLocale();
return percentFormat.get().format(errorPercent());
}
protected void checkPercentileAndSet(Double key, Long value) {
if (value != null) {
percentilesValues.put(key, value);
isCalculatedPercentilesValues = true;
}
}
protected List parsePercentiles(String percentiles) {
final List res = new ArrayList<>();
if (percentiles != null && !percentiles.isBlank()) {
String[] percs = percentiles.split(",");
for (String perc : percs) {
try {
res.add(Double.parseDouble(perc));
} catch (NumberFormatException ex) {
LOGGER.log(Level.WARNING, "Cannot parse percentile value " + perc);
}
}
}
return res;
}
public abstract long getAverage();
public String getAverageFormated() {
return dataFormat.get().format(getAverage());
}
public abstract long getMedian();
public String getMeanFormated() {
return dataFormat.get().format(getMedian());
}
public abstract long get90Line();
public String get90LineFormated() {
return dataFormat.get().format(get90Line());
}
public abstract long get95Line();
public String get95LineFormated() {
return dataFormat.get().format(get95Line());
}
public abstract long getMax();
public String getMaxFormated() {
return dataFormat.get().format(getMax());
}
public abstract long getMin();
public abstract int samplesCount();
public abstract String getHttpCode();
public abstract long getAverageDiff();
public abstract long getMedianDiff();
public abstract long get90LineDiff();
public abstract long get95LineDiff();
public abstract double getErrorPercentDiff();
public abstract String getLastBuildHttpCodeIfChanged();
public abstract int getSamplesCountDiff();
public boolean isExcludeResponseTime() {
return excludeResponseTime;
}
public void setExcludeResponseTime(boolean excludeResponseTime) {
this.excludeResponseTime = excludeResponseTime;
}
public boolean isShowTrendGraphs() {
return showTrendGraphs;
}
public void setShowTrendGraphs(boolean showTrendGraphs) {
this.showTrendGraphs = showTrendGraphs;
}
protected boolean isIncludeResponseTime(HttpSample sample) {
return !(sample.isFailed() && excludeResponseTime && !sample.isSummarizer());
}
public Map getPercentilesValues() {
if (!isCalculatedPercentilesValues) {
calculatePercentiles();
calculateDiffPercentiles();
}
return percentilesValues;
}
public Map getPercentilesDiffValues() {
if (!isCalculatedPercentilesValues) {
calculatePercentiles();
calculateDiffPercentiles();
}
return percentilesDiffValues;
}
public String getPercentileLabel(Double perc) {
if (perc == 0.0) {
return "Min(ms)";
} else if (perc == 50.0) {
return "Median(ms)";
} else if (perc == 100.0) {
return "Max(ms)";
} else {
return "Line " + perc + "(ms)";
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/reports/ConstraintReport.java
================================================
package hudson.plugins.performance.reports;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import hudson.plugins.performance.constraints.AbsoluteConstraint;
import hudson.plugins.performance.constraints.AbstractConstraint;
import hudson.plugins.performance.constraints.ConstraintEvaluation;
import hudson.plugins.performance.constraints.RelativeConstraint;
import jenkins.model.Jenkins;
/**
* Creates a report of the constraint evaluation and stores it into a consecutive log file, a build
* environment variable and prints it to the Jenkins console output.
*
* @author Rene Kugel
*/
public class ConstraintReport {
/**
* Log file that is created and the log is printed to
*/
private File performanceLog;
/**
* Newly created build object. Used to determine build number and build date
*/
private Run, ?> newBuild;
/**
* Number of the build
*/
private int buildNumber;
/**
* Date when the build was started
*/
private Calendar buildDate;
/**
* Result of the build
*/
private Result buildResult;
/**
* Link to the build with the build number
*/
private String linkToBuild;
/**
* Number of all constraints in this build
*/
private short allConstraints = 0;
/**
* Number of all relative constraints in this build
*/
private short relativeConstraints = 0;
/**
* Number of all absolute constraints in this build
*/
private short absoluteConstraints = 0;
/**
* Number of all successful constraints in this build
*/
private short successfulConstraints = 0;
/**
* Number of all violated constraints in this build
*/
private short violatedConstraints = 0;
/**
* Number of all violated constraints with the escalation level INFORMATION in this build
*/
private short violatedInformation = 0;
/**
* Number of all violated constraints with the escalation level WARNING in this build
*/
private short violatedUnstable = 0;
/**
* Number of all violated constraints with the escalation level ERROR in this build
*/
private short violatedError = 0;
/**
* Logger message for console output
*/
private String loggerMsg;
/**
* Logger message for log file and environment variable
*/
private String loggerMsgAdv;
/**
* Buld result in JUnit report format
*/
private String junitReport;
public ConstraintReport(ArrayList ceList, Run, ?> globBuild, boolean persistConstraintLog) throws IOException {
this.newBuild = globBuild;
this.createMetaData(ceList, newBuild);
this.createLoggerMsg(ceList);
this.createLoggerMsgAdv();
this.writeResultsToEnvVar();
if (persistConstraintLog) {
this.writeResultsToFile();
}
this.junitReport = this.createJunitReport(ceList);
}
/**
* Creates the head data for the report
*
* @param ceList ArrayList of evaluated constraints
* @param newBuild Newly created build
*/
private void createMetaData(ArrayList ceList, Run, ?> newBuild) {
this.buildNumber = newBuild.getNumber();
this.buildDate = newBuild.getTimestamp();
this.buildResult = determineBuildResult(ceList);
/*
* Jenkins cannot reliable resolve it's own root URL unless it is set in the Jenkins System
* Configuration. This will cause that getRootUrl() returns null
*/
if (Jenkins.get().getRootUrl() == null) {
this.linkToBuild = "Could not resolve URL - Please set the root URL in the Jenkins System Configuration";
} else {
this.linkToBuild = Jenkins.get().getRootUrl() + newBuild.getUrl();
}
for (ConstraintEvaluation ce : ceList) {
if (ce.getAbstractConstraint() instanceof AbsoluteConstraint) {
this.allConstraints++;
this.absoluteConstraints++;
if (ce.getAbstractConstraint().getSuccess()) {
this.successfulConstraints++;
} else {
this.violatedConstraints++;
switch (ce.getAbstractConstraint().getEscalationLevel().ordinal()) {
case 0:
this.violatedInformation++;
break;
case 1:
this.violatedUnstable++;
break;
case 2:
this.violatedError++;
break;
default:
break;
}
}
} else if (ce.getAbstractConstraint() instanceof RelativeConstraint) {
this.allConstraints++;
this.relativeConstraints++;
if (ce.getAbstractConstraint().getSuccess()) {
this.successfulConstraints++;
} else {
this.violatedConstraints++;
switch (ce.getAbstractConstraint().getEscalationLevel().ordinal()) {
case 0:
this.violatedInformation++;
break;
case 1:
this.violatedUnstable++;
break;
case 2:
this.violatedError++;
break;
default:
break;
}
}
}
}
}
/**
* Determines the build result based on the violated constraint with the highest escalation
* level
*
* @param ceList ArrayList of evaluated constraints
* @return The determined build result. The build will be marked based on this result: SUCCESS,
* UNSTABLE, FAILURE
*/
private Result determineBuildResult(ArrayList ceList) {
int highestViolatedEscalation = 0;
for (ConstraintEvaluation ce : ceList) {
if (ce.getAbstractConstraint().getEscalationLevel().ordinal() > highestViolatedEscalation && !ce.getAbstractConstraint().getSuccess()) {
highestViolatedEscalation = ce.getAbstractConstraint().getEscalationLevel().ordinal();
}
}
switch (highestViolatedEscalation) {
case 0:
return Result.SUCCESS;
case 1:
return Result.UNSTABLE;
case 2:
return Result.FAILURE;
default:
return Result.FAILURE;
}
}
/**
* Creates the body data for the report.
*
* @param ceList ArrayList of evaluated constraints
*/
private void createLoggerMsg(ArrayList ceList) {
loggerMsg = "----------------------------------------------------------- \n";
if (relativeConstraints == 0) {
loggerMsg += "There are no relative constraints to evaluate! \n-------------- \n";
} else {
loggerMsg += "Evaluating all relative constraints! \n-------------- \n";
for (ConstraintEvaluation ce : ceList) {
if (ce.getAbstractConstraint() instanceof RelativeConstraint) {
loggerMsg += ce.getAbstractConstraint().getResultMessage() + "\n-------------- \n";
}
}
}
if (absoluteConstraints == 0) {
loggerMsg += "There are no absolute constraints to evaluate! \n-------------- \n";
} else {
loggerMsg += "Evaluating all absolute constraints! \n-------------- \n";
for (ConstraintEvaluation ce : ceList) {
if (ce.getAbstractConstraint() instanceof AbsoluteConstraint) {
loggerMsg += ce.getAbstractConstraint().getResultMessage() + "\n-------------- \n";
}
}
}
if (violatedConstraints == 0) {
loggerMsg += "There were no failing Constraints! The build will be marked as SUCCESS";
} else if (buildResult.equals(Result.SUCCESS)) {
loggerMsg += "The highest escalation: Information! The build will be marked as SUCCESS";
} else if (buildResult.equals(Result.UNSTABLE)) {
loggerMsg += "The highest escalation: Warning! The build will be marked as UNSTABLE";
} else if (buildResult.equals(Result.FAILURE)) {
loggerMsg += "The highest escalation: Error! The build will be marked as FAILURE";
}
loggerMsg += "\n";
if (violatedConstraints == 0)
return;
int maxUriColumnWidth = 8; // header column widths
int maxReportColumnWidth = 6;
for (ConstraintEvaluation ce : ceList) {
AbstractConstraint c = ce.getAbstractConstraint();
maxUriColumnWidth = Math.max(c.isSpecifiedTestCase() ? c.getTestCaseBlock().getTestCase().length() : 0, maxUriColumnWidth);
maxReportColumnWidth = Math.max(c.getRelatedPerfReport().length(), maxReportColumnWidth);
}
String logFormat = "%1$-"+maxReportColumnWidth+"s %2$-"+maxUriColumnWidth+"s %3$-10s %4$-20s %5$10s %6$-20s\n";
loggerMsg += "\nSummary of failed constraints:\n"+
String.format(logFormat, "Report", "Testcase", "Metric", "Operator", "Value", "Level");
for (ConstraintEvaluation ce : ceList) {
AbstractConstraint c = ce.getAbstractConstraint();
if (!c.getSuccess()) {
loggerMsg += String.format(logFormat,
c.getRelatedPerfReport(),
c.isSpecifiedTestCase() ? c.getTestCaseBlock().getTestCase() : AbstractConstraint.ANY,
c.getMeteredValue().toString(),
c.getOperator().toString(),
c instanceof RelativeConstraint ? String.format("%9.3f%%", ((RelativeConstraint)c).getTolerance())
: String.format("%10d", ((AbsoluteConstraint)c).getValue()),
c.getEscalationLevel().toString());
}
}
loggerMsg += "\n";
}
/**
* Concatenates the body and the head of the message
*/
private void createLoggerMsgAdv() {
loggerMsgAdv = "----------------------------------------------------------- \n" + "Build Number: #" + this.getBuildNumber() + "\n" + "Build Date: " + this.getBuildDate().getTime() + "\n"
+ "Build State: " + this.getBuildResult().toString() + "\n" + "Link to build: " + this.linkToBuild + "\n" + "-------------- \n" + "Number of all constraints: "
+ this.getAllConstraints() + "\n" + "Relative constraints: " + this.getRelativeConstraints() + "\n" + "Absolute constraints: " + this.getAbsoluteConstraints() + "\n"
+ "Successful constraints: " + this.getSuccessfulConstraints() + "\n" + "Violated constraints: " + this.getViolatedConstraints() + "\n" + "->INFORMATION: "
+ this.getViolatedInformation() + "\n" + "->UNSTABLE: " + this.getViolatedUnstable() + "\n" + "->ERROR: " + this.getViolatedError() + "\n" + "-------------- \n" + loggerMsg + "\n";
}
/**
* Generate JUnit XML output format for all evaluated constraints.
* Depends on results from previous createMetaData call.
*/
private String createJunitReport(ArrayList ceList) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
for (ConstraintEvaluation ce : ceList) {
AbstractConstraint c = ce.getAbstractConstraint();
sb.append(c.getJunitResult());
}
sb.append("");
return sb.toString();
}
/**
* Creates the log file if not present and writes the report to the log file. Only executed if
* persistConstraintLog == true
*
* @throws IOException
*/
public void writeResultsToFile() throws IOException {
performanceLog = new File(newBuild.getRootDir() + File.separator + "performance-results" + File.separator + "performance.log");
if (!performanceLog.exists()) {
boolean dirsCreated = performanceLog.getParentFile().mkdirs();
if (!dirsCreated && !performanceLog.getParentFile().exists()) {
throw new IOException("Failed to create directory " + performanceLog.getParentFile());
}
if (!performanceLog.createNewFile()) {
throw new IOException("Cannot create new file "+performanceLog.getAbsolutePath());
}
}
try (FileOutputStream outWriter = new FileOutputStream(performanceLog, true)){
outWriter.write(getLoggerMsgAdv().getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Writes the complete report to the environment variable: BUILD_CONSTRAINT_LOG
*/
public void writeResultsToEnvVar() {
List params = new ArrayList<>();
params.add(new StringParameterValue("BUILD_CONSTRAINT_LOG", getLoggerMsgAdv()));
newBuild.addAction(new ParametersAction(params));
}
public int getBuildNumber() {
return buildNumber;
}
public Calendar getBuildDate() {
return buildDate;
}
public Result getBuildResult() {
return buildResult;
}
public String getLinkToBuild() {
return linkToBuild;
}
public short getAllConstraints() {
return allConstraints;
}
public short getRelativeConstraints() {
return relativeConstraints;
}
public short getAbsoluteConstraints() {
return absoluteConstraints;
}
public short getSuccessfulConstraints() {
return successfulConstraints;
}
public short getViolatedConstraints() {
return violatedConstraints;
}
public short getViolatedInformation() {
return violatedInformation;
}
public short getViolatedUnstable() {
return violatedUnstable;
}
public short getViolatedError() {
return violatedError;
}
public String getLoggerMsg() {
return loggerMsg;
}
public String getLoggerMsgAdv() {
return loggerMsgAdv;
}
public String getJunitReport() {
return junitReport;
}
public File getPerformanceLog() {
return performanceLog;
}
public void setPerformanceLog(File performanceLog) {
this.performanceLog = performanceLog;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/reports/PerformanceReport.java
================================================
package hudson.plugins.performance.reports;
import hudson.model.Run;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.PerformanceReportMap;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.data.TaurusFinalStats;
import hudson.plugins.performance.parsers.PerformanceReportParser;
import hudson.plugins.performance.tools.SafeMaths;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Pattern;
/**
* Represents a single performance report, which consists of multiple
* {@link UriReport}s for different URLs that was tested.
*
* This object belongs under {@link PerformanceReportMap}.
*/
public class PerformanceReport extends AbstractReport implements Serializable,
Comparable {
private static final long serialVersionUID = 675698410989941826L;
public static final String INCLUDE_ALL = null;
private transient PerformanceBuildAction buildAction;
private String reportFileName = null;
/**
* {@link UriReport}s keyed by their {@link UriReport#getStaplerUri()}.
*/
private final Map uriReportMap = new LinkedHashMap<>();
private PerformanceReport lastBuildReport;
/**
* A lazy cache of all duration values of all HTTP samples in all UriReports, ordered by duration.
*/
private transient List durationsSortedBySize = null;
/**
* A lazy cache of all UriReports, reverse-ordered.
*/
private transient List uriReportsOrdered = null;
/**
* The amount of http samples that are not successful.
*/
private int nbError = 0;
/**
* The sum of summarizerErrors values from all samples;
*/
private float summarizerErrors = 0;
/**
* The amount of samples in all uriReports combined.
*/
private int size;
private int samplesCount;
/**
* The duration of all samples combined, in milliseconds.
*/
private long totalDuration = 0;
/**
* The size of all samples combined, in kilobytes.
*/
private double totalSizeInKB = 0;
private long summarizerMin;
private long summarizerMax;
private long summarizerAvg;
private String summarizerErrorPercent = null;
private long summarizerSize;
private Long average;
private Long perc0;
private Long perc50;
private Long perc90;
private Long perc95;
private Long perc100;
private Long throughput;
protected String percentiles;
protected int baselineBuild = 0;
private transient Pattern filterRegexPattern;
private String filterRegex;
public PerformanceReport() {
this(DEFAULT_PERCENTILES);
}
public PerformanceReport(String percentiles, String filterRegex) {
this.percentiles = percentiles;
this.filterRegex = filterRegex;
if (filterRegex != null && filterRegex.trim().length() > 0) {
this.filterRegexPattern = Pattern.compile(filterRegex);
}
}
public PerformanceReport(String defaultPercentiles) {
this(defaultPercentiles, INCLUDE_ALL);
}
public Object readResolve() {
if (size != 0) {
samplesCount = size;
}
if (throughput == null) {
for (UriReport uriReport : getUriListOrdered()) {
Long uriThroughput = uriReport.getThroughput();
if (uriThroughput != null) {
throughput = (throughput == null) ? uriThroughput : (uriThroughput + throughput);
}
}
}
checkPercentileAndSet(0.0, perc0);
checkPercentileAndSet(50.0, perc50);
checkPercentileAndSet(90.0, perc90);
checkPercentileAndSet(95.0, perc95);
checkPercentileAndSet(100.0, perc100);
if (percentiles == null || percentiles.isBlank()) {
this.percentiles = DEFAULT_PERCENTILES;
}
return this;
}
public static String asStaplerURI(String uri) {
return uri.replace("http:", "").replace("https:", "").replaceAll("/", "_").replaceAll(":", "_");
}
public void addSample(HttpSample pHttpSample) {
String uri = pHttpSample.getUri();
if (uri == null) {
buildAction
.getHudsonConsoleWriter()
.println("label cannot be empty, please ensure your jmx file specifies "
+ "name properly for each http sample: skipping sample");
return;
}
if (!isIncluded(uri)) {
return;
}
String staplerUri = PerformanceReport.asStaplerURI(uri);
synchronized (uriReportMap) {
UriReport uriReport = uriReportMap.get(staplerUri);
if (uriReport == null) {
uriReport = new UriReport(this, staplerUri, uri);
uriReport.setExcludeResponseTime(excludeResponseTime);
uriReport.setShowTrendGraphs(showTrendGraphs);
uriReportMap.put(staplerUri, uriReport);
}
uriReport.addHttpSample(pHttpSample);
// reset the lazy loaded caches.
durationsSortedBySize = null;
uriReportsOrdered = null;
}
if (!pHttpSample.isSuccessful()) {
nbError++;
}
summarizerErrors += pHttpSample.getSummarizerErrors();
samplesCount++;
if (isIncludeResponseTime(pHttpSample)) {
totalDuration += pHttpSample.getDuration();
}
totalSizeInKB += pHttpSample.getSizeInKb();
}
private boolean isIncluded(String name) {
if (filterRegexPattern != null) {
return filterRegexPattern.matcher(name).matches();
} else {
return true;
}
}
public void addSample(TaurusFinalStats sample, boolean isSummaryReport) {
String uri = sample.getLabel();
if (uri == null) {
buildAction
.getHudsonConsoleWriter()
.println("label cannot be empty, please ensure your jmx file specifies "
+ "name properly for each http sample: skipping sample");
return;
}
if (!isIncluded(uri)) {
return;
}
if (isSummaryReport) {
summarizerErrors = nbError = sample.getFail();
int sampleCount = sample.getFail() + sample.getSucc();
samplesCount = sampleCount;
Double testDuration = sample.getTestDuration();
totalDuration = (testDuration == null) ? ((long) sample.getAverageResponseTime() * sampleCount) : (testDuration.longValue());
totalSizeInKB = sample.getBytes();
average = (long) sample.getAverageResponseTime();
perc50 = (long) sample.getPerc50();
perc90 = (long) sample.getPerc90();
perc95 = (long) sample.getPerc95();
perc0 = (long) sample.getPerc0();
perc100 = (long) sample.getPerc100();
this.percentilesValues.put(0.0, (long) sample.getPerc0());
this.percentilesValues.put(50.0, (long) sample.getPerc50());
this.percentilesValues.put(90.0, (long) sample.getPerc90());
this.percentilesValues.put(95.0, (long) sample.getPerc95());
this.percentilesValues.put(100.0, (long) sample.getPerc100());
calculateDiffPercentiles();
isCalculatedPercentilesValues = true;
long durationSec = (long) Math.ceil((float) totalDuration / 1000);
if (durationSec < 1) {
LOGGER.log(Level.INFO, String.format("Performance test had a duration of only %d second(s), please check your configuration.", durationSec));
}
throughput = (testDuration == null) ?
sample.getThroughput() :
(long) SafeMaths.safeDivide(sampleCount, durationSec);
} else {
String staplerUri = PerformanceReport.asStaplerURI(uri);
synchronized (uriReportMap) {
UriReport uriReport = uriReportMap.get(staplerUri);
if (uriReport == null) {
uriReport = new UriReport(this, staplerUri, uri);
uriReportMap.put(staplerUri, uriReport);
}
uriReport.setFromTaurusFinalStats(sample);
// reset the lazy loaded caches.
durationsSortedBySize = null;
uriReportsOrdered = null;
}
}
}
public int compareTo(PerformanceReport jmReport) {
if (this == jmReport) {
return 0;
}
return getReportFileName().compareTo(jmReport.getReportFileName());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
PerformanceReport that = (PerformanceReport) obj;
return getReportFileName() != null ? getReportFileName().equals(that.getReportFileName()) : that.getReportFileName() == null;
}
@Override
public int hashCode() {
return getReportFileName() != null ? getReportFileName().hashCode() : 0;
}
public int countErrors() {
return nbError;
}
public double errorPercent() {
if (ifSummarizerParserUsed(reportFileName)) {
if (uriReportMap.size() == 0) return 0;
return Math.round((summarizerErrors / uriReportMap.size()) * 1000.0) / 1000.0;
} else {
return Math.round((samplesCount() == 0 ? 0 : SafeMaths.safeDivide((double) countErrors(), samplesCount()) * 100) * 1000.0) / 1000.0;
}
}
public long getAverage() {
if (average == null) {
average = (samplesCount == 0) ? 0 : (long) SafeMaths.safeDivide(totalDuration, samplesCount);
}
return average;
}
public double getAverageSizeInKb() {
if (samplesCount == 0) {
return 0;
}
return SafeMaths.roundTwoDecimals(SafeMaths.safeDivide(totalSizeInKB, samplesCount));
}
/**
* 0 percent will give the first value from ordered list of durations
* 100 percent will give the last value from ordered list of durations
*
* @param percentage must be a value between 0 and 100 (inclusive)
* @return value at the percentage specified.
*/
public long getDurationAt(double percentage) {
if (percentage < ZERO_PERCENT || percentage > ONE_HUNDRED_PERCENT) {
throw new IllegalArgumentException("Argument 'percentage' must be a value between 0 and 100 (inclusive)");
}
if (samplesCount == 0) {
return 0;
}
synchronized (uriReportMap) {
if (durationsSortedBySize == null) {
durationsSortedBySize = new ArrayList<>();
for (UriReport currentReport : uriReportMap.values()) {
durationsSortedBySize.addAll(currentReport.getDurations());
}
Collections.sort(durationsSortedBySize);
}
if (durationsSortedBySize.isEmpty()) {
return 0;
}
final double percentInDecimals = percentage / 100;
int indexToReturn = ((int) (durationsSortedBySize.size() * percentInDecimals)) - 1;
// Make sure a valid index is used.
if (indexToReturn < 0) {
indexToReturn = 0;
} else if (indexToReturn >= durationsSortedBySize.size()) {
indexToReturn = durationsSortedBySize.size() - 1;
}
return durationsSortedBySize.get(indexToReturn);
}
}
@Override
public void calculatePercentiles() {
List percs = super.parsePercentiles(percentiles);
for (Double perc : percs) {
super.percentilesValues.put(perc, getDurationAt(perc));
}
super.isCalculatedPercentilesValues = true;
}
@Override
public void calculateDiffPercentiles() {
List percs = super.parsePercentiles(percentiles);
for (Double perc : percs) {
Long diff = 0L;
if (lastBuildReport != null) {
Long previousValue = lastBuildReport.getPercentilesValues().get(perc);
Long currentValue = getPercentilesValues().get(perc);
if (previousValue != null && currentValue != null) {
diff = currentValue - previousValue;
}
}
super.percentilesDiffValues.put(perc, diff);
}
}
public long get90Line() {
if (perc90 == null) {
perc90 = getDurationAt(NINETY_PERCENT);
}
return perc90;
}
public long get95Line() {
if (perc95 == null) {
perc95 = getDurationAt(NINETY_FIVE_PERCENT);
}
return perc95;
}
public long getMedian() {
if (perc50 == null) {
perc50 = getDurationAt(FIFTY_PERCENT);
}
return perc50;
}
public String getHttpCode() {
return "";
}
public Run, ?> getBuild() {
return buildAction.getBuild();
}
PerformanceBuildAction getBuildAction() {
return buildAction;
}
public String getDisplayName() {
return Messages.Report_DisplayName();
}
public UriReport getDynamic(String token) throws IOException {
return getUriReportMap().get(token);
}
public long getMax() {
if (perc100 == null) {
perc100 = getDurationAt(ONE_HUNDRED_PERCENT);
}
return perc100;
}
public double getTotalTrafficInKb() {
return SafeMaths.roundTwoDecimals(totalSizeInKB);
}
public long getMin() {
if (perc0 == null) {
perc0 = getDurationAt(ZERO_PERCENT);
}
return perc0;
}
public String getReportFileName() {
return reportFileName;
}
public List getUriListOrdered() {
synchronized (uriReportMap) {
if (uriReportsOrdered == null) {
uriReportsOrdered = new ArrayList<>(uriReportMap.values());
Collections.sort(uriReportsOrdered, Collections.reverseOrder());
}
return uriReportsOrdered;
}
}
public Map getUriReportMap() {
return uriReportMap;
}
public void setBuildAction(PerformanceBuildAction buildAction) {
this.buildAction = buildAction;
}
public void setReportFileName(String reportFileName) {
this.reportFileName = reportFileName;
}
public int samplesCount() {
return samplesCount;
}
public void setLastBuildReport(PerformanceReport lastBuildReport) {
Map lastBuildUriReportMap = lastBuildReport
.getUriReportMap();
for (Map.Entry item : uriReportMap.entrySet()) {
UriReport lastBuildUri = lastBuildUriReportMap.get(item.getKey());
if (lastBuildUri != null) {
item.getValue().addLastBuildUriReport(lastBuildUri);
}
}
this.lastBuildReport = lastBuildReport;
calculateDiffPercentiles();
}
public long getAverageDiff() {
if (lastBuildReport == null) {
return 0;
}
return getAverage() - lastBuildReport.getAverage();
}
public long getMedianDiff() {
if (lastBuildReport == null) {
return 0;
}
return getMedian() - lastBuildReport.getMedian();
}
public long get90LineDiff() {
if (lastBuildReport == null) {
return 0;
}
return get90Line() - lastBuildReport.get90Line();
}
public long get95LineDiff() {
if (lastBuildReport == null) {
return 0;
}
return get95Line() - lastBuildReport.get95Line();
}
public double getErrorPercentDiff() {
if (lastBuildReport == null) {
return 0;
}
return Math.round((errorPercent() - lastBuildReport.errorPercent()) * 1000.0) / 1000.0;
}
public String getLastBuildHttpCodeIfChanged() {
return "";
}
public int getSamplesCountDiff() {
if (lastBuildReport == null) {
return 0;
}
return samplesCount() - lastBuildReport.samplesCount();
}
/**
* Check if the filename of the file being parsed is being parsed by a
* summarized parser (JMeterSummarizer).
*
* @param filename name of the file being parsed
* @return boolean indicating usage of summarized parser
*/
public boolean ifSummarizerParserUsed(String filename) {
PerformanceReportParser parser = buildAction.getParserByDisplayName("JmeterSummarizer");
if (parser != null) {
String fileExt = parser.glob;
String[] parts = fileExt.split("\\s*[;:,]+\\s*");
for (String path : parts) {
if (filename.endsWith(path.substring(5))) {
return true;
}
}
}
parser = buildAction.getParserByDisplayName("Iago");
return parser != null;
}
public void setSummarizerSize(long summarizerSize) {
this.summarizerSize = summarizerSize;
}
public long getSummarizerSize() {
return summarizerSize;
}
public void setSummarizerMin(long summarizerMin) {
this.summarizerMin = summarizerMin;
}
public long getSummarizerMin() {
return summarizerMin;
}
public void setSummarizerMax(long summarizerMax) {
this.summarizerMax = summarizerMax;
}
public long getSummarizerMax() {
return summarizerMax;
}
public void setSummarizerAvg(long summarizerAvg) {
this.summarizerAvg = summarizerAvg;
}
public long getSummarizerAvg() {
return summarizerAvg;
}
public void setSummarizerErrors(String summarizerErrorPercent) {
this.summarizerErrorPercent = summarizerErrorPercent;
}
public String getSummarizerErrors() {
return summarizerErrorPercent;
}
public Long getThroughput() {
return throughput;
}
public int getBaselineBuild() {
return baselineBuild;
}
public void setBaselineBuild(int baselineBuild) {
this.baselineBuild = baselineBuild;
}
/**
* @return the filterRegex
*/
public String getFilterRegex() {
return filterRegex;
}
/**
* @param filterRegex the filterRegex to set
*/
public void setFilterRegex(String filterRegex) {
this.filterRegex = filterRegex;
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/reports/ThroughputReport.java
================================================
package hudson.plugins.performance.reports;
import java.util.List;
/**
* @author Artem Stasiuk (artem.stasuk@gmail.com)
*/
public class ThroughputReport {
private static final int MILLISECONDS_IN_SECOND = 1000;
private final PerformanceReport performanceReport;
public ThroughputReport(final PerformanceReport performanceReport) {
this.performanceReport = performanceReport;
}
public double get() {
Long throughput = performanceReport.getThroughput();
if (throughput != null) {
return throughput;
} else {
final List uriReports = performanceReport.getUriListOrdered();
if (uriReports.isEmpty()) {
return 0;
}
long sumSamplesCount = 0;
long startTime = Long.MAX_VALUE;
long endTime = 0;
for (UriReport uriReport : uriReports) {
sumSamplesCount += uriReport.samplesCount();
if (startTime > uriReport.getStart().getTime()) {
startTime = uriReport.getStart().getTime();
}
if (endTime < uriReport.getEnd().getTime()) {
endTime = uriReport.getEnd().getTime();
}
}
final long duration = endTime - startTime;
if (duration == 0) {
return sumSamplesCount; // more than zero requests should always take at least some time. If that didn't get logged, this is the most suitable alternative.
}
return (sumSamplesCount / ((double) duration / MILLISECONDS_IN_SECOND));
}
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/reports/UriReport.java
================================================
package hudson.plugins.performance.reports;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListMap;
import org.jfree.chart.JFreeChart;
import org.jfree.data.time.FixedMillisecond;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import hudson.model.ModelObject;
import hudson.model.Run;
import hudson.plugins.performance.Messages;
import hudson.plugins.performance.actions.PerformanceProjectAction;
import hudson.plugins.performance.data.HttpSample;
import hudson.plugins.performance.data.TaurusFinalStats;
import hudson.plugins.performance.details.GraphConfigurationDetail;
import hudson.plugins.performance.tools.SafeMaths;
import hudson.util.Graph;
/**
* A report about a particular tested URI.
*
* This object belongs under {@link PerformanceReport}.
*/
public class UriReport extends AbstractReport implements Serializable, ModelObject,
Comparable {
private static final long serialVersionUID = -5269155428479638524L;
public final static String END_PERFORMANCE_PARAMETER = ".endperformanceparameter";
/**
* Escaped {@link #uri} that doesn't contain any letters that cannot be used
* as a token in URL.
*/
private final String staplerUri;
private UriReport lastBuildUriReport;
/**
* The parent object to which this object belongs.
*/
private final PerformanceReport performanceReport;
private String uri;
/**
* The amount of http samples that are not successful.
*/
private int nbError = 0;
/**
* A list that contains the date and duration (in milliseconds) of all individual samples.
*/
private final List samples = new ArrayList<>(); // retain insertion order.
/**
* A lazy cache of all duration values in {@link #samples}, insertion order (same as {@link #samples}
*/
private transient List durationsIO = new ArrayList<>();
/**
* A lazy cache of all duration values in {@link #samples}, ordered by duration.
*/
private transient List durationsSortedBySize = new ArrayList<>();
/**
* Indicates if the collection {@link #durationsSortedBySize} is in a sorted state.
*/
private transient boolean isSorted = false;
/**
* The duration of all samples combined, in milliseconds.
*/
private long totalDuration = 0; // note that this is the sum of all elements in #durations, but need not be recalculated every time.
/**
* The set of (unique) HTTP status codes from all samples.
*/
private Set httpCodes = new HashSet<>();
/**
* The sum of summarizerSample values from all samples;
*/
private long summarizerSize = 0;
/**
* The sum of summarizerErrors values from all samples;
*/
private float summarizerErrors = 0;
/**
* The point in time of the start of the oldest sample.
*/
private Date start = null;
/**
* The point in time of the end of the youngest sample.
*/
private Date end = null;
private Long average;
private Long perc0;
private Long perc50;
private Long perc90;
private Long perc95;
private Long perc100;
@Deprecated
private Long throughput;
private int samplesCount;
protected String percentiles;
private double sizeInKb;
public Object readResolve() {
checkPercentileAndSet(0.0, perc0);
checkPercentileAndSet(50.0, perc50);
checkPercentileAndSet(90.0, perc90);
checkPercentileAndSet(95.0, perc95);
checkPercentileAndSet(100.0, perc100);
if (percentiles == null || percentiles.isBlank()) {
this.percentiles = DEFAULT_PERCENTILES;
}
return this;
}
public UriReport(PerformanceReport performanceReport, String staplerUri, String uri) {
this.performanceReport = performanceReport;
this.staplerUri = staplerUri;
this.uri = uri;
this.percentiles = performanceReport.percentiles;
}
public void addHttpSample(HttpSample sample) {
if (!sample.isSuccessful()) {
nbError++;
}
synchronized (samples) {
if (samples.add(Sample.convertFromHttpSample(sample))) {
isSorted = false;
samplesCount++;
}
}
if (isIncludeResponseTime(sample)) {
totalDuration += sample.getDuration();
}
httpCodes.add(sample.getHttpCode()); // The Set implementation will ensure that no duplicates will be saved.
summarizerSize += sample.getSummarizerSamples();
summarizerErrors += sample.getSummarizerErrors();
sizeInKb += sample.getSizeInKb();
if (start == null || sample.getDate().before(start)) {
start = sample.getDate();
}
Date finish = new Date(sample.getDate().getTime() + sample.getDuration());
if (end == null || finish.after(end)) {
end = finish;
}
}
public void setFromTaurusFinalStats(TaurusFinalStats report) {
average = (long) report.getAverageResponseTime();
perc0 = (long) report.getPerc0();
perc50 = (long) report.getPerc50();
perc90 = (long) report.getPerc90();
perc95 = (long) report.getPerc95();
perc100 = (long) report.getPerc100();
this.percentilesValues.put(0.0, (long) report.getPerc0());
this.percentilesValues.put(50.0, (long) report.getPerc50());
this.percentilesValues.put(90.0, (long) report.getPerc90());
this.percentilesValues.put(95.0, (long) report.getPerc95());
this.percentilesValues.put(100.0, (long) report.getPerc100());
calculateDiffPercentiles();
isCalculatedPercentilesValues = true;
summarizerSize = report.getBytes();
summarizerErrors = report.getFail();
nbError = report.getFail();
synchronized (samples) {
samplesCount = report.getSucc() + report.getFail();
}
}
public int compareTo(UriReport uriReport) {
if (uriReport == this) {
return 0;
}
return uriReport.getUri().compareTo(this.getUri());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
UriReport uriReport = (UriReport) obj;
return getUri() != null ? getUri().equals(uriReport.getUri()) : uriReport.getUri() == null;
}
@Override
public int hashCode() {
return getUri() != null ? getUri().hashCode() : 0;
}
public int countErrors() {
return nbError;
}
public double errorPercent() {
return Math.round((SafeMaths.safeDivide((double) countErrors(), samplesCount()) * 100) * 1000.0) / 1000.0;
}
public long getAverage() {
if (average == null) {
int samplesCount = samplesCount();
average = (samplesCount == 0) ? 0 : (long) SafeMaths.safeDivide(totalDuration, samplesCount);
}
return average;
}
private long getDurationAt(double percentage) {
if (percentage < ZERO_PERCENT || percentage > ONE_HUNDRED_PERCENT) {
throw new IllegalArgumentException("Argument 'percentage' must be a value between 0 and 100 (inclusive)");
}
synchronized (samples) {
final List durations = getSortedDuration();
if (durations.isEmpty()) {
return 0;
}
final double percentInDecimals = percentage / 100;
int indexToReturn = ((int) (durationsSortedBySize.size() * percentInDecimals)) - 1;
// Make sure a valid index is used.
if (indexToReturn < 0) {
indexToReturn = 0;
} else if (indexToReturn >= durationsSortedBySize.size()) {
indexToReturn = durationsSortedBySize.size() - 1;
}
return durations.get(indexToReturn);
}
}
@Override
public void calculatePercentiles() {
List percs = super.parsePercentiles(percentiles);
for (Double perc : percs) {
super.percentilesValues.put(perc, getDurationAt(perc));
}
super.isCalculatedPercentilesValues = true;
}
@Override
public void calculateDiffPercentiles() {
List percs = super.parsePercentiles(percentiles);
for (Double perc : percs) {
Long diff = 0L;
if (lastBuildUriReport != null) {
Long previousValue = lastBuildUriReport.getPercentilesValues().get(perc);
Long currentValue = getPercentilesValues().get(perc);
if (previousValue != null && currentValue != null) {
diff = currentValue - previousValue;
}
}
super.percentilesDiffValues.put(perc, diff);
}
}
public long get90Line() {
if (perc90 == null) {
perc90 = getDurationAt(NINETY_PERCENT);
}
return perc90;
}
public long get95Line() {
if (perc95 == null) {
perc95 = getDurationAt(NINETY_FIVE_PERCENT);
}
return perc95;
}
public String getHttpCode() {
return String.join(",", httpCodes);
}
public long getMedian() {
if (perc50 == null) {
perc50 = getDurationAt(FIFTY_PERCENT);
}
return perc50;
}
public Run, ?> getBuild() {
return performanceReport.getBuild();
}
public String getDisplayName() {
return getUri();
}
public List getHttpSampleList() {
return samples;
}
public PerformanceReport getPerformanceReport() {
return performanceReport;
}
protected List getSortedDuration() {
synchronized (samples) {
if (!isSorted || durationsSortedBySize == null || durationsSortedBySize.size() != samples.size()) {
durationsSortedBySize = new ArrayList<>(samplesCount());
for (Sample sample : samples) {
if (isIncludeResponseTime(sample)) {
durationsSortedBySize.add(sample.duration);
}
}
Collections.sort(durationsSortedBySize);
isSorted = true;
}
return durationsSortedBySize;
}
}
public List getDurations() {
synchronized (samples) {
if (durationsIO == null || durationsIO.size() != samples.size()) {
durationsIO = new ArrayList<>(samples.size());
for (Sample sample : samples) {
if (isIncludeResponseTime(sample)) {
durationsIO.add(sample.duration);
}
}
}
return durationsIO;
}
}
public long getMax() {
if (perc100 == null) {
perc100 = getDurationAt(ONE_HUNDRED_PERCENT);
}
return perc100;
}
public long getMin() {
if (perc0 == null) {
perc0 = getDurationAt(ZERO_PERCENT);
}
return perc0;
}
public String getStaplerUri() {
return staplerUri;
}
public String getUri() {
return uri;
}
public String getShortUri() {
if (uri.length() > 130) {
return uri.substring(0, 129);
}
return uri;
}
public boolean isFailed() {
return countErrors() != 0;
}
public int samplesCount() {
synchronized (samples) {
return samplesCount;
}
}
public String encodeUriReport() throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder(120);
sb.append(performanceReport.getReportFileName()).append(
GraphConfigurationDetail.SEPARATOR).append(getStaplerUri()).append(
END_PERFORMANCE_PARAMETER);
return URLEncoder.encode(sb.toString(), StandardCharsets.UTF_8.name());
}
public void addLastBuildUriReport(UriReport lastBuildUriReport) {
this.lastBuildUriReport = lastBuildUriReport;
calculateDiffPercentiles();
}
public long getAverageDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return getAverage() - lastBuildUriReport.getAverage();
}
public long getMedianDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return getMedian() - lastBuildUriReport.getMedian();
}
public long get90LineDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return get90Line() - lastBuildUriReport.get90Line();
}
public long get95LineDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return get95Line() - lastBuildUriReport.get95Line();
}
public double getErrorPercentDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return Math.round((errorPercent() - lastBuildUriReport.errorPercent()) * 1000.0) / 1000.0;
}
public String getLastBuildHttpCodeIfChanged() {
if (lastBuildUriReport == null) {
return "";
}
if (lastBuildUriReport.getHttpCode().equals(getHttpCode())) {
return "";
}
return lastBuildUriReport.getHttpCode();
}
public int getSamplesCountDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return samplesCount() - lastBuildUriReport.samplesCount();
}
public float getSummarizerErrors() {
return summarizerErrors / summarizerSize * 100;
}
public void doSummarizerTrendGraph(StaplerRequest request, StaplerResponse response) throws IOException {
TimeSeries responseTimes = new TimeSeries(Messages.ProjectAction_RespondingTime());
synchronized (samples) {
for (Sample sample : samples) {
if (isIncludeResponseTime(sample)) {
responseTimes.addOrUpdate(new FixedMillisecond(sample.date), sample.duration);
}
}
}
TimeSeriesCollection resp = new TimeSeriesCollection();
resp.addSeries(responseTimes);
ArrayList dataset = new ArrayList<>();
dataset.add(resp);
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return PerformanceProjectAction.createSummarizerTrend(dataset, uri);
}
}.doPng(request, response);
}
public void doErrorGraph(StaplerRequest request, StaplerResponse response) throws IOException {
TimeSeries errors = new TimeSeries(Messages.ProjectAction_Errors());
synchronized (samples) {
for (Sample sample : samples) {
if (sample.isFailed() && !sample.isSummarizer()) {
errors.addOrUpdate(new FixedMillisecond(sample.date), sample.duration);
}
}
}
ArrayList dataset = new ArrayList<>();
dataset.add(new TimeSeriesCollection(errors));
// Re-use the same scatter plotter for error response times:
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return PerformanceProjectAction.createSummarizerTrend(dataset, uri);
}
}.doPng(request, response);
}
public void doPercentileGraph(StaplerRequest request, StaplerResponse response) throws IOException {
final ConcurrentSkipListMap responseTimesHistogram = new ConcurrentSkipListMap<>(); // we want keys in sorted order
long totalNoOfSamples = 0L;
synchronized (samples) {
for (Sample sample : samples) {
if (isIncludeResponseTime(sample)) {
totalNoOfSamples++;
// count number of sample occurrences with the same response time value:
responseTimesHistogram.put(sample.duration,
responseTimesHistogram.containsKey(sample.duration) ? responseTimesHistogram.get(sample.duration) + 1 : 1);
}
}
}
XYSeries percentiles = new XYSeries(Messages.TrendReportDetail_ResponseTimePercentiles());
long cumulativeTotal = 0; // adds up the running total number of samples for percentile calculation
percentiles.add(0, responseTimesHistogram.firstKey()); // add 0th percentile (minimum response time)
for (Map.Entry responseTimeOccurrence : responseTimesHistogram.entrySet()) {
cumulativeTotal += responseTimeOccurrence.getValue();
percentiles.addOrUpdate((double) 100.0 * cumulativeTotal / totalNoOfSamples, (double) responseTimeOccurrence.getKey()); // float value will result in smoother curve
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return PerformanceProjectAction.createUriPercentileChart(new XYSeriesCollection(percentiles), uri);
}
}.doPng(request, response);
}
public void doThroughputGraph(StaplerRequest request, StaplerResponse response) throws IOException {
final Map throughputIntervals = new HashMap<>();
synchronized (samples) {
for (Sample sample : samples) {
if (isIncludeResponseTime(sample)) {
Minute timeBucket = new Minute(sample.date);
// count number of samples in the same time bucket
throughputIntervals.put(timeBucket,
throughputIntervals.containsKey(timeBucket) ? throughputIntervals.get(timeBucket) + 1 : 1);
}
}
}
TimeSeries throughput = new TimeSeries(Messages.TrendReportDetail_RequestThroughput());
for (Map.Entry timeBucket : throughputIntervals.entrySet()) {
throughput.add(timeBucket.getKey(), timeBucket.getValue());
}
new Graph(-1, 400, 200) {
@Override
protected JFreeChart createGraph() {
return PerformanceProjectAction.createUriThroughputChart(new TimeSeriesCollection(throughput), uri);
}
}.doPng(request, response);
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
protected boolean isIncludeResponseTime(Sample sample) {
return !(sample.isFailed() && excludeResponseTime && !sample.isSummarizer());
}
public static class Sample implements Serializable, Comparable {
private static final long serialVersionUID = 4458431861223813407L;
protected final Date date;
protected final long duration;
protected final String httpCode;
protected final boolean isSuccessful;
protected final boolean isSummarizer;
public Sample(Date date, long duration, String httpCode, boolean isSuccessful, boolean isSummarizer) {
this.date = date;
this.duration = duration;
this.httpCode = httpCode;
this.isSuccessful = isSuccessful;
this.isSummarizer = isSummarizer;
}
public static Sample convertFromHttpSample(HttpSample httpSample) {
return new Sample(httpSample.getDate(), httpSample.getDuration(), httpSample.getHttpCode(),
httpSample.isSuccessful(), httpSample.isSummarizer());
}
public String getHttpCode() {
return httpCode;
}
public Date getDate() {
return date;
}
public long getDuration() {
return duration;
}
public boolean isSuccessful() {
return isSuccessful;
}
public boolean isFailed() {
return !isSuccessful();
}
public boolean isSummarizer() {
return isSummarizer;
}
/**
* Compare first based on duration, next on date.
*/
public int compareTo(Sample other) {
if (this == other) return 0;
if (this.duration < other.duration) return -1;
if (this.duration > other.duration) return 1;
if (this.date == null || other.date == null) return 0;
if (this.date.before(other.date)) return -1;
if (this.date.after(other.date)) return 1;
return 0;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Sample other = (Sample) obj;
if (duration != other.duration) return false;
return date != null ? date.equals(other.date) : other.date == null;
}
@Override
public int hashCode() {
int result = (int) (duration ^ (duration >>> 32));
result = 31 * result + (date != null ? date.hashCode() : 0);
return result;
}
}
@Deprecated
public void setThroughput(Long throughput) {
this.throughput = throughput;
}
@Deprecated
public Long getThroughput() {
return throughput;
}
public boolean hasSamples() {
return !samples.isEmpty();
}
public double getAverageSizeInKb() {
return SafeMaths.roundTwoDecimals(SafeMaths.safeDivide(sizeInKb, samplesCount()));
}
public double getTotalTrafficInKb() {
return SafeMaths.roundTwoDecimals(sizeInKb);
}
public double getAverageSizeInKbDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return SafeMaths.roundTwoDecimals(getAverageSizeInKb() - lastBuildUriReport.getAverageSizeInKb());
}
public double getTotalTrafficInKbDiff() {
if (lastBuildUriReport == null) {
return 0;
}
return SafeMaths.roundTwoDecimals(getTotalTrafficInKb() - lastBuildUriReport.getTotalTrafficInKb());
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/tools/SafeMaths.java
================================================
package hudson.plugins.performance.tools;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class SafeMaths {
public static double safeDivide(double dividend, double divisor) {
if (Double.compare(divisor, Double.NaN) == 0) {
return Double.NaN;
}
if (Double.compare(dividend, Double.NaN) == 0) {
return Double.NaN;
}
if (Double.compare(divisor, 0.0) == 0) {
if (Double.compare(dividend, 0.0) == -1) {
return Double.NEGATIVE_INFINITY;
}
return Double.POSITIVE_INFINITY;
}
if (Double.compare(divisor, -0.0) == 0) {
if (Double.compare(dividend, -0.0) == 1) {
return Double.NEGATIVE_INFINITY;
}
return Double.POSITIVE_INFINITY;
}
return dividend / divisor;
}
public static double roundTwoDecimals(double d) {
BigDecimal bd = BigDecimal.valueOf(d);
bd = bd.setScale(2, RoundingMode.HALF_UP);
return bd.doubleValue();
}
}
================================================
FILE: src/main/java/hudson/plugins/performance/workflow/WorkflowActionsFactory.java
================================================
package hudson.plugins.performance.workflow;
import hudson.Extension;
import hudson.model.Action;
import hudson.model.Job;
import hudson.model.Run;
import hudson.plugins.performance.actions.PerformanceBuildAction;
import hudson.plugins.performance.actions.PerformanceProjectAction;
import jenkins.model.TransientActionFactory;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collection;
import java.util.Collections;
@Extension
public class WorkflowActionsFactory extends TransientActionFactory {
@Override
public Class type() {
return Job.class;
}
@NonNull
@Override
public Collection extends Action> createFor(@NonNull Job job) {
if (job.getClass().getCanonicalName().startsWith("org.jenkinsci.plugins.workflow")) {
final Run, ?> r = job.getLastSuccessfulBuild();
if (r != null && !r.getActions(PerformanceBuildAction.class).isEmpty()) {
return Collections.singletonList(new PerformanceProjectAction(job));
}
}
return Collections.emptyList();
}
}
================================================
FILE: src/main/resources/hudson/plugins/performance/Messages.properties
================================================
ProjectAction.PercentageOfErrors=Percentage of errors
ProjectAction.RespondingTime=Response time
ProjectAction.Throughput=Throughput
ProjectAction.RequestsPerSeconds=Requests Per Second
ProjectAction.Errors=errors
ProjectAction.Maximum=max
ProjectAction.Minimum=min
ProjectAction.TotalTrafficKB=total kb
ProjectAction.AverageKB=average kb
ProjectAction.Average=average
ProjectAction.Median=median
ProjectAction.Line90=90% line
ProjectAction.Line95=95% line
ProjectAction.PercentageOfFailedTests=Percentage of failed tests
BuildAction.DisplayName=Performance Report
ProjectAction.DisplayName=Performance Trend
Publisher.DisplayName=Publish Performance test result report
Report.DisplayName=Performance
CsvParser.validation.MissingFields=Missing required fields
CsvParser.validation.delimiterEmpty=Delimiter can't be empty
CsvParser.validation.patternEmpty=Pattern is required
GraphConfigurationDetail.DisplayName=Configure
TrendReportDetail.DisplayName=Trend report
TrendReportDetail.ResponseTime=Response Time (ms)
TrendReportDetail.Time=Time
TrendReportDetail.Percent=Percent
TrendReportDetail.RequestsPerMinute=Requests Per Minute
TrendReportDetail.RequestThroughput=Request Throughput
TrendReportDetail_ResponseTimePercentiles=Response Time Percentiles
TestSuiteReportDetail.DisplayName=Test Suite report
PerformanceTest.Name=Run Performance Test
PerformanceTest.Config=Performance Test
================================================
FILE: src/main/resources/hudson/plugins/performance/Messages_es.properties
================================================
ProjectAction.PercentageOfErrors=Porcentaje de errores
ProjectAction.RespondingTime=Tiempo de respuesta
ProjectAction.Errors=errores
ProjectAction.Maximum=mx
ProjectAction.Minimum=mn
ProjectAction.Average=media
ProjectAction.Median=mediana
ProjectAction.Line90=Lnea 90%
ProjectAction.Line95=Lnea 95%
ProjectAction.TotalTrafficKB=total kb
ProjectAction.AverageKB=average kb
BuildAction.DisplayName=Informe de Rendimiento
ProjectAction.DisplayName=Tendencia de Rendimiento
Publisher.DisplayName=Publicar informes de tests de rendimiento
Report.DisplayName=Rendimiento
GraphConfigurationDetail.DisplayName=Configurar
TrendReportDetail.DisplayName=Informe de tendencia
================================================
FILE: src/main/resources/hudson/plugins/performance/PerformancePublisher/config.jelly
================================================
Standard Mode