/* * JBoss, Home of Professional Open Source * Copyright [2011], Red Hat, Inc., and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ package org.modeshape.report; import org.modeshape.jcr.perftests.StatisticalData; import java.awt.*; import java.util.*; import java.util.List; import java.util.concurrent.TimeUnit; /** * Class which generates a report with box charts for each of the ran tests, comparing the different values for the different * repositories. * * @author Horia Chiorean */ public final class GoogleBoxChartReport extends SingleAggregatedReport { protected static final Map<String, String> REPOSITORY_COLOR_MAP = new HashMap<String, String>(); private static final Random RANDOM = new Random(); private static final String REPORT_NAME = "google/google-box-chart.html"; private static final String TEMPLATE_NAME = "google/google-box-chart-template.ftl"; @Override Map<String, ?> getTemplateModel( Map<String, Map<String, List<Double>>> aggregateDataMap, TimeUnit timeUnit ) { Map<String, String> chartUrlsMap = new HashMap<String, String>(); for (String testName : aggregateDataMap.keySet()) { GoogleBoxChart chart = createChartForTest(testName, aggregateDataMap.get(testName), timeUnit); chartUrlsMap.put(testName, chart.toUrl()); } Map<String, Object> templateModel = new HashMap<String, Object>(); templateModel.put("chartUrlsMap", chartUrlsMap); templateModel.put("title", "Performance Report"); return templateModel; } @Override String getReportFilename() { return REPORT_NAME; } @Override String getTemplateFilename() { return TEMPLATE_NAME; } private GoogleBoxChart createChartForTest( String testName, Map<String, List<Double>> repositoriesValuesMap, TimeUnit timeUnit ) { GoogleBoxChart boxChart = new GoogleBoxChart(400, 500, testName + "(" + timeUnit.toString().toLowerCase() + ")"); for (String repositoryName : repositoriesValuesMap.keySet()) { boxChart.addDataForRepository(repositoryName, repositoriesValuesMap.get(repositoryName)); } boxChart.generateChartValues(); return boxChart; } protected static String colorHex( Color color ) { return Integer.toHexString((color.getRGB() & 0xffffff) | 0x1000000).substring(1).toUpperCase(); } protected static String randomColorHex() { return colorHex(new Color(RANDOM.nextInt(256), RANDOM.nextInt(256), RANDOM.nextInt(256))); } private class GoogleBoxChart { private static final String MULTI_VALUE_SEPARATOR = ","; private static final String SERIES_SEPARATOR = "|"; private static final String CHART_BOUNDARY_GUARD = "-1"; private static final String BAR_WIDTH = "30"; private String size; private String title; private StringBuilder minSeries = new StringBuilder(CHART_BOUNDARY_GUARD).append(MULTI_VALUE_SEPARATOR); private StringBuilder lowerQuartileSeries = new StringBuilder(CHART_BOUNDARY_GUARD).append(MULTI_VALUE_SEPARATOR); private StringBuilder medianSeries = new StringBuilder(CHART_BOUNDARY_GUARD).append(MULTI_VALUE_SEPARATOR); private StringBuilder upperQuartileSeries = new StringBuilder(CHART_BOUNDARY_GUARD).append(MULTI_VALUE_SEPARATOR); private StringBuilder maxSeries = new StringBuilder(CHART_BOUNDARY_GUARD).append(MULTI_VALUE_SEPARATOR); private StringBuilder seriesDisplay = new StringBuilder(); private StringBuilder labels = new StringBuilder(); private StringBuilder labelColors = new StringBuilder(); private Map<String, StatisticalData> repositoryData = new HashMap<String, StatisticalData>(); GoogleBoxChart( int width, int height, String title ) { this.size = String.valueOf(width) + "x" + String.valueOf(height); this.title = title; } void addDataForRepository( String repositoryName, List<Double> values ) { repositoryData.put(repositoryName, new StatisticalData(values)); } protected void generateChartValues() { int seriesIndex = 1; for (Iterator<String> it = repositoryData.keySet().iterator(); it.hasNext(); ) { String repositoryName = it.next(); StatisticalData statisticalData = repositoryData.get(repositoryName); appendSeriesValue(minSeries, statisticalData.min(), !it.hasNext()); appendSeriesValue(lowerQuartileSeries, statisticalData.lowerQuartile(), !it.hasNext()); appendSeriesValue(medianSeries, statisticalData.median(), !it.hasNext()); appendSeriesValue(upperQuartileSeries, statisticalData.upperQuartile(), !it.hasNext()); appendSeriesValue(maxSeries, statisticalData.max(), !it.hasNext()); String repositoryColor = getRepositoryColor(repositoryName); appendMultipleValues(seriesDisplay, true, "F", repositoryColor, "0", String.valueOf(seriesIndex), BAR_WIDTH); labels.append(repositoryName).append(SERIES_SEPARATOR); labelColors.append(repositoryColor).append(MULTI_VALUE_SEPARATOR); seriesIndex++; } String seriesRange = "1:" + String.valueOf(repositoryData.size()); String markerWidth = "1:" + BAR_WIDTH; String minColor = colorHex(Color.CYAN); appendMultipleValues(seriesDisplay, true, "H", minColor, "0", seriesRange, markerWidth); labels.append("Min").append(SERIES_SEPARATOR); labelColors.append(minColor).append(MULTI_VALUE_SEPARATOR); String maxColor = colorHex(Color.BLUE); appendMultipleValues(seriesDisplay, true, "H", maxColor, "3", seriesRange, markerWidth); labels.append("Max").append(SERIES_SEPARATOR); labelColors.append(maxColor).append(MULTI_VALUE_SEPARATOR); String medianColor = colorHex(Color.BLACK); appendMultipleValues(seriesDisplay, false, "H", medianColor, "4", seriesRange, markerWidth); labels.append("Median"); labelColors.append(medianColor); } private String getRepositoryColor( String repositoryName ) { String repositoryColor = REPOSITORY_COLOR_MAP.get(repositoryName); if (repositoryColor == null) { repositoryColor = randomColorHex(); REPOSITORY_COLOR_MAP.put(repositoryName, repositoryColor); } return repositoryColor; } private void appendSeriesValue( StringBuilder seriesBuilder, double value, boolean isLastValue ) { if (Double.NaN == value) { seriesBuilder.append("-1"); } else { seriesBuilder.append(String.valueOf(value)); } seriesBuilder.append(MULTI_VALUE_SEPARATOR); if (isLastValue) { seriesBuilder.append(CHART_BOUNDARY_GUARD); } } private void appendMultipleValues( StringBuilder holder, boolean hasNextSeries, String... values ) { for (int i = 0; i < values.length; i++) { holder.append(values[i]); if (i < values.length - 1) { holder.append(MULTI_VALUE_SEPARATOR); } } if (hasNextSeries) { holder.append(SERIES_SEPARATOR); } } String toUrl() { StringBuilder urlBuilder = new StringBuilder("https://chart.googleapis.com/chart?"); urlBuilder.append("chtt=").append(title);//chart title urlBuilder.append("&chs=").append(size);//chart size urlBuilder.append("&chds=a");//autoscale axis urlBuilder.append("&cht=ls");//don't show axis lines urlBuilder.append("&chd=t0:") .append(minSeries).append(SERIES_SEPARATOR) .append(upperQuartileSeries).append(SERIES_SEPARATOR) .append(lowerQuartileSeries).append(SERIES_SEPARATOR) .append(maxSeries).append(SERIES_SEPARATOR) .append(medianSeries);//chart series urlBuilder.append("&chm=").append(seriesDisplay); //ui settings for each series (color, width etc) urlBuilder.append("&chxt=y"); //axes to display urlBuilder.append("&chdl=").append(labels); //labels urlBuilder.append("&chco=").append(labelColors); //label colors return urlBuilder.toString(); } } }