com.google.gwt.benchmarks.viewer.server.ReportImageServer.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.benchmarks.viewer.server.ReportImageServer.java

Source

/*
 * 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);
    }
}