KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > junit > benchmarks > BenchmarkReport


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.junit.benchmarks;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.typeinfo.HasMetaData;
20 import com.google.gwt.core.ext.typeinfo.JClassType;
21 import com.google.gwt.core.ext.typeinfo.JMethod;
22 import com.google.gwt.core.ext.typeinfo.TypeOracle;
23 import com.google.gwt.dev.util.Util;
24 import com.google.gwt.junit.client.TestResults;
25 import com.google.gwt.junit.client.Trial;
26 import com.google.gwt.junit.rebind.BenchmarkGenerator;
27 import com.google.gwt.util.tools.Utility;
28
29 import junit.framework.TestCase;
30
31 import org.eclipse.jdt.internal.compiler.IProblemFactory;
32 import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
33 import org.eclipse.jdt.internal.compiler.SourceElementParser;
34 import org.eclipse.jdt.internal.compiler.SourceElementRequestorAdapter;
35 import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
36 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
37 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
38 import org.w3c.dom.Document JavaDoc;
39 import org.w3c.dom.Element JavaDoc;
40
41 import java.io.BufferedReader JavaDoc;
42 import java.io.File JavaDoc;
43 import java.io.FileOutputStream JavaDoc;
44 import java.io.FileReader JavaDoc;
45 import java.io.IOException JavaDoc;
46 import java.text.BreakIterator JavaDoc;
47 import java.text.DateFormat JavaDoc;
48 import java.util.ArrayList JavaDoc;
49 import java.util.Date JavaDoc;
50 import java.util.HashMap JavaDoc;
51 import java.util.Iterator JavaDoc;
52 import java.util.List JavaDoc;
53 import java.util.Locale JavaDoc;
54 import java.util.Map JavaDoc;
55 import java.util.regex.Matcher JavaDoc;
56 import java.util.regex.Pattern JavaDoc;
57
58 import javax.xml.parsers.DocumentBuilder JavaDoc;
59 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
60 import javax.xml.parsers.ParserConfigurationException JavaDoc;
61
62 /**
63  * Generates a detailed report that contains the results of all of the
64  * benchmark-related unit tests executed during a unit test session. The primary
65  * user of this class is JUnitShell.
66  *
67  * The report is in XML format. To view the XML reports, use benchmarkViewer.
68  */

