package hudson.plugins.performance;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PerformancePublisher extends Recorder {
@Extension
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Override
public String getDisplayName() {
return Messages.Publisher_DisplayName();
}
@Override
public String getHelpFile() {
return "/plugin/performance/help.html";
}
public List<PerformanceReportParserDescriptor> getParserDescriptors() {
return PerformanceReportParserDescriptor.all();
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}
}
private int errorFailedThreshold = 0;
private int errorUnstableThreshold = 0;
/**
* @deprecated as of 1.3. for compatibility
*/
private transient String filename;
/**
* Configured report parseres.
*/
private List<PerformanceReportParser> parsers;
@DataBoundConstructor
public PerformancePublisher(int errorFailedThreshold, int errorUnstableThreshold, List<? extends PerformanceReportParser> parsers) {
this.errorFailedThreshold = errorFailedThreshold;
this.errorUnstableThreshold = errorUnstableThreshold;
if (parsers == null)
parsers = Collections.emptyList();
this.parsers = new ArrayList<PerformanceReportParser>(parsers);
}
public static File getPerformanceReport(AbstractBuild<?, ?> build, String performanceReportName) {
return new File(build.getRootDir(), PerformanceReportMap.getPerformanceReportFileRelativePath(getPerformanceReportBuildFileName(performanceReportName)));
}
@Override
public Action getProjectAction(AbstractProject<?, ?> project) {
return new PerformanceProjectAction(project);
}
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.BUILD;
}
public List<PerformanceReportParser> getParsers() {
return parsers;
}
/**
* <p>
* Delete the date suffix appended to the Performance result files by the
* Maven Performance plugin
* </p>
*
* @param performanceReportWorkspaceName
* @return the name of the PerformanceReport in the Build
*/
public static String getPerformanceReportBuildFileName(String performanceReportWorkspaceName) {
String result = performanceReportWorkspaceName;
if (performanceReportWorkspaceName != null) {
Pattern p = Pattern.compile("-[0-9]*\\.xml");
Matcher matcher = p.matcher(performanceReportWorkspaceName);
if (matcher.find()) {
result = matcher.replaceAll(".xml");
}
}
return result;
}
/**
* look for performance reports based in the configured parameter includes.
* 'includes' is - an Ant-style pattern - a list of files and folders
* separated by the characters ;:,
*/
protected static List<FilePath> locatePerformanceReports(FilePath workspace, String includes) throws IOException, InterruptedException {
// First use ant-style pattern
try {
FilePath[] ret = workspace.list(includes);
if (ret.length > 0) {
return Arrays.asList(ret);
}
} catch (IOException e) {
}
// If it fails, do a legacy search
ArrayList<FilePath> files = new ArrayList<FilePath>();
String parts[] = includes.split("\\s*[;:,]+\\s*");
for (String path : parts) {
FilePath src = workspace.child(path);
if (src.exists()) {
if (src.isDirectory()) {
files.addAll(Arrays.asList(src.list("**/*")));
} else {
files.add(src);
}
}
}
return files;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
PrintStream logger = listener.getLogger();
if (errorUnstableThreshold > 0 && errorUnstableThreshold < 100) {
logger.println("Performance: Percentage of errors greater or equal than " + errorUnstableThreshold + "% sets the build as " + Result.UNSTABLE.toString().toLowerCase());
} else {
logger.println("Performance: No threshold configured for making the test " + Result.UNSTABLE.toString().toLowerCase());
}
if (errorFailedThreshold > 0 && errorFailedThreshold < 100) {
logger.println("Performance: Percentage of errors greater or equal than " + errorFailedThreshold + "% sets the build as " + Result.FAILURE.toString().toLowerCase());
} else {
logger.println("Performance: No threshold configured for making the test " + Result.FAILURE.toString().toLowerCase());
}
// add the report to the build object.
PerformanceBuildAction a = new PerformanceBuildAction(build, logger, parsers);
build.addAction(a);
for (PerformanceReportParser parser : parsers) {
String glob = parser.glob;
logger.println("Performance: Recording " + parser.getReportName() + " reports '" + glob + "'");
List<FilePath> files = locatePerformanceReports(build.getWorkspace(), glob);
if (files.isEmpty()) {
if (build.getResult().isWorseThan(Result.UNSTABLE)) {
return true;
}
build.setResult(Result.FAILURE);
logger.println("Performance: no " + parser.getReportName() + " files matching '" + glob + "' have been found. Has the report generated?. Setting Build to " + build.getResult());
return true;
}
List<File> localReports = copyReportsToMaster(build, logger, files);
Collection<PerformanceReport> parsedReports = parser.parse(build, localReports, listener);
// mark the build as unstable or failure depending on the outcome.
for (PerformanceReport r : parsedReports) {
r.setBuildAction(a);
double errorPercent = r.errorPercent();
if (errorFailedThreshold > 0 && errorPercent >= errorFailedThreshold) {
build.setResult(Result.FAILURE);
} else if (errorUnstableThreshold > 0 && errorPercent >= errorUnstableThreshold) {
build.setResult(Result.UNSTABLE);
}
logger.println("Performance: File " + r.getReportFileName() + " reported " + errorPercent + "% of errors during the tests. Build status is: " + build.getResult());
}
}
return true;
}
private List<File> copyReportsToMaster(AbstractBuild<?, ?> build, PrintStream logger, List<FilePath> files) throws IOException, InterruptedException {
List<File> localReports = new ArrayList<File>();
for (FilePath src : files) {
final File localReport = getPerformanceReport(build, src.getName());
if (src.isDirectory()) {
logger.println("Performance: File '" + src.getName() + "' is a directory, not a Performance Report");
continue;
}
src.copyTo(new FilePath(localReport));
localReports.add(localReport);
}
return localReports;
}
public Object readResolve() {
// data format migration
if (parsers == null)
parsers = new ArrayList<PerformanceReportParser>();
if (filename != null) {
parsers.add(new JMeterParser(filename));
filename = null;
}
return this;
}
public int getErrorFailedThreshold() {
return errorFailedThreshold;
}
public void setErrorFailedThreshold(int errorFailedThreshold) {
this.errorFailedThreshold = Math.max(0, Math.min(errorFailedThreshold, 100));
}
public int getErrorUnstableThreshold() {
return errorUnstableThreshold;
}
public void setErrorUnstableThreshold(int errorUnstableThreshold) {
this.errorUnstableThreshold = Math.max(0, Math.min(errorUnstableThreshold, 100));
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}