/*
* Copyright (C) 2010 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.ast.grammar;
import static org.junit.Assert.fail;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import lombok.Cleanup;
import lombok.ast.grammar.RunForEachFileInDirRunner.DirDescriptor;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.parser.Parser;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.problem.ProblemReporter;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.parboiled.Parboiled;
import org.parboiled.ReportingParseRunner;
import com.google.common.collect.Lists;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
@RunWith(RunForEachFileInDirRunner.class)
public class PerformanceTest extends RunForEachFileInDirRunner.SourceFileBasedTester {
private static final int REPS = 50;
private static final boolean VERBOSE = System.getProperty("lombok.ast.test.verbose") != null;
private static final boolean EXTENDED = System.getProperty("lombok.ast.test.extended") != null;
private static final double MAX_FACTOR = 15;
private static long javacTotal, lombokTotal, ecjTotal, parboiledTotal;
@BeforeClass
public void init() {
if (VERBOSE) {
System.out.printf("[%20s / %30s] Per entry: time in millis for %d reps [lombok takes X longer than ~ : ~ takes X longer than javac]\n",
"path", "file", REPS);
}
}
@AfterClass
public void summary() {
if (VERBOSE) {
System.out.printf("[%20s / %30s] l.ast: %5d [ 1.00 : %6.02f] jc: %5d [%6.02f : 1.00] ecj: %5d [%6.02f : %6.02f] pb: %5d [%6.02f : %6.02f]\n",
"", "*** TOTALS ***",
lombokTotal, (double)lombokTotal / javacTotal,
javacTotal, (double)lombokTotal / javacTotal,
ecjTotal, (double)lombokTotal / ecjTotal, (double)ecjTotal / javacTotal,
parboiledTotal, (double)lombokTotal / parboiledTotal, (double)parboiledTotal / javacTotal);
}
}
@Override protected Collection<DirDescriptor> getDirDescriptors() {
List<DirDescriptor> descriptors = Lists.newArrayList();
descriptors.add(DirDescriptor.of(new File("test/resources/idempotency"), true));
descriptors.add(DirDescriptor.of(new File("test/resources/alias"), true));
descriptors.add(DirDescriptor.of(new File("test/resources/special"), true));
if (VERBOSE) {
descriptors.add(DirDescriptor.of(new File("test/resources/performance"), true));
}
return descriptors;
}
@Test
public boolean testPerformance(Source source) {
if (!EXTENDED) return false;
parseWithJavac(source);
long takenByJavac = System.currentTimeMillis();
for (int i = 0; i < REPS; i++) {
parseWithJavac(source);
}
takenByJavac = System.currentTimeMillis() - takenByJavac;
javacTotal += takenByJavac;
parseWithEcj(source);
long takenByEcj = System.currentTimeMillis();
for (int i = 0; i < REPS; i++) {
parseWithEcj(source);
}
takenByEcj = System.currentTimeMillis() - takenByEcj;
ecjTotal += takenByEcj;
source.parseCompilationUnit();
long takenByLombok = System.currentTimeMillis();
for (int i = 0; i < REPS; i++) {
source.clear();
source.parseCompilationUnit();
}
takenByLombok = System.currentTimeMillis() - takenByLombok;
lombokTotal += takenByLombok;
parseWithParboiled(source);
long takenByParboiled = System.currentTimeMillis();
for (int i = 0; i < REPS; i++) {
parseWithParboiled(source);
}
takenByParboiled = System.currentTimeMillis() - takenByParboiled;
parboiledTotal += takenByParboiled;
String fn = source.getName();
String fnPrefix, fnSuffix, fileName; {
int sep = fn.lastIndexOf('/');
if (sep == -1) {
fnPrefix = "";
fileName = fnSuffix = fn;
} else {
fnPrefix = fn.substring(0, sep);
fileName = fnSuffix = fn.substring(sep + 1);
}
if (fnSuffix.endsWith(".java")) fnSuffix = fnSuffix.substring(0, fnSuffix.length() - ".java".length());
if (fnPrefix.length() > 20) fnPrefix = "\u2026" + fnPrefix.substring(fnPrefix.length() - 19);
if (fnSuffix.length() > 30) fnSuffix = fnSuffix.substring(0, 10) + "\u2026" + fnSuffix.substring(fnSuffix.length() - 19);
}
if (VERBOSE) {
System.out.printf("[%20s / %30s] l.ast: %5d [ 1.00 : %6.02f] jc: %5d [%6.02f : 1.00] ecj: %5d [%6.02f : %6.02f] pb: %5d [%6.02f : %6.02f]\n",
fnPrefix, fnSuffix,
takenByLombok, (double)takenByLombok / takenByJavac,
takenByJavac, (double)takenByLombok / takenByJavac,
takenByEcj, (double)takenByLombok / takenByEcj, (double)takenByEcj / takenByJavac,
takenByParboiled, (double)takenByLombok / takenByParboiled, (double)takenByParboiled / takenByJavac);
}
double factorVsJavac = (double)takenByLombok / takenByJavac;
if (factorVsJavac > MAX_FACTOR) {
if (VERBOSE) {
try {
File reportFile = new File("test/reports/" + fileName + ".report");
reportFile.getParentFile().mkdirs();
@Cleanup FileOutputStream rawOut = new FileOutputStream(reportFile);
Writer out = new BufferedWriter(new OutputStreamWriter(rawOut, "UTF-8"));
out.write(String.format("Parse Profile for: %s which is slower than javac by a factor of %.02f\n", source.getName(), factorVsJavac));
for (String report : source.getDetailedProfileInformation(25)) {
out.write(report);
out.write("===================================");
out.write("\n");
}
out.close();
rawOut.close();
System.out.println("Profile report written to: " + reportFile.getCanonicalPath());
} catch (IOException e) {
System.err.println("I/O error writing profile report on " + source.getName() + "; Possibly ./test/reports is not writable?");
e.printStackTrace();
}
}
fail(String.format("Performance is slower than javac by factor %d on %s", (int)factorVsJavac, source.getName()));
}
return true;
}
private void parseWithParboiled(Source source) {
if (VERBOSE) {
ParboiledJavaGrammar parser = Parboiled.createParser(ParboiledJavaGrammar.class);
ReportingParseRunner.run(parser.CompilationUnit(), source.getRawInput());
}
}
private void parseWithJavac(Source source) {
Context context = new Context();
JavaCompiler compiler = new JavaCompiler(context);
compiler.genEndPos = true;
compiler.parse(new ContentBasedJavaFileObject(source.getName(), source.getRawInput()));
}
protected CompilerOptions ecjCompilerOptions() {
CompilerOptions options = new CompilerOptions();
options.complianceLevel = ClassFileConstants.JDK1_6;
options.sourceLevel = ClassFileConstants.JDK1_6;
options.targetJDK = ClassFileConstants.JDK1_6;
options.parseLiteralExpressionsAsConstants = true;
return options;
}
private void parseWithEcj(Source source) {
if (VERBOSE) {
CompilerOptions compilerOptions = ecjCompilerOptions();
Parser parser = new Parser(new ProblemReporter(
DefaultErrorHandlingPolicies.proceedWithAllProblems(),
compilerOptions,
new DefaultProblemFactory()
), compilerOptions.parseLiteralExpressionsAsConstants);
parser.javadocParser.checkDocComment = true;
CompilationUnit sourceUnit = new CompilationUnit(source.getRawInput().toCharArray(), source.getName(), "UTF-8");
CompilationResult compilationResult = new CompilationResult(sourceUnit, 0, 0, 0);
parser.parse(sourceUnit, compilationResult);
}
}
}