From 782d0f5947e9d0b3fd80e304aa878dd97805ad9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Thu, 7 Aug 2025 15:51:51 +0200 Subject: [PATCH 1/8] Make ProgressReporter Top 10 printing package based --- .../svm/hosted/CodeBreakdownProvider.java | 55 ++---------- .../svm/hosted/HeapBreakdownProvider.java | 5 +- .../oracle/svm/hosted/ProgressReporter.java | 86 +++++++++++++++---- 3 files changed, 79 insertions(+), 67 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CodeBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CodeBreakdownProvider.java index 06f978b845b9..5871090816d0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CodeBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/CodeBreakdownProvider.java @@ -24,40 +24,22 @@ */ package com.oracle.svm.hosted; -import java.security.CodeSource; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.svm.hosted.code.CompileQueue.CompileTask; -import com.oracle.svm.hosted.meta.HostedMethod; class CodeBreakdownProvider { - private Map codeBreakdown; + private Map, Long> codeBreakdown; CodeBreakdownProvider(Collection compilationTasks) { - Map nameToSizeMap = new HashMap<>(); - for (CompileTask task : compilationTasks) { - String key = null; - Class javaClass = task.method.getDeclaringClass().getJavaClass(); - Module module = javaClass.getModule(); - if (module.isNamed()) { - key = module.getName(); - if ("org.graalvm.nativeimage.builder".equals(key)) { - key = "svm.jar (Native Image)"; - } - } else { - key = findJARFile(javaClass); - if (key == null) { - key = findPackageOrClassName(task.method); - } - } - nameToSizeMap.merge(key, (long) task.result.getTargetCodeSize(), Long::sum); - } - codeBreakdown = Collections.unmodifiableMap(nameToSizeMap); + codeBreakdown = Map.copyOf(compilationTasks.stream().collect( + Collectors.groupingBy( + compileTask -> compileTask.method.getDeclaringClass().getJavaClass(), + Collectors.summingLong(compileTask -> compileTask.result.getTargetCodeSize())))); } /** @@ -66,31 +48,10 @@ class CodeBreakdownProvider { * * @return the code breakdown */ - public static Map getAndClear() { + public static Map, Long> getAndClear() { CodeBreakdownProvider singleton = ImageSingletons.lookup(CodeBreakdownProvider.class); - Map map = singleton.codeBreakdown; + var map = singleton.codeBreakdown; singleton.codeBreakdown = null; return map; } - - private static String findJARFile(Class javaClass) { - CodeSource codeSource = javaClass.getProtectionDomain().getCodeSource(); - if (codeSource != null && codeSource.getLocation() != null) { - String path = codeSource.getLocation().getPath(); - if (path.endsWith(".jar")) { - // Use String API to determine basename of path to handle both / and \. - return path.substring(Math.max(path.lastIndexOf('/') + 1, path.lastIndexOf('\\') + 1)); - } - } - return null; - } - - private static String findPackageOrClassName(HostedMethod method) { - String qualifier = method.format("%H"); - int lastDotIndex = qualifier.lastIndexOf('.'); - if (lastDotIndex > 0) { - qualifier = qualifier.substring(0, lastDotIndex); - } - return qualifier; - } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index e96e397d3be6..ec0489853d7f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -147,7 +147,7 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre /* Extract byte[] for Strings. */ if (stringByteArrayTotalSize > 0) { - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX + "java.lang.String"), stringByteArrayTotalSize, stringByteArrayTotalCount); + addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX + "string data"), stringByteArrayTotalSize, stringByteArrayTotalCount); } /* Extract byte[] for code info. */ List codeInfoByteArrayLengths = CodeInfoTable.getCurrentLayerImageCodeCache().getTotalByteArrayLengths(); @@ -215,7 +215,8 @@ public static class HeapBreakdownEntry { int count; public HeapBreakdownEntry(HostedClass hostedClass) { - this(hostedClass.toJavaName(true)); + this(ProgressReporter.moduleNamePrefix(hostedClass.getJavaClass().getModule()) + + ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 40)); } public HeapBreakdownEntry(String name) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 26d24c625a4b..92d476cb08eb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -31,6 +31,7 @@ import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.nio.file.Path; +import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -75,6 +76,7 @@ import com.oracle.svm.core.VM; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.hub.ClassForNameSupport; +import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; @@ -639,13 +641,50 @@ public void ensureCreationStageEndCompleted() { } } + private record BreakDownClassifier(Package javaPackage, Module javaModule, String location) { + static BreakDownClassifier of(Class clazz) { + return new BreakDownClassifier(clazz.getPackage(), clazz.getModule(), sourcePath(clazz)); + } + + private static String sourcePath(Class clazz) { + CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); + if (codeSource != null && codeSource.getLocation() != null) { + String path = codeSource.getLocation().getPath(); + // Use String API to determine basename of path to handle both / and \. + return path.substring(Math.max(path.lastIndexOf('/') + 1, path.lastIndexOf('\\') + 1)); + } + return null; + } + + @Override + public String toString() { + String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 14) + ")"; + return moduleNamePrefix(javaModule) + Utils.truncateFQN(javaPackage.getName(), 22) + locationSuffix; + } + + } + + static String moduleNamePrefix(Module javaModule) { + if (!javaModule.isNamed()) { + return ""; + } + if (javaModule.equals(DynamicHub.class.getModule())) { + return "VM "; + } + return Utils.truncateFQN(javaModule.getName(), 19) + "/"; + } + private void printBreakdowns() { if (!SubstrateOptions.BuildOutputBreakdowns.getValue()) { return; } l().printLineSeparator(); - Map codeBreakdown = CodeBreakdownProvider.getAndClear(); - Iterator> packagesBySize = codeBreakdown.entrySet().stream() + Map codeBreakdown = CodeBreakdownProvider.getAndClear().entrySet().stream() + .collect(Collectors.groupingBy( + entry -> BreakDownClassifier.of(entry.getKey()), + Collectors.summingLong(entry -> entry.getValue()))); + + Iterator> packagesBySize = codeBreakdown.entrySet().stream() .sorted(Entry.comparingByValue(Comparator.reverseOrder())).iterator(); HeapBreakdownProvider heapBreakdown = HeapBreakdownProvider.singleton(); @@ -663,10 +702,9 @@ private void printBreakdowns() { for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; if (packagesBySize.hasNext()) { - Entry e = packagesBySize.next(); - String className = Utils.truncateClassOrPackageName(e.getKey()); - codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(e.getValue()), className); - printedCodeBytes += e.getValue(); + Entry entry = packagesBySize.next(); + codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(entry.getValue()), entry.getKey()); + printedCodeBytes += entry.getValue(); printedCodeItems++; } @@ -676,7 +714,7 @@ private void printBreakdowns() { String className = e.label.renderToString(linkStrategy); // Do not truncate special breakdown items, they can contain links. if (e.label instanceof HeapBreakdownProvider.SimpleHeapObjectKindName) { - className = Utils.truncateClassOrPackageName(className); + className = Utils.truncateFQN(className); } long byteSize = e.byteSize; heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(byteSize), className); @@ -920,9 +958,10 @@ private static OperatingSystemMXBean getOperatingSystemMXBean() { return (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); } - private static final class Utils { + static final class Utils { private static final double MILLIS_TO_SECONDS = 1000d; private static final double NANOS_TO_SECONDS = 1000d * 1000d * 1000d; + public static final String TRUNCATION_PLACEHOLDER = "~"; private static double millisToSeconds(double millis) { return millis / MILLIS_TO_SECONDS; @@ -937,39 +976,50 @@ private static String getUsedMemory() { } private static String stringFilledWith(int size, String fill) { - return new String(new char[size]).replace("\0", fill); + return fill.repeat(size); } private static double toPercentage(long part, long total) { return part / (double) total * 100; } - private static String truncateClassOrPackageName(String classOrPackageName) { - int classNameLength = classOrPackageName.length(); - int maxLength = CHARACTERS_PER_LINE / 2 - 10; + private static String truncateName(String name, int maxLength) { + int length = name.length(); + if (length <= maxLength) { + return name; + } + return TRUNCATION_PLACEHOLDER + name.substring(length - maxLength + TRUNCATION_PLACEHOLDER.length(), length); + } + + private static String truncateFQN(String fqn) { + return truncateFQN(fqn, CHARACTERS_PER_LINE / 2 - 10); + } + + static String truncateFQN(String fqn, int maxLength) { + int classNameLength = fqn.length(); if (classNameLength <= maxLength) { - return classOrPackageName; + return fqn; } StringBuilder sb = new StringBuilder(); int currentDot = -1; while (true) { - int nextDot = classOrPackageName.indexOf('.', currentDot + 1); + int nextDot = fqn.indexOf('.', currentDot + 1); if (nextDot < 0) { // Not more dots, handle the rest and return. - String rest = classOrPackageName.substring(currentDot + 1); + String rest = fqn.substring(currentDot + 1); int sbLength = sb.length(); int restLength = rest.length(); if (sbLength + restLength <= maxLength) { sb.append(rest); } else { int remainingSpaceDivBy2 = (maxLength - sbLength) / 2; - sb.append(rest, 0, remainingSpaceDivBy2 - 1).append("~").append(rest, restLength - remainingSpaceDivBy2, restLength); + sb.append(rest, 0, remainingSpaceDivBy2 - 1).append(TRUNCATION_PLACEHOLDER).append(rest, restLength - remainingSpaceDivBy2, restLength); } break; } - sb.append(classOrPackageName.charAt(currentDot + 1)).append('.'); + sb.append(fqn.charAt(currentDot + 1)).append('.'); if (sb.length() + (classNameLength - nextDot) <= maxLength) { // Rest fits maxLength, append and return. - sb.append(classOrPackageName.substring(nextDot + 1)); + sb.append(fqn.substring(nextDot + 1)); break; } currentDot = nextDot; From 3028b13d092d5637c2244c6b491a05b2c6852979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Tue, 12 Aug 2025 09:49:32 +0200 Subject: [PATCH 2/8] Make truncation-settings based on ProgressReporter#CHARACTERS_PER_LINE --- .../svm/hosted/HeapBreakdownProvider.java | 2 +- .../oracle/svm/hosted/ProgressReporter.java | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index ec0489853d7f..34cf4f7d2aa7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -216,7 +216,7 @@ public static class HeapBreakdownEntry { public HeapBreakdownEntry(HostedClass hostedClass) { this(ProgressReporter.moduleNamePrefix(hostedClass.getJavaClass().getModule()) + - ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 40)); + ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 0.32)); } public HeapBreakdownEntry(String name) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 92d476cb08eb..df08f0fb983c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -658,8 +658,13 @@ private static String sourcePath(Class clazz) { @Override public String toString() { - String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 14) + ")"; - return moduleNamePrefix(javaModule) + Utils.truncateFQN(javaPackage.getName(), 22) + locationSuffix; + String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 0.12) + ")"; + double packageNameRatio = 0.19; + if (!javaModule.isNamed()) { + /* Give extra package name space if module not shown */ + packageNameRatio += 0.13; + } + return moduleNamePrefix(javaModule) + Utils.truncateFQN(javaPackage.getName(), packageNameRatio) + locationSuffix; } } @@ -671,7 +676,7 @@ static String moduleNamePrefix(Module javaModule) { if (javaModule.equals(DynamicHub.class.getModule())) { return "VM "; } - return Utils.truncateFQN(javaModule.getName(), 19) + "/"; + return Utils.truncateFQN(javaModule.getName(), 0.16) + "/"; } private void printBreakdowns() { @@ -701,6 +706,7 @@ private void printBreakdowns() { long printedHeapItems = 0; for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; + /* <- 16% for module name -><- 19% or 32% for class FQN -><- 12% for location -> */ if (packagesBySize.hasNext()) { Entry entry = packagesBySize.next(); codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(entry.getValue()), entry.getKey()); @@ -709,15 +715,12 @@ private void printBreakdowns() { } String heapSizePart = ""; + /* <- 16% for module name -><- 32% for class FQN -> */ if (typesBySizeInHeap.hasNext()) { HeapBreakdownProvider.HeapBreakdownEntry e = typesBySizeInHeap.next(); - String className = e.label.renderToString(linkStrategy); - // Do not truncate special breakdown items, they can contain links. - if (e.label instanceof HeapBreakdownProvider.SimpleHeapObjectKindName) { - className = Utils.truncateFQN(className); - } + String labelString = e.label.renderToString(linkStrategy); long byteSize = e.byteSize; - heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(byteSize), className); + heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(byteSize), labelString); printedHeapBytes += byteSize; printedHeapItems++; } @@ -983,19 +986,20 @@ private static double toPercentage(long part, long total) { return part / (double) total * 100; } - private static String truncateName(String name, int maxLength) { + private static String truncateName(String name, double maxLineRatio) { int length = name.length(); + int maxLength = (int) (CHARACTERS_PER_LINE * maxLineRatio); if (length <= maxLength) { return name; } return TRUNCATION_PLACEHOLDER + name.substring(length - maxLength + TRUNCATION_PLACEHOLDER.length(), length); } - private static String truncateFQN(String fqn) { - return truncateFQN(fqn, CHARACTERS_PER_LINE / 2 - 10); + static String truncateFQN(String fqn, double maxLineRatio) { + return truncateFQN(fqn, (int) (CHARACTERS_PER_LINE * maxLineRatio)); } - static String truncateFQN(String fqn, int maxLength) { + private static String truncateFQN(String fqn, int maxLength) { int classNameLength = fqn.length(); if (classNameLength <= maxLength) { return fqn; From 25a07797d351ba1179b1295519b680df1a818445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Mon, 18 Aug 2025 10:03:57 +0200 Subject: [PATCH 3/8] Ensure remainder of size in 1st column goes to package label --- .../svm/hosted/HeapBreakdownProvider.java | 2 +- .../oracle/svm/hosted/ProgressReporter.java | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index 34cf4f7d2aa7..84ccb1c1de27 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -216,7 +216,7 @@ public static class HeapBreakdownEntry { public HeapBreakdownEntry(HostedClass hostedClass) { this(ProgressReporter.moduleNamePrefix(hostedClass.getJavaClass().getModule()) + - ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 0.32)); + ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 0.29)); } public HeapBreakdownEntry(String name) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index df08f0fb983c..497913adc3fa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -656,17 +656,14 @@ private static String sourcePath(Class clazz) { return null; } - @Override - public String toString() { - String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 0.12) + ")"; - double packageNameRatio = 0.19; - if (!javaModule.isNamed()) { - /* Give extra package name space if module not shown */ - packageNameRatio += 0.13; - } - return moduleNamePrefix(javaModule) + Utils.truncateFQN(javaPackage.getName(), packageNameRatio) + locationSuffix; + public String renderToString(int maxLength) { + String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 0.10) + ")"; + String packageName = javaPackage == null ? "null" : javaPackage.getName(); + String moduleNamePrefix = moduleNamePrefix(javaModule); + // Give remainder of space to package-part + int maxLengthPackage = maxLength - moduleNamePrefix.length() - locationSuffix.length(); + return moduleNamePrefix + Utils.truncateFQN(packageName, maxLengthPackage) + locationSuffix; } - } static String moduleNamePrefix(Module javaModule) { @@ -676,7 +673,7 @@ static String moduleNamePrefix(Module javaModule) { if (javaModule.equals(DynamicHub.class.getModule())) { return "VM "; } - return Utils.truncateFQN(javaModule.getName(), 0.16) + "/"; + return Utils.truncateFQN(javaModule.getName(), 0.10) + "/"; } private void printBreakdowns() { @@ -706,16 +703,18 @@ private void printBreakdowns() { long printedHeapItems = 0; for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; - /* <- 16% for module name -><- 19% or 32% for class FQN -><- 12% for location -> */ + /* <- 10% for module name -><- remainder for class FQN -><- 10% for location -> */ if (packagesBySize.hasNext()) { Entry entry = packagesBySize.next(); - codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(entry.getValue()), entry.getKey()); + String sizeStr = String.format("%9s ", ByteFormattingUtil.bytesToHuman(entry.getValue())); + String entryStr = entry.getKey().renderToString(p.middle() - sizeStr.length()); + codeSizePart = sizeStr + entryStr; printedCodeBytes += entry.getValue(); printedCodeItems++; } String heapSizePart = ""; - /* <- 16% for module name -><- 32% for class FQN -> */ + /* <- 10% for module name -><- 29% for class FQN -> */ if (typesBySizeInHeap.hasNext()) { HeapBreakdownProvider.HeapBreakdownEntry e = typesBySizeInHeap.next(); String labelString = e.label.renderToString(linkStrategy); @@ -988,7 +987,7 @@ private static double toPercentage(long part, long total) { private static String truncateName(String name, double maxLineRatio) { int length = name.length(); - int maxLength = (int) (CHARACTERS_PER_LINE * maxLineRatio); + int maxLength = maxLength(maxLineRatio); if (length <= maxLength) { return name; } @@ -996,7 +995,11 @@ private static String truncateName(String name, double maxLineRatio) { } static String truncateFQN(String fqn, double maxLineRatio) { - return truncateFQN(fqn, (int) (CHARACTERS_PER_LINE * maxLineRatio)); + return truncateFQN(fqn, maxLength(maxLineRatio)); + } + + static int maxLength(double maxLineRatio) { + return (int) Math.floor(CHARACTERS_PER_LINE * maxLineRatio); } private static String truncateFQN(String fqn, int maxLength) { @@ -1429,12 +1432,16 @@ public TwoColumnPrinter a(String value) { } TwoColumnPrinter jumpToMiddle() { - int remaining = (CHARACTERS_PER_LINE / 2) - getCurrentTextLength(); + int remaining = middle() - getCurrentTextLength(); assert remaining >= 0 : "Column text too wide"; a(Utils.stringFilledWith(remaining, " ")); - assert getCurrentTextLength() == CHARACTERS_PER_LINE / 2; + assert getCurrentTextLength() == middle(); return this; } + + private int middle() { + return CHARACTERS_PER_LINE / 2; + } } public final class CenteredTextPrinter extends LinePrinter { From 51eded68df344edff30b7690bf3283916deeb992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Mon, 18 Aug 2025 12:20:02 +0200 Subject: [PATCH 4/8] Implement BuildOutputCodeBreakdownFile generation --- .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../oracle/svm/hosted/ProgressReporter.java | 56 ++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 3a5669274f89..4eacc9da849b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -803,6 +803,9 @@ public static boolean hasColorsEnabled(OptionValues values) { @Option(help = "Print GC warnings as part of build output", type = OptionType.User)// public static final HostedOptionKey BuildOutputGCWarnings = new HostedOptionKey<>(true); + @Option(help = "Write code breakdown information into CSV file", type = OptionType.User)// + public static final HostedOptionKey BuildOutputCodeBreakdownFile = new HostedOptionKey<>(false); + @BundleMember(role = BundleMember.Role.Output)// @Option(help = "Print build output statistics as JSON to the specified file. " + "The output conforms to the JSON schema located at: " + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 497913adc3fa..d97e0afb9102 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -30,6 +30,7 @@ import java.io.PrintWriter; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; +import java.nio.file.Files; import java.nio.file.Path; import java.security.CodeSource; import java.util.ArrayList; @@ -664,35 +665,59 @@ public String renderToString(int maxLength) { int maxLengthPackage = maxLength - moduleNamePrefix.length() - locationSuffix.length(); return moduleNamePrefix + Utils.truncateFQN(packageName, maxLengthPackage) + locationSuffix; } + + public String[] elements() { + String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; + String packageName = javaPackage == null ? "" : javaPackage.getName(); + String locationName = location == null ? "" : location; + return new String[]{mapToNativeImageRuntime(moduleName), packageName, locationName}; + } } static String moduleNamePrefix(Module javaModule) { - if (!javaModule.isNamed()) { - return ""; - } - if (javaModule.equals(DynamicHub.class.getModule())) { - return "VM "; + String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; + return Utils.truncateFQN(mapToNativeImageRuntime(moduleName), 0.10) + "/"; + } + + private static String mapToNativeImageRuntime(String moduleName) { + String modulePrefix = "org.graalvm.nativeimage."; + if (moduleName.equals(modulePrefix + "builder")) { + return modulePrefix + "runtime"; } - return Utils.truncateFQN(javaModule.getName(), 0.10) + "/"; + return moduleName; } private void printBreakdowns() { - if (!SubstrateOptions.BuildOutputBreakdowns.getValue()) { - return; - } - l().printLineSeparator(); Map codeBreakdown = CodeBreakdownProvider.getAndClear().entrySet().stream() .collect(Collectors.groupingBy( entry -> BreakDownClassifier.of(entry.getKey()), Collectors.summingLong(entry -> entry.getValue()))); - Iterator> packagesBySize = codeBreakdown.entrySet().stream() - .sorted(Entry.comparingByValue(Comparator.reverseOrder())).iterator(); + List> sortedBreakdownData = codeBreakdown.entrySet().stream() + .sorted(Entry.comparingByValue(Comparator.reverseOrder())).toList(); + + if (SubstrateOptions.BuildOutputCodeBreakdownFile.getValue()) { + String valueSeparator = ","; + List lines = new ArrayList<>(); + lines.add(String.join(valueSeparator, "Size", "Module", "Package", "Location")); + sortedBreakdownData.forEach(entry -> { + lines.add(entry.getValue() + valueSeparator + String.join(valueSeparator, entry.getKey().elements())); + }); + Path breakdownFile = SubstrateOptions.getImagePath().resolve(SubstrateOptions.Name.getValue() + ".code_breakdown.csv"); + BuildArtifacts.singleton().add(BuildArtifacts.ArtifactType.BUILD_INFO, breakdownFile); + try { + Files.write(breakdownFile, lines); + } catch (IOException e) { + throw VMError.shouldNotReachHere("Failed generating " + breakdownFile, e); + } + } - HeapBreakdownProvider heapBreakdown = HeapBreakdownProvider.singleton(); - Iterator typesBySizeInHeap = heapBreakdown.getSortedBreakdownEntries().iterator(); + if (!SubstrateOptions.BuildOutputBreakdowns.getValue()) { + return; + } final TwoColumnPrinter p = new TwoColumnPrinter(); + l().printLineSeparator(); p.l().yellowBold().a(String.format("Top %d ", MAX_NUM_BREAKDOWN)).doclink("origins", "#glossary-code-area-origins").a(" of code area:") .jumpToMiddle() .a(String.format("Top %d object types in image heap:", MAX_NUM_BREAKDOWN)).reset().flushln(); @@ -701,6 +726,9 @@ private void printBreakdowns() { long printedHeapBytes = 0; long printedCodeItems = 0; long printedHeapItems = 0; + Iterator> packagesBySize = sortedBreakdownData.iterator(); + HeapBreakdownProvider heapBreakdown = HeapBreakdownProvider.singleton(); + Iterator typesBySizeInHeap = heapBreakdown.getSortedBreakdownEntries().iterator(); for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; /* <- 10% for module name -><- remainder for class FQN -><- 10% for location -> */ From ab50d01c2b0259e6abfa0acec4e2e5a742048be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Tue, 19 Aug 2025 12:32:50 +0200 Subject: [PATCH 5/8] Ensure other use of HeapBreakdownEntry is unaffected by truncation --- .../svm/hosted/HeapBreakdownProvider.java | 69 +++++++++++++------ .../oracle/svm/hosted/ProgressReporter.java | 3 +- .../WebImageJSHeapBreakdownProvider.java | 2 +- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index 84ccb1c1de27..2409dc706fce 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -114,7 +114,7 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre } long objectSize = o.getSize(); totalObjectSize += objectSize; - classToDataMap.computeIfAbsent(o.getClazz(), c -> new HeapBreakdownEntry(c)).add(objectSize); + classToDataMap.computeIfAbsent(o.getClazz(), HeapBreakdownEntry::of).add(objectSize); if (reportStringBytesConstant && o.getObject() instanceof String string) { byte[] bytes = getInternalByteArray(string); /* Ensure every byte[] is counted only once. */ @@ -140,24 +140,24 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre long heapAlignmentSize = getTotalHeapSize() - totalObjectSize; assert heapAlignmentSize >= 0 : "Incorrect heap alignment detected: " + heapAlignmentSize; if (heapAlignmentSize > 0) { - HeapBreakdownEntry heapAlignmentEntry = new HeapBreakdownEntry("", "heap alignment", "#glossary-heap-alignment"); + HeapBreakdownEntry heapAlignmentEntry = HeapBreakdownEntry.of("", "heap alignment", "#glossary-heap-alignment"); heapAlignmentEntry.add(heapAlignmentSize); entries.add(heapAlignmentEntry); } /* Extract byte[] for Strings. */ if (stringByteArrayTotalSize > 0) { - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX + "string data"), stringByteArrayTotalSize, stringByteArrayTotalCount); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX + "string data"), stringByteArrayTotalSize, stringByteArrayTotalCount); } /* Extract byte[] for code info. */ List codeInfoByteArrayLengths = CodeInfoTable.getCurrentLayerImageCodeCache().getTotalByteArrayLengths(); long codeInfoSize = codeInfoByteArrayLengths.stream().map(l -> objectLayout.getArraySize(JavaKind.Byte, l, true)).reduce(0L, Long::sum); - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX, "code metadata", "#glossary-code-metadata"), codeInfoSize, codeInfoByteArrayLengths.size()); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX, "code metadata", "#glossary-code-metadata"), codeInfoSize, codeInfoByteArrayLengths.size()); /* Extract byte[] for metadata. */ int metadataByteLength = ImageSingletons.lookup(RuntimeMetadataDecoder.class).getMetadataByteLength(); if (metadataByteLength > 0) { long metadataSize = objectLayout.getArraySize(JavaKind.Byte, metadataByteLength, true); - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX, "reflection metadata", "#glossary-reflection-metadata"), metadataSize, 1); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX, "reflection metadata", "#glossary-reflection-metadata"), metadataSize, 1); } ProgressReporter reporter = ProgressReporter.singleton(); long resourcesByteArraySize = 0; @@ -177,7 +177,7 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre } } if (resourcesByteArraySize > 0) { - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX, "embedded resources", "#glossary-embedded-resources"), resourcesByteArraySize, resourcesByteArrayCount); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX, "embedded resources", "#glossary-embedded-resources"), resourcesByteArraySize, resourcesByteArrayCount); } } reporter.recordJsonMetric(ImageDetailKey.RESOURCE_SIZE_BYTES, resourcesByteArraySize); @@ -185,11 +185,11 @@ protected void calculate(BeforeImageWriteAccessImpl access, boolean resourcesAre if (graphEncodingByteLength >= 0) { long graphEncodingSize = objectLayout.getArraySize(JavaKind.Byte, graphEncodingByteLength, true); reporter.recordJsonMetric(ImageDetailKey.GRAPH_ENCODING_SIZE, graphEncodingSize); - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX, "graph encodings", "#glossary-graph-encodings"), graphEncodingSize, 1); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX, "graph encodings", "#glossary-graph-encodings"), graphEncodingSize, 1); } /* Add remaining byte[]. */ assert byteArrayEntry.byteSize >= 0 && byteArrayEntry.count >= 0; - addEntry(entries, byteArrayEntry, new HeapBreakdownEntry(BYTE_ARRAY_PREFIX, "general heap data", "#glossary-general-heap-data"), byteArrayEntry.byteSize, byteArrayEntry.count); + addEntry(entries, byteArrayEntry, HeapBreakdownEntry.of(BYTE_ARRAY_PREFIX, "general heap data", "#glossary-general-heap-data"), byteArrayEntry.byteSize, byteArrayEntry.count); assert byteArrayEntry.byteSize == 0 && byteArrayEntry.count == 0; setBreakdownEntries(entries); } @@ -209,27 +209,23 @@ private static byte[] getInternalByteArray(String string) { } } - public static class HeapBreakdownEntry { - final HeapBreakdownLabel label; + public abstract static class HeapBreakdownEntry { long byteSize; int count; - public HeapBreakdownEntry(HostedClass hostedClass) { - this(ProgressReporter.moduleNamePrefix(hostedClass.getJavaClass().getModule()) + - ProgressReporter.Utils.truncateFQN(hostedClass.toJavaName(true), 0.29)); + public static HeapBreakdownEntry of(HostedClass hostedClass) { + return new HeapBreakdownEntryForClass(hostedClass.getJavaClass()); } - public HeapBreakdownEntry(String name) { - label = new SimpleHeapObjectKindName(name); + public static HeapBreakdownEntry of(String name) { + return new HeapBreakdownEntryFixed(new SimpleHeapObjectKindName(name)); } - HeapBreakdownEntry(String prefix, String name, String htmlAnchor) { - label = new LinkyHeapObjectKindName(prefix, name, htmlAnchor); + public static HeapBreakdownEntry of(String prefix, String name, String htmlAnchor) { + return new HeapBreakdownEntryFixed(new LinkyHeapObjectKindName(prefix, name, htmlAnchor)); } - public HeapBreakdownLabel getLabel() { - return label; - } + public abstract HeapBreakdownLabel getLabel(boolean truncate); public long getByteSize() { return byteSize; @@ -254,6 +250,39 @@ void remove(long subByteSize, int subCount) { } } + static class HeapBreakdownEntryFixed extends HeapBreakdownEntry { + + private final HeapBreakdownLabel label; + + HeapBreakdownEntryFixed(HeapBreakdownLabel label) { + this.label = label; + } + + @Override + public HeapBreakdownLabel getLabel(boolean unused) { + return label; + } + } + + static class HeapBreakdownEntryForClass extends HeapBreakdownEntry { + + private final Class clazz; + + HeapBreakdownEntryForClass(Class clazz) { + this.clazz = clazz; + } + + @Override + public HeapBreakdownLabel getLabel(boolean truncate) { + if (truncate) { + return new SimpleHeapObjectKindName(ProgressReporter.moduleNamePrefix(clazz.getModule()) + + ProgressReporter.Utils.truncateFQN(clazz.getTypeName(), 0.29)); + } else { + return new SimpleHeapObjectKindName(clazz.getTypeName()); + } + } + } + public interface HeapBreakdownLabel { String renderToString(LinkStrategy linkStrategy); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index d97e0afb9102..6f50e0d5d579 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -77,7 +77,6 @@ import com.oracle.svm.core.VM; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.hub.ClassForNameSupport; -import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; @@ -745,7 +744,7 @@ private void printBreakdowns() { /* <- 10% for module name -><- 29% for class FQN -> */ if (typesBySizeInHeap.hasNext()) { HeapBreakdownProvider.HeapBreakdownEntry e = typesBySizeInHeap.next(); - String labelString = e.label.renderToString(linkStrategy); + String labelString = e.getLabel(true).renderToString(linkStrategy); long byteSize = e.byteSize; heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(byteSize), labelString); printedHeapBytes += byteSize; diff --git a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/WebImageJSHeapBreakdownProvider.java b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/WebImageJSHeapBreakdownProvider.java index c8e29c7cdc2c..e1b099dbe2d0 100644 --- a/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/WebImageJSHeapBreakdownProvider.java +++ b/web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/WebImageJSHeapBreakdownProvider.java @@ -56,7 +56,7 @@ protected void calculate(FeatureImpl.BeforeImageWriteAccessImpl access, boolean for (ConstantIdentityMapping.IdentityNode node : identityMapping.identityNodes()) { HostedClass type = (HostedClass) providers.getMetaAccess().lookupJavaType(node.getDefinition().getConstant()); long size = node.getDefinition().getSize(); - objectTypeEntries.computeIfAbsent(type, HeapBreakdownEntry::new).add(size); + objectTypeEntries.computeIfAbsent(type, HeapBreakdownEntry::of).add(size); totalByteSize += size; } From 354cdcdf261a70739e2ce189194999f6b6812b94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Wed, 20 Aug 2025 16:56:46 +0200 Subject: [PATCH 6/8] Move BreakDownClassifier into ProgressReporterUtils (former ProgressReporter$Utils) --- .../svm/hosted/HeapBreakdownProvider.java | 4 +- .../oracle/svm/hosted/ProgressReporter.java | 181 +++--------------- .../svm/hosted/ProgressReporterUtils.java | 149 ++++++++++++++ 3 files changed, 181 insertions(+), 153 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index 2409dc706fce..1d57ef635389 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -275,8 +275,8 @@ static class HeapBreakdownEntryForClass extends HeapBreakdownEntry { @Override public HeapBreakdownLabel getLabel(boolean truncate) { if (truncate) { - return new SimpleHeapObjectKindName(ProgressReporter.moduleNamePrefix(clazz.getModule()) + - ProgressReporter.Utils.truncateFQN(clazz.getTypeName(), 0.29)); + return new SimpleHeapObjectKindName(ProgressReporterUtils.moduleNamePrefix(clazz.getModule()) + + ProgressReporterUtils.truncateFQN(clazz.getTypeName(), 0.29)); } else { return new SimpleHeapObjectKindName(clazz.getTypeName()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 6f50e0d5d579..0ebc35edbcd8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -32,7 +32,6 @@ import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.nio.file.Path; -import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -54,8 +53,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.oracle.svm.core.configure.ConditionalRuntimeValue; -import com.oracle.svm.core.jdk.resources.ResourceStorageEntryBase; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.ImageSingletonsSupport; @@ -75,9 +72,11 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.VM; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.core.jdk.resources.ResourceStorageEntryBase; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.HostedOptionValues; @@ -116,7 +115,7 @@ @SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Independent.class) public class ProgressReporter { - private static final int CHARACTERS_PER_LINE; + static final int CHARACTERS_PER_LINE; private static final String HEADLINE_SEPARATOR; private static final String LINE_SEPARATOR; private static final int MAX_NUM_BREAKDOWN = 10; @@ -178,8 +177,8 @@ private enum BuildStage { static { CHARACTERS_PER_LINE = SubstrateUtil.isNonInteractiveTerminal() ? ProgressReporterCHelper.MAX_CHARACTERS_PER_LINE : ProgressReporterCHelper.getTerminalWindowColumnsClamped(); - HEADLINE_SEPARATOR = Utils.stringFilledWith(CHARACTERS_PER_LINE, "="); - LINE_SEPARATOR = Utils.stringFilledWith(CHARACTERS_PER_LINE, "-"); + HEADLINE_SEPARATOR = ProgressReporterUtils.stringFilledWith(CHARACTERS_PER_LINE, "="); + LINE_SEPARATOR = ProgressReporterUtils.stringFilledWith(CHARACTERS_PER_LINE, "-"); } public static ProgressReporter singleton() { @@ -444,9 +443,9 @@ private void printResourceInfo() { l().printLineSeparator(); l().yellowBold().doclink("Build resources", "#glossary-build-resources").a(":").reset().println(); - l().a(" - %s of memory (%.1f%% of system memory, %s)", ByteFormattingUtil.bytesToHuman(maxMemory), Utils.toPercentage(maxMemory, totalMemorySize), memoryUsageReason).println(); + l().a(" - %s of memory (%.1f%% of system memory, %s)", ByteFormattingUtil.bytesToHuman(maxMemory), ProgressReporterUtils.toPercentage(maxMemory, totalMemorySize), memoryUsageReason).println(); l().a(" - %s thread(s) (%.1f%% of %s available processor(s), %s)", - maxNumberOfThreads, Utils.toPercentage(maxNumberOfThreads, availableProcessors), availableProcessors, maxNumberOfThreadsSuffix).println(); + maxNumberOfThreads, ProgressReporterUtils.toPercentage(maxNumberOfThreads, availableProcessors), availableProcessors, maxNumberOfThreadsSuffix).println(); } public ReporterClosable printAnalysis(AnalysisUniverse universe, Collection libraries) { @@ -534,7 +533,7 @@ private void printAnalysisStatistics(AnalysisUniverse universe, Collection= 0) { recordJsonMetric(ImageDetailKey.RUNTIME_COMPILED_METHODS_COUNT, numRuntimeCompiledMethods); l().a("%,9d ", numRuntimeCompiledMethods).doclink("runtime compiled methods", "#glossary-runtime-methods") - .a(" (%.1f%% of all reachable methods)", Utils.toPercentage(numRuntimeCompiledMethods, reachableMethods), reachableMethods).println(); + .a(" (%.1f%% of all reachable methods)", ProgressReporterUtils.toPercentage(numRuntimeCompiledMethods, reachableMethods), reachableMethods).println(); } } @@ -592,7 +591,7 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH stagePrinter.end(imageTimer.getTotalTime() + writeTimer.getTotalTime() + archiveTimer.getTotalTime()); creationStageEndCompleted = true; String format = "%9s (%5.2f%%) for "; - l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), Utils.toPercentage(codeAreaSize, imageFileSize)) + l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), ProgressReporterUtils.toPercentage(codeAreaSize, imageFileSize)) .doclink("code area", "#glossary-code-area").a(":%,10d compilation units", numCompilations).println(); int numResources = 0; for (ConditionalRuntimeValue entry : Resources.currentLayer().resources().getValues()) { @@ -601,16 +600,16 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH } } recordJsonMetric(ImageDetailKey.IMAGE_HEAP_RESOURCE_COUNT, numResources); - l().a(format, ByteFormattingUtil.bytesToHuman(imageHeapSize), Utils.toPercentage(imageHeapSize, imageFileSize)) + l().a(format, ByteFormattingUtil.bytesToHuman(imageHeapSize), ProgressReporterUtils.toPercentage(imageHeapSize, imageFileSize)) .doclink("image heap", "#glossary-image-heap").a(":%,9d objects and %,d resource%s", heapObjectCount, numResources, numResources == 1 ? "" : "s").println(); long otherBytes = imageFileSize - codeAreaSize - imageHeapSize; if (debugInfoSize > 0) { recordJsonMetric(ImageDetailKey.DEBUG_INFO_SIZE, debugInfoSize); // Optional metric - DirectPrinter l = l().a(format, ByteFormattingUtil.bytesToHuman(debugInfoSize), Utils.toPercentage(debugInfoSize, imageFileSize)) + DirectPrinter l = l().a(format, ByteFormattingUtil.bytesToHuman(debugInfoSize), ProgressReporterUtils.toPercentage(debugInfoSize, imageFileSize)) .doclink("debug info", "#glossary-debug-info"); if (debugInfoTimer != null) { - l.a(" generated in %.1fs", Utils.millisToSeconds(debugInfoTimer.getTotalTime())); + l.a(" generated in %.1fs", ProgressReporterUtils.millisToSeconds(debugInfoTimer.getTotalTime())); } l.println(); if (!(ImageSingletons.contains(NativeImageDebugInfoStripFeature.class) && ImageSingletons.lookup(NativeImageDebugInfoStripFeature.class).hasStrippedSuccessfully())) { @@ -623,7 +622,7 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH recordJsonMetric(ImageDetailKey.TOTAL_SIZE, imageFileSize); recordJsonMetric(ImageDetailKey.CODE_AREA_SIZE, codeAreaSize); recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations); - l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), Utils.toPercentage(otherBytes, imageFileSize)) + l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), ProgressReporterUtils.toPercentage(otherBytes, imageFileSize)) .doclink("other data", "#glossary-other-data").println(); l().a("%9s in total image size", ByteFormattingUtil.bytesToHuman(imageFileSize)); if (imageDiskFileSize >= 0) { @@ -641,58 +640,13 @@ public void ensureCreationStageEndCompleted() { } } - private record BreakDownClassifier(Package javaPackage, Module javaModule, String location) { - static BreakDownClassifier of(Class clazz) { - return new BreakDownClassifier(clazz.getPackage(), clazz.getModule(), sourcePath(clazz)); - } - - private static String sourcePath(Class clazz) { - CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); - if (codeSource != null && codeSource.getLocation() != null) { - String path = codeSource.getLocation().getPath(); - // Use String API to determine basename of path to handle both / and \. - return path.substring(Math.max(path.lastIndexOf('/') + 1, path.lastIndexOf('\\') + 1)); - } - return null; - } - - public String renderToString(int maxLength) { - String locationSuffix = location == null ? "" : " (" + Utils.truncateName(location, 0.10) + ")"; - String packageName = javaPackage == null ? "null" : javaPackage.getName(); - String moduleNamePrefix = moduleNamePrefix(javaModule); - // Give remainder of space to package-part - int maxLengthPackage = maxLength - moduleNamePrefix.length() - locationSuffix.length(); - return moduleNamePrefix + Utils.truncateFQN(packageName, maxLengthPackage) + locationSuffix; - } - - public String[] elements() { - String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; - String packageName = javaPackage == null ? "" : javaPackage.getName(); - String locationName = location == null ? "" : location; - return new String[]{mapToNativeImageRuntime(moduleName), packageName, locationName}; - } - } - - static String moduleNamePrefix(Module javaModule) { - String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; - return Utils.truncateFQN(mapToNativeImageRuntime(moduleName), 0.10) + "/"; - } - - private static String mapToNativeImageRuntime(String moduleName) { - String modulePrefix = "org.graalvm.nativeimage."; - if (moduleName.equals(modulePrefix + "builder")) { - return modulePrefix + "runtime"; - } - return moduleName; - } - private void printBreakdowns() { - Map codeBreakdown = CodeBreakdownProvider.getAndClear().entrySet().stream() + Map codeBreakdown = CodeBreakdownProvider.getAndClear().entrySet().stream() .collect(Collectors.groupingBy( - entry -> BreakDownClassifier.of(entry.getKey()), - Collectors.summingLong(entry -> entry.getValue()))); + entry -> ProgressReporterUtils.BreakDownClassifier.of(entry.getKey()), + Collectors.summingLong(Entry::getValue))); - List> sortedBreakdownData = codeBreakdown.entrySet().stream() + List> sortedBreakdownData = codeBreakdown.entrySet().stream() .sorted(Entry.comparingByValue(Comparator.reverseOrder())).toList(); if (SubstrateOptions.BuildOutputCodeBreakdownFile.getValue()) { @@ -725,14 +679,14 @@ private void printBreakdowns() { long printedHeapBytes = 0; long printedCodeItems = 0; long printedHeapItems = 0; - Iterator> packagesBySize = sortedBreakdownData.iterator(); + Iterator> packagesBySize = sortedBreakdownData.iterator(); HeapBreakdownProvider heapBreakdown = HeapBreakdownProvider.singleton(); Iterator typesBySizeInHeap = heapBreakdown.getSortedBreakdownEntries().iterator(); for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; /* <- 10% for module name -><- remainder for class FQN -><- 10% for location -> */ if (packagesBySize.hasNext()) { - Entry entry = packagesBySize.next(); + Entry entry = packagesBySize.next(); String sizeStr = String.format("%9s ", ByteFormattingUtil.bytesToHuman(entry.getValue())); String entryStr = entry.getKey().renderToString(p.middle() - sizeStr.length()); codeSizePart = sizeStr + entryStr; @@ -778,7 +732,7 @@ private void printRecommendations() { l().printLineSeparator(); l().yellowBold().a("Recommendations:").reset().println(); for (UserRecommendation r : topApplicableRecommendations) { - String alignment = Utils.stringFilledWith(Math.max(1, 5 - r.id().length()), " "); + String alignment = ProgressReporterUtils.stringFilledWith(Math.max(1, 5 - r.id().length()), " "); l().a(" ").doclink(r.id(), "#recommendation-" + r.id().toLowerCase(Locale.ROOT)).a(":").a(alignment).a(r.description()).println(); } } @@ -811,7 +765,7 @@ public void printEpilog(Optional optionalImageName, Optional 0) { - cpuLoad = Utils.nanosToSeconds(processCPUTime) / totalProcessTimeSeconds; + cpuLoad = ProgressReporterUtils.nanosToSeconds(processCPUTime) / totalProcessTimeSeconds; p.a(" | ").doclink("CPU load", "#glossary-cpu-load").a(": ").a("%.2f", cpuLoad); } recordJsonMetric(ResourceUsageKey.CPU_LOAD, cpuLoad); @@ -947,7 +901,7 @@ private void checkForExcessiveGarbageCollection() { if (gcTimeDeltaMillis > EXCESSIVE_GC_MIN_THRESHOLD_MILLIS && ratio > EXCESSIVE_GC_RATIO) { l().redBold().a("GC warning").reset() .a(": %.1fs spent in %d GCs during the last stage, taking up %.2f%% of the time.", - Utils.millisToSeconds(gcTimeDeltaMillis), currentGCStats.totalCount - lastGCStats.totalCount, ratio * 100) + ProgressReporterUtils.millisToSeconds(gcTimeDeltaMillis), currentGCStats.totalCount - lastGCStats.totalCount, ratio * 100) .println(); l().a(" Please ensure more than %s of memory is available for Native Image", ByteFormattingUtil.bytesToHuman(ProgressReporterCHelper.getPeakRSS())).println(); l().a(" to reduce GC overhead and improve image build time.").println(); @@ -987,81 +941,6 @@ private static OperatingSystemMXBean getOperatingSystemMXBean() { return (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); } - static final class Utils { - private static final double MILLIS_TO_SECONDS = 1000d; - private static final double NANOS_TO_SECONDS = 1000d * 1000d * 1000d; - public static final String TRUNCATION_PLACEHOLDER = "~"; - - private static double millisToSeconds(double millis) { - return millis / MILLIS_TO_SECONDS; - } - - private static double nanosToSeconds(double nanos) { - return nanos / NANOS_TO_SECONDS; - } - - private static String getUsedMemory() { - return ByteFormattingUtil.bytesToHumanGB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); - } - - private static String stringFilledWith(int size, String fill) { - return fill.repeat(size); - } - - private static double toPercentage(long part, long total) { - return part / (double) total * 100; - } - - private static String truncateName(String name, double maxLineRatio) { - int length = name.length(); - int maxLength = maxLength(maxLineRatio); - if (length <= maxLength) { - return name; - } - return TRUNCATION_PLACEHOLDER + name.substring(length - maxLength + TRUNCATION_PLACEHOLDER.length(), length); - } - - static String truncateFQN(String fqn, double maxLineRatio) { - return truncateFQN(fqn, maxLength(maxLineRatio)); - } - - static int maxLength(double maxLineRatio) { - return (int) Math.floor(CHARACTERS_PER_LINE * maxLineRatio); - } - - private static String truncateFQN(String fqn, int maxLength) { - int classNameLength = fqn.length(); - if (classNameLength <= maxLength) { - return fqn; - } - StringBuilder sb = new StringBuilder(); - int currentDot = -1; - while (true) { - int nextDot = fqn.indexOf('.', currentDot + 1); - if (nextDot < 0) { // Not more dots, handle the rest and return. - String rest = fqn.substring(currentDot + 1); - int sbLength = sb.length(); - int restLength = rest.length(); - if (sbLength + restLength <= maxLength) { - sb.append(rest); - } else { - int remainingSpaceDivBy2 = (maxLength - sbLength) / 2; - sb.append(rest, 0, remainingSpaceDivBy2 - 1).append(TRUNCATION_PLACEHOLDER).append(rest, restLength - remainingSpaceDivBy2, restLength); - } - break; - } - sb.append(fqn.charAt(currentDot + 1)).append('.'); - if (sb.length() + (classNameLength - nextDot) <= maxLength) { - // Rest fits maxLength, append and return. - sb.append(fqn.substring(nextDot + 1)); - break; - } - currentDot = nextDot; - } - return sb.toString(); - } - } - private record GCStats(long totalCount, long totalTimeMillis) { private static GCStats getCurrent() { long totalCount = 0; @@ -1318,7 +1197,7 @@ private void appendStageStart() { } final String progressBarStartPadding() { - return Utils.stringFilledWith(PROGRESS_BAR_START - getCurrentTextLength(), " "); + return ProgressReporterUtils.stringFilledWith(PROGRESS_BAR_START - getCurrentTextLength(), " "); } void reportProgress() { @@ -1338,11 +1217,11 @@ void end(double totalTime) { a("]").reset(); } - String suffix = String.format("(%.1fs @ %s)", Utils.millisToSeconds(totalTime), Utils.getUsedMemory()); + String suffix = String.format("(%.1fs @ %s)", ProgressReporterUtils.millisToSeconds(totalTime), ProgressReporterUtils.getUsedMemory()); int textLength = getCurrentTextLength(); // TODO: `assert textLength > 0;` should be used here but tests do not start stages // properly (GR-35721) - String padding = Utils.stringFilledWith(Math.max(0, CHARACTERS_PER_LINE - textLength - suffix.length()), " "); + String padding = ProgressReporterUtils.stringFilledWith(Math.max(0, CHARACTERS_PER_LINE - textLength - suffix.length()), " "); a(padding).dim().a(suffix).reset().flushln(); activeBuildStage = null; @@ -1461,7 +1340,7 @@ public TwoColumnPrinter a(String value) { TwoColumnPrinter jumpToMiddle() { int remaining = middle() - getCurrentTextLength(); assert remaining >= 0 : "Column text too wide"; - a(Utils.stringFilledWith(remaining, " ")); + a(ProgressReporterUtils.stringFilledWith(remaining, " ")); assert getCurrentTextLength() == middle(); return this; } @@ -1479,7 +1358,7 @@ CenteredTextPrinter getThis() { @Override public void flushln() { - String padding = Utils.stringFilledWith((Math.max(0, CHARACTERS_PER_LINE - getCurrentTextLength())) / 2, " "); + String padding = ProgressReporterUtils.stringFilledWith((Math.max(0, CHARACTERS_PER_LINE - getCurrentTextLength())) / 2, " "); print(padding); super.flushln(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java new file mode 100644 index 000000000000..bda88b4e185d --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.hosted; + +import java.security.CodeSource; + +final class ProgressReporterUtils { + + private static final double MILLIS_TO_SECONDS = 1000d; + private static final double NANOS_TO_SECONDS = 1000d * 1000d * 1000d; + public static final String TRUNCATION_PLACEHOLDER = "~"; + + static double millisToSeconds(double millis) { + return millis / MILLIS_TO_SECONDS; + } + + static double nanosToSeconds(double nanos) { + return nanos / NANOS_TO_SECONDS; + } + + static String getUsedMemory() { + return ByteFormattingUtil.bytesToHumanGB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()); + } + + static String stringFilledWith(int size, String fill) { + return fill.repeat(size); + } + + static double toPercentage(long part, long total) { + return part / (double) total * 100; + } + + private static String truncateName(String name, double maxLineRatio) { + int length = name.length(); + int maxLength = maxLength(maxLineRatio); + if (length <= maxLength) { + return name; + } + return TRUNCATION_PLACEHOLDER + name.substring(length - maxLength + TRUNCATION_PLACEHOLDER.length(), length); + } + + static String truncateFQN(String fqn, double maxLineRatio) { + return truncateFQN(fqn, maxLength(maxLineRatio)); + } + + static int maxLength(double maxLineRatio) { + return (int) Math.floor(ProgressReporter.CHARACTERS_PER_LINE * maxLineRatio); + } + + private static String truncateFQN(String fqn, int maxLength) { + int classNameLength = fqn.length(); + if (classNameLength <= maxLength) { + return fqn; + } + StringBuilder sb = new StringBuilder(); + int currentDot = -1; + while (true) { + int nextDot = fqn.indexOf('.', currentDot + 1); + if (nextDot < 0) { // Not more dots, handle the rest and return. + String rest = fqn.substring(currentDot + 1); + int sbLength = sb.length(); + int restLength = rest.length(); + if (sbLength + restLength <= maxLength) { + sb.append(rest); + } else { + int remainingSpaceDivBy2 = (maxLength - sbLength) / 2; + sb.append(rest, 0, remainingSpaceDivBy2 - 1).append(TRUNCATION_PLACEHOLDER).append(rest, restLength - remainingSpaceDivBy2, restLength); + } + break; + } + sb.append(fqn.charAt(currentDot + 1)).append('.'); + if (sb.length() + (classNameLength - nextDot) <= maxLength) { + // Rest fits maxLength, append and return. + sb.append(fqn.substring(nextDot + 1)); + break; + } + currentDot = nextDot; + } + return sb.toString(); + } + + static String moduleNamePrefix(Module javaModule) { + String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; + return truncateFQN(mapToNativeImageRuntime(moduleName), 0.10) + "/"; + } + + private static String mapToNativeImageRuntime(String moduleName) { + String modulePrefix = "org.graalvm.nativeimage."; + if (moduleName.equals(modulePrefix + "builder")) { + return modulePrefix + "runtime"; + } + return moduleName; + } + + record BreakDownClassifier(Package javaPackage, Module javaModule, String location) { + static BreakDownClassifier of(Class clazz) { + return new BreakDownClassifier(clazz.getPackage(), clazz.getModule(), sourcePath(clazz)); + } + + private static String sourcePath(Class clazz) { + CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); + if (codeSource != null && codeSource.getLocation() != null) { + String path = codeSource.getLocation().getPath(); + // Use String API to determine basename of path to handle both / and \. + return path.substring(Math.max(path.lastIndexOf('/') + 1, path.lastIndexOf('\\') + 1)); + } + return null; + } + + public String renderToString(int maxLength) { + String locationSuffix = location == null ? "" : " (" + truncateName(location, 0.10) + ")"; + String packageName = javaPackage == null ? "null" : javaPackage.getName(); + String moduleNamePrefix = moduleNamePrefix(javaModule); + // Give remainder of space to package-part + int maxLengthPackage = maxLength - moduleNamePrefix.length() - locationSuffix.length(); + return moduleNamePrefix + truncateFQN(packageName, maxLengthPackage) + locationSuffix; + } + + public String[] elements() { + String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; + String packageName = javaPackage == null ? "" : javaPackage.getName(); + String locationName = location == null ? "" : location; + return new String[]{mapToNativeImageRuntime(moduleName), packageName, locationName}; + } + } +} From f03a6960964c3516a2d4204f2e31e4c34292901b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Thu, 21 Aug 2025 10:49:25 +0200 Subject: [PATCH 7/8] Do not print location info in Top 10 printing --- .../svm/hosted/HeapBreakdownProvider.java | 14 ++++---- .../oracle/svm/hosted/ProgressReporter.java | 15 ++++++--- .../svm/hosted/ProgressReporterUtils.java | 33 ++++++++----------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java index 1d57ef635389..ba975b6d465d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HeapBreakdownProvider.java @@ -225,7 +225,7 @@ public static HeapBreakdownEntry of(String prefix, String name, String htmlAncho return new HeapBreakdownEntryFixed(new LinkyHeapObjectKindName(prefix, name, htmlAnchor)); } - public abstract HeapBreakdownLabel getLabel(boolean truncate); + public abstract HeapBreakdownLabel getLabel(int maxLength); public long getByteSize() { return byteSize; @@ -259,7 +259,7 @@ static class HeapBreakdownEntryFixed extends HeapBreakdownEntry { } @Override - public HeapBreakdownLabel getLabel(boolean unused) { + public HeapBreakdownLabel getLabel(int unused) { return label; } } @@ -273,10 +273,12 @@ static class HeapBreakdownEntryForClass extends HeapBreakdownEntry { } @Override - public HeapBreakdownLabel getLabel(boolean truncate) { - if (truncate) { - return new SimpleHeapObjectKindName(ProgressReporterUtils.moduleNamePrefix(clazz.getModule()) + - ProgressReporterUtils.truncateFQN(clazz.getTypeName(), 0.29)); + public HeapBreakdownLabel getLabel(int maxLength) { + if (maxLength >= 0) { + String moduleNamePrefix = ProgressReporterUtils.moduleNamePrefix(clazz.getModule()); + int maxLengthClassName = maxLength - moduleNamePrefix.length(); + String truncatedClassName = ProgressReporterUtils.truncateFQN(clazz.getTypeName(), maxLengthClassName); + return new SimpleHeapObjectKindName(moduleNamePrefix + truncatedClassName); } else { return new SimpleHeapObjectKindName(clazz.getTypeName()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java index 0ebc35edbcd8..84eebd8186a2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java @@ -684,10 +684,10 @@ private void printBreakdowns() { Iterator typesBySizeInHeap = heapBreakdown.getSortedBreakdownEntries().iterator(); for (int i = 0; i < MAX_NUM_BREAKDOWN; i++) { String codeSizePart = ""; - /* <- 10% for module name -><- remainder for class FQN -><- 10% for location -> */ + /* <- 12% for module name -><- remainder for package FQN -> */ if (packagesBySize.hasNext()) { Entry entry = packagesBySize.next(); - String sizeStr = String.format("%9s ", ByteFormattingUtil.bytesToHuman(entry.getValue())); + String sizeStr = getBreakdownSizeString(entry.getValue()); String entryStr = entry.getKey().renderToString(p.middle() - sizeStr.length()); codeSizePart = sizeStr + entryStr; printedCodeBytes += entry.getValue(); @@ -695,12 +695,13 @@ private void printBreakdowns() { } String heapSizePart = ""; - /* <- 10% for module name -><- 29% for class FQN -> */ + /* <- 12% for module name -><- remainder for class FQN -> */ if (typesBySizeInHeap.hasNext()) { HeapBreakdownProvider.HeapBreakdownEntry e = typesBySizeInHeap.next(); - String labelString = e.getLabel(true).renderToString(linkStrategy); long byteSize = e.byteSize; - heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(byteSize), labelString); + String sizeStr = getBreakdownSizeString(byteSize); + String labelStr = e.getLabel(CHARACTERS_PER_LINE - p.middle() - sizeStr.length()).renderToString(linkStrategy); + heapSizePart = sizeStr + labelStr; printedHeapBytes += byteSize; printedHeapItems++; } @@ -720,6 +721,10 @@ private void printBreakdowns() { .flushln(); } + private static String getBreakdownSizeString(long sizeInBytes) { + return String.format("%9s ", ByteFormattingUtil.bytesToHuman(sizeInBytes)); + } + private void printRecommendations() { if (!SubstrateOptions.BuildOutputRecommendations.getValue()) { return; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java index bda88b4e185d..603070275125 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporterUtils.java @@ -25,6 +25,7 @@ package com.oracle.svm.hosted; +import java.net.URL; import java.security.CodeSource; final class ProgressReporterUtils { @@ -53,15 +54,6 @@ static double toPercentage(long part, long total) { return part / (double) total * 100; } - private static String truncateName(String name, double maxLineRatio) { - int length = name.length(); - int maxLength = maxLength(maxLineRatio); - if (length <= maxLength) { - return name; - } - return TRUNCATION_PLACEHOLDER + name.substring(length - maxLength + TRUNCATION_PLACEHOLDER.length(), length); - } - static String truncateFQN(String fqn, double maxLineRatio) { return truncateFQN(fqn, maxLength(maxLineRatio)); } @@ -70,7 +62,7 @@ static int maxLength(double maxLineRatio) { return (int) Math.floor(ProgressReporter.CHARACTERS_PER_LINE * maxLineRatio); } - private static String truncateFQN(String fqn, int maxLength) { + static String truncateFQN(String fqn, int maxLength) { int classNameLength = fqn.length(); if (classNameLength <= maxLength) { return fqn; @@ -103,8 +95,11 @@ private static String truncateFQN(String fqn, int maxLength) { } static String moduleNamePrefix(Module javaModule) { - String moduleName = javaModule.isNamed() ? javaModule.getName() : ""; - return truncateFQN(mapToNativeImageRuntime(moduleName), 0.10) + "/"; + if (!javaModule.isNamed()) { + return ""; + } + String moduleName = javaModule.getName(); + return truncateFQN(mapToNativeImageRuntime(moduleName), 0.12) + "/"; } private static String mapToNativeImageRuntime(String moduleName) { @@ -122,21 +117,21 @@ static BreakDownClassifier of(Class clazz) { private static String sourcePath(Class clazz) { CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); - if (codeSource != null && codeSource.getLocation() != null) { - String path = codeSource.getLocation().getPath(); - // Use String API to determine basename of path to handle both / and \. - return path.substring(Math.max(path.lastIndexOf('/') + 1, path.lastIndexOf('\\') + 1)); + if (codeSource != null) { + URL sourceLocation = codeSource.getLocation(); + if (sourceLocation != null && !"jrt".equals(sourceLocation.getProtocol())) { + return sourceLocation.getPath(); + } } return null; } public String renderToString(int maxLength) { - String locationSuffix = location == null ? "" : " (" + truncateName(location, 0.10) + ")"; String packageName = javaPackage == null ? "null" : javaPackage.getName(); String moduleNamePrefix = moduleNamePrefix(javaModule); // Give remainder of space to package-part - int maxLengthPackage = maxLength - moduleNamePrefix.length() - locationSuffix.length(); - return moduleNamePrefix + truncateFQN(packageName, maxLengthPackage) + locationSuffix; + int maxLengthPackage = maxLength - moduleNamePrefix.length(); + return moduleNamePrefix + truncateFQN(packageName, maxLengthPackage); } public String[] elements() { From e60154e73ea4d51c1b140a23c4deb37a69cf6cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20W=C3=B6gerer?= Date: Thu, 21 Aug 2025 15:42:56 +0200 Subject: [PATCH 8/8] Update BuildOutput.md to reflect Top 10 printing changes --- .../native-image/BuildOutput.md | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/docs/reference-manual/native-image/BuildOutput.md b/docs/reference-manual/native-image/BuildOutput.md index 4c96f85d339b..0ce44161076d 100644 --- a/docs/reference-manual/native-image/BuildOutput.md +++ b/docs/reference-manual/native-image/BuildOutput.md @@ -23,58 +23,60 @@ Below is the example output when building a native executable of the `HelloWorld ================================================================================ GraalVM Native Image: Generating 'helloworld' (executable)... ================================================================================ -[1/8] Initializing... (2.8s @ 0.15GB) - Java version: 25+13, vendor version: GraalVM CE 25-dev+13.1 +[1/8] Initializing... (2.0s @ 0.19GB) + Java version: 26+9, vendor version: GraalVM CE 26-dev+9.1 Graal compiler: optimization level: 2, target machine: x86-64-v3 - C compiler: gcc (linux, x86_64, 12.2.0) + C compiler: gcc (linux, x86_64, 15.2.1) Garbage collector: Serial GC (max heap size: 80% of RAM) -------------------------------------------------------------------------------- - Build resources: - - 13.24GB of memory (42.7% of system memory, using available memory) - - 16 thread(s) (100.0% of 16 available processor(s), determined at start) -[2/8] Performing analysis... [****] (4.5s @ 0.54GB) - 3,158 types, 3,625 fields, and 14,804 methods found reachable - 1,012 types, 36 fields, and 377 methods registered for reflection - 57 types, 57 fields, and 52 methods registered for JNI access +Build resources: + - 28.45GB of memory (42.5% of system memory, using available memory) + - 20 thread(s) (100.0% of 20 available processor(s), determined at start) +[2/8] Performing analysis... [******] (3.4s @ 0.40GB) + 3,297 types, 3,733 fields, and 15,247 methods found reachable + 1,066 types, 36 fields, and 415 methods registered for reflection + 58 types, 59 fields, and 52 methods registered for JNI access 0 downcalls and 0 upcalls registered for foreign access 4 native libraries: dl, pthread, rt, z -[3/8] Building universe... (0.8s @ 0.99GB) -[4/8] Parsing methods... [*] (0.6s @ 0.75GB) -[5/8] Inlining methods... [***] (0.3s @ 0.32GB) -[6/8] Compiling methods... [**] (3.7s @ 0.60GB) -[7/8] Laying out methods... [*] (0.8s @ 0.83GB) -[8/8] Creating image... [**] (3.1s @ 0.58GB) - 5.32MB (24.22%) for code area: 8,702 compilation units - 7.03MB (32.02%) for image heap: 93,301 objects and 5 resources - 8.96MB (40.83%) for debug info generated in 1.0s - 659.13kB ( 2.93%) for other data - 21.96MB in total image size, 21.04MB in total file size +[3/8] Building universe... (1.0s @ 0.60GB) +[4/8] Parsing methods... [*] (0.4s @ 0.62GB) +[5/8] Inlining methods... [****] (0.2s @ 0.59GB) +[6/8] Compiling methods... [**] (3.7s @ 0.66GB) +[7/8] Laying out methods... [*] (0.7s @ 0.60GB) +[8/8] Creating image... [**] (2.3s @ 0.65GB) + 5.24MB (21.86%) for code area: 8,788 compilation units + 7.67MB (32.01%) for image heap: 90,323 objects and 55 resources + 9.43MB (39.34%) for debug info generated in 0.3s + 11.05MB (46.13%) for other data + 23.96MB in total image size, 13.31MB in total file size -------------------------------------------------------------------------------- Top 10 origins of code area: Top 10 object types in image heap: - 4.03MB java.base 1.14MB byte[] for code metadata - 927.05kB svm.jar (Native Image) 927.31kB java.lang.String - 111.71kB java.logging 839.68kB byte[] for general heap data - 63.38kB org.graalvm.nativeimage.base 736.91kB java.lang.Class - 47.59kB jdk.proxy1 713.13kB byte[] for java.lang.String - 35.85kB jdk.proxy3 272.85kB c.o.s.c.h.DynamicHubCompanion - 27.06kB jdk.internal.vm.ci 250.83kB java.util.HashMap$Node - 23.44kB org.graalvm.sdk 196.52kB java.lang.Object[] - 11.42kB jdk.proxy2 182.77kB java.lang.String[] - 8.07kB jdk.graal.compiler 154.26kB byte[] for embedded resources - 1.39kB for 2 more packages 1.38MB for 884 more object types + 791.32kB java.base/java.util 1.41MB byte[] for code metadata + 363.66kB java.base/java.lang 1.21MB byte[] for string data + 323.39kB java.base/java.text 838.53kB java.base/java.lang.String + 241.87kB java.base/java.util.stream 633.02kB o.g.n.~e/c.o.s.c.h.Dyna~anion + 229.23kB java.base/java.util.regex 431.58kB heap alignment + 214.23kB java.base/java.util.concurrent 428.26kB java.base/java.lang.Class + 166.60kB o.g.n.~e/c.o.svm.core.code 323.23kB java.base/j.util.HashMap$Node + 153.78kB java.base/java.time.format 284.47kB byte[] for general heap data + 152.90kB java.base/java.math 232.06kB java.base/java.lang.Object[] + 142.02kB o.g.n.~e/c.o.s.c.genscavenge 183.10kB java.base/j.u.HashMap$Node[] + 2.32MB for 146 more packages 1.70MB for 966 more object types -------------------------------------------------------------------------------- Recommendations: + FUTR: Use '--future-defaults=all' to prepare for future releases. HEAP: Set max heap for improved and more predictable memory usage. CPU: Enable more CPU features with '-march=native' for improved performance. -------------------------------------------------------------------------------- - 0.8s (4.6% of total time) in 35 GCs | Peak RSS: 1.93GB | CPU load: 9.61 + 0.9s (6.1% of total time) in 54 GCs | Peak RSS: 1.82GB | CPU load: 13.25 -------------------------------------------------------------------------------- Build artifacts: + /home/janedoe/helloworld/gdb-debughelpers.py (debug_info) /home/janedoe/helloworld/helloworld (executable) /home/janedoe/helloworld/helloworld.debug (debug_info) /home/janedoe/helloworld/sources (debug_info) ================================================================================ -Finished generating 'helloworld' in 17.0s. +Finished generating 'helloworld' in 14.2s. ``` ## Build Stages