| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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 com.google.gwt.benchmarks.viewer.server; |
| |
| import com.google.gwt.benchmarks.viewer.client.Benchmark; |
| import com.google.gwt.benchmarks.viewer.client.BrowserInfo; |
| import com.google.gwt.benchmarks.viewer.client.Category; |
| import com.google.gwt.benchmarks.viewer.client.Report; |
| import com.google.gwt.benchmarks.viewer.client.Result; |
| import com.google.gwt.benchmarks.viewer.client.Trial; |
| |
| import org.jfree.chart.ChartFactory; |
| import org.jfree.chart.JFreeChart; |
| import org.jfree.chart.axis.ValueAxis; |
| import org.jfree.chart.encoders.EncoderUtil; |
| import org.jfree.chart.encoders.ImageFormat; |
| import org.jfree.chart.plot.CategoryPlot; |
| import org.jfree.chart.plot.DefaultDrawingSupplier; |
| import org.jfree.chart.plot.DrawingSupplier; |
| import org.jfree.chart.plot.Plot; |
| import org.jfree.chart.plot.PlotOrientation; |
| import org.jfree.chart.plot.XYPlot; |
| import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; |
| import org.jfree.data.category.DefaultCategoryDataset; |
| import org.jfree.data.xy.XYSeries; |
| import org.jfree.data.xy.XYSeriesCollection; |
| |
| import java.awt.BasicStroke; |
| import java.awt.Color; |
| import java.awt.Font; |
| import java.awt.GradientPaint; |
| import java.awt.Polygon; |
| import java.awt.Shape; |
| import java.awt.geom.Ellipse2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.image.BufferedImage; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.URLDecoder; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| /** |
| * Serves up report images for the ReportViewer application. Generates the |
| * charts/graphs which contain the benchmarking data for a report. |
| * |
| * <p> |
| * This servlet requires the name of the report file, the category, the |
| * benchmark class, the test method, and the browser agent. |
| * <p> |
| * |
| * <p> |
| * An Example URI: |
| * |
| * <pre> |
| * /com.google.gwt.junit.viewer.ReportViewer/test_images/ |
| * report-12345.xml/ |
| * RemoveCategory/ |
| * com.google.gwt.junit.client.ArrayListAndVectorBenchmark/ |
| * testArrayListRemoves/ |
| * Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050920/ |
| * </pre> |
| * |
| * </p> |
| */ |
| public class ReportImageServer extends HttpServlet { |
| |
| private static final String charset = "UTF-8"; |
| |
| @Override |
| public void doGet(HttpServletRequest request, HttpServletResponse response) { |
| try { |
| handleRequest(request, response); |
| } catch (Exception e) { |
| if (e.getClass().getName().endsWith(".ClientAbortException") |
| || e.getClass().getName().endsWith(".EofException")) { |
| // No big deal, the client browser terminated a download. |
| } else { |
| logException("An error occured while trying to create the chart.", e, |
| response); |
| } |
| return; |
| } |
| } |
| |
| private JFreeChart createChart(String testName, Result result, String title, |
| List<Result> comparativeResults) { |
| |
| // Find the maximum values across both axes for all of the results |
| // (this chart's own results, plus all comparative results). |
| // |
| // This is a stop-gap solution that helps us compare different charts for |
| // the same benchmark method (usually with different user agents) until we |
| // get real comparative functionality in version two. |
| |
| double maxTime = 0; |
| |
| for (Result r : comparativeResults) { |
| for (Trial t : r.getTrials()) { |
| maxTime = Math.max(maxTime, t.getRunTimeMillis()); |
| } |
| } |
| |
| // Determine the number of variables in this benchmark method |
| List<Trial> trials = result.getTrials(); |
| Trial firstTrial = new Trial(); |
| int numVariables = 0; |
| if (trials.size() > 0) { |
| firstTrial = trials.get(0); |
| numVariables = firstTrial.getVariables().size(); |
| } |
| |
| // Display the trial data. |
| // |
| // First, pick the domain and series variables for our graph. |
| // Right now we only handle up to two "user" variables. |
| // We set the domain variable to the be the one containing the most unique |
| // values. |
| // This might be easier if the results had meta information telling us |
| // how many total variables there are, what types they are of, etc.... |
| |
| String domainVariable = null; |
| String seriesVariable = null; |
| |
| Map<String, Set<String>> variableValues = null; |
| |
| if (numVariables == 1) { |
| domainVariable = firstTrial.getVariables().keySet().iterator().next(); |
| } else { |
| // TODO(tobyr): Do something smarter, like allow the user to specify which |
| // variables are domain and series, along with the variables which are |
| // held constant. |
| |
| variableValues = new HashMap<String, Set<String>>(); |
| |
| for (int i = 0; i < trials.size(); ++i) { |
| Trial trial = trials.get(i); |
| Map<String, String> variables = trial.getVariables(); |
| |
| for (Map.Entry<String, String> entry : variables.entrySet()) { |
| String variable = entry.getKey(); |
| Set<String> set = variableValues.get(variable); |
| if (set == null) { |
| set = new TreeSet<String>(); |
| variableValues.put(variable, set); |
| } |
| set.add(entry.getValue()); |
| } |
| } |
| |
| TreeMap<Integer, List<String>> numValuesMap = new TreeMap<Integer, List<String>>(); |
| |
| for (Map.Entry<String, Set<String>> entry : variableValues.entrySet()) { |
| Integer numValues = new Integer(entry.getValue().size()); |
| List<String> variables = numValuesMap.get(numValues); |
| if (variables == null) { |
| variables = new ArrayList<String>(); |
| numValuesMap.put(numValues, variables); |
| } |
| variables.add(entry.getKey()); |
| } |
| |
| if (numValuesMap.values().size() > 0) { |
| domainVariable = numValuesMap.get(numValuesMap.lastKey()).get(0); |
| seriesVariable = numValuesMap.get(numValuesMap.firstKey()).get(0); |
| } |
| } |
| |
| String valueTitle = "time (ms)"; // This axis is time across all charts. |
| |
| if (numVariables == 0) { |
| // Show a bar graph, with a single centered simple bar |
| // 0 variables means there is only 1 trial |
| DefaultCategoryDataset data = new DefaultCategoryDataset(); |
| data.addValue(firstTrial.getRunTimeMillis(), "result", "result"); |
| |
| JFreeChart chart = ChartFactory.createBarChart(title, testName, |
| valueTitle, data, PlotOrientation.VERTICAL, false, false, false); |
| CategoryPlot p = chart.getCategoryPlot(); |
| ValueAxis axis = p.getRangeAxis(); |
| axis.setUpperBound(maxTime + maxTime * 0.1); |
| return chart; |
| } else if (numVariables == 1) { |
| |
| // Show a line graph with only 1 series |
| // Or.... choose between a line graph and a bar graph depending upon |
| // whether the type of the domain is numeric. |
| |
| XYSeriesCollection data = new XYSeriesCollection(); |
| |
| XYSeries series = new XYSeries(domainVariable); |
| |
| for (Trial trial : trials) { |
| double time = trial.getRunTimeMillis(); |
| String domainValue = trial.getVariables().get(domainVariable); |
| series.add(Double.parseDouble(domainValue), time); |
| } |
| |
| data.addSeries(series); |
| |
| JFreeChart chart = ChartFactory.createXYLineChart(title, domainVariable, |
| valueTitle, data, PlotOrientation.VERTICAL, false, false, false); |
| XYPlot plot = chart.getXYPlot(); |
| plot.getRangeAxis().setUpperBound(maxTime + maxTime * 0.1); |
| double maxDomainValue = getMaxValue(comparativeResults, domainVariable); |
| plot.getDomainAxis().setUpperBound(maxDomainValue + maxDomainValue * 0.1); |
| return chart; |
| } else if (numVariables == 2) { |
| // Show a line graph with two series |
| XYSeriesCollection data = new XYSeriesCollection(); |
| |
| Set<String> seriesValues = variableValues.get(seriesVariable); |
| |
| for (String seriesValue : seriesValues) { |
| XYSeries series = new XYSeries(seriesValue); |
| |
| for (Trial trial : trials) { |
| Map<String, String> variables = trial.getVariables(); |
| if (variables.get(seriesVariable).equals(seriesValue)) { |
| double time = trial.getRunTimeMillis(); |
| String domainValue = trial.getVariables().get(domainVariable); |
| series.add(Double.parseDouble(domainValue), time); |
| } |
| } |
| data.addSeries(series); |
| } |
| // TODO(tobyr) - Handle graphs above 2 variables |
| |
| JFreeChart chart = ChartFactory.createXYLineChart(title, domainVariable, |
| valueTitle, data, PlotOrientation.VERTICAL, true, true, false); |
| XYPlot plot = chart.getXYPlot(); |
| plot.getRangeAxis().setUpperBound(maxTime + maxTime * 0.1); |
| double maxDomainValue = getMaxValue(comparativeResults, domainVariable); |
| plot.getDomainAxis().setUpperBound(maxDomainValue + maxDomainValue * 0.1); |
| return chart; |
| } |
| |
| throw new RuntimeException("The ReportImageServer is not yet able to " |
| + "create charts for benchmarks with more than two variables."); |
| |
| // Sample JFreeChart code for creating certain charts: |
| // Leaving this around until we can handle multivariate charts in dimensions |
| // greater than two. |
| |
| // Code for creating a category data set - probably better with a bar chart |
| // instead of line chart |
| /* |
| * DefaultCategoryDataset data = new DefaultCategoryDataset(); String series = |
| * domainVariable; |
| * |
| * for ( Iterator it = trials.iterator(); it.hasNext(); ) { Trial trial = |
| * (Trial) it.next(); double time = trial.getRunTimeMillis(); String |
| * domainValue = (String) trial.getVariables().get( domainVariable ); |
| * data.addValue( time, series, domainValue ); } |
| * |
| * String title = ""; String categoryTitle = domainVariable; PlotOrientation |
| * orientation = PlotOrientation.VERTICAL; |
| * |
| * chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle, |
| * data, orientation, true, true, false ); |
| */ |
| |
| /* |
| * DefaultCategoryDataset data = new DefaultCategoryDataset(); String |
| * series1 = "firefox"; String series2 = "ie"; |
| * |
| * data.addValue( 1.0, series1, "1024"); data.addValue( 2.0, series1, |
| * "2048"); data.addValue( 4.0, series1, "4096"); data.addValue( 8.0, |
| * series1, "8192"); |
| * |
| * data.addValue( 2.0, series2, "1024"); data.addValue( 4.0, series2, |
| * "2048"); data.addValue( 8.0, series2, "4096"); data.addValue( 16.0, |
| * series2,"8192"); |
| * |
| * String title = ""; String categoryTitle = "size"; PlotOrientation |
| * orientation = PlotOrientation.VERTICAL; |
| * |
| * chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle, |
| * data, orientation, true, true, false ); |
| */ |
| } |
| |
| private Benchmark getBenchmarkByName(List<Benchmark> benchmarks, String name) { |
| for (Benchmark benchmark : benchmarks) { |
| if (benchmark.getName().equals(name)) { |
| return benchmark; |
| } |
| } |
| return null; |
| } |
| |
| private Category getCategoryByName(List<Category> categories, |
| String categoryName) { |
| for (Category category : categories) { |
| if (category.getName().equals(categoryName)) { |
| return category; |
| } |
| } |
| return null; |
| } |
| |
| private DrawingSupplier getDrawingSupplier() { |
| Color[] colors = new Color[] {new Color(176, 29, 29, 175), // dark red |
| new Color(10, 130, 86, 175), // dark green |
| new Color(8, 26, 203, 175), // dark blue |
| new Color(145, 162, 66, 175), // light pea green |
| new Color(196, 140, 6, 175), // sienna |
| }; |
| |
| float size = 8; |
| float offset = size / 2; |
| |
| int iOffset = (int) offset; |
| |
| Shape square = new Rectangle2D.Double(-offset, -offset, size, size); |
| Shape circle = new Ellipse2D.Double(-offset, -offset, size, size); |
| Shape triangle = new Polygon(new int[] {0, iOffset, -iOffset}, new int[] { |
| -iOffset, iOffset, iOffset}, 3); |
| Shape diamond = new Polygon(new int[] {0, iOffset, 0, -iOffset}, new int[] { |
| -iOffset, 0, iOffset, 0}, 4); |
| Shape ellipse = new Ellipse2D.Double(-offset, -offset / 2, size, size / 2); |
| |
| return new DefaultDrawingSupplier(colors, |
| DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE, |
| DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE, |
| DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE, new Shape[] { |
| circle, square, triangle, diamond, ellipse}); |
| } |
| |
| private double getMaxValue(List<Result> results, String variable) { |
| double value = 0.0; |
| |
| for (int i = 0; i < results.size(); ++i) { |
| Result r = results.get(i); |
| List<Trial> resultTrials = r.getTrials(); |
| |
| for (int j = 0; j < resultTrials.size(); ++j) { |
| Trial t = resultTrials.get(j); |
| Map<String, String> variables = t.getVariables(); |
| value = Math.max(value, Double.parseDouble(variables.get(variable))); |
| } |
| } |
| |
| return value; |
| } |
| |
| private Result getResultsByAgent(List<Result> results, String agent) { |
| for (Object element : results) { |
| Result result = (Result) element; |
| if (result.getAgent().equals(agent)) { |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| private void handleRequest(HttpServletRequest request, |
| HttpServletResponse response) throws IOException { |
| |
| String uri = request.getRequestURI(); |
| String requestString = uri.split("test_images/")[1]; |
| String[] requestParams = requestString.split("/"); |
| |
| String reportName = URLDecoder.decode(requestParams[0], charset); |
| String categoryName = URLDecoder.decode(requestParams[1], charset); |
| // String className = URLDecoder.decode(requestParams[2], charset); |
| String testName = URLDecoder.decode(requestParams[3], charset); |
| String agent = URLDecoder.decode(requestParams[4], charset); |
| |
| ReportDatabase db = ReportDatabase.getInstance(); |
| Report report = db.getReport(reportName); |
| List<Category> categories = report.getCategories(); |
| Category category = getCategoryByName(categories, categoryName); |
| List<Benchmark> benchmarks = category.getBenchmarks(); |
| Benchmark benchmark = getBenchmarkByName(benchmarks, testName); |
| List<Result> results = benchmark.getResults(); |
| Result result = getResultsByAgent(results, agent); |
| |
| String title = BrowserInfo.getBrowser(agent); |
| JFreeChart chart = createChart(testName, result, title, results); |
| |
| chart.getTitle().setFont(Font.decode("Verdana BOLD 12")); |
| chart.setAntiAlias(true); |
| chart.setBorderVisible(true); |
| chart.setBackgroundPaint(new Color(241, 241, 241)); |
| |
| Plot plot = chart.getPlot(); |
| |
| plot.setDrawingSupplier(getDrawingSupplier()); |
| plot.setBackgroundPaint(new GradientPaint(0, 0, Color.white, 640, 480, |
| new Color(200, 200, 200))); |
| |
| if (plot instanceof XYPlot) { |
| XYPlot xyplot = (XYPlot) plot; |
| Font labelFont = Font.decode("Verdana PLAIN"); |
| xyplot.getDomainAxis().setLabelFont(labelFont); |
| xyplot.getRangeAxis().setLabelFont(labelFont); |
| org.jfree.chart.renderer.xy.XYItemRenderer xyitemrenderer = xyplot.getRenderer(); |
| xyitemrenderer.setStroke(new BasicStroke(4)); |
| if (xyitemrenderer instanceof XYLineAndShapeRenderer) { |
| XYLineAndShapeRenderer xylineandshaperenderer = (XYLineAndShapeRenderer) xyitemrenderer; |
| xylineandshaperenderer.setShapesVisible(true); |
| xylineandshaperenderer.setShapesFilled(true); |
| } |
| } |
| |
| // Try to fit all the graphs into a 1024 window, with a min of 240 and a max |
| // of 480 |
| final int graphWidth = Math.max(240, Math.min(480, |
| (1024 - 10 * results.size()) / results.size())); |
| BufferedImage img = chart.createBufferedImage(graphWidth, 240); |
| byte[] image = EncoderUtil.encode(img, ImageFormat.PNG); |
| |
| // The images have unique URLs; might as well set them to never expire. |
| response.setHeader("Cache-Control", "max-age=0"); |
| response.setHeader("Expires", "Fri, 2 Jan 1970 00:00:00 GMT"); |
| response.setContentType("image/png"); |
| response.setContentLength(image.length); |
| |
| OutputStream output = response.getOutputStream(); |
| output.write(image); |
| } |
| |
| private void logException(String msg, Exception e, |
| HttpServletResponse response) { |
| ServletContext servletContext = getServletContext(); |
| servletContext.log(msg, e); |
| response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| } |
| } |