69 public class BenchmarkReport {
70
71   /**
72    * Converts a set of test results for a single benchmark method into XML.
73    */

74   private class BenchmarkXml {
75
76     private MetaData metaData;
77
78     private List JavaDoc/* <JUnitMessageQueue.TestResult> */results;
79
80     private TestCase test;
81
82     BenchmarkXml(TestCase test,
83         List JavaDoc/* <JUnitMessageQueue.TestResult> */results) {
84       this.test = test;
85       this.results = results;
86       Map JavaDoc/* <String,MetaData> */methodMetaData = (Map JavaDoc/* <String,MetaData> */) testMetaData.get(test.getClass().toString());
87       metaData = (MetaData) methodMetaData.get(test.getName());
88     }
89
90     Element JavaDoc toElement(Document JavaDoc doc) {
91       Element JavaDoc benchmark = doc.createElement("benchmark");
92       benchmark.setAttribute("class", test.getClass().getName());
93       benchmark.setAttribute("name", metaData.getTestName());
94       benchmark.setAttribute("description", metaData.getTestDescription());
95
96       String JavaDoc sourceCode = metaData.getSourceCode();
97       if (sourceCode != null) {
98         Element JavaDoc sourceCodeElement = doc.createElement("source_code");
99         sourceCodeElement.appendChild(doc.createTextNode(sourceCode));
100         benchmark.appendChild(sourceCodeElement);
101       }
102
103       // TODO(tobyr): create target_code element
104

105       for (Iterator JavaDoc it = results.iterator(); it.hasNext();) {
106         TestResults result = (TestResults) it.next();
107         benchmark.appendChild(toElement(doc, result));
108       }
109
110       return benchmark;
111     }
112
113     private Element JavaDoc toElement(Document JavaDoc doc, TestResults result) {
114       Element JavaDoc resultElement = doc.createElement("result");
115       resultElement.setAttribute("host", result.getHost());
116       resultElement.setAttribute("agent", result.getAgent());
117
118       List JavaDoc trials = result.getTrials();
119
120       for (Iterator JavaDoc it = trials.iterator(); it.hasNext();) {
121         Trial trial = (Trial) it.next();
122         Element JavaDoc trialElement = toElement(doc, trial);
123         resultElement.appendChild(trialElement);
124       }
125
126       return resultElement;
127     }
128
129     private Element JavaDoc toElement(Document JavaDoc doc, Trial trial) {
130       Element JavaDoc trialElement = doc.createElement("trial");
131
132       Map JavaDoc variables = trial.getVariables();
133
134       for (Iterator JavaDoc it = variables.entrySet().iterator(); it.hasNext();) {
135         Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
136         Object JavaDoc name = entry.getKey();
137         Object JavaDoc value = entry.getValue();
138         Element JavaDoc variableElement = doc.createElement("variable");
139         variableElement.setAttribute("name", name.toString());
140         variableElement.setAttribute("value", value.toString());
141         trialElement.appendChild(variableElement);
142       }
143
144       trialElement.setAttribute("timing",
145           String.valueOf(trial.getRunTimeMillis()));
146
147       Throwable JavaDoc exception = trial.getException();
148
149       if (exception != null) {
150         Element JavaDoc exceptionElement = doc.createElement("exception");
151         exceptionElement.appendChild(doc.createTextNode(exception.toString()));
152         trialElement.appendChild(exceptionElement);
153       }
154
155       return trialElement;
156     }
157   }
158
159   /**
160    * Parses a .java source file to get the source code for methods.
161    *
162    * This Parser takes some shortcuts based on the fact that it's only being
163    * used to locate test methods for unit tests. (For example, only requiring a
164    * method name instead of a full type signature for lookup).
165    *
166    * TODO(tobyr) I think that I might be able to replace all this code with a
167    * call to the existing metadata interface. Check declEnd/declStart in
168    * JAbstractMethod.
169    */

170   private static class Parser {
171
172     static class MethodBody {
173
174       int declarationEnd; // the character index of the end of the method
175

176       int declarationStart; // the character index of the start of the method
177

178       String JavaDoc source;
179     }
180
181     private MethodBody currentMethod; // Only used during the visitor
182

183     // But it's less painful
184
private Map JavaDoc/* <String,MethodBody> */methods = new HashMap JavaDoc/* <String,MethodBody> */();
185
186     // Contains the contents of the entire source file
187
private char[] sourceContents;
188
189     Parser(JClassType klass) throws IOException JavaDoc {
190
191       Map JavaDoc settings = new HashMap JavaDoc();
192       settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_4);
193       settings.put(CompilerOptions.OPTION_TargetPlatform,
194           CompilerOptions.VERSION_1_4);
195       settings.put(CompilerOptions.OPTION_DocCommentSupport,
196           CompilerOptions.ENABLED);
197       CompilerOptions options = new CompilerOptions(settings);
198
199       IProblemFactory problemFactory = new DefaultProblemFactory(
200           Locale.getDefault());
201
202       // Save off the bounds of any method that is a test method
203
ISourceElementRequestor requestor = new SourceElementRequestorAdapter() {
204         public void enterMethod(MethodInfo methodInfo) {
205           String JavaDoc name = new String JavaDoc(methodInfo.name);
206           if (name.startsWith("test")) {
207             currentMethod = new MethodBody();
208             currentMethod.declarationStart = methodInfo.declarationStart;
209             methods.put(name, currentMethod);
210           }
211         }
212
213         public void exitMethod(int declarationEnd, int defaultValueStart,
214             int defaultValueEnd) {
215           if (currentMethod != null) {
216             currentMethod.declarationEnd = declarationEnd;
217             currentMethod = null;
218           }
219         }
220       };
221
222       boolean reportLocalDeclarations = true;
223       boolean optimizeStringLiterals = true;
224
225       SourceElementParser parser = new SourceElementParser(requestor,
226           problemFactory, options, reportLocalDeclarations,
227           optimizeStringLiterals);
228
229       File JavaDoc sourceFile = findSourceFile(klass);
230       sourceContents = read(sourceFile);
231       CompilationUnit unit = new CompilationUnit(sourceContents,
232           sourceFile.getName(), null);
233
234       parser.parseCompilationUnit(unit, true);
235     }
236
237     /**
238      * Returns the source code for the method of the given name.
239      *
240      * @return null if the source code for the method can not be located
241      */

242     public String JavaDoc getMethod(JMethod method) {
243       /*
244        * MethodBody methodBody = (MethodBody)methods.get( method.getName() ); if (
245        * methodBody == null ) { return null; } if ( methodBody.source == null ) {
246        * methodBody.source = new String(sourceContents,
247        * methodBody.declarationStart, methodBody.declarationEnd - methodBody.
248        * declarationStart + 1); } return methodBody.source;
249        */

250       return new String JavaDoc(sourceContents, method.getDeclStart(),
251           method.getDeclEnd() - method.getDeclStart() + 1);
252     }
253   }
254
255   /**
256    * Converts an entire report into XML.
257    */

258   private class ReportXml {
259
260     private Map JavaDoc/* <String,Element> */categoryElementMap = new HashMap JavaDoc/* <String,Element> */();
261
262     private Date JavaDoc date = new Date JavaDoc();
263
264     private String JavaDoc version = "unknown";
265
266     Element JavaDoc toElement(Document JavaDoc doc) {
267       Element JavaDoc report = doc.createElement("gwt_benchmark_report");
268       String JavaDoc dateString = DateFormat.getDateTimeInstance().format(date);
269       report.setAttribute("date", dateString);
270       report.setAttribute("gwt_version", version);
271
272       // - Add each test result into the report.
273
// - Add the category for the test result, if necessary.
274
for (Iterator JavaDoc entryIt = testResults.entrySet().iterator(); entryIt.hasNext();) {
275         Map.Entry JavaDoc entry = (Map.Entry JavaDoc) entryIt.next();
276         TestCase test = (TestCase) entry.getKey();
277         List JavaDoc/* <JUnitMessageQueue.TestResult> */results = (List JavaDoc/* <JUnitMessageQueue.TestResult> */) entry.getValue();
278         BenchmarkXml xml = new BenchmarkXml(test, results);
279         Element JavaDoc categoryElement = getCategoryElement(doc, report,
280             xml.metaData.getCategory().getClassName());
281         categoryElement.appendChild(xml.toElement(doc));
282       }
283
284       return report;
285     }
286
287     /**
288      * Locates or creates the category element by the specified name.
289      *
290      * @param doc The document to search
291      * @return The matching category element
292      */

293     private Element JavaDoc getCategoryElement(Document JavaDoc doc, Element JavaDoc report, String JavaDoc name) {
294       Element JavaDoc e = (Element JavaDoc) categoryElementMap.get(name);
295
296       if (e != null) {
297         return e;
298       }
299
300       Element JavaDoc categoryElement = doc.createElement("category");
301       categoryElementMap.put(name, categoryElement);
302       CategoryImpl category = (CategoryImpl) testCategories.get(name);
303       categoryElement.setAttribute("name", category.getName());
304       categoryElement.setAttribute("description", category.getDescription());
305
306       report.appendChild(categoryElement);
307
308       return categoryElement;
309     }
310   }
311
312   private static final String JavaDoc GWT_BENCHMARK_CATEGORY = "gwt.benchmark.category";
313
314   private static final String JavaDoc GWT_BENCHMARK_DESCRIPTION = "gwt.benchmark.description";
315
316   private static final String JavaDoc GWT_BENCHMARK_NAME = "gwt.benchmark.name";
317
318   private static File JavaDoc findSourceFile(JClassType klass) {
319     final char separator = File.separator.charAt(0);
320     String JavaDoc filePath = klass.getPackage().getName().replace('.', separator)
321         + separator + klass.getSimpleSourceName() + ".java";
322     String JavaDoc[] paths = getClassPath();
323
324     for (int i = 0; i < paths.length; ++i) {
325       File JavaDoc maybeSourceFile = new File JavaDoc(paths[i] + separator + filePath);
326
327       if (maybeSourceFile.exists()) {
328         return maybeSourceFile;
329       }
330     }
331
332     return null;
333   }
334
335   private static String JavaDoc[] getClassPath() {
336     String JavaDoc path = System.getProperty("java.class.path");
337     return path.split(File.pathSeparator);
338   }
339
340   private static String JavaDoc getSimpleMetaData(HasMetaData hasMetaData, String JavaDoc name) {
341     String JavaDoc[][] allValues = hasMetaData.getMetaData(name);
342
343     if (allValues == null) {
344       return null;
345     }
346
347     StringBuffer JavaDoc result = new StringBuffer JavaDoc();
348
349     for (int i = 0; i < allValues.length; ++i) {
350       String JavaDoc[] values = allValues[i];
351       for (int j = 0; j < values.length; ++j) {
352         result.append(values[j]);
353         result.append(" ");
354       }
355     }
356
357     String JavaDoc resultString = result.toString().trim();
358     return resultString.equals("") ? null : resultString;
359   }
360
361   private static char[] read(File JavaDoc f) throws IOException JavaDoc {
362     // TODO(tobyr) Can be done oh so much faster by just reading directly into
363
// a char[]
364

365     BufferedReader JavaDoc reader = new BufferedReader JavaDoc(new FileReader JavaDoc(f));
366     StringBuffer JavaDoc source = new StringBuffer JavaDoc((int) f.length());
367
368     while (true) {
369       String JavaDoc line = reader.readLine();
370       if (line == null) {
371         break;
372       }
373       source.append(line);
374       source.append("\n");
375     }
376
377     char[] buf = new char[source.length()];
378     source.getChars(0, buf.length, buf, 0);
379
380     return buf;
381   }
382
383   private Map JavaDoc/* <String,Map<CategoryImpl> */testCategories = new HashMap JavaDoc/* <String,CategoryImpl> */();
384
385   private Map JavaDoc/* <String,Map<String,MetaData>> */testMetaData = new HashMap JavaDoc/* <String,Map<String,MetaData>> */();
386
387   private Map JavaDoc/* <TestCase,List<JUnitMessageQueue.TestResult>> */testResults = new HashMap JavaDoc/* <TestCase,JUnitMessageQueue.List<TestResult>> */();
388
389   private TypeOracle typeOracle;
390
391   private TreeLogger logger;
392
393   public BenchmarkReport(TreeLogger logger) {
394     this.logger = logger;
395   }
396
397   /**
398    * Adds the Benchmark to the report. All of the metadata about the benchmark
399    * (category, name, description, etc...) is recorded from the TypeOracle.
400    */

