diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index 877f4beba..172dcc59a 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -15,12 +15,18 @@ */ package org.teavm.backend.javascript; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -83,6 +89,9 @@ import org.teavm.vm.spi.RendererListener; import org.teavm.vm.spi.TeaVMHostExtension; public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { + private static final NumberFormat STATS_NUM_FORMAT = new DecimalFormat("#,##0"); + private static final NumberFormat STATS_PERCENT_FORMAT = new DecimalFormat("0.000 %"); + private TeaVMTargetController controller; private boolean minifying = true; private final Map methodGenerators = new HashMap<>(); @@ -285,6 +294,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { for (RendererListener listener : rendererListeners) { listener.begin(renderer, target); } + int start = sourceWriter.getOffset(); sourceWriter.append("\"use strict\";").newLine(); renderer.renderRuntime(); renderer.render(clsNodes); @@ -300,11 +310,46 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { for (RendererListener listener : rendererListeners) { listener.complete(); } + int totalSize = sourceWriter.getOffset() - start; + printStats(renderer, totalSize); } catch (IOException e) { throw new RenderingException("IO Error occured", e); } } + private void printStats(Renderer renderer, int totalSize) { + if (!Boolean.parseBoolean(System.getProperty("teavm.js.stats", "false"))) { + return; + } + + System.out.println("Total output size: " + STATS_NUM_FORMAT.format(totalSize)); + System.out.println("Metadata size: " + getSizeWithPercentage(renderer.getMetadataSize(), totalSize)); + System.out.println("String pool size: " + getSizeWithPercentage(renderer.getStringPoolSize(), totalSize)); + + ObjectIntMap packageSizeMap = new ObjectIntHashMap<>(); + for (String className : renderer.getClassesInStats()) { + String packageName = className.substring(0, className.lastIndexOf('.') + 1); + int classSize = renderer.getClassSize(className); + packageSizeMap.put(packageName, packageSizeMap.getOrDefault(packageName, 0) + classSize); + } + + String[] packageNames = packageSizeMap.keys().toArray(String.class); + Arrays.sort(packageNames, Comparator.comparing(p -> -packageSizeMap.getOrDefault(p, 0))); + for (String packageName : packageNames) { + System.out.println("Package '" + packageName + "' size: " + + getSizeWithPercentage(packageSizeMap.get(packageName), totalSize)); + } + } + + private String getSizeWithPercentage(int size, int totalSize) { + return STATS_NUM_FORMAT.format(size) + " (" + STATS_PERCENT_FORMAT.format((double) size / totalSize) + ")"; + } + + static class PackageNode { + String name; + Map children = new HashMap<>(); + } + private List modelToAst(ListableClassHolderSource classes) { AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(), controller.getDiagnostics()); diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/LocationProvider.java b/core/src/main/java/org/teavm/backend/javascript/codegen/LocationProvider.java index 87a386d29..6ddad3482 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/LocationProvider.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/LocationProvider.java @@ -19,4 +19,6 @@ public interface LocationProvider { int getLine(); int getColumn(); + + int getOffset(); } diff --git a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java index 1effd2742..f5a12443f 100644 --- a/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java +++ b/core/src/main/java/org/teavm/backend/javascript/codegen/SourceWriter.java @@ -30,6 +30,7 @@ public class SourceWriter implements Appendable, LocationProvider { private final int lineWidth; private int column; private int line; + private int offset; SourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { this.naming = naming; @@ -62,6 +63,7 @@ public class SourceWriter implements Appendable, LocationProvider { newLine(); } else { column++; + offset++; } return this; } @@ -92,6 +94,7 @@ public class SourceWriter implements Appendable, LocationProvider { } appendIndent(); column += end - start; + offset += end - start; innerWriter.append(csq, start, end); } @@ -149,6 +152,7 @@ public class SourceWriter implements Appendable, LocationProvider { for (int i = 0; i < indentSize; ++i) { innerWriter.append(" "); column += 4; + offset += 4; } lineStart = false; } @@ -158,6 +162,7 @@ public class SourceWriter implements Appendable, LocationProvider { innerWriter.append('\n'); column = 0; ++line; + ++offset; lineStart = true; return this; } @@ -169,6 +174,7 @@ public class SourceWriter implements Appendable, LocationProvider { if (!minified) { innerWriter.append(' '); column++; + offset++; } } return this; @@ -185,6 +191,7 @@ public class SourceWriter implements Appendable, LocationProvider { if (!minified) { innerWriter.append('\n'); column = 0; + ++offset; ++line; lineStart = true; } @@ -214,4 +221,9 @@ public class SourceWriter implements Appendable, LocationProvider { public int getLine() { return line; } + + @Override + public int getOffset() { + return offset; + } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index d3e6b999b..4d7da2d8c 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -15,6 +15,8 @@ */ package org.teavm.backend.javascript.rendering; +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; @@ -66,6 +68,10 @@ public class Renderer implements RenderingManager { private RenderingContext context; private List postponedFieldInitializers = new ArrayList<>(); + private ObjectIntMap sizeByClass = new ObjectIntHashMap<>(); + private int stringPoolSize; + private int metadataSize; + public Renderer(SourceWriter writer, Set asyncMethods, Set asyncFamilyMethods, Diagnostics diagnostics, RenderingContext context) { this.naming = context.getNaming(); @@ -79,6 +85,22 @@ public class Renderer implements RenderingManager { this.context = context; } + public int getStringPoolSize() { + return stringPoolSize; + } + + public int getMetadataSize() { + return metadataSize; + } + + public String[] getClassesInStats() { + return sizeByClass.keys().toArray(String.class); + } + + public int getClassSize(String className) { + return sizeByClass.getOrDefault(className, 0); + } + @Override public SourceWriter getWriter() { return writer; @@ -133,6 +155,7 @@ public class Renderer implements RenderingManager { return; } try { + int start = writer.getOffset(); writer.append("$rt_stringPool(["); for (int i = 0; i < context.getStringPool().size(); ++i) { if (i > 0) { @@ -141,6 +164,7 @@ public class Renderer implements RenderingManager { writer.append('"').append(RenderingUtil.escapeString(context.getStringPool().get(i))).append('"'); } writer.append("]);").newLine(); + stringPoolSize = writer.getOffset() - start; } catch (IOException e) { throw new RenderingException("IO error", e); } @@ -149,14 +173,21 @@ public class Renderer implements RenderingManager { public void renderStringConstants() throws RenderingException { try { for (PostponedFieldInitializer initializer : postponedFieldInitializers) { + int start = writer.getOffset(); writer.appendStaticField(initializer.field).ws().append("=").ws() .append(context.constantToString(initializer.value)).append(";").softNewLine(); + int sz = writer.getOffset() - start; + appendClassSize(initializer.field.getClassName(), sz); } } catch (IOException e) { throw new RenderingException("IO error", e); } } + private void appendClassSize(String className, int sz) { + sizeByClass.put(className, sizeByClass.getOrDefault(className, 0) + sz); + } + public void renderRuntime() throws RenderingException { try { renderSetCloneMethod(); @@ -291,8 +322,10 @@ public class Renderer implements RenderingManager { } } for (ClassNode cls : classes) { + int start = writer.getOffset(); renderDeclaration(cls); renderMethodBodies(cls); + appendClassSize(cls.getName(), writer.getOffset() - start); } renderClassMetadata(classes); } @@ -452,6 +485,7 @@ public class Renderer implements RenderingManager { } private void renderClassMetadata(List classes) { + int start = writer.getOffset(); try { writer.append("$rt_metadata(["); boolean first = true; @@ -506,6 +540,8 @@ public class Renderer implements RenderingManager { } catch (IOException e) { throw new RenderingException("IO error occurred", e); } + + metadataSize = writer.getOffset() - start; } private void collectMethodsToCopyFromInterfaces(ClassReader cls, List targetList) {