package nl.codecentric.jenkins.appd; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.util.ChartUtil; import hudson.util.DataSetBuilder; import hudson.util.Graph; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.PlotOrientation; import org.jfree.data.category.CategoryDataset; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import java.awt.*; import java.io.IOException; import java.util.*; import java.util.List; import java.util.logging.Logger; import static nl.codecentric.jenkins.appd.util.LocalMessages.PROJECTACTION_DISPLAYNAME; /** * The {@link Action} that will be executed from your project and fetch the AppDynamics performance * data and display after a build. * The Project Action will show the graph for overall performance from all builds. */ public class AppDynamicsProjectAction implements Action { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(AppDynamicsProjectAction.class.getName()); private static final long serialVersionUID = 1L; private static final String PLUGIN_NAME = "appdynamics-dashboard"; private final AbstractProject<?, ?> project; private final String mainMetricKey; private final String[] allMetricKeys; public AppDynamicsProjectAction(final AbstractProject project, final String mainMetricKey, final String[] allMetricKeys) { this.project = project; this.mainMetricKey = mainMetricKey; this.allMetricKeys = allMetricKeys; } public String getDisplayName() { return PROJECTACTION_DISPLAYNAME.toString(); } public String getUrlName() { return PLUGIN_NAME; } public String getIconFileName() { return "graph.gif"; } /** * Method necessary to get the side-panel included in the Jelly file * @return this {@link AbstractProject} */ public AbstractProject<?, ?> getProject() { return this.project; } public boolean isTrendVisibleOnProjectDashboard() { return getExistingReportsList().size() >= 1; } public List<String> getAvailableMetricKeys() { return Arrays.asList(allMetricKeys); } /** * Graph of metric points over time. */ public void doSummarizerGraphMainMetric(final StaplerRequest request, final StaplerResponse response) throws IOException { final Map<ChartUtil.NumberOnlyBuildLabel, Double> averagesFromReports = getAveragesFromAllReports(getExistingReportsList(), mainMetricKey); final Graph graph = new GraphImpl(mainMetricKey + " Overall Graph") { protected DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> createDataSet() { DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dataSetBuilder = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>(); for (ChartUtil.NumberOnlyBuildLabel label : averagesFromReports.keySet()) { dataSetBuilder.add(averagesFromReports.get(label), mainMetricKey, label); } return dataSetBuilder; } }; graph.doPng(request, response); } /** * Graph of metric points over time, metric to plot set as request parameter. */ public void doSummarizerGraphForMetric(final StaplerRequest request, final StaplerResponse response) throws IOException { final String metricKey = request.getParameter("metricDataKey"); final Map<ChartUtil.NumberOnlyBuildLabel, Double> averagesFromReports = getAveragesFromAllReports(getExistingReportsList(), metricKey); final Graph graph = new GraphImpl(metricKey + " Overall Graph") { protected DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> createDataSet() { DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> dataSetBuilder = new DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel>(); for (ChartUtil.NumberOnlyBuildLabel label : averagesFromReports.keySet()) { dataSetBuilder.add(averagesFromReports.get(label), metricKey, label); } return dataSetBuilder; } }; graph.doPng(request, response); } private abstract class GraphImpl extends Graph { private final String graphTitle; protected GraphImpl(final String metricKey) { super(-1, 400, 300); // cannot use timestamp, since ranges may change this.graphTitle = stripTitle(metricKey); } private String stripTitle(final String metricKey) { return metricKey.substring(metricKey.lastIndexOf("|") + 1); } protected abstract DataSetBuilder<String, ChartUtil.NumberOnlyBuildLabel> createDataSet(); protected JFreeChart createGraph() { final CategoryDataset dataset = createDataSet().build(); final JFreeChart chart = ChartFactory.createLineChart(graphTitle, // title "Build Number #", // category axis label null, // value axis label dataset, // data PlotOrientation.VERTICAL, // orientation false, // include legend true, // tooltips false // urls ); chart.setBackgroundPaint(Color.white); return chart; } } private List<AppDynamicsReport> getExistingReportsList() { final List<AppDynamicsReport> adReportList = new ArrayList<AppDynamicsReport>(); if (null == this.project) { return adReportList; } final List<? extends AbstractBuild<?, ?>> builds = project.getBuilds(); for (AbstractBuild<?, ?> currentBuild : builds) { final AppDynamicsBuildAction performanceBuildAction = currentBuild.getAction(AppDynamicsBuildAction.class); if (performanceBuildAction == null) { continue; } final AppDynamicsReport report = performanceBuildAction.getBuildActionResultsDisplay().getAppDynamicsReport(); if (report == null) { continue; } adReportList.add(report); } return adReportList; } private Map<ChartUtil.NumberOnlyBuildLabel, Double> getAveragesFromAllReports( final List<AppDynamicsReport> reports, final String metricKey) { Map<ChartUtil.NumberOnlyBuildLabel, Double> averages = new TreeMap<ChartUtil.NumberOnlyBuildLabel, Double>(); for (AppDynamicsReport report : reports) { double value = report.getAverageForMetric(metricKey); if (value >= 0) { ChartUtil.NumberOnlyBuildLabel label = new ChartUtil.NumberOnlyBuildLabel(report.getBuild()); averages.put(label, value); } } return averages; } }