401   public void addBenchmark(JClassType benchmarkClass, TypeOracle typeOracle) {
402
403     this.typeOracle = typeOracle;
404     String JavaDoc categoryType = getSimpleMetaData(benchmarkClass,
405         GWT_BENCHMARK_CATEGORY);
406
407     Map JavaDoc zeroArgMethods = BenchmarkGenerator.getNotOverloadedTestMethods(benchmarkClass);
408     Map JavaDoc/* <String,JMethod> */parameterizedMethods = BenchmarkGenerator.getParameterizedTestMethods(
409         benchmarkClass, TreeLogger.NULL);
410     List JavaDoc/* <JMethod> */testMethods = new ArrayList JavaDoc/* <JMethod> */(
411         zeroArgMethods.size() + parameterizedMethods.size());
412     testMethods.addAll(zeroArgMethods.values());
413     testMethods.addAll(parameterizedMethods.values());
414
415     Map JavaDoc/* <String,MetaData> */metaDataMap = (Map JavaDoc/* <String,MetaData> */) testMetaData.get(benchmarkClass.toString());
416     if (metaDataMap == null) {
417       metaDataMap = new HashMap JavaDoc/* <String,MetaData> */();
418       testMetaData.put(benchmarkClass.toString(), metaDataMap);
419     }
420
421     Parser parser = null;
422
423     try {
424       parser = new Parser(benchmarkClass);
425     } catch (IOException JavaDoc e) {
426       // if we fail to create the parser for some reason, we'll have to just
427
// deal with a null parser.
428
logger.log(TreeLogger.WARN, "Unable to parse the code for "
429           + benchmarkClass, e);
430     }
431
432     // Add all of the benchmark methods
433
for (int i = 0; i < testMethods.size(); ++i) {
434       JMethod method = (JMethod) testMethods.get(i);
435       String JavaDoc methodName = method.getName();
436       String JavaDoc methodCategoryType = getSimpleMetaData(method,
437           GWT_BENCHMARK_CATEGORY);
438       if (methodCategoryType == null) {
439         methodCategoryType = categoryType;
440       }
441       CategoryImpl methodCategory = getCategory(methodCategoryType);
442       StringBuffer JavaDoc sourceCode = parser == null ? null : new StringBuffer JavaDoc(
443           parser.getMethod(method));
444       StringBuffer JavaDoc summary = new StringBuffer JavaDoc();
445       StringBuffer JavaDoc comment = new StringBuffer JavaDoc();
446       getComment(sourceCode, summary, comment);
447
448       MetaData metaData = new MetaData(benchmarkClass.toString(), methodName,
449           sourceCode != null ? sourceCode.toString() : null, methodCategory,
450           methodName, summary.toString());
451       metaDataMap.put(methodName, metaData);
452     }
453   }
454
455   public void addBenchmarkResults(TestCase test, TestResults results) {
456     List JavaDoc/* <TestResults> */currentResults = (List JavaDoc/* <TestResults> */) testResults.get(test);
457     if (currentResults == null) {
458       currentResults = new ArrayList JavaDoc/* <TestResults> */();
459       testResults.put(test, currentResults);
460     }
461     currentResults.add(results);
462   }
463
464   /**
465    * Generates reports for all of the benchmarks which were added to the
466    * generator.
467    *
468    * @param outputPath The path to write the reports to.
469    * @throws ParserConfigurationException
470    * @throws IOException
471    * @throws IOException If anything goes wrong writing to outputPath
472    */

473   public void generate(String JavaDoc outputPath) throws ParserConfigurationException JavaDoc,
474       IOException JavaDoc {
475
476     // Don't generate a new report if no tests were actually run.
477
if (testResults.size() == 0) {
478       return;
479     }
480
481     DocumentBuilderFactory JavaDoc factory = DocumentBuilderFactory.newInstance();
482     DocumentBuilder JavaDoc builder = factory.newDocumentBuilder();
483     Document JavaDoc doc = builder.newDocument();
484     doc.appendChild(new ReportXml().toElement(doc));
485     byte[] xmlBytes = Util.toXmlUtf8(doc);
486     FileOutputStream JavaDoc fos = null;
487     try {
488       fos = new FileOutputStream JavaDoc(outputPath);
489       fos.write(xmlBytes);
490     } finally {
491       Utility.close(fos);
492     }
493
494     // TODO(bruce) The code below is commented out because of GWT Issue 958.
495

496     // // TODO(tobyr) Looks like indenting is busted
497
// // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446
498
// // Not a big deal, since we don't intend to read the XML by hand anyway
499
// TransformerFactory transformerFactory = TransformerFactory.newInstance();
500
// // Think this can be used with JDK 1.5
501
// // transformerFactory.setAttribute( "indent-number", new Integer(2) );
502
// Transformer serializer = transformerFactory.newTransformer();
503
// serializer.setOutputProperty(OutputKeys.METHOD, "xml");
504
// serializer.setOutputProperty(OutputKeys.INDENT, "yes");
505
// serializer
506
// .setOutputProperty("{ http://xml.apache.org/xslt }indent-amount", "2");
507
// BufferedOutputStream docOut = new BufferedOutputStream(
508
// new FileOutputStream(outputPath));
509
// serializer.transform(new DOMSource(doc), new StreamResult(docOut));
510
// docOut.close();
511
}
512
513   private CategoryImpl getCategory(String JavaDoc name) {
514     CategoryImpl c = (CategoryImpl) testCategories.get(name);
515
516     if (c != null) {
517       return c;
518     }
519
520     String JavaDoc categoryName = "";
521     String JavaDoc categoryDescription = "";
522
523     if (name != null) {
524       JClassType categoryType = typeOracle.findType(name);
525
526       if (categoryType != null) {
527         categoryName = getSimpleMetaData(categoryType, GWT_BENCHMARK_NAME);
528         categoryDescription = getSimpleMetaData(categoryType,
529             GWT_BENCHMARK_DESCRIPTION);
530       }
531     }
532
533     c = new CategoryImpl(name, categoryName, categoryDescription);
534     testCategories.put(name, c);
535     return c;
536   }
537
538   /**
539    * Parses out the JavaDoc comment from a string of source code. Returns the
540    * first sentence summary in <code>summary</code> and the body of the entire
541    * comment (including the summary) in <code>comment</code>.
542    */

