package hudson.plugins.PerfPublisher.projectsAction; import hudson.matrix.MatrixBuild; import hudson.matrix.MatrixProject; import hudson.model.AbstractBuild; import hudson.model.Action; import hudson.model.FreeStyleProject; import hudson.model.Project; import hudson.model.Result; import hudson.plugins.PerfPublisher.AbstractPerfPublisherAction; import hudson.plugins.PerfPublisher.MatrixTestReportAction; import hudson.plugins.PerfPublisher.PerfPublisherBuildAction; import hudson.plugins.PerfPublisher.PerfPublisherPlugin; import hudson.plugins.PerfPublisher.Report.ReportContainer; import hudson.plugins.PerfPublisher.matrixBuild.PerfPublisherMatrixBuild; import hudson.plugins.PerfPublisher.matrixBuild.PerfPublisherMatrixSubBuild; import hudson.util.ChartUtil; import hudson.util.ChartUtil.NumberOnlyBuildLabel; import hudson.util.ColorPalette; import hudson.util.DataSetBuilder; 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.CategoryItemRenderer; import org.jfree.ui.RectangleInsets; import org.jfree.ui.StrokeSample; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import java.awt.Color; import java.awt.Shape; import java.awt.Stroke; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Action used for PerfPublisher report on matrix project level. * * @see AbstractPerfPublisherAction * @author Georges Bossert */ public class PerfPublisherMatrixProjectAction extends AbstractPerfPublisherAction { /** * The associated matrix project */ private final MatrixProject project; /** * The maximum number of build to display */ private final int max_nb_build = 10; /** * Constructor * * @param project * the current matrix project */ public PerfPublisherMatrixProjectAction(MatrixProject project) { this.project = project; } /** * Getter for the display name which it used by hudson for the link menu * display */ public String getDisplayName() { return PerfPublisherPlugin.GENERAL_DISPLAY_NAME; } /** * Getter of the current matrix project * * @return the current matrix project */ public MatrixProject getProject() { return project; } /** * * @return */ public ReportContainer getReports() { Object ob = getProject().getLastSuccessfulBuild(); AbstractBuild build = (AbstractBuild) ob; if (build != null) { PerfPublisherBuildAction ac = build .getAction(PerfPublisherBuildAction.class); if (ac != null) { return ac.getReports(); } } return null; } public PerfPublisherBuildAction getActionByBuildNumber(int number) { return project.getBuildByNumber(number).getAction( PerfPublisherBuildAction.class); } public String getHtmlArrayDisplay() { StringBuilder strb = new StringBuilder(); /** * Compute builds to display */ List<Integer> buildsNumber = computeBuildsToDisplay(); /** * Generate HTML Header Table */ strb.append("<table class=\"global_matrix_table\">\n"); strb.append(generateHtmlArrayHeader(buildsNumber)); /** * Generate HTML Table Content */ strb.append(generateHtmlArrayContent(buildsNumber)); strb.append("</table>"); return strb.toString(); } /** * Compute the builds numbers we will integer in the html table * * @return an ordered array containing all the builds numbers */ private List<Integer> computeBuildsToDisplay() { List<Integer> builds = new ArrayList<Integer>(); for (MatrixBuild build : this.project.getBuilds()) { if (!build.isBuilding() && build.getResult().isBetterOrEqualTo(Result.SUCCESS) && build.getAction(MatrixTestReportAction.class) != null) { builds.add(build.getNumber()); } } Collections.sort(builds); List<Integer> result = new ArrayList<Integer>(); /** * Only display the last ${max_nb_build} builds */ int start = 0; if (builds.size() > max_nb_build) { start = builds.size() - max_nb_build; } for (int i = builds.size() - 1; i >= start; i--) { result.add(builds.get(i)); } Collections.sort(result); return result; } private String generateHtmlArrayHeader(List<Integer> builds) { StringBuilder strb = new StringBuilder(); strb.append("<tr class=\"header\">"); strb .append("<td style=\"width:170px;\">Statistics</td><td>Combination</td>"); for (int i_build = builds.size() - 1; i_build >= 0; i_build--) { MatrixBuild matrixBuild = this.project.getBuildByNumber(builds .get(i_build)); strb.append("<td"); if (matrixBuild.getResult() == Result.SUCCESS) { strb.append(" class=\"blue\""); } else if (matrixBuild.getResult() == Result.ABORTED || matrixBuild.getResult() == Result.FAILURE) { strb.append(" class=\"red\""); } else if (matrixBuild.getResult() == Result.UNSTABLE) { strb.append(" class=\"yellow\""); } else if (matrixBuild.getResult() == Result.NOT_BUILT) { strb.append(" class=\"grey\""); } strb.append("><small>build</small> " + matrixBuild.getNumber() + "</td>"); } strb.append("</tr>"); return strb.toString(); } /** * Generate the html source code for the array content it must be called by * the jelly * * @return a string containing the html source code */ private String generateHtmlArrayContent(List<Integer> builds) { StringBuilder strb = new StringBuilder(); Map<String, Map<String, Map<Integer,Float>>> values = getStaticticsValues(builds); Set<String> statsNames = values.keySet(); Iterator<String> iteratorOnStatsNames = statsNames.iterator(); while (iteratorOnStatsNames.hasNext()) { // Get the stats name String statsName = iteratorOnStatsNames.next(); Map<String, Map<Integer,Float>> values2 = values.get(statsName); // Remove the classifying solution from the stats name. format : // {number - stats_name} statsName = statsName.substring(statsName.indexOf('-') + 2); strb.append("<tr"); strb.append(" onMouseOver=\"this.style.backgroundColor='#FFCC66';\""); strb.append(" onMouseOut=\"this.style.backgroundColor='#FFFFFF';\""); strb.append(">\n"); strb.append("<td rowspan=\"" + values2.size() + "\" class=\"statsRow\">\n"); strb.append("<b>" + statsName + "</b>"); strb.append("</td>\n"); Set<String> combinations = values2.keySet(); Iterator<String> iteratorOnCombinations = combinations.iterator(); while (iteratorOnCombinations.hasNext()) { // Min and max local for this combination List<Integer> i_min_local = new ArrayList<Integer>(); List<Integer> i_max_local = new ArrayList<Integer>(); float min_local = 0, max_local = 0; // Get the combination name String combination = iteratorOnCombinations.next(); Map<Integer,Float> buildValues = values2.get(combination); String tdClass = ""; if (!iteratorOnCombinations.hasNext()) { tdClass = "class=\"combinationRow\" style=\"border-bottom:1px solid #000000;\""; } else { tdClass = "class=\"combinationRow\""; } strb.append("<td " + tdClass + ">" + combination + "</td>\n"); // Compute the min and max for (int i_buildNumber= builds.size() - 1; i_buildNumber >= 0; i_buildNumber--) { if (buildValues.containsKey(builds.get(i_buildNumber))) { Float buildValue = buildValues.get(builds.get(i_buildNumber)); if (i_buildNumber == builds.size() - 1) { i_max_local.add(builds.get(i_buildNumber)); i_min_local.add(builds.get(i_buildNumber)); max_local = min_local = buildValue; } else { if (buildValue < min_local) { min_local = buildValue; i_min_local = new ArrayList<Integer>(); i_min_local.add(builds.get(i_buildNumber)); } else if (buildValue > max_local) { max_local = buildValue; i_max_local = new ArrayList<Integer>(); i_max_local.add(builds.get(i_buildNumber)); } else if (buildValue == min_local) { i_min_local.add(builds.get(i_buildNumber)); } else if (buildValue == max_local) { i_max_local.add(builds.get(i_buildNumber)); } } } } // Generate the content of the table for (int i_buildNumber= builds.size() - 1; i_buildNumber >= 0; i_buildNumber--) { if (buildValues.containsKey(builds.get(i_buildNumber))) { Float buildValue = buildValues.get(builds.get(i_buildNumber)); if (i_min_local.contains(builds.get(i_buildNumber))) { strb.append("<td " + tdClass + "><font color=\"red\">" + buildValue + "</font></td>\n"); } else if (i_max_local.contains(builds.get(i_buildNumber))) { strb.append("<td " + tdClass + "><font color=\"blue\">" + buildValue + "</font></td>\n"); } else { strb.append("<td " + tdClass + ">" + buildValue + "</td>\n"); } } else { strb.append("<td " + tdClass + ">-</td>\n"); } } strb.append("</tr>\n"); if (iteratorOnCombinations.hasNext()) { strb.append("<tr"); strb .append(" onMouseOver=\"this.style.backgroundColor='#FFCC66';\""); strb .append(" onMouseOut=\"this.style.backgroundColor='#FFFFFF';\""); strb.append(">\n"); } } } return strb.toString(); } /** * Format datas to optimize the html generation * * @return the data formated Map<StatsName,Map<Combination, * List<StatsValue>>> */ private Map<String, Map<String, Map<Integer,Float>>> getStaticticsValues( List<Integer> builds) { // Data container Map<String, Map<String, Map<Integer,Float>>> values = new TreeMap<String, Map<String, Map<Integer, Float>>>(); for (int i_build = 0; i_build < builds.size(); i_build++) { MatrixBuild build = this.project.getBuildByNumber(builds .get(i_build)); // Get the report from this build MatrixTestReportAction reportAction = build .getAction(MatrixTestReportAction.class); // Get the matrix build report PerfPublisherMatrixBuild matrixBuild = reportAction .getMatrixBuild(); if (matrixBuild != null) { List<PerfPublisherMatrixSubBuild> subBuilds = matrixBuild .getSubBuilds(); for (int i_subBuild = 0; i_subBuild < subBuilds.size(); i_subBuild++) { // Get the report associated to a specific subBuild PerfPublisherMatrixSubBuild subBuild = subBuilds .get(i_subBuild); // Compute the number of test values = updateStatistics(values, "0 - Number of test", subBuild.getStringCombinations(), build.getNumber(), subBuild .getReport().getNumberOfTest() + 0f); // Compute the number of executed test values = updateStatistics(values, "1 - Number of executed test", subBuild .getStringCombinations(), build.getNumber(), subBuild .getReport().getNumberOfExecutedTest() + 0f); // Compute the number of not executed test values = updateStatistics( values, "2 - Number of not executed test", subBuild.getStringCombinations(),build.getNumber(), subBuild.getReport().getNumberOfNotExecutedTest() + 0f); // Compute the number of successfull test values = updateStatistics(values, "3 - Number of passed test", subBuild .getStringCombinations(), build.getNumber(), subBuild .getReport().getNumberOfPassedTest() + 0f); // Compute the number of failed test values = updateStatistics(values, "4 - Number of failed test", subBuild .getStringCombinations(), build.getNumber(), subBuild .getReport().getNumberOfFailedTest() + 0f); // Compute the average of compile time values = updateStatistics(values, "5 - Average of compile time", subBuild .getStringCombinations(), build.getNumber(), (float) subBuild .getReport().getAverageOfCompileTime()); // Compute the average of execution time values = updateStatistics(values, "6 - Average of execution time", subBuild .getStringCombinations(), build.getNumber(), (float) subBuild .getReport().getAverageOfExecutionTime()); // Compute the average of performance values = updateStatistics(values, "7 - Average of performance", subBuild .getStringCombinations(), build.getNumber(), (float) subBuild .getReport().getAverageOfPerformance()); // Compute the number of files values = updateStatistics(values, "8 - Number of files", subBuild.getStringCombinations(), build.getNumber(), subBuild .getReport().getNumberOfFiles() + 0f); } } } return values; } /** * Insert into the map the current statistics values * * @param values * the representation of datas * @param statsName * statistic name like "Number of test" * @param stringCombinations * combination which has generated the stats * @param statsValue * statistic value associated to the statsName * @return the current map added with the current stats value */ private Map<String, Map<String, Map<Integer,Float>>> updateStatistics( Map<String, Map<String, Map<Integer,Float>>> values, String statsName, String stringCombinations, int buildNumber, float statsValue) { if (values.containsKey(statsName)) { Map<String, Map<Integer,Float>> tmp2 = values.get(statsName); if (tmp2.containsKey(stringCombinations)) { Map<Integer,Float> tmp_float = tmp2.get(stringCombinations); tmp_float.put(buildNumber, statsValue); tmp2.put(stringCombinations, tmp_float); values.put(statsName, tmp2); } else { Map<Integer, Float> tmp_float = new HashMap<Integer, Float>(); tmp_float.put(buildNumber, statsValue); tmp2.put(stringCombinations, tmp_float); values.put(statsName, tmp2); } } else { Map<Integer, Float> tmp_float = new HashMap<Integer, Float>(); tmp_float.put(buildNumber, statsValue); Map<String, Map<Integer, Float>> tmp2 = new HashMap<String, Map<Integer, Float>>(); tmp2.put(stringCombinations, tmp_float); values.put(statsName, tmp2); } return values; } public void doSuccessGraph(StaplerRequest request, StaplerResponse response) throws IOException { ChartUtil.generateGraph(request, response, createSuccessGraph(), 800, 250); } public void doMiniSuccessGraph(StaplerRequest request, StaplerResponse response) throws IOException { ChartUtil.generateGraph(request, response, createSuccessGraph(), 350, 1000); } private JFreeChart createSuccessGraph() { DataSetBuilder<String, NumberOnlyBuildLabel> builder = new DataSetBuilder<String, NumberOnlyBuildLabel>(); for (Object build : project.getBuilds()) { AbstractBuild abstractBuild = (AbstractBuild) build; if (!abstractBuild.isBuilding() && abstractBuild.getResult().isBetterOrEqualTo( Result.UNSTABLE)) { MatrixTestReportAction action = abstractBuild .getAction(MatrixTestReportAction.class); if (action != null && action.getMatrixBuild() != null && action.getMatrixBuild().getSubBuilds() != null) { List<PerfPublisherMatrixSubBuild> subBuilds = action .getMatrixBuild().getSubBuilds(); Collections.sort(subBuilds); for (int i = 0; i < subBuilds.size(); i++) { builder.add(subBuilds.get(i).getReport() .getNumberOfExecutedTest(), subBuilds.get(i) .getStringCombinations(), new NumberOnlyBuildLabel(abstractBuild)); } } } } JFreeChart chart = ChartFactory.createStackedAreaChart( "Evolution of tests success", "Build", "Number of test", builder.build(), PlotOrientation.VERTICAL, true, true, false); chart.setBackgroundPaint(Color.WHITE); CategoryPlot plot = chart.getCategoryPlot(); plot.setBackgroundPaint(Color.WHITE); plot.setOutlinePaint(null); plot.setForegroundAlpha(0.8f); 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); CategoryItemRenderer renderer = plot.getRenderer(); renderer.setSeriesPaint(2, ColorPalette.BLUE); renderer.setSeriesPaint(1, ColorPalette.RED); renderer.setSeriesPaint(0, ColorPalette.YELLOW); NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); // crop extra space around the graph plot.setInsets(new RectangleInsets(0, 0, 0, 5.0)); return chart; } private boolean shouldReloadGraph(StaplerRequest request, StaplerResponse response) throws IOException { return shouldReloadGraph(request, response, project .getLastSuccessfulBuild()); } }