blob: e2b34098126015c12b187bccdeb4542fe9a31ba6 [file] [log] [blame]
/*
* 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.soyc;
import com.google.gwt.core.ext.linker.CompilationMetricsArtifact;
import com.google.gwt.core.ext.linker.ModuleMetricsArtifact;
import com.google.gwt.core.ext.linker.PrecompilationMetricsArtifact;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.soyc.MakeTopLevelHtmlForPerm.DependencyLinker;
import com.google.gwt.soyc.MakeTopLevelHtmlForPerm.NullDependencyLinker;
import com.google.gwt.soyc.io.FileSystemOutputDirectory;
import com.google.gwt.soyc.io.OutputDirectory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.zip.GZIPInputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* The command-line entry point for creating a compile report.
*/
public class SoycDashboard {
private static class FormatException extends RuntimeException {
public FormatException() {
super();
}
public FormatException(String message) {
super(message);
}
public FormatException(Throwable cause) {
super(cause);
}
}
public static void main(final String[] args) throws InterruptedException {
System.out.println("WARNING: The direct use of the SoycDashboard is deprecated and will be removed. " +
"The preferred usage is to invoke the compiler with the -compileReport option, which " +
"writes the compile report directly to the extra directory.");
Thread.currentThread();
Thread.sleep(5000);
Settings settings;
try {
settings = Settings.fromArgumentList(args);
} catch (Settings.ArgumentListException e) {
System.err.println(e.getMessage());
System.err.println("Usage: "
+ "java com.google.gwt.soyc.SoycDashboard -resources dir -soycDir dir -symbolMaps dir [-out dir]");
System.err.println("(Legacy usage: "
+ "java com.google.gwt.soyc.SoycDashboard options stories0.xml[.gz] [dependencies0.xml[.gz]] [splitpoints0.xml[.gz]])");
System.err.println("Options:");
System.err.println(Settings.settingsHelp());
System.exit(1);
return; // not reached
}
System.out.println("Generating the Story of Your Compile...");
OutputDirectory outDir = new FileSystemOutputDirectory(new File(
settings.out.get()));
try {
Map<String, List<String>> permInfo = readPermutationInfo(settings);
SoycDashboard dashboard = new SoycDashboard(outDir);
for (String permutationId : permInfo.keySet()) {
dashboard.startNewPermutation(permutationId);
if (settings.symbolMapsDir.get() == null) {
dashboard.readFromFilesNamed(settings.storiesFileName,
settings.depFileName, settings.splitPointsFileName);
} else {
String soycDir = settings.soycDir.get();
dashboard.readFromFilesNamed(soycInputFile(soycDir, "stories",
permutationId), soycInputFile(soycDir, "dependencies",
permutationId), soycInputFile(soycDir, "splitPoints",
permutationId));
}
dashboard.generateForOnePermutation();
System.out.println("Finished creating reports for permutation.");
}
dashboard.generateCrossPermutationFiles(permInfo);
System.out.println("Finished creating reports. To see the dashboard, open index.html in your browser.");
} catch (ParserConfigurationException e) {
System.err.println("Could not parse document. " + e.getMessage());
System.exit(1);
} catch (SAXException e) {
System.err.println("Could not create SAX parser. " + e.getMessage());
System.exit(1);
} catch (FileNotFoundException e) {
System.err.println("Cannot open file " + e.getMessage());
System.exit(1);
} catch (IOException e) {
System.err.println("Error creating html file. " + e.getMessage());
System.exit(1);
}
}
/**
* Open a file for reading. If the filename ends in .gz, then wrap the stream
* with a {@link GZIPInputStream}.
*/
public static InputStream openPossiblyGzippedFile(String filename)
throws IOException {
InputStream in = new FileInputStream(filename);
if (filename.endsWith(".gz")) {
in = new GZIPInputStream(in);
}
in = new BufferedInputStream(in);
return in;
}
/*
* cleans up the RPC code categories
*/
private static void foldInRPCHeuristic(
final HashMap<String, CodeCollection> nameToCodeColl) {
/**
* Heuristic: this moves all classes that override serializable from RPC to
* "Other Code" *if* there is no RPC generated code, i.e., if the
* application really is not using RPC
*/
if (nameToCodeColl.get("rpcGen").classes.size() == 0) {
for (String className : nameToCodeColl.get("rpcUser").classes) {
if ((!nameToCodeColl.get("widget").classes.contains(className))
&& (!nameToCodeColl.get("jre").classes.contains(className))
&& (!nameToCodeColl.get("gwtLang").classes.contains(className))) {
nameToCodeColl.get("allOther").classes.add(className);
}
}
nameToCodeColl.get("rpcUser").classes.clear();
for (String className : nameToCodeColl.get("rpcGwt").classes) {
if ((!nameToCodeColl.get("widget").classes.contains(className))
&& (!nameToCodeColl.get("jre").classes.contains(className))
&& (!nameToCodeColl.get("gwtLang").classes.contains(className))) {
nameToCodeColl.get("allOther").classes.add(className);
}
}
nameToCodeColl.get("rpcGwt").classes.clear();
}
}
private static DefaultHandler parseXMLDocumentDependencies(
final Map<String, Map<String, String>> allDependencies) {
DefaultHandler handler = new DefaultHandler() {
// may want to create a class for this later
String curMethod;
Map<String, String> dependencies = new TreeMap<String, String>();
String graphExtends = null;
StringBuilder valueBuilder = new StringBuilder();
@Override
public void endElement(String uri, String localName, String qName) {
if (localName.compareTo("table") == 0) {
if (graphExtends != null) {
// Add in elements from the extended graph
for (Entry<String, String> entry : allDependencies.get(graphExtends).entrySet()) {
dependencies.put(entry.getKey(), entry.getValue());
}
}
}
}
@Override
public void startElement(String nsUri, String strippedName,
String tagName, final Attributes attributes) {
valueBuilder.delete(0, valueBuilder.length());
if (strippedName.compareTo("table") == 0
&& (attributes.getValue("name") != null)) {
String name = attributes.getValue("name");
dependencies = new TreeMap<String, String>();
allDependencies.put(StringInterner.get().intern(name), dependencies);
if (attributes.getValue("extends") != null) {
graphExtends = StringInterner.get().intern(attributes.getValue("extends"));
if (!allDependencies.containsKey(graphExtends)) {
throw new FormatException("Graph " + name
+ " extends an unknown graph " + graphExtends);
}
} else {
graphExtends = null;
}
} else if ((strippedName.compareTo("method") == 0)
&& (attributes.getValue("name") != null)) {
curMethod = StringInterner.get().intern(attributes.getValue("name"));
} else if ((strippedName.compareTo("called") == 0)
&& (attributes.getValue("by") != null)) {
String curDepMethod = attributes.getValue("by");
if (!dependencies.containsKey(curMethod)) {
dependencies.put(StringInterner.get().intern(curMethod),
StringInterner.get().intern(curDepMethod));
}
}
}
};
return handler;
}
private static Map<String, List<String>> readPermutationInfo(Settings settings)
throws FileNotFoundException {
Map<String, List<String>> allPermsInfo = new TreeMap<String, List<String>>();
if (settings.symbolMapsDir.get() == null) {
String permutationId = settings.storiesFileName;
permutationId = permutationId.replaceAll(".*/stories", "");
permutationId = permutationId.replaceAll("\\.xml(\\.gz)?", "");
} else {
File dir = new File(settings.symbolMapsDir.get());
String files[] = dir.list();
for (Integer i = 0; i < files.length; i++) {
String permFileName = settings.symbolMapsDir.get() + "/" + files[i];
FileReader fir = new FileReader(permFileName);
Scanner sc = new Scanner(fir);
String permutationId = "";
String permutationInfo = "";
List<String> permutationInfoList = new ArrayList<String>();
boolean firstLine = true;
String curLine = sc.nextLine();
curLine = curLine.trim();
while (curLine.startsWith("# {")) {
curLine = curLine.replace("# {", "");
curLine = curLine.replace("}", "");
curLine = curLine.trim();
if (firstLine) {
permutationId = curLine;
firstLine = false;
} else {
if (permutationInfo.length() > 0) {
permutationInfo += "<br>";
}
permutationInfo += curLine;
permutationInfoList.add(curLine);
}
if (sc.hasNextLine()) {
curLine = sc.nextLine();
curLine = curLine.trim();
}
}
allPermsInfo.put(permutationId, permutationInfoList);
}
}
return allPermsInfo;
}
private static String soycInputFile(String soycDir, String baseName,
String permutationId) {
String name = soycDir + "/" + baseName + permutationId + ".xml.gz";
if (new File(name).exists()) {
return name;
}
return soycDir + "/" + baseName + permutationId + ".xml";
}
/**
* Global information for the current permutation being emitted.
*/
private GlobalInformation globalInformation;
/**
* HTML emitter for the current permutation being emitted.
*/
private MakeTopLevelHtmlForPerm makeTopLevelHtmlForPerm;
private final OutputDirectory outDir;
public SoycDashboard(OutputDirectory outDir) {
this.outDir = outDir;
}
public void generateCompilerMetricsForOnePermuation(
ModuleMetricsArtifact moduleMetrics,
PrecompilationMetricsArtifact precompilationMetrics,
CompilationMetricsArtifact compilationMetrics) throws IOException {
makeTopLevelHtmlForPerm.makeCompilerMetricsPermFiles(moduleMetrics,
precompilationMetrics, compilationMetrics);
}
public void generateCrossPermutationFiles(Map<String, List<String>> permInfo)
throws IOException {
StaticResources.emit(outDir);
MakeTopLevelHtmlForPerm.makeTopLevelHtmlForAllPerms(permInfo, outDir);
}
public void generateForOnePermutation() throws IOException {
if (globalInformation.dependencies != null) {
makeTopLevelHtmlForPerm.makeDependenciesHtml();
}
if (globalInformation.getNumSplitPoints() > 0) {
makeTopLevelHtmlForPerm.makeSplitStatusPages();
makeTopLevelHtmlForPerm.makeLeftoverStatusPages();
}
for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) {
DependencyLinker linker = chooseDependencyLinker(breakdown);
makeHTMLFiles(makeTopLevelHtmlForPerm, breakdown, linker);
}
}
public void readDependencies(InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
globalInformation.dependencies = new TreeMap<String, Map<String, String>>();
DefaultHandler depHandler = parseXMLDocumentDependencies(globalInformation.dependencies);
SAXParserFactory depFactoryMain = SAXParserFactory.newInstance();
depFactoryMain.setNamespaceAware(true);
SAXParser saxParser = depFactoryMain.newSAXParser();
saxParser.parse(stream, depHandler);
}
public void readSizeMaps(InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
DefaultHandler handler = parseXMLDocumentSizeMap(globalInformation);
SAXParserFactory factoryMain = SAXParserFactory.newInstance();
factoryMain.setNamespaceAware(true);
SAXParser saxParser = factoryMain.newSAXParser();
saxParser.parse(stream, handler);
// Now clean up the information that has been read in various ways
globalInformation.computePackageSizes();
// add to "All Other Code" if none of the special categories apply
for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) {
updateAllOtherCodeType(breakdown.nameToCodeColl);
}
// clean up the RPC categories
for (SizeBreakdown breakdown : globalInformation.allSizeBreakdowns()) {
foldInRPCHeuristic(breakdown.nameToCodeColl);
}
}
public void readSplitPoints(InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
DefaultHandler splitPointHandler = parseXMLDocumentSplitPoints();
SAXParserFactory splitPointsFactoryMain = SAXParserFactory.newInstance();
splitPointsFactoryMain.setNamespaceAware(true);
SAXParser saxParser = splitPointsFactoryMain.newSAXParser();
saxParser.parse(stream, splitPointHandler);
}
public void startNewPermutation(String permutationId) {
globalInformation = new GlobalInformation(permutationId);
makeTopLevelHtmlForPerm = new MakeTopLevelHtmlForPerm(globalInformation,
outDir);
}
private Collection<SizeBreakdown> breakdownsForFragment(Integer fragment) {
List<SizeBreakdown> breakdowns = new ArrayList<SizeBreakdown>();
breakdowns.add(globalInformation.getTotalCodeBreakdown());
if (fragment == 0) {
breakdowns.add(globalInformation.getInitialCodeBreakdown());
}
if (fragment == (globalInformation.getNumSplitPoints() + 1)) {
breakdowns.add(globalInformation.getLeftoversBreakdown());
}
if (fragment >= 1 && fragment <= globalInformation.getNumSplitPoints()) {
breakdowns.add(globalInformation.splitPointCodeBreakdown(fragment));
}
return breakdowns;
}
private DependencyLinker chooseDependencyLinker(SizeBreakdown breakdown) {
if (globalInformation.dependencies == null) {
// no dependencies are available
return new NullDependencyLinker();
}
if (breakdown == globalInformation.getTotalCodeBreakdown()) {
if (globalInformation.getNumSplitPoints() > 0) {
return makeTopLevelHtmlForPerm.new DependencyLinkerForTotalBreakdown();
} else {
return makeTopLevelHtmlForPerm.new DependencyLinkerForInitialCode();
}
} else if (breakdown == globalInformation.getInitialCodeBreakdown()) {
return makeTopLevelHtmlForPerm.new DependencyLinkerForInitialCode();
} else if (breakdown == globalInformation.getLeftoversBreakdown()) {
assert globalInformation.getNumSplitPoints() > 0;
return makeTopLevelHtmlForPerm.new DependencyLinkerForLeftoversFragment();
} else {
return new NullDependencyLinker();
}
}
/**
* Generates all the HTML files for one size breakdown.
*/
private void makeHTMLFiles(MakeTopLevelHtmlForPerm makeTopLevelHtmlForPerm,
SizeBreakdown breakdown, DependencyLinker depLinker) throws IOException {
makeTopLevelHtmlForPerm.makePackageClassesHtmls(breakdown, depLinker);
makeTopLevelHtmlForPerm.makeCodeTypeClassesHtmls(breakdown);
makeTopLevelHtmlForPerm.makeLiteralsClassesTableHtmls(breakdown);
makeTopLevelHtmlForPerm.makeBreakdownShell(breakdown);
makeTopLevelHtmlForPerm.makeTopLevelShell();
}
private DefaultHandler parseXMLDocumentSizeMap(
final GlobalInformation globalInformation) {
return new DefaultHandler() {
int fragment = -1;
@Override
public void endElement(String uri, String localName, String qName) {
if (localName.compareTo("sizemap") == 0) {
fragment = -1;
}
}
@Override
public void startElement(String uri, String localName, String qName,
final Attributes attributes) {
if (localName.compareTo("sizemap") == 0) {
// starting a new size map
String fragString = attributes.getValue("fragment");
if (fragString == null) {
throw new FormatException();
}
try {
fragment = Integer.valueOf(fragString);
} catch (NumberFormatException e) {
throw new FormatException(e);
}
String sizeString = attributes.getValue("size");
if (sizeString == null) {
throw new FormatException();
}
int size;
try {
size = Integer.valueOf(sizeString);
} catch (NumberFormatException e) {
throw new FormatException(e);
}
for (SizeBreakdown breakdown : breakdownsForFragment(fragment)) {
breakdown.sizeAllCode += size;
}
} else if (localName.compareTo("size") == 0) {
String type = attributes.getValue("type");
if (type == null) {
throw new FormatException();
}
String ref = attributes.getValue("ref");
if (ref == null) {
throw new FormatException();
}
String sizeString = attributes.getValue("size");
if (sizeString == null) {
throw new FormatException();
}
int size;
try {
size = Integer.valueOf(sizeString);
} catch (NumberFormatException e) {
throw new FormatException(e);
}
recordSize(type, ref, size, globalInformation);
}
}
private void accountForSize(SizeBreakdown breakdown, String refType,
String ref, int size, GlobalInformation globalInformation) {
if (refType.equals("string")) {
LiteralsCollection stringLiterals = breakdown.nameToLitColl.get("string");
stringLiterals.size += size;
stringLiterals.literals.add(ref);
} else if (refType.equals("var")) {
// Nothing to record, because no breakdown is provided for random
// variables
} else {
if (!refType.equals("type") && !refType.equals("method") && !refType.equals("field")) {
throw new FormatException();
}
if (refType.equals("method")) {
breakdown.methodToSize.put(ref, Integer.valueOf(size));
}
String className = ref;
if (className.contains("::")) {
/*
* It's a method or field reference. Discard the method/field part.
*/
int idx = className.indexOf(':');
className = className.substring(0, idx);
}
className = StringInterner.get().intern(className);
// derive the package name from the class
String packageName;
if (!globalInformation.getClassToPackage().containsKey(className)) {
packageName = className;
packageName = packageName.replaceAll("\\.[A-Z].*", "");
globalInformation.getClassToPackage().put(className, packageName);
} else {
packageName = globalInformation.getClassToPackage().get(className);
}
if (!globalInformation.getPackageToClasses().containsKey(packageName)) {
TreeSet<String> insertSet = new TreeSet<String>();
insertSet.add(className);
globalInformation.getPackageToClasses().put(packageName, insertSet);
} else {
globalInformation.getPackageToClasses().get(packageName).add(
className);
}
recordClassCategories(breakdown.nameToCodeColl, className,
packageName);
if (breakdown.classToSize.containsKey(className)) {
int newSize = breakdown.classToSize.get(className) + size;
breakdown.classToSize.put(className, newSize);
} else {
breakdown.classToSize.put(className, size);
}
}
}
private void recordClassCategories(
final HashMap<String, CodeCollection> nameToCodeColl,
String className, String packageName) {
if (packageName.startsWith("java")) {
nameToCodeColl.get("jre").classes.add(className);
} else if (packageName.startsWith("com.google.gwt.lang")) {
nameToCodeColl.get("gwtLang").classes.add(className);
}
if (className.contains("_CustomFieldSerializer")) {
nameToCodeColl.get("rpcUser").classes.add(className);
} else if (className.endsWith("_FieldSerializer")
|| className.endsWith("_Proxy")
|| className.endsWith("_TypeSerializer")) {
nameToCodeColl.get("rpcGen").classes.add(className);
}
}
private void recordSize(String refType, String ref, int size,
GlobalInformation globalInformation) {
refType = StringInterner.get().intern(refType);
ref = StringInterner.get().intern(ref);
for (SizeBreakdown breakdown : breakdownsForFragment(fragment)) {
accountForSize(breakdown, refType, ref, size, globalInformation);
}
}
};
}
private DefaultHandler parseXMLDocumentSplitPoints() {
DefaultHandler handler = new DefaultHandler() {
private boolean inInitialLoadSequence = false;
@Override
public void endElement(String uri, String localName, String qName) {
if (localName.compareTo("initialesq") == 0) {
inInitialLoadSequence = false;
}
}
@Override
public void startElement(String nsUri, String strippedName,
String tagName, final Attributes attributes) {
if (strippedName.compareTo("splitpoint") == 0) {
parseSplitPoint(attributes);
} else if (strippedName.compareTo("initialseq") == 0) {
inInitialLoadSequence = true;
} else if (inInitialLoadSequence
&& strippedName.compareTo("splitpointref") == 0) {
globalInformation.getSplitPointInitialLoadSequence().add(
parseSplitPointReference(attributes));
}
}
/*
* parses the split points
*/
private void parseSplitPoint(final Attributes attributes) {
if (attributes.getValue("id") != null) {
String curSplitPoint = attributes.getValue("id");
if (attributes.getValue("location") != null) {
String curSplitPointLocation = attributes.getValue("location");
curSplitPointLocation = curSplitPointLocation.replaceAll("\\(L.*",
"");
globalInformation.getSplitPointToLocation().put(
Integer.parseInt(curSplitPoint), curSplitPointLocation);
globalInformation.incrementSplitPoints();
}
}
}
private Integer parseSplitPointReference(final Attributes attributes) {
String spString = attributes.getValue("id");
if (spString == null) {
throw new FormatException("Could not parse split point reference");
}
return Integer.valueOf(spString);
}
};
return handler;
}
private void readFromFilesNamed(String storiesFileName,
String dependenciesFileName, String splitPointsFileName)
throws ParserConfigurationException, SAXException, IOException {
if (dependenciesFileName != null && new File(dependenciesFileName).exists()) {
readDependencies(openPossiblyGzippedFile(dependenciesFileName));
}
if (splitPointsFileName != null && new File(splitPointsFileName).exists()) {
readSplitPoints(openPossiblyGzippedFile(splitPointsFileName));
}
readSizeMaps(openPossiblyGzippedFile(storiesFileName));
}
/*
* assigns code to "all other code" if none of the special categories apply
*/
private void updateAllOtherCodeType(
final HashMap<String, CodeCollection> nameToCodeColl) {
// all classes not in any of the other categories
for (String className : globalInformation.getClassToPackage().keySet()) {
if ((!nameToCodeColl.get("widget").classes.contains(className))
&& (!nameToCodeColl.get("rpcUser").classes.contains(className))
&& (!nameToCodeColl.get("rpcGwt").classes.contains(className))
&& (!nameToCodeColl.get("rpcGen").classes.contains(className))
&& (!nameToCodeColl.get("jre").classes.contains(className))
&& (!nameToCodeColl.get("gwtLang").classes.contains(className))) {
nameToCodeColl.get("allOther").classes.add(className);
}
}
}
}