543   private void getComment(StringBuffer JavaDoc sourceCode, StringBuffer JavaDoc summary,
544       StringBuffer JavaDoc comment) {
545
546     if (sourceCode == null) {
547       return;
548     }
549
550     summary.setLength(0);
551     comment.setLength(0);
552
553     String JavaDoc regex = "/\\*\\*(.(?!}-\\*/))*\\*/";
554
555     Pattern JavaDoc p = Pattern.compile(regex, Pattern.DOTALL);
556     Matcher JavaDoc m = p.matcher(sourceCode);
557
558     // Early out if there is no javadoc comment.
559
if (!m.find()) {
560       return;
561     }
562
563     String JavaDoc commentStr = m.group();
564
565     p = Pattern.compile("(/\\*\\*\\s*)" + // The comment header
566
"(((\\s*\\**\\s*)([^\n\r]*)[\n\r]+)*)" // The comment body
567
);
568
569     m = p.matcher(commentStr);
570
571     if (!m.find()) {
572       return;
573     }
574
575     String JavaDoc stripped = m.group(2);
576
577     p = Pattern.compile("^\\p{Blank}*\\**\\p{Blank}*", Pattern.MULTILINE);
578     String JavaDoc bareComment = p.matcher(stripped).replaceAll("");
579
580     BreakIterator JavaDoc iterator = BreakIterator.getSentenceInstance();
581     iterator.setText(bareComment);
582     int firstSentenceEnd = iterator.next();
583     if (firstSentenceEnd == BreakIterator.DONE) {
584       summary.append(bareComment);
585     } else {
586       summary.append(bareComment.substring(0, firstSentenceEnd));
587     }
588
589     comment.append(bareComment);
590
591     // Measure the indentation width on the second line to infer what the
592
// first line indent should be.
593
p = Pattern.compile("[^\\r\\n]+[\\r\\n]+(\\s+)\\*", Pattern.MULTILINE);
594     m = p.matcher(sourceCode);
595     int indentLen = 0;
596     if (m.find()) {
597       String JavaDoc indent = m.group(1);
598       indentLen = indent.length() - 1;
599     }
600     StringBuffer JavaDoc leadingIndent = new StringBuffer JavaDoc();
601     for (int i = 0; i < indentLen; ++i) {
602       leadingIndent.append(' ');
603     }
604
605     // By inserting at 0 here, we are assuming that sourceCode begins with
606
// /**, which is actually a function of how JDT sees a declaration start.
607
// If in the future, you see bogus indentation here, it means that this
608
// assumption is bad.
609
sourceCode.insert(0, leadingIndent);
610   }
611 }
612
Popular Tags