001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.junit.viewer.server;
017:
018: import com.google.gwt.junit.viewer.client.Benchmark;
019: import com.google.gwt.junit.viewer.client.BrowserInfo;
020: import com.google.gwt.junit.viewer.client.Category;
021: import com.google.gwt.junit.viewer.client.Report;
022: import com.google.gwt.junit.viewer.client.Result;
023: import com.google.gwt.junit.viewer.client.Trial;
024:
025: import org.jfree.chart.ChartFactory;
026: import org.jfree.chart.JFreeChart;
027: import org.jfree.chart.axis.ValueAxis;
028: import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
029: import org.jfree.chart.encoders.EncoderUtil;
030: import org.jfree.chart.encoders.ImageFormat;
031: import org.jfree.chart.plot.PlotOrientation;
032: import org.jfree.chart.plot.XYPlot;
033: import org.jfree.chart.plot.Plot;
034: import org.jfree.chart.plot.DrawingSupplier;
035: import org.jfree.chart.plot.DefaultDrawingSupplier;
036: import org.jfree.chart.plot.CategoryPlot;
037: import org.jfree.data.category.DefaultCategoryDataset;
038: import org.jfree.data.xy.XYSeries;
039: import org.jfree.data.xy.XYSeriesCollection;
040:
041: import java.awt.image.BufferedImage;
042: import java.awt.Color;
043: import java.awt.Font;
044: import java.awt.GradientPaint;
045: import java.awt.BasicStroke;
046: import java.awt.Shape;
047: import java.awt.Polygon;
048: import java.awt.geom.Rectangle2D;
049: import java.awt.geom.Ellipse2D;
050: import java.io.ByteArrayInputStream;
051: import java.io.IOException;
052: import java.io.InputStream;
053: import java.io.OutputStream;
054: import java.net.URLDecoder;
055: import java.util.ArrayList;
056: import java.util.HashMap;
057: import java.util.Iterator;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.Set;
061: import java.util.TreeMap;
062: import java.util.TreeSet;
063:
064: import javax.servlet.ServletContext;
065: import javax.servlet.http.HttpServlet;
066: import javax.servlet.http.HttpServletRequest;
067: import javax.servlet.http.HttpServletResponse;
068:
069: /**
070: * Serves up report images for the ReportViewer application. Generates the
071: * charts/graphs which contain the benchmarking data for a report.
072: *
073: * <p>
074: * This servlet requires the name of the report file, the category, the
075: * benchmark class, the test method, and the browser agent.
076: * <p>
077: *
078: * <p>
079: * An Example URI:
080: *
081: * <pre>
082: * /com.google.gwt.junit.viewer.ReportViewer/test_images/
083: * report-12345.xml/
084: * RemoveCategory/
085: * com.google.gwt.junit.client.ArrayListAndVectorBenchmark/
086: * testArrayListRemoves/
087: * Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.12) Gecko/20050920/
088: * </pre>
089: *
090: * </p>
091: */
092: public class ReportImageServer extends HttpServlet {
093:
094: private static final String charset = "UTF-8";
095:
096: private static void copy(InputStream in, OutputStream out)
097: throws IOException {
098: byte[] buf = new byte[512];
099:
100: while (true) {
101: int bytesRead = in.read(buf);
102: if (bytesRead == -1) {
103: break;
104: }
105: out.write(buf, 0, bytesRead);
106: }
107: }
108:
109: public void doGet(HttpServletRequest request,
110: HttpServletResponse response) {
111: try {
112: handleRequest(request, response);
113: } catch (Exception e) {
114: logException(
115: "An error occured while trying to create the chart.",
116: e, response);
117: return;
118: }
119: }
120:
121: private JFreeChart createChart(String testName, Result result,
122: String title, List comparativeResults) {
123:
124: // Find the maximum values across both axes for all of the results
125: // (this chart's own results, plus all comparative results).
126: //
127: // This is a stop-gap solution that helps us compare different charts for
128: // the same benchmark method (usually with different user agents) until we
129: // get real comparative functionality in version two.
130:
131: double maxTime = 0;
132:
133: for (int i = 0; i < comparativeResults.size(); ++i) {
134: Result r = (Result) comparativeResults.get(i);
135: List resultTrials = r.getTrials();
136:
137: for (int j = 0; j < resultTrials.size(); ++j) {
138: Trial t = (Trial) resultTrials.get(j);
139: maxTime = Math.max(maxTime, t.getRunTimeMillis());
140: }
141: }
142:
143: // Determine the number of variables in this benchmark method
144: List trials = result.getTrials();
145: Trial firstTrial = (Trial) trials.get(0);
146: int numVariables = firstTrial.getVariables().size();
147:
148: // Display the trial data.
149: //
150: // First, pick the domain and series variables for our graph.
151: // Right now we only handle up to two "user" variables.
152: // We set the domain variable to the be the one containing the most unique
153: // values.
154: // This might be easier if the results had meta information telling us
155: // how many total variables there are, what types they are of, etc....
156:
157: String domainVariable = null;
158: String seriesVariable = null;
159:
160: Map/* <String,Set<String>> */variableValues = null;
161:
162: if (numVariables == 1) {
163: domainVariable = (String) firstTrial.getVariables()
164: .keySet().iterator().next();
165: } else {
166: // TODO(tobyr): Do something smarter, like allow the user to specify which
167: // variables are domain and series, along with the variables which are
168: // held constant.
169:
170: variableValues = new HashMap();
171:
172: for (int i = 0; i < trials.size(); ++i) {
173: Trial trial = (Trial) trials.get(i);
174: Map variables = trial.getVariables();
175:
176: for (Iterator it = variables.entrySet().iterator(); it
177: .hasNext();) {
178: Map.Entry entry = (Map.Entry) it.next();
179: String variable = (String) entry.getKey();
180: String value = (String) entry.getValue();
181: Set set = (Set) variableValues.get(variable);
182: if (set == null) {
183: set = new TreeSet();
184: variableValues.put(variable, set);
185: }
186: set.add(value);
187: }
188: }
189:
190: TreeMap numValuesMap = new TreeMap();
191:
192: for (Iterator it = variableValues.entrySet().iterator(); it
193: .hasNext();) {
194: Map.Entry entry = (Map.Entry) it.next();
195: String variable = (String) entry.getKey();
196: Set values = (Set) entry.getValue();
197: Integer numValues = new Integer(values.size());
198: List variables = (List) numValuesMap.get(numValues);
199: if (variables == null) {
200: variables = new ArrayList();
201: numValuesMap.put(numValues, variables);
202: }
203: variables.add(variable);
204: }
205:
206: if (numValuesMap.values().size() > 0) {
207: domainVariable = (String) ((List) numValuesMap
208: .get(numValuesMap.lastKey())).get(0);
209: seriesVariable = (String) ((List) numValuesMap
210: .get(numValuesMap.firstKey())).get(0);
211: }
212: }
213:
214: String valueTitle = "time (ms)"; // This axis is time across all charts.
215:
216: if (numVariables == 0) {
217: // Show a bar graph, with a single centered simple bar
218: // 0 variables means there is only 1 trial
219: Trial trial = (Trial) trials.iterator().next();
220:
221: DefaultCategoryDataset data = new DefaultCategoryDataset();
222: data.addValue(trial.getRunTimeMillis(), "result", "result");
223:
224: JFreeChart chart = ChartFactory.createBarChart(title,
225: testName, valueTitle, data,
226: PlotOrientation.VERTICAL, false, false, false);
227: CategoryPlot p = chart.getCategoryPlot();
228: ValueAxis axis = p.getRangeAxis();
229: axis.setUpperBound(maxTime + maxTime * 0.1);
230: return chart;
231: } else if (numVariables == 1) {
232:
233: // Show a line graph with only 1 series
234: // Or.... choose between a line graph and a bar graph depending upon
235: // whether the type of the domain is numeric.
236:
237: XYSeriesCollection data = new XYSeriesCollection();
238:
239: XYSeries series = new XYSeries(domainVariable);
240:
241: for (Iterator it = trials.iterator(); it.hasNext();) {
242: Trial trial = (Trial) it.next();
243: if (trial.getException() != null) {
244: continue;
245: }
246: double time = trial.getRunTimeMillis();
247: String domainValue = (String) trial.getVariables().get(
248: domainVariable);
249: series.add(Double.parseDouble(domainValue), time);
250: }
251:
252: data.addSeries(series);
253:
254: JFreeChart chart = ChartFactory.createXYLineChart(title,
255: domainVariable, valueTitle, data,
256: PlotOrientation.VERTICAL, false, false, false);
257: XYPlot plot = chart.getXYPlot();
258: plot.getRangeAxis().setUpperBound(maxTime + maxTime * 0.1);
259: double maxDomainValue = getMaxValue(comparativeResults,
260: domainVariable);
261: plot.getDomainAxis().setUpperBound(
262: maxDomainValue + maxDomainValue * 0.1);
263: return chart;
264: } else if (numVariables == 2) {
265: // Show a line graph with two series
266: XYSeriesCollection data = new XYSeriesCollection();
267:
268: Set seriesValues = (Set) variableValues.get(seriesVariable);
269:
270: for (Iterator it = seriesValues.iterator(); it.hasNext();) {
271: String seriesValue = (String) it.next();
272: XYSeries series = new XYSeries(seriesValue);
273:
274: for (Iterator trialsIt = trials.iterator(); trialsIt
275: .hasNext();) {
276: Trial trial = (Trial) trialsIt.next();
277: if (trial.getException() != null) {
278: continue;
279: }
280: Map variables = trial.getVariables();
281: if (variables.get(seriesVariable).equals(
282: seriesValue)) {
283: double time = trial.getRunTimeMillis();
284: String domainValue = (String) trial
285: .getVariables().get(domainVariable);
286: series.add(Double.parseDouble(domainValue),
287: time);
288: }
289: }
290: data.addSeries(series);
291: }
292: // TODO(tobyr) - Handle graphs above 2 variables
293:
294: JFreeChart chart = ChartFactory.createXYLineChart(title,
295: domainVariable, valueTitle, data,
296: PlotOrientation.VERTICAL, true, true, false);
297: XYPlot plot = chart.getXYPlot();
298: plot.getRangeAxis().setUpperBound(maxTime + maxTime * 0.1);
299: double maxDomainValue = getMaxValue(comparativeResults,
300: domainVariable);
301: plot.getDomainAxis().setUpperBound(
302: maxDomainValue + maxDomainValue * 0.1);
303: return chart;
304: }
305:
306: throw new RuntimeException(
307: "The ReportImageServer is not yet able to "
308: + "create charts for benchmarks with more than two variables.");
309:
310: // Sample JFreeChart code for creating certain charts:
311: // Leaving this around until we can handle multivariate charts in dimensions
312: // greater than two.
313:
314: // Code for creating a category data set - probably better with a bar chart
315: // instead of line chart
316: /*
317: * DefaultCategoryDataset data = new DefaultCategoryDataset(); String series =
318: * domainVariable;
319: *
320: * for ( Iterator it = trials.iterator(); it.hasNext(); ) { Trial trial =
321: * (Trial) it.next(); double time = trial.getRunTimeMillis(); String
322: * domainValue = (String) trial.getVariables().get( domainVariable );
323: * data.addValue( time, series, domainValue ); }
324: *
325: * String title = ""; String categoryTitle = domainVariable; PlotOrientation
326: * orientation = PlotOrientation.VERTICAL;
327: *
328: * chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle,
329: * data, orientation, true, true, false );
330: */
331:
332: /*
333: * DefaultCategoryDataset data = new DefaultCategoryDataset(); String
334: * series1 = "firefox"; String series2 = "ie";
335: *
336: * data.addValue( 1.0, series1, "1024"); data.addValue( 2.0, series1,
337: * "2048"); data.addValue( 4.0, series1, "4096"); data.addValue( 8.0,
338: * series1, "8192");
339: *
340: * data.addValue( 2.0, series2, "1024"); data.addValue( 4.0, series2,
341: * "2048"); data.addValue( 8.0, series2, "4096"); data.addValue( 16.0,
342: * series2,"8192");
343: *
344: * String title = ""; String categoryTitle = "size"; PlotOrientation
345: * orientation = PlotOrientation.VERTICAL;
346: *
347: * chart = ChartFactory.createLineChart( title, categoryTitle, valueTitle,
348: * data, orientation, true, true, false );
349: */
350: }
351:
352: private Benchmark getBenchmarkByName(List benchmarks, String name) {
353: for (Iterator it = benchmarks.iterator(); it.hasNext();) {
354: Benchmark benchmark = (Benchmark) it.next();
355: if (benchmark.getName().equals(name)) {
356: return benchmark;
357: }
358: }
359: return null;
360: }
361:
362: private Category getCategoryByName(List categories,
363: String categoryName) {
364: for (Iterator catIt = categories.iterator(); catIt.hasNext();) {
365: Category category = (Category) catIt.next();
366: if (category.getName().equals(categoryName)) {
367: return category;
368: }
369: }
370: return null;
371: }
372:
373: private DrawingSupplier getDrawingSupplier() {
374: Color[] colors = new Color[] { new Color(176, 29, 29, 175), // dark red
375: new Color(10, 130, 86, 175), // dark green
376: new Color(8, 26, 203, 175), // dark blue
377: new Color(145, 162, 66, 175), // light pea green
378: new Color(196, 140, 6, 175), // sienna
379: };
380:
381: float size = 8;
382: float offset = size / 2;
383:
384: int iOffset = (int) offset;
385:
386: Shape square = new Rectangle2D.Double(-offset, -offset, size,
387: size);
388: Shape circle = new Ellipse2D.Double(-offset, -offset, size,
389: size);
390: Shape triangle = new Polygon(
391: new int[] { 0, iOffset, -iOffset }, new int[] {
392: -iOffset, iOffset, iOffset }, 3);
393: Shape diamond = new Polygon(
394: new int[] { 0, iOffset, 0, -iOffset }, new int[] {
395: -iOffset, 0, iOffset, 0 }, 4);
396: Shape ellipse = new Ellipse2D.Double(-offset, -offset / 2,
397: size, size / 2);
398:
399: return new DefaultDrawingSupplier(colors,
400: DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE,
401: DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE,
402: DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
403: new Shape[] { circle, square, triangle, diamond,
404: ellipse });
405: }
406:
407: private double getMaxValue(List results, String variable) {
408: double value = 0.0;
409:
410: for (int i = 0; i < results.size(); ++i) {
411: Result r = (Result) results.get(i);
412: List resultTrials = r.getTrials();
413:
414: for (int j = 0; j < resultTrials.size(); ++j) {
415: Trial t = (Trial) resultTrials.get(j);
416: Map variables = t.getVariables();
417: value = Math.max(value, Double
418: .parseDouble((String) variables.get(variable)));
419: }
420: }
421:
422: return value;
423: }
424:
425: private Result getResultsByAgent(List results, String agent) {
426: for (Iterator it = results.iterator(); it.hasNext();) {
427: Result result = (Result) it.next();
428: if (result.getAgent().equals(agent)) {
429: return result;
430: }
431: }
432: return null;
433: }
434:
435: private void handleRequest(HttpServletRequest request,
436: HttpServletResponse response) throws IOException {
437:
438: String uri = request.getRequestURI();
439: String requestString = uri.split("test_images/")[1];
440: String[] requestParams = requestString.split("/");
441:
442: String reportName = URLDecoder
443: .decode(requestParams[0], charset);
444: String categoryName = URLDecoder.decode(requestParams[1],
445: charset);
446: // String className = URLDecoder.decode(requestParams[2], charset);
447: String testName = URLDecoder.decode(requestParams[3], charset);
448: String agent = URLDecoder.decode(requestParams[4], charset);
449:
450: ReportDatabase db = ReportDatabase.getInstance();
451: Report report = db.getReport(reportName);
452: List categories = report.getCategories();
453: Category category = getCategoryByName(categories, categoryName);
454: List benchmarks = category.getBenchmarks();
455: Benchmark benchmark = getBenchmarkByName(benchmarks, testName);
456: List results = benchmark.getResults();
457: Result result = getResultsByAgent(results, agent);
458:
459: String title = BrowserInfo.getBrowser(agent);
460: JFreeChart chart = createChart(testName, result, title, results);
461:
462: chart.getTitle().setFont(Font.decode("Verdana BOLD 12"));
463: chart.setAntiAlias(true);
464: chart.setBorderVisible(true);
465: chart.setBackgroundPaint(new Color(241, 241, 241));
466:
467: Plot plot = chart.getPlot();
468:
469: plot.setDrawingSupplier(getDrawingSupplier());
470: plot.setBackgroundPaint(new GradientPaint(0, 0, Color.white,
471: 640, 480, new Color(200, 200, 200)));
472:
473: if (plot instanceof XYPlot) {
474: XYPlot xyplot = (XYPlot) plot;
475: Font labelFont = Font.decode("Verdana PLAIN");
476: xyplot.getDomainAxis().setLabelFont(labelFont);
477: xyplot.getRangeAxis().setLabelFont(labelFont);
478: org.jfree.chart.renderer.xy.XYItemRenderer xyitemrenderer = xyplot
479: .getRenderer();
480: xyitemrenderer.setStroke(new BasicStroke(4));
481: if (xyitemrenderer instanceof XYLineAndShapeRenderer) {
482: XYLineAndShapeRenderer xylineandshaperenderer = (XYLineAndShapeRenderer) xyitemrenderer;
483: xylineandshaperenderer.setShapesVisible(true);
484: xylineandshaperenderer.setShapesFilled(true);
485: }
486: }
487:
488: // Try to fit all the graphs into a 1024 window, with a min of 240 and a max
489: // of 480
490: final int graphWidth = Math.max(240, Math.min(480,
491: (1024 - 10 * results.size()) / results.size()));
492: BufferedImage img = chart.createBufferedImage(graphWidth, 240);
493: byte[] image = EncoderUtil.encode(img, ImageFormat.PNG);
494:
495: response.setContentType("image/png");
496:
497: OutputStream output = response.getOutputStream();
498: copy(new ByteArrayInputStream(image), output);
499: }
500:
501: private void logException(String msg, Exception e,
502: HttpServletResponse response) {
503: ServletContext servletContext = getServletContext();
504: servletContext.log(msg, e);
505: response
506: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
507: }
508: